Per-host SSO: Node auth endpoints + Redis session (#57)

Adds the /__proxy_auth OIDC flow served on every proxied host:
- routes/host_auth.js: /start (PKCE+state, per-host redirect_uri), /callback
  (exchange, enforce the host allow-list via utils/host_sso.identityAllowed,
  mint session + set __proxy_sso cookie), /logout.
- models/sso_session.js: SsoSession (Redis-backed, TTL'd; read directly by the
  Lua gate) and HostSsoState (in-flight auth request).
- utils/oidc.js: per-host redirect_uri override on buildAuthUrl/exchangeCode.
- conf.hostSso (reuses conf.oidc). Allow-list logic unit-tested.

Enforcement (Lua gate + nginx location) lands next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 15:41:28 -04:00
parent c98214cc76
commit 87c0d024d5
9 changed files with 301 additions and 10 deletions
+8 -6
View File
@@ -36,13 +36,14 @@ function createAuthRequest(){
return {state, codeVerifier, codeChallenge};
}
// Build the SSO authorize URL the browser is redirected to.
function buildAuthUrl(state, codeChallenge){
// Build the SSO authorize URL the browser is redirected to. `redirectUri`
// overrides conf.oidc.redirectUri (per-host SSO uses a per-host callback).
function buildAuthUrl(state, codeChallenge, redirectUri){
let o = conf.oidc;
let params = new URLSearchParams({
response_type: 'code',
client_id: o.clientId,
redirect_uri: o.redirectUri,
redirect_uri: redirectUri || o.redirectUri,
scope: (o.scopes || ['openid', 'profile', 'email', 'groups']).join(' '),
state,
code_challenge: codeChallenge,
@@ -51,13 +52,14 @@ function buildAuthUrl(state, codeChallenge){
return `${o.authorizationEndpoint}?${params.toString()}`;
}
// Exchange an authorization code for tokens at the token endpoint.
async function exchangeCode(code, codeVerifier){
// Exchange an authorization code for tokens at the token endpoint. `redirectUri`
// must match the one used in buildAuthUrl (per-host for per-host SSO).
async function exchangeCode(code, codeVerifier, redirectUri){
let o = conf.oidc;
let body = new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: o.redirectUri,
redirect_uri: redirectUri || o.redirectUri,
client_id: o.clientId,
client_secret: o.clientSecret,
code_verifier: codeVerifier,