Skip to content

fix(oauth2): harden identity linking, tokens and flow security - #133

Merged
m-t-a97 merged 7 commits into
mainfrom
fix/oauth2
Nov 4, 2026
Merged

m-t-a97 merged 7 commits into
mainfrom
fix/oauth2

Conversation

@m-t-a97

@m-t-a97 m-t-a97 commented Nov 4, 2026

Copy link
Copy Markdown
Member

Summary

Hardens the OAuth2 plugin to prevent account takeover and fixes token persistence, flow validation, provider configuration, and response handling.

Security

  • Provider account identity is now authoritative. An OAuth identity always resolves to the account owner and cannot switch to a user matched only by email.
  • Auto-linking by email requires a provider-asserted verified email. Unverified provider emails never link an existing user.
  • Authenticated callbacks now exchange the code and link the provider account instead of returning early.
  • OAuth token writes are account-scoped, encrypted at write time, preserve stored refresh tokens and expiry when omitted, and reject expired tokens.
  • Added provider-bound, signed, single-use state and PKCE end to end.
  • redirect_to is validated against trusted origins and relative paths.
  • OAuth2 flow cookies use configuration for Secure, Domain, and SameSite and are cleared on every terminal path.
  • Errors use typed HTTP status mapping and no longer leak internal or provider details.
  • Callback responses no longer expose the session token, IP address, or user agent.
  • Provider HTTP calls now have a timeout and a 1 MiB response limit.

Added

  • AccountService.UpdateOAuth2Tokens for account-scoped token persistence.
  • UserInfo.EmailVerified populated from provider data.
  • GenericProvider registration with configurable endpoints, scopes, and field mappings.
  • ProviderConfig.Enabled (*bool), provider HTTP timeout, and email verification field mapping.
  • OAuth2 route mappings in the sample config.toml.

Changed

  • ProviderConfig.Enabled is a pointer: absent means enabled, explicit false disables it.
  • Environment credentials override only client ID and secret; partial environment credentials fail at startup.
  • Provider IDs are canonicalized to lower case; missing or malformed account IDs are rejected.
  • Provider registration is deterministic, and use cases are built once during Init.
  • Callback responses use a safe session DTO instead of the full session model.
  • Error statuses changed from 400 to 401, 404, 409, 422, 502, or 500.

Removed

  • Unimplemented RefreshRequest, RefreshResponse, LinkAccountRequest, LinkAccountResponse, RefreshResult, and LinkAccountResult types.
  • Unused OAuth2 event constants, eventBus wiring, VerifyHMAC, IsProviderEnabled, and handler constructors.

Migration

  • In-flight OAuth2 flows are invalidated by the cookie and state format changes (maximum five minutes).
  • Audit and relink or delete account rows with account_id values of '', '<nil>', or scientific notation.
  • Force reauthentication for accounts whose stored tokens cannot be decrypted.
  • Clients reading session.token from the callback JSON must use the session cookie instead.

@m-t-a97 m-t-a97 self-assigned this Nov 4, 2026
- Normalize user and email-account emails and account IDs on write and lookup.
- Add table-driven repository and service tests for email normalization.
- Harden OAuth2 callback identity resolution, session reuse, token persistence, and redirect handling.
- Add handler-facing OAuth2 use case interfaces and expand callback test coverage.
- Store OAuth2 use cases as interfaces instead of pointers to interfaces.
- Refactor callback SQLite integration tests into a table-driven test.
- Remove redundant provider implementation comments.
S256ChallengeOption already hashes the value it receives, so passing the pre-computed challenge sent a double-hashed code_challenge while the token exchange sent the raw verifier. Providers enforcing PKCE (Discord, Google) rejected the exchange with invalid_grant "Code challenge failed.".

GeneratePKCEVerifier now only produces the verifier and lets x/oauth2 derive the challenge, and the authorize test asserts the challenge matches the signed verifier cookie.
GitHub now documents code_challenge (S256) on the authorization request and code_verifier on the token exchange for both OAuth apps and GitHub Apps, so enable PKCE ahead of the OAuth 2.1 transition.
@m-t-a97
m-t-a97 merged commit bc98ae8 into main Nov 4, 2026
6 checks passed
@m-t-a97
m-t-a97 deleted the fix/oauth2 branch November 4, 2026 13:40
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.

1 participant