Skip to content

feat(auth): sign in from guard's verified identity (COR-1083) - #4

Open
malpou wants to merge 1 commit into
mainfrom
malthe/cor-1083-fider-fork-login-through-guard-identity
Open

malpou wants to merge 1 commit into
mainfrom
malthe/cor-1083-fider-fork-login-through-guard-identity

Conversation

@malpou

@malpou malpou commented Oct 30, 2026

Copy link
Copy Markdown
Owner

Issue: Linear COR-1083 "Fider fork login through guard identity" (project "Central auth in guard", P-COR-63). This is a fork-only change, so nothing goes upstream.

When GUARD_IDENTITY_ENABLED=true, Fider trusts the identity guard verified, and stops using its own OAuth sign in. Guard is the OIDC client and owns the session. Fider checks the x-bastion-identity JWT with platform-kit edgeauth against guard's JWKS.

What changed

  • One shared rule set. handlers.SignInExternalUser now holds the OAuth callback's find-or-create and role logic, unchanged. Both OAuthToken and the new guard path use it. It finds the user by provider + uid, then by email, or creates them. It applies the allowed-roles gate and maps admin/collaborator roles on every sign in. The last administrator is never demoted.
  • middlewares.User(). With guard on, the user comes only from the verified identity (handlers.SignInGuardIdentity):
    • GUARD_IDENTITY_PROVIDER (set to the old custom provider _c144tcijpa) plus the Zitadel sub match existing accounts.
    • The email fallback is only used for linking when the email is verified.
    • fider-admin maps to administrator and fider-user to visitor. Both are env defaults (GUARD_IDENTITY_ADMIN_ROLES / _ALLOWED_ROLES / _COLLABORATOR_ROLES).
    • Refusals: no allowed role gets 403, an invalid identity 401, unreachable guard keys 503, a blocked user 401. A request without the header is anonymous.
    • Fider's auth cookie is ignored and cleared, and no cookie is minted. Identity is checked on every request, so logout and revocation take effect on the next request.
    • API keys still work where guard lets the request through.
  • Sign in and sign out go to guard. /signin redirects to /.well-known/bastion/login?return_to=<sanitised path> and /signout to /.well-known/bastion/logout. The sign-in control (modal and page) and the user menu link there directly, using settings.guardIdentity.
  • Removed: the OAUTH_CUSTOM_CLIENTID / OAUTH_CUSTOM_SECRET override of the custom provider's secret, with its admin-form lock and its tests.
  • Kept: the OIDC provider code (upstream paths plus the fork's RP), compiled but not used for sessions while guard is on. Removing it would make the fork diff bigger, and the image smoke test checks that it is there. JWT_SECRET is still needed (email verification, state).
  • Docs: docs/GUARD_IDENTITY.md, plus guard settings in .example.env.
  • Toolchain: Go 1.26, which platform-kit requires (the Dockerfile already uses 1.26.10). Added .golangci.yml, which excludes node_modules (flatted ships Go files). I fixed the 7 findings the newer linter reports: reflect.Pointer, fmt.Fprintf, and ecdsa.PublicKey.Bytes in an OIDC test.

Needs the owner: .github/workflows/build.yml still says Go 1.25 and golangci-lint v2.7.2. Bump them to 1.26 and v2.12.2. This session's token cannot push workflow changes (the workflows permission), and CI's lint fails until that is done.

Tests

  • app/middlewares/user_guard_test.go uses edgeauthtest identities. Cases:
    • existing user by provider uid
    • existing user by email (links the provider)
    • unverified email never links an account
    • new user on a private site
    • fider-admin promotes; losing it demotes, except the last admin
    • missing role gets 403
    • blocked user gets 401
    • invalid token gets 401 (garbage, wrong audience, unknown key)
    • keys unavailable gets 503
    • no identity is anonymous, and the old cookie is ignored and cleared
    • guard off ignores the header
  • Handler tests cover /signin and /signout redirects with guard on, and that a foreign redirect falls back to /.
  • Results:
    • golangci-lint v2.12.2: 0 issues. eslint: 0 errors. jest: 127 passed.
    • go test ./... -race -short against a local Postgres 16 (no Docker): all green except 3 tests that need network or MinIO here: TestGravatarHandler (gravatar.com), TestWebhookURL_AllowedAddresses (DNS; it fails on main too), and TestBlobStorage S3 (no MinIO).

Dependencies

Pins platform-kit at the COR-1076 branch commit; bump to v0.3.0 once MetsaApp/platform-kit#6 is released, before merging.

Depends on: guard COR-1074 (login, sessions), the deploy-repo guard config for this host.

Cutover

  1. Register guard's callback https://feedback.metsa.app/.well-known/bastion/callback on the fider Zitadel client. Add REFRESH_TOKEN and move fider-oidc to guard-production.
  2. Add the guard fider client and a whole-host rule (auth: required, requireRoles: [fider-admin, fider-user]). The board is private.
  3. Deploy this image with GUARD_IDENTITY_ENABLED=true, PLATFORM_KIT_EDGEAUTH_AUDIENCES=feedback.metsa.app and GUARD_IDENTITY_PROVIDER=_c144tcijpa. Drop OAUTH_CUSTOM_*.
  4. Everyone is signed out once. Afterwards, disable the _c144tcijpa provider row and drop the old redirect URI.

Generated by Claude Code

Behind guard (GUARD_IDENTITY_ENABLED), the user comes only from the
x-bastion-identity JWT, verified with platform-kit edgeauth against
guard's JWKS. The OAuth callback's find-or-create and role mapping move
into handlers.SignInExternalUser, shared by both paths: match by provider
and subject (GUARD_IDENTITY_PROVIDER keeps the old custom provider's key
so existing users keep their accounts), then by verified email, else
create; fider-admin maps to administrator on every request with the
last-administrator guard; no allowed role is 403, an invalid identity
401, unreachable keys 503. Fider's auth cookie is ignored and cleared,
none is minted. /signin, /signout, the sign-in control and the user menu
go to guard's /.well-known/bastion/ login and logout.

Drops the OAUTH_CUSTOM_CLIENTID/SECRET override of the custom provider's
secret; the OIDC provider code stays compiled. Go 1.26 (platform-kit),
golangci-lint v2.12.2 config with node_modules excluded, and the findings
the newer toolchain raises.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MwB9uUtkK8BFT7ZGoXwX9u
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants