mirror of
https://github.com/hcengineering/platform.git
synced 2026-10-14 12:15:19 +02:00
* fix(authProviders): instrument OIDC callback + redirect on fail (not 500) Production-blocking: every OIDC callback (bogus or real code) returned 500 Internal Server Error, even with valid sessions. No log was emitted between `try auth via openid` and the 500 — passport strategy threw silently before `handleProviderAuth` could log. Per Codex plan-review amendments: - Switch to explicit-callback passport.authenticate variant so strategy errors surface as err/info/status args instead of being swallowed. - Privacy-safe diagnostic shape (no raw code/state/tokens/user). Fields: stage, errMessage/Name/Stack, hasUser, infoSummary, statusCode, hasSession + sessionKeys (names only), cookie + state + code PRESENCE (lengths only), host, forwardedProto. - Permanent invalid-callback guard: ALL failures → 302 to /login with ?error=oidc_callback_failed, never 500. Same wrap covers handleProviderAuth throws. Image-tag: hardcoreeng/account:integration-wac-2026-06-21-codex-r7. Next step (P0-T2): user attempts login, we capture the actual error from the diagnostic, then ship a surgical fix in r8. Signed-off-by: Michael Uray <michael.uray@gmail.com> Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> * fix(account): admin.ts trim+lowercase on both env-set AND lookup Codex plan-review Critical: server/account/src/admin.ts:15-18 built the ADMIN_EMAILS set without normalizing, AND isAdminEmail() didn't normalize the input. Today value is clean so isAdmin works, but a future env-value like ADMIN_EMAILS=" Michael@Uray.io " would silently break admin detection (Set lookup would miss the lowercased input). Mongo backend already has this hardening (collections/mongo.ts:1141-1147). Postgres + this helper didn't. Now BOTH sides normalize: env split -> trim -> toLowerCase -> filter non-empty + warn on no-@ entries (Codex Optional: warn not reject -- too sharp for a latent fix). Lookup also trim().toLowerCase(). Standalone upstream-PR-able per Codex Q-I3. Tests cover whitespace, case-insensitive env, case-insensitive lookup, invalid-shape warn, empty/unset env, multi-entry. Signed-off-by: Michael Uray <michael.uray@gmail.com> Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> * chore(authProviders): drop cookieHeaderLen from OIDC diagnostics (E4 amendment) Codex E4 amendment (optional, low-priority): hasCookieHeader is sufficient to diagnose missing session-cookie issues; the cookieHeaderLen byte count adds no operational value and is one more PII-adjacent surface to keep an eye on. Drop it from both diagnostic paths in pods/authProviders/src/openid.ts. Signed-off-by: Michael Uray <michael.uray@gmail.com> Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> * auth(oidc): gate verbose callback diagnostics behind OIDC_DEBUG (L-AUTH-2) Bei jedem fehlgeschlagenen OIDC-Callback wurde ein umfangreicher Diag-Block (errStack/sessionKeys/host/…) auf error-Level geloggt -> anonymes Log- Flooding via wiederholter invalider Callbacks. Normalbetrieb loggt jetzt nur eine knappe Fehlerzeile; der volle Diag-Block nur mit OIDC_DEBUG=true. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> * account(admin): drop non-email ADMIN_EMAILS entries unless explicitly allowed (L-AUTH-3) Ein Tippfehler wie ADMIN_EMAILS=admin,michel@... legte bislang einen unbeabsichtigten Admin-Identifier an (Eintrag ohne @ wurde still behalten). Jetzt werden @-lose Eintraege per Default verworfen (fail-closed) und nur bei ADMIN_EMAILS_ALLOW_LOGIN_ID=true als Login-ID akzeptiert; Warnung stets. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> * auth(oidc): redirect to /login when provider auth yields no account (L-AUTH-4) handleProviderAuth liefert '' bei einem nicht aufloesbaren Login (z.B. kein Account + Signup disabled). Bisher erfolgte dann kein Redirect und next() lief ins Leere -> haengende/leere Antwort. Jetzt fail-closed: explizites 302 auf /login?error=oidc_no_account. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> * test(account): use example.com fixtures in adminEmails tests Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> * auth(oidc): retry strategy registration with capped backoff (L-AUTH-5) The account service registered the OIDC passport strategy only once at startup via a single fire-and-forget Issuer.discover(). A transient issuer outage in that one boot moment left the 'oidc' strategy permanently unregistered, producing 500 "Unknown authentication strategy" on every OpenID login until a manual container restart (observed: ~10 days). Make registration self-healing: - Extract registerOidcStrategyWithRetry(), which retries the ENTIRE registration chain (discover -> new Client -> new Strategy -> passport.use) with capped exponential backoff (5s, x2, cap 60s), unbounded. The loop only returns after passport.use succeeds, so a failure at client or strategy construction (e.g. temporarily incomplete issuer metadata during an IdP upgrade) is retried too instead of permanently disabling OIDC. discover and sleep are injectable for tests; passport is a structural { use } interface. It never rejects: the success log runs failure-safe AFTER passport.use and returns immediately, so a throwing logger can never re-enter the loop and register the strategy a second time; the failure warn is likewise guarded so a throwing sink cannot break the never-rejects contract. - Gate log flooding via shouldWarnOnAttempt(): warn on the first 5 attempts and every 60th thereafter (~24 warn lines/day at the 60s cap instead of ~1440), following the branch's L-AUTH-2 rationale; error stacks only under OIDC_DEBUG. Success logs once with the attempt count. - Wire it fire-and-forget in registerOpenid() and flip an oidcReady flag on success. The .then body is a plain boolean flip that cannot throw; a trailing .catch(() => {}) is belt-and-suspenders so this fire-and-forget chain can never surface an unhandled rejection. Routes stay registered synchronously. - During the pending window, /auth/openid responds 503 with Retry-After: 5 and a short text body instead of a 500 or a redirect (the login app ignores an error query param, and L-AUTH-4 only covers the callback path). Add the package's first tests: the retry helper in isolation (schedule, 60s cap, whole-attempt retry incl. client-construction failure, warn gating, OIDC_DEBUG both ways, a throwing success log that must not re-register, and a throwing failure warn that must not reject) and the registerOpenid wiring (synchronous route registration, 503 pending window, exactly-once strategy registration). Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> * account(admin): keep non-email ADMIN_EMAILS entries by default; strict mode opt-in Review feedback on #10931: dropping entries without "@" by default would silently revoke admin rights on upgrade for deployments that use login-id style values. The repo's own dev/tests/ws-tests compose files ship ADMIN_EMAILS=admin,${PLATFORM_ADMIN_EMAILS} and tests/prepare-pg.sh creates that `admin` account. Restore the original semantics as the default: every non-empty entry is kept (trimmed + lowercased), and entries without "@" are logged once at startup so a typo stays visible. The fail-closed behaviour becomes an explicit opt-in via ADMIN_EMAILS_STRICT=true. ADMIN_EMAILS_ALLOW_LOGIN_ID, introduced earlier on this branch and never released, is removed so there is a single switch. Empty entries stay dropped. This is a deliberate tightening compared to the original `new Set(ADMIN_EMAILS.split(','))`, which put '' into the set for any empty entry (ADMIN_EMAILS='', ',admin', 'admin,', 'admin,,ops@...') and so made isAdminEmail('') true. loginOrSignUpWithProvider passes '' when the provider returns no email, so such a login received an admin token. isAdminEmail('') is now always false. Tests: default keeps `admin` and warns, strict drops, only the exact value "true" enables strict mode, no warning for clean values, empty entries in every position never make an empty email an admin, trim/lowercase coverage unchanged. Each test saves, clears and restores ADMIN_EMAILS and ADMIN_EMAILS_STRICT so none depends on the outer environment. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> * auth(oidc): settle the passport callback wrapper on rejection; address review Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> * auth(providers): pass only provider-verified emails; abort OIDC retry on shutdown; more callback tests Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> --------- Signed-off-by: Michael Uray <michael.uray@gmail.com> Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com> Co-authored-by: Michael Uray <michaeluray@users.noreply.github.com>