;; lib/host/auth.sx — browser login on top of host sessions (lib/host/session.sx). ;; A login form posts credentials; on success the principal is written to the ;; session cookie. The guarded write routes then accept EITHER a logged-in session ;; OR a Bearer token (host/require-user), so the same routes serve browsers and API ;; clients. Single admin user; credentials come from $SX_ADMIN_USER / _PASSWORD ;; (set in serve.sh) — the in-source defaults are dev-only. ;; ;; Depends on lib/host/session.sx, lib/host/{handler,middleware}.sx, lib/dream/* ;; (form/types/session) + the kernel render-page primitive. ;; ── page shell (own copy; render-page renders the static SX tree) ─── (define host/-auth-page (fn (title body) (str "" (render-page (quasiquote (html (head (meta :charset "utf-8") (title (unquote title))) (body (unquote body)))))))) ;; ── admin credential (override from env in serve.sh) ──────────────── (define host/admin-user "admin") (define host/admin-password "letmein") (define host/auth-set-admin! (fn (u p) (begin (set! host/admin-user u) (set! host/admin-password p)))) (define host/-verify-cred (fn (user pass) (and (not (= pass "")) (= user host/admin-user) (= pass host/admin-password)))) ;; ── GET /login — minimal SX login form ────────────────────────────── (define host/login-page (fn (req) (dream-html (host/-auth-page "Log in" (quasiquote (div (h1 "Log in") (form :method "post" :action "/login" (p (input :name "username" :placeholder "username")) (p (input :name "password" :type "password" :placeholder "password")) (p (button :type "submit" "Log in"))))))))) ;; ── POST /login — verify, write session principal, redirect home ──── ;; The session middleware (host/sessions) has already created/loaded the session ;; and will set the cookie on this response, so writing :principal here lands on ;; the right sid and the browser keeps the cookie. (define host/login-submit (fn (req) (let ((user (dream-form-field req "username")) (pass (dream-form-field req "password"))) (if (host/-verify-cred user pass) (begin (host/login! req user) (dream-redirect "/")) (dream-html-status 401 (host/-auth-page "Log in" (quasiquote (div (h1 "Log in") (p "Invalid credentials.") (p (a :href "/login" "Try again.")))))))))) ;; ── POST /logout — clear the session, redirect home ───────────────── (define host/logout-submit (fn (req) (begin (host/logout! req) (dream-redirect "/")))) ;; ── login routes (mounted by host/make-app) ───────────────────────── (define host/auth-routes (list (dream-get "/login" host/login-page) (dream-post "/login" host/login-submit) (dream-post "/logout" host/logout-submit))) ;; ── auth middleware: session principal OR bearer token ────────────── ;; Place AFTER the session middleware (so host/current-principal can read the ;; session) and BEFORE host/require-permission. resolve : token -> principal | nil ;; is the bearer fallback for API clients; a logged-in browser needs no token. (define host/require-user (fn (resolve) (fn (next) (fn (req) (let ((sp (host/current-principal req))) (let ((principal (if (and sp (not (= sp ""))) sp (let ((tok (dream-bearer-token req))) (if tok (resolve tok) nil))))) (if (or (nil? principal) (= principal "")) (dream-add-header (host/error 401 "unauthorized") "www-authenticate" "Bearer") (next (assoc req :dream-principal principal)))))))))