users need always login

This commit is contained in:
2026-08-28 13:19:30 +02:00
parent cd16e3ae42
commit 1316e49456
9 changed files with 129 additions and 96 deletions
+18 -15
View File
@@ -37,7 +37,7 @@ there is no browser-side persistence or independent playback state.
flowchart TB flowchart TB
Main[main.rkt<br/>configuration and lifecycle] Main[main.rkt<br/>configuration and lifecycle]
Server[private/server.rkt<br/>HTTP adapter] Server[private/server.rkt<br/>HTTP adapter]
Users[private/users.rkt<br/>users, networks and sessions] Users[private/users.rkt<br/>users, trusted proxies and sessions]
Player[private/player.rkt<br/>application state and commands] Player[private/player.rkt<br/>application state and commands]
Playlists[private/playlists.rkt<br/>durable playlist tabs] Playlists[private/playlists.rkt<br/>durable playlist tabs]
DLNAAdapter[private/dlna-playback.rkt<br/>playlist transition orchestration] DLNAAdapter[private/dlna-playback.rkt<br/>playlist transition orchestration]
@@ -241,16 +241,19 @@ on every render.
### 3.7 User authentication ### 3.7 User authentication
Authentication is enabled by adding Argon2id password hashes under `[users]`. Authentication is enabled by adding Argon2id password hashes under `[users]`.
Requests whose effective client address belongs to a configured local network All browser clients then authenticate, including clients on the local network.
bypass login. A forwarded address is accepted only when the direct peer belongs A forwarded address is accepted only when the direct peer belongs to
to `[authentication] trusted-proxies`; the rightmost `X-Forwarded-For` value is `[authentication] trusted-proxies`; the rightmost `X-Forwarded-For` value is
used so an untrusted client cannot prepend a local address. used so an untrusted client cannot prepend a forged address.
Successful logins create opaque 256-bit session tokens. Only the token is sent Successful logins create opaque 256-bit session tokens. Only the token is sent
to the browser in a `Secure`, `HttpOnly`, `SameSite=Strict` cookie; server-side to the browser in a `Secure`, `HttpOnly`, `SameSite=Strict` cookie; server-side
session state has an idle timeout and is intentionally volatile. Login failures session state has an idle timeout and is intentionally volatile. Login failures
are rate-limited per effective client address. Agent endpoints are outside user are rate-limited per effective client address. Authenticated requests move the
sessions and retain their separate application-ID authorization. server-side idle deadline. Once half the configured lifetime has elapsed, the
cookie is reissued with a fresh lifetime; this avoids a `Set-Cookie` header on
every one-second state poll. Agent endpoints are outside user sessions and
retain their separate application-ID authorization.
## 4. Key runtime flows ## 4. Key runtime flows
@@ -334,7 +337,7 @@ file:
setting remains supported); setting remains supported);
- allowed 256-bit playback-agent IDs under `[playback-agents]`; - allowed 256-bit playback-agent IDs under `[playback-agents]`;
- the optional playlist-keystore override under `[player]`; - the optional playlist-keystore override under `[player]`;
- Argon2id user hashes, local networks, trusted proxies, and session timeout. - Argon2id user hashes, trusted proxies, and session timeout.
Command-line network settings override INI values. Library paths from both Command-line network settings override INI values. Library paths from both
sources are combined and de-duplicated. sources are combined and de-duplicated.
@@ -345,13 +348,13 @@ and sessions still reset when the process restarts.
## 7. Security and operational boundaries ## 7. Security and operational boundaries
The browser API has optional user authentication but no TLS termination or The browser API has optional user authentication but no TLS termination.
per-user player state. Authentication must be enabled before exposing it to an Authentication must be enabled before exposing it to an untrusted network, and
untrusted network, and a reverse proxy must provide HTTPS because session a reverse proxy must provide HTTPS because session cookies are always marked
cookies are always marked `Secure`. Local-network bypass and forwarded client `Secure`. Forwarded client addresses are security-sensitive configuration:
addresses are security-sensitive configuration: only known reverse-proxy peers only known reverse-proxy peers may be trusted, and the application port should
may be trusted, and the application port should remain firewalled from the remain firewalled from the internet. Playlists are isolated by username, while
internet. renderer selection and transport state remain shared.
Playback-agent registration and polling are authorized against a default-deny Playback-agent registration and polling are authorized against a default-deny
INI allowlist. Media URLs additionally contain an opaque per-track token. The INI allowlist. Media URLs additionally contain an opaque per-track token. The
+17 -15
View File
@@ -41,9 +41,8 @@ podcasts=D:\Podcasts
7b4776ef27104e8eb9f7ea2c622ce76ca23de4260b0f94e6880d321017b32a0e4=true 7b4776ef27104e8eb9f7ea2c622ce76ca23de4260b0f94e6880d321017b32a0e4=true
[authentication] [authentication]
local-networks=127.0.0.0/8;::1/128;10.0.0.0/8;172.16.0.0/12;192.168.0.0/16
trusted-proxies=127.0.0.0/8;::1/128 trusted-proxies=127.0.0.0/8;::1/128
session-seconds=43200 session-seconds=604800
[users] [users]
hans=$argon2id$v=19$m=19456,t=2,p=1$... hans=$argon2id$v=19$m=19456,t=2,p=1$...
@@ -57,8 +56,8 @@ ondersteund. Playlisttabs worden standaard opgeslagen in de keystore
`playlist-keystore=...` onder `[player]` kan desgewenst een ander pad worden `playlist-keystore=...` onder `[player]` kan desgewenst een ander pad worden
gebruikt. gebruikt.
Zodra `[users]` minstens één gebruiker bevat, toont de webinterface voor Zodra `[users]` minstens één gebruiker bevat, moeten alle browserclients
niet-lokale clients een eigen loginvenster. Wachtwoorden staan uitsluitend als inloggen, zowel lokaal als via internet. Wachtwoorden staan uitsluitend als
Argon2id-hash in de INI. Maak zo'n hash vanuit Racket: Argon2id-hash in de INI. Maak zo'n hash vanuit Racket:
```racket ```racket
@@ -69,7 +68,6 @@ Argon2id-hash in de INI. Maak zo'n hash vanuit Racket:
(displayln (make-password-hash "een lang en uniek wachtwoord")) (displayln (make-password-hash "een lang en uniek wachtwoord"))
``` ```
`local-networks` bepaalt welke clients zonder login mogen werken.
`trusted-proxies` bepaalt uitsluitend van welke directe peers de laatste `trusted-proxies` bepaalt uitsluitend van welke directe peers de laatste
`X-Forwarded-For`-waarde wordt geaccepteerd. Laat die lijst zo klein mogelijk; `X-Forwarded-For`-waarde wordt geaccepteerd. Laat die lijst zo klein mogelijk;
bij Apache op dezelfde machine zijn loopbackadressen voldoende. Zonder bij Apache op dezelfde machine zijn loopbackadressen voldoende. Zonder
@@ -108,21 +106,25 @@ tab verdwijnt daarbij ook uit de keystore. Voor iedere gebruiker bevat de key
GUID-key staan de naam en tracks van die playlist. Tracks uit verschillende GUID-key staan de naam en tracks van die playlist. Tracks uit verschillende
geconfigureerde libraries mogen in dezelfde playlist staan; ontbrekende of geconfigureerde libraries mogen in dezelfde playlist staan; ontbrekende of
buiten de libraries gelegen bestanden worden bij het laden overgeslagen. buiten de libraries gelegen bestanden worden bij het laden overgeslagen.
`local`, `anonymous` en iedere aangemelde gebruiker hebben daarbij een eigen Iedere aangemelde gebruiker heeft daarbij een eigen playlistverzameling. Als
playlistverzameling. Het fysieke afspeelpunt en de transportbediening blijven authenticatie is uitgeschakeld, wordt de verzameling van `anonymous` gebruikt.
gedeeld; een playlistcommando van een andere gebruiker neemt die speler over. Het fysieke afspeelpunt en de transportbediening blijven gedeeld; een
playlistcommando van een andere gebruiker neemt die speler over. Een oudere
`playlists-for-local`-verzameling blijft in de keystore staan, maar wordt niet
automatisch aan een gebruiker toegewezen.
De server luistert standaard alleen op localhost. Geef alleen bewust een De server luistert standaard alleen op localhost. Geef alleen bewust een
LAN-adres aan `--listen-ip`. Configureer gebruikersauthenticatie voordat de LAN-adres aan `--listen-ip`. Configureer gebruikersauthenticatie voordat de
webinterface via een publiek bereikbare reverse proxy wordt aangeboden. webinterface via een publiek bereikbare reverse proxy wordt aangeboden.
Externe gebruikers krijgen na succesvolle aanmelding een willekeurige 256-bit Gebruikers krijgen na succesvolle aanmelding een willekeurige 256-bit
sessiecookie met `Secure`, `HttpOnly` en `SameSite=Strict`. Sessies verlopen na sessiecookie met `Secure`, `HttpOnly` en `SameSite=Strict`. De standaard
de ingestelde inactiviteitsduur en worden niet over een serverherstart heen inactiviteitsduur is zeven dagen. Geldig gebruik verschuift de servertermijn;
bewaard. Na vijf mislukte pogingen vanaf hetzelfde clientadres wordt aanmelden halverwege de termijn wordt ook de browsercookie opnieuw voor zeven dagen
vijf minuten geblokkeerd. De `/api/agent/*`-routes gebruiken geen uitgegeven. Sessies worden niet over een serverherstart heen bewaard. Na vijf
gebruikerssessie: daarvoor blijft de afzonderlijke playback-agent-allowlist mislukte pogingen vanaf hetzelfde clientadres wordt aanmelden vijf minuten
gelden. geblokkeerd. De `/api/agent/*`-routes gebruiken geen gebruikerssessie: daarvoor
blijft de afzonderlijke playback-agent-allowlist gelden.
## Windows playback agent ## Windows playback agent
+2 -16
View File
@@ -55,14 +55,9 @@
(define/contract (run-web-player music-paths (define/contract (run-web-player music-paths
#:allowed-agent-ids [allowed-agent-ids '()] #:allowed-agent-ids [allowed-agent-ids '()]
#:users [users '()] #:users [users '()]
#:local-networks
[local-networks
'("127.0.0.0/8" "::1/128"
"10.0.0.0/8" "172.16.0.0/12"
"192.168.0.0/16")]
#:trusted-proxies #:trusted-proxies
[trusted-proxies '("127.0.0.0/8" "::1/128")] [trusted-proxies '("127.0.0.0/8" "::1/128")]
#:session-seconds [session-seconds 43200] #:session-seconds [session-seconds 604800]
#:listen-ip [listen-ip "127.0.0.1"] #:listen-ip [listen-ip "127.0.0.1"]
#:port [port 8080] #:port [port 8080]
#:dlna-port [dlna-port 8734] #:dlna-port [dlna-port 8734]
@@ -72,7 +67,6 @@
(->* ((listof library-spec/c)) (->* ((listof library-spec/c))
(#:allowed-agent-ids (listof string?) (#:allowed-agent-ids (listof string?)
#:users (listof (cons/c string? string?)) #:users (listof (cons/c string? string?))
#:local-networks (listof string?)
#:trusted-proxies (listof string?) #:trusted-proxies (listof string?)
#:session-seconds exact-positive-integer? #:session-seconds exact-positive-integer?
#:listen-ip string? #:listen-ip string?
@@ -88,7 +82,6 @@
#:dlna-port dlna-port)) #:dlna-port dlna-port))
(auth-manager (auth-manager
(make-auth-manager users (make-auth-manager users
#:local-networks local-networks
#:trusted-proxies trusted-proxies #:trusted-proxies trusted-proxies
#:session-seconds session-seconds))) #:session-seconds session-seconds)))
(info-rkt-web-player (info-rkt-web-player
@@ -179,19 +172,13 @@
#:when (string? (cdr entry))) #:when (string? (cdr entry)))
entry)) entry))
(define local-networks
(configuration-list
(ini-get config 'authentication 'local-networks "")
'("127.0.0.0/8" "::1/128"
"10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16")))
(define trusted-proxies (define trusted-proxies
(configuration-list (configuration-list
(ini-get config 'authentication 'trusted-proxies "") (ini-get config 'authentication 'trusted-proxies "")
'("127.0.0.0/8" "::1/128"))) '("127.0.0.0/8" "::1/128")))
(define session-seconds (define session-seconds
(ini-get config 'authentication 'session-seconds 43200)) (ini-get config 'authentication 'session-seconds 604800))
(define all-libraries (define all-libraries
(append configured-libraries (append configured-libraries
@@ -203,7 +190,6 @@
all-libraries all-libraries
#:allowed-agent-ids allowed-agent-ids #:allowed-agent-ids allowed-agent-ids
#:users configured-users #:users configured-users
#:local-networks local-networks
#:trusted-proxies trusted-proxies #:trusted-proxies trusted-proxies
#:session-seconds session-seconds #:session-seconds session-seconds
#:listen-ip (or listen-ip #:listen-ip (or listen-ip
+10 -10
View File
@@ -1126,7 +1126,7 @@
(string-downcase app-id))) (string-downcase app-id)))
(define store (open-playlist-store playlist-keystore)) (define store (open-playlist-store playlist-keystore))
(define stored-tabs (define stored-tabs
(load-user-playlists store "local" libraries)) (load-user-playlists store "anonymous" libraries))
(let* ((library (and (pair? libraries) (car libraries))) (let* ((library (and (pair? libraries) (car libraries)))
(browser-entries (browser-entries
(if library (if library
@@ -1142,7 +1142,7 @@
(selected-index 0) (selected-index 0)
(contexts (make-hash)) (contexts (make-hash))
(initial-context (playlist-context tabs selected-index))) (initial-context (playlist-context tabs selected-index)))
(hash-set! contexts "local" initial-context) (hash-set! contexts "anonymous" initial-context)
(define value (define value
(player libraries (player libraries
(remove-duplicates normalized-agent-ids string=?) (remove-duplicates normalized-agent-ids string=?)
@@ -1175,7 +1175,7 @@
(make-hash) (make-hash)
store store
contexts contexts
"local" "anonymous"
dlna-port)) dlna-port))
value)) value))
@@ -1185,7 +1185,7 @@
; post : Cached DLNA playback information has been incorporated. ; post : Cached DLNA playback information has been incorporated.
; result : A JSON-compatible hash. ; result : A JSON-compatible hash.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (player-state->jsexpr value #:username [username "local"]) (define (player-state->jsexpr value #:username [username "anonymous"])
(define normalized (normal-playlist-username username)) (define normalized (normal-playlist-username username))
(call-with-semaphore (call-with-semaphore
(player-command-lock value) (player-command-lock value)
@@ -1264,7 +1264,7 @@
"playlist-clear" "tab-add" "tab-select" "tab-rename" "playlist-clear" "tab-add" "tab-select" "tab-rename"
"tab-delete" "play")) "tab-delete" "play"))
(define (player-command! value command data #:username [username "local"]) (define (player-command! value command data #:username [username "anonymous"])
(define normalized (normal-playlist-username username)) (define normalized (normal-playlist-username username))
(call-with-semaphore (call-with-semaphore
(player-command-lock value) (player-command-lock value)
@@ -1488,7 +1488,7 @@
; post : Player state remains unchanged. ; post : Player state remains unchanged.
; result : Artwork bytes and MIME type, or #f when unavailable. ; result : Artwork bytes and MIME type, or #f when unavailable.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (player-track-artwork value artwork-id #:username [username "local"]) (define (player-track-artwork value artwork-id #:username [username "anonymous"])
(define item (define item
(call-with-semaphore (call-with-semaphore
(player-command-lock value) (player-command-lock value)
@@ -1757,10 +1757,10 @@
"tab-rename" "tab-rename"
(hasheq 'index 1 'name "Hans favorieten") (hasheq 'index 1 'name "Hans favorieten")
#:username "hans") #:username "hans")
(define local-after-hans (define anonymous-after-hans
(player-state->jsexpr example-player #:username "local")) (player-state->jsexpr example-player #:username "anonymous"))
(check-equal? (length (hash-ref local-after-hans 'tabs)) 1) (check-equal? (length (hash-ref anonymous-after-hans 'tabs)) 1)
(check-equal? (length (hash-ref local-after-hans 'tracks)) 2) (check-equal? (length (hash-ref anonymous-after-hans 'tracks)) 2)
(player-close! example-player) (player-close! example-player)
(define restored-player (define restored-player
+19 -2
View File
@@ -54,7 +54,6 @@
(let ((user (auth-request-user current-auth request))) (let ((user (auth-request-user current-auth request)))
(json-response (json-response
(hasheq 'enabled (auth-enabled? current-auth) (hasheq 'enabled (auth-enabled? current-auth)
'local (auth-request-local? current-auth request)
'authenticated (and user #t) 'authenticated (and user #t)
'username (or user 'null))))) 'username (or user 'null)))))
@@ -215,6 +214,24 @@
(regexp-match? #px"^/api/(?:auth|agent)(?:/|$)" (regexp-match? #px"^/api/(?:auth|agent)(?:/|$)"
(request-path request))) (request-path request)))
(define (response-add-header value extra-header)
(response (response-code value)
(response-message value)
(response-seconds value)
(response-mime value)
(cons extra-header (response-headers value))
(response-output value)))
(define (dispatch-api request)
(define value (api-dispatch request))
(define renewed-cookie
(and (not (regexp-match? #px"^/api/agent(?:/|$)"
(request-path request)))
(auth-renewal-cookie current-auth request)))
(if renewed-cookie
(response-add-header value (header #"Set-Cookie" renewed-cookie))
value))
(define (dispatch request) (define (dispatch request)
(cond (cond
((and (bytes=? (request-method request) #"POST") ((and (bytes=? (request-method request) #"POST")
@@ -225,7 +242,7 @@
#:code 415)) #:code 415))
((or (public-api-request? request) ((or (public-api-request? request)
(auth-request-user current-auth request)) (auth-request-user current-auth request))
(api-dispatch request)) (dispatch-api request))
(else (else
(json-response (json-response
(hasheq 'error "Aanmelden is vereist" (hasheq 'error "Aanmelden is vereist"
+54 -31
View File
@@ -14,18 +14,20 @@
make-auth-manager make-auth-manager
auth-manager? auth-manager?
auth-enabled? auth-enabled?
auth-request-local?
auth-request-user auth-request-user
auth-login! auth-login!
auth-logout! auth-logout!
auth-session-cookie auth-session-cookie
auth-renewal-cookie
auth-expired-cookie) auth-expired-cookie)
(struct ip-network (address prefix) #:transparent) (struct ip-network (address prefix) #:transparent)
(struct session (username [last-seen #:mutable]) #:transparent) (struct session
(username [last-seen #:mutable] [last-cookie-renewal #:mutable])
#:transparent)
(struct failures ([attempts #:mutable] [started #:mutable]) #:transparent) (struct failures ([attempts #:mutable] [started #:mutable]) #:transparent)
(struct auth-manager (struct auth-manager
(users local-networks trusted-proxies session-seconds sessions failed lock) (users trusted-proxies session-seconds sessions failed lock)
#:transparent) #:transparent)
(define password-kdf (define password-kdf
@@ -136,11 +138,6 @@
(define (auth-enabled? manager) (define (auth-enabled? manager)
(positive? (hash-count (auth-manager-users manager)))) (positive? (hash-count (auth-manager-users manager))))
(define (auth-request-local? manager request)
(ormap (lambda (network)
(network-contains? network (request-address manager request)))
(auth-manager-local-networks manager)))
(define (request-session-token request) (define (request-session-token request)
(for/or ((cookie (in-list (request-cookies request)))) (for/or ((cookie (in-list (request-cookies request))))
(and (string=? (client-cookie-name cookie) session-cookie-name) (and (string=? (client-cookie-name cookie) session-cookie-name)
@@ -156,7 +153,6 @@
(define (auth-request-user manager request) (define (auth-request-user manager request)
(cond (cond
((not (auth-enabled? manager)) "anonymous") ((not (auth-enabled? manager)) "anonymous")
((auth-request-local? manager request) "local")
(else (else
(let ((token (request-session-token request)) (let ((token (request-session-token request))
(now (current-seconds))) (now (current-seconds)))
@@ -215,7 +211,7 @@
(prune-sessions! manager now) (prune-sessions! manager now)
(hash-set! (auth-manager-sessions manager) (hash-set! (auth-manager-sessions manager)
token token
(session normalized now)) (session normalized now now))
token) token)
(begin (begin
(record-failure! manager address now) (record-failure! manager address now)
@@ -237,6 +233,30 @@
token token
(auth-manager-session-seconds manager)))) (auth-manager-session-seconds manager))))
;; Return a refreshed cookie at most once per half session lifetime. The
;; server-side inactivity timer is updated on every authenticated request, but
;; limiting Set-Cookie avoids rewriting it for every one-second player poll.
(define (auth-renewal-cookie manager request)
(and (auth-enabled? manager)
(let ((token (request-session-token request))
(now (current-seconds)))
(and token
(call-with-semaphore
(auth-manager-lock manager)
(lambda ()
(prune-sessions! manager now)
(define value
(hash-ref (auth-manager-sessions manager) token #f))
(and value
(>= (- now (session-last-cookie-renewal value))
(max 1
(quotient
(auth-manager-session-seconds manager)
2)))
(begin
(set-session-last-cookie-renewal! value now)
(auth-session-cookie manager token)))))))))
(define (auth-expired-cookie) (define (auth-expired-cookie)
(string->bytes/utf-8 (string->bytes/utf-8
(format (format
@@ -244,14 +264,9 @@
session-cookie-name))) session-cookie-name)))
(define (make-auth-manager user-pairs (define (make-auth-manager user-pairs
#:local-networks
[local-network-values
'("127.0.0.0/8" "::1/128"
"10.0.0.0/8" "172.16.0.0/12"
"192.168.0.0/16")]
#:trusted-proxies #:trusted-proxies
[trusted-proxy-values '("127.0.0.0/8" "::1/128")] [trusted-proxy-values '("127.0.0.0/8" "::1/128")]
#:session-seconds [session-seconds 43200]) #:session-seconds [session-seconds 604800])
(unless (exact-positive-integer? session-seconds) (unless (exact-positive-integer? session-seconds)
(raise-argument-error 'make-auth-manager "exact-positive-integer?" (raise-argument-error 'make-auth-manager "exact-positive-integer?"
session-seconds)) session-seconds))
@@ -277,7 +292,6 @@
(hash-set! users (string-downcase (string-trim (car entry))) (hash-set! users (string-downcase (string-trim (car entry)))
(cdr entry))) (cdr entry)))
(auth-manager users (auth-manager users
(map parse-network local-network-values)
(map parse-network trusted-proxy-values) (map parse-network trusted-proxy-values)
session-seconds session-seconds
(make-hash) (make-hash)
@@ -297,30 +311,35 @@
(define manager (define manager
(make-auth-manager (make-auth-manager
(list (cons "Hans" test-hash)) (list (cons "Hans" test-hash))
#:local-networks '("192.168.1.0/24")
#:trusted-proxies '("127.0.0.1/32"))) #:trusted-proxies '("127.0.0.1/32")))
(define (test-request peer [headers '()]) (define (test-request peer [headers '()])
(request #"GET" (string->url "http://example.test/api/state") (request #"GET" (string->url "http://example.test/api/state")
headers (delay '()) #f "127.0.0.1" 80 peer)) headers (delay '()) #f "127.0.0.1" 80 peer))
(check-true (auth-request-local? manager (test-request "192.168.1.42")))
(check-false (auth-request-local? manager (test-request "192.168.2.42")))
(check-true
(auth-request-local?
manager
(test-request "127.0.0.1"
(list (header #"X-Forwarded-For" #"198.51.100.2, 192.168.1.8")))))
(check-false
(auth-request-local?
manager
(test-request "198.51.100.2"
(list (header #"X-Forwarded-For" #"192.168.1.8")))))
(define remote (test-request "198.51.100.2")) (define remote (test-request "198.51.100.2"))
;; Local and remote browser requests follow the same login path.
(check-false (auth-request-user manager (test-request "127.0.0.1")))
(check-equal?
(request-address
manager
(test-request
"127.0.0.1"
(list (header #"X-Forwarded-For"
#"198.51.100.8, 203.0.113.9"))))
"203.0.113.9")
(check-equal?
(request-address
manager
(test-request
"198.51.100.2"
(list (header #"X-Forwarded-For" #"203.0.113.9"))))
"198.51.100.2")
(define token (define token
(auth-login! manager remote "hans" "correct horse battery staple")) (auth-login! manager remote "hans" "correct horse battery staple"))
(check-true (string? token)) (check-true (string? token))
(check-true
(regexp-match? #rx#"Max-Age=604800" (auth-session-cookie manager token)))
(define authenticated (define authenticated
(test-request (test-request
"198.51.100.2" "198.51.100.2"
@@ -329,6 +348,10 @@
(string->bytes/utf-8 (string->bytes/utf-8
(format "~a=~a" session-cookie-name token)))))) (format "~a=~a" session-cookie-name token))))))
(check-equal? (auth-request-user manager authenticated) "hans") (check-equal? (auth-request-user manager authenticated) "hans")
(define stored-session (hash-ref (auth-manager-sessions manager) token))
(set-session-last-cookie-renewal! stored-session 0)
(check-true (bytes? (auth-renewal-cookie manager authenticated)))
(check-false (auth-renewal-cookie manager authenticated))
(auth-logout! manager authenticated) (auth-logout! manager authenticated)
(check-false (auth-request-user manager authenticated)) (check-false (auth-request-user manager authenticated))
(check-false (auth-login! manager remote "hans" "wrong password"))) (check-false (auth-login! manager remote "hans" "wrong password")))
+2 -2
View File
@@ -101,8 +101,8 @@ function hideLogin() {
async function refreshAuth() { async function refreshAuth() {
try { try {
const auth = await api("/api/auth/status"); const auth = await api("/api/auth/status");
elements.logout.hidden = !auth.enabled || auth.local || !auth.authenticated; elements.logout.hidden = !auth.enabled || !auth.authenticated;
if (auth.enabled && !auth.local && !auth.authenticated) showLogin(); if (auth.enabled && !auth.authenticated) showLogin();
} catch (error) { } catch (error) {
setStatus(`Authenticatiestatus onbekend: ${error.message}`); setStatus(`Authenticatiestatus onbekend: ${error.message}`);
} }
+2 -3
View File
@@ -14,11 +14,10 @@ dlna-port=8734
; 7b4776ef27104e8eb9f7ea2c622ce76ca23de4260b0f94e6880d321017b32a0e4=true ; 7b4776ef27104e8eb9f7ea2c622ce76ca23de4260b0f94e6880d321017b32a0e4=true
[authentication] [authentication]
; Local clients do not need to log in. Separate networks with semicolons.
local-networks=127.0.0.0/8;::1/128;10.0.0.0/8;172.16.0.0/12;192.168.0.0/16
; Only these direct peers may supply X-Forwarded-For. ; Only these direct peers may supply X-Forwarded-For.
trusted-proxies=127.0.0.0/8;::1/128 trusted-proxies=127.0.0.0/8;::1/128
session-seconds=43200 ; Seven-day sliding idle timeout. Active cookies are renewed halfway through.
session-seconds=604800
[users] [users]
; Generate a hash with (make-password-hash "a long password") from ; Generate a hash with (make-password-hash "a long password") from
+5 -2
View File
@@ -21,9 +21,8 @@ browser by Racket's web server.
(list/c string? path-string?)))] (list/c string? path-string?)))]
[#:allowed-agent-ids allowed-agent-ids (listof string?) null] [#:allowed-agent-ids allowed-agent-ids (listof string?) null]
[#:users users (listof (cons/c string? string?)) null] [#:users users (listof (cons/c string? string?)) null]
[#:local-networks local-networks (listof string?)]
[#:trusted-proxies trusted-proxies (listof string?)] [#:trusted-proxies trusted-proxies (listof string?)]
[#:session-seconds session-seconds exact-positive-integer? 43200] [#:session-seconds session-seconds exact-positive-integer? 604800]
[#:listen-ip listen-ip string? "127.0.0.1"] [#:listen-ip listen-ip string? "127.0.0.1"]
[#:port port exact-positive-integer? 8080] [#:port port exact-positive-integer? 8080]
[#:dlna-port dlna-port exact-positive-integer? 8734] [#:dlna-port dlna-port exact-positive-integer? 8734]
@@ -45,6 +44,10 @@ network renderer. Playlist tabs are atomically persisted in
resources are closed when the web server exits. The default is resources are closed when the web server exits. The default is
@tt{data/playlists.keystore} below the installed rkt-web-player collection. @tt{data/playlists.keystore} below the installed rkt-web-player collection.
Each username owns an ordered GUID index and separate playlist values. Each username owns an ordered GUID index and separate playlist values.
When @racket[users] is non-empty, every browser client must authenticate.
Sessions have a sliding idle timeout; an active browser cookie is renewed once
half of @racket[session-seconds] has elapsed. Playback-agent endpoints continue
to use their separate application-ID allowlist.
The default listen address only exposes the interface to the local computer. The default listen address only exposes the interface to the local computer.
Use a LAN address deliberately if other devices should control the player. Use a LAN address deliberately if other devices should control the player.