feat(19-03): wire /callback link branch, OIDC-guard skip, de-Authelia comments

- /callback: reads signed state, extracts linkUserId, calls linkOidcToUser after OIDC session set; OidcLinkConflictError redirects to /?error=oidc-link-conflict
- OIDC guard: oidcAuthMiddleware() factory called once at construction, handler invoked per-request inside skip-when-user-set wrapper (D-03)
- middleware.ts: de-Authelia-ize comments — generic OIDC identity provider language (D-06, AUTH-LOCAL-18)
- localAuthMiddleware.ts: cast to typeof DEV_USER for ContextVariableMap type compatibility
- All 446 tests pass; typecheck clean
This commit is contained in:
Lucas Berger
2026-06-17 16:59:06 -04:00
parent c437f408bb
commit 9b569efeab
3 changed files with 76 additions and 11 deletions
+4 -2
View File
@@ -33,6 +33,7 @@ import { eq } from 'drizzle-orm';
import { db } from '../db/client.js';
import { users } from '../db/schema.js';
import { verifyLocalSessionCookie } from './localSession.js';
import type { DEV_USER } from './devBypass.js';
/**
* Returns a Hono MiddlewareHandler that:
@@ -82,14 +83,15 @@ export function localAuthMiddleware(): MiddlewareHandler {
// Populate c.get('user') with the same shape as DEV_USER (devBypass.ts ContextVariableMap).
// oidcIss/oidcSub: local users have nullable oidcIss/oidcSub — use fallback strings so the
// shape matches typeof DEV_USER (all required fields, no undefined in the value object).
// shape is compatible with typeof DEV_USER at runtime. Cast required because ContextVariableMap
// is narrowed to the const DEV_USER literal type.
c.set('user', {
id: row.id,
oidcIss: row.oidcIss ?? 'local',
oidcSub: row.oidcSub ?? String(row.id),
displayName: row.displayName ?? null,
color: row.color ?? '#4A90D9',
});
} as typeof DEV_USER);
await next();
};
+4 -4
View File
@@ -1,12 +1,12 @@
/**
* OIDC authentication middleware wiring.
*
* Configures @hono/oidc-auth for Authelia as the identity provider.
* Configures @hono/oidc-auth for the generic OIDC identity provider (D-06).
*
* Required env vars:
* OIDC_AUTH_SECRET — 32+ char random string for JWT cookie signing (T-02-03)
* OIDC_ISSUER — Authelia base URL (middleware fetches /.well-known/openid-configuration)
* OIDC_CLIENT_ID — registered client ID in Authelia
* OIDC_ISSUER — OIDC issuer URL (middleware fetches /.well-known/openid-configuration)
* OIDC_CLIENT_ID — registered client ID at the OIDC provider
* OIDC_CLIENT_SECRET — PLAIN text secret (NOT the pbkdf2 hash — see Pitfall 7)
* OIDC_REDIRECT_URI — https://familysync.<domain>/callback
* OIDC_AUTH_EXTERNAL_URL — https://familysync.<domain> — MANDATORY behind Pangolin (Pitfall 1)
@@ -30,7 +30,7 @@
* Every OIDC_AUTH_REFRESH_INTERVAL (default 15 min) the middleware calls
* the token endpoint with the stored refresh token — no iframe required (D-12).
* Session lifespan is governed by OIDC_AUTH_EXPIRES (default 1 day) and
* Authelia's refresh_token_lifespan.
* the OIDC provider's refresh_token_lifespan.
*
* Scopes: openid, profile, email only — no 'groups' scope (D-11).
*