skill added

This commit is contained in:
2026-08-28 14:08:51 +02:00
parent 1316e49456
commit 2743dfb4c0
3 changed files with 322 additions and 5 deletions
+16
View File
@@ -214,6 +214,13 @@
(regexp-match? #px"^/api/(?:auth|agent)(?:/|$)"
(request-path request)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Add one HTTP header to an existing response.
; pre : Value is a response and extra-header is an HTTP header.
; post : The original response remains unchanged.
; result : A response with the same body and metadata and the additional
; header prepended to its header list.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (response-add-header value extra-header)
(response (response-code value)
(response-message value)
@@ -222,6 +229,15 @@
(cons extra-header (response-headers value))
(response-output value)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Dispatch an API request and renew an eligible browser cookie.
; pre : Current-player and current-auth are initialized and request targets
; an API route.
; post : The selected handler has run. A due browser-session renewal is
; recorded and returned as Set-Cookie; agent requests never renew it.
; result : The HTTP response produced by the API handler, optionally extended
; with the renewed session cookie.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (dispatch-api request)
(define value (api-dispatch request))
(define renewed-cookie
+83 -5
View File
@@ -26,6 +26,13 @@
(username [last-seen #:mutable] [last-cookie-renewal #:mutable])
#:transparent)
(struct failures ([attempts #:mutable] [started #:mutable]) #:transparent)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Hold configured users, trusted proxies, and volatile auth state.
; pre : Constructor fields contain normalized and parsed internal values.
; post : Creating or recognizing a value does not change external state.
; result : auth-manager? recognizes values used by the authentication API.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(struct auth-manager
(users trusted-proxies session-seconds sessions failed lock)
#:transparent)
@@ -45,6 +52,12 @@
(define failure-window-seconds 300)
(define maximum-failures 5)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Create an Argon2id password hash for configuration storage.
; pre : Password is a string containing at least twelve characters.
; post : No module state is changed.
; result : A salted Argon2id hash encoded as a string.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (make-password-hash password)
(unless (and (string? password)
(>= (string-length password) 12))
@@ -56,6 +69,12 @@
(string->bytes/utf-8 password)
password-parameters))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Verify a password against an encoded Argon2id hash.
; pre : Password and encoded are arbitrary values.
; post : No module state is changed.
; result : #t only when both values are strings and the password matches.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (password-hash-valid? password encoded)
(and (string? password)
(string? encoded)
@@ -135,6 +154,12 @@
(string-trim (last (string-split forwarded ",")))
peer))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Report whether browser authentication is configured.
; pre : Manager is an auth-manager.
; post : Manager remains unchanged.
; result : #t when at least one configured user can log in, otherwise #f.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (auth-enabled? manager)
(positive? (hash-count (auth-manager-users manager))))
@@ -150,6 +175,14 @@
(auth-manager-session-seconds manager))
(hash-remove! (auth-manager-sessions manager) token)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Resolve the browser user represented by a request cookie.
; pre : Manager is an auth-manager and request is an HTTP request.
; post : Expired sessions are removed and a valid session's last-seen time
; is updated.
; result : "anonymous" when authentication is disabled, the normalized
; username for a valid session, or #f when login is required.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (auth-request-user manager request)
(cond
((not (auth-enabled? manager)) "anonymous")
@@ -187,7 +220,16 @@
address
(failures 1 now))))
;; Returns a new token, #f for invalid credentials, or 'rate-limited.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Authenticate credentials and start a browser session.
; pre : Manager is an auth-manager, request is an HTTP request, and
; username and password are strings.
; post : A valid login creates a new session; a failed login updates the
; rate-limit state for the effective client address.
; result : A new opaque token, #f for invalid credentials, or 'rate-limited.
; internals: Unknown users follow the same Argon2id verification path as known
; users to reduce username-dependent timing differences.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (auth-login! manager request username password)
(define address (request-address manager request))
(define now (current-seconds))
@@ -217,6 +259,12 @@
(record-failure! manager address now)
#f)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : End the browser session named by the request cookie.
; pre : Manager is an auth-manager and request is an HTTP request.
; post : The matching server-side session is removed when it exists.
; result : Void.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (auth-logout! manager request)
(let ((token (request-session-token request)))
(when token
@@ -225,6 +273,13 @@
(lambda ()
(hash-remove! (auth-manager-sessions manager) token))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Encode an authenticated session token as a browser cookie.
; pre : Manager is an auth-manager and token is a session token string.
; post : Manager remains unchanged.
; result : A Secure, HttpOnly, SameSite=Strict Set-Cookie value whose Max-Age
; equals the configured session lifetime.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (auth-session-cookie manager token)
(string->bytes/utf-8
(format
@@ -233,9 +288,17 @@
token
(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.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Renew an actively used browser cookie at a bounded frequency.
; pre : Manager is an auth-manager and request is an HTTP request.
; post : Expired sessions are removed. When renewal is due, the session's
; last-cookie-renewal time is advanced.
; result : A fresh Set-Cookie value after half the configured lifetime has
; elapsed, otherwise #f.
; internals: The server idle timer moves on every authenticated request, while
; this half-life threshold prevents the one-second player poll from
; returning Set-Cookie every second.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (auth-renewal-cookie manager request)
(and (auth-enabled? manager)
(let ((token (request-session-token request))
@@ -243,7 +306,7 @@
(and token
(call-with-semaphore
(auth-manager-lock manager)
(lambda ()
(λ ()
(prune-sessions! manager now)
(define value
(hash-ref (auth-manager-sessions manager) token #f))
@@ -257,12 +320,27 @@
(set-session-last-cookie-renewal! value now)
(auth-session-cookie manager token)))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Encode deletion of the browser session cookie.
; pre : None.
; post : No module state is changed.
; result : A Secure, HttpOnly, SameSite=Strict Set-Cookie value with Max-Age 0.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (auth-expired-cookie)
(string->bytes/utf-8
(format
"~a=; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=Strict"
session-cookie-name)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Create the authentication and session manager used by the server.
; pre : User-pairs contains username and Argon2id-hash pairs, trusted proxy
; values are IP addresses or CIDR networks, and session-seconds is a
; positive exact integer.
; post : No external state is changed; session and rate-limit tables start
; empty.
; result : A new auth-manager with normalized usernames and parsed networks.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (make-auth-manager user-pairs
#:trusted-proxies
[trusted-proxy-values '("127.0.0.0/8" "::1/128")]