feat(auth): add OIDC/SSO login support (e.g. Authentik) - #592
feat(auth): add OIDC/SSO login support (e.g. Authentik)#592KeplerAeroIT wants to merge 6 commits into
Conversation
Adds a generic OpenID Connect authorization-code + PKCE login flow (via openid-client) alongside the existing local email/password auth, so ConvertX can be deployed behind an identity provider like Authentik, Keycloak, or any standards-compliant OIDC issuer. - New GET /login/oidc and /login/oidc/callback routes handle the authorization redirect and token exchange, then mint the same JWT session cookie issued by password login, so every existing authenticated route works unchanged. - Users are matched by OIDC subject, auto-linked to an existing local account by email, or auto-provisioned on first login. - New OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_REDIRECT_URI, OIDC_SCOPES, OIDC_NAME and OIDC_ONLY env vars configure and optionally gate out local login entirely. - README documents the new env vars and a step-by-step Authentik provider/application setup.
There was a problem hiding this comment.
All reported issues were addressed across 12 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
… races)
- Fix the actual production bug: Elysia's cookie parser auto-JSON.parses
any cookie value shaped like {...}/[...], so the JSON-encoded oidcFlow
cookie was silently turned into an object and rejected by its own
t.String() schema before the handler ever ran. Switch to a dot-delimited
string (PKCE verifier/state/nonce are base64url, so "." is a safe
delimiter) and validate its shape explicitly instead of a bare JSON.parse.
- Store the OIDC issuer alongside sub and match on the (issuer, sub) pair,
since sub is only guaranteed unique within a single issuer.
- Only auto-link an OIDC login to an existing local account when the
provider has verified the email; an unverified/attacker-set email claim
could otherwise hijack an existing account.
- Add a unique index on (oidc_issuer, oidc_sub) and use
INSERT ... ON CONFLICT DO NOTHING so two concurrent first-logins for the
same new identity can't provision duplicate user rows.
- Move OIDC discovery into a shared helper exposing isOidcReady(), and use
it (rather than just checking that the env vars are set) to decide
whether the login page shows the SSO button - so a failed discovery at
startup doesn't advertise a provider that can never complete a login.
- Fix compose.yaml's TZ typo (Asia/Koltaka -> Asia/Kolkata), which
otherwise throws at runtime and breaks the history page's date formatting.
- Gate the Register link behind OIDC_ONLY everywhere Header is rendered,
so OIDC-only deployments don't show a dead local-registration link.
Adds a production compose file that pulls the published image and loads all configuration from a single .env file via env_file, plus a fully documented .env.example listing every environment variable ConvertX supports (auth, OIDC/SSO, app behavior, ffmpeg tuning). Lets users manage config in one place instead of inlining it into the compose file.
|
Addressed all findings from @cubic-dev-ai's review in ba87632:
Also added All changes verified with |
There was a problem hiding this comment.
All reported issues were addressed across 10 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…non-blocking discovery - openid-client derives the redirect_uri it sends to the token endpoint from the callback request's own URL, stripped of query/hash - not from the configured OIDC_REDIRECT_URI. Behind a reverse proxy or tunnel that terminates TLS and forwards to the origin over plain HTTP (Cloudflare Tunnel, nginx, etc.), the app sees "http://" even though the browser used "https://", so the redirect_uri sent to the token endpoint never matches what's registered with the provider, and the provider rejects the exchange. Build the callback URL from the trusted, admin-configured OIDC_REDIRECT_URI instead, carrying over only the real request's query string (code, state). - Move OIDC discovery off the module's top-level await and into a fire-and-forget retry loop. discovery()'s own default timeout is 30s, so an unreachable/stalled issuer was delaying the entire server from binding its port and failing health checks - including for deployments not otherwise depending on OIDC being up yet. isOidcReady() now reflects completion, and a transient IdP outage recovers automatically via retry without needing a container restart.
|
Two more fixes in e62830c:
Both verified against a real Authentik instance / real proxy setup, plus |
@KeplerAeroIT Both look right to me. The redirect_URI fix is the kind of thing you only catch when you actually run it through a real TLS-terminating proxy — The discovery fix is also solid. Blocking server startup on a 30s timeout (the default for Good catches, clean fixes. Tip: get faster answers by chatting with cubic’s review copilot in the review UI |
Fixes the lint:prettier failures flagged by CI.
Summary
Adds OpenID Connect (OIDC) login as an alternative/companion to the existing local email+password auth, so ConvertX can be deployed behind an identity provider such as Authentik, Keycloak, Zitadel, or any standards-compliant OIDC issuer — useful for orgs that want centralized login/SSO instead of managing separate ConvertX accounts.
It uses the standard authorization-code flow with PKCE (via
openid-client) and slots in alongside the existing session system: a successful OIDC login mints the exact same JWT cookie that password login does, so every existing authenticated route (history, conversions, account, etc.) works completely unchanged.How it works
GET /login/oidc— starts the flow (PKCE verifier/state/nonce stored in a short-lived, scoped cookie) and redirects to the provider.GET /login/oidc/callback— exchanges the code, validates state/nonce, reads the ID token claims (falling back to the userinfo endpoint if the email claim isn't in the ID token), then:sub,/loginpage shows a "Login with{OIDC_NAME}" button below the existing form (separated by a divider) whenever OIDC is configured.Configuration
All new behavior is opt-in and controlled entirely by environment variables — nothing changes for existing deployments that don't set them.
OIDC_ISSUERhttps://authentik.example.com/application/o/convertx/OIDC_CLIENT_IDOIDC_CLIENT_SECRETOIDC_REDIRECT_URIhttps://convertx.example.com/login/oidc/callbackOIDC_SCOPESopenid profile emailOIDC_NAMESSOAuthentikOIDC_ONLYfalseSetting
OIDC_ISSUER,OIDC_CLIENT_ID,OIDC_CLIENT_SECRET, andOIDC_REDIRECT_URItogether enables the button; nothing else needs to change.Example (Authentik)
https://convertx.example.com/login/oidc/callback, and bind it to an Application.Full walkthrough and the env var table are added to the README.
Database
Adds a nullable
oidc_subcolumn to theuserstable (schema version bumped to 2, with an automatic migration for existing installs — no action needed on upgrade).Testing
tsc --noEmit/eslint— no new errors introduced (verified against the existing pre-existing warnings/errors baseline).GET /login/oidcredirects to Authentik with correctclient_id/PKCE challenge/state/nonce/redirect_uri, and a full login round-trip (including auto-provisioning the first user) works end-to-end.OIDC_ONLYgate hides local login/registration and bypasses the first-run local setup page, and that the hybrid case (local form + SSO button, separated by a divider) renders correctly.Notes
OIDC_ONLYis not set, existing local accounts keep working alongside SSO, and accounts are linked by email on first SSO login.Summary by cubic
Add OpenID Connect (OIDC) SSO login alongside email/password using Authorization Code + PKCE via
openid-client. It issues the same JWT cookie, auto-migrates the DB to v3, and adds a prod compose +.env.examplefor simpler config.New Features
/login/oidcand/login/oidc/callback.OIDC_ONLY=truehides the local form and Register links..env.exampleanddocker-compose.prod.yml; README documents OIDC setup and env vars.Bug Fixes
OIDC_REDIRECT_URI.compose.yaml(Asia/Kolkata) to avoid runtime errors.Written for commit eede2cb. Summary will update on new commits.