Skip to content

feat(auth): fine-grained OAuth scope grants and consent records (#250) - #768

Merged
mathrmartins merged 12 commits into
developfrom
feature/issue-250-oauth-scopes-20261010
Oct 10, 2026
Merged

mathrmartins merged 12 commits into
developfrom
feature/issue-250-oauth-scopes-20261010

Conversation

@mathrmartins

Copy link
Copy Markdown
Collaborator

Fixes #250

(Supersedes #760/#767 — identical commits; GitHub was not delivering pull_request events on the previous branch name, so this PR comes from a renamed branch.)

What

Records and manages fine-grained OAuth scope consent per external login link:

  • Model: ExternalLogin gains GrantedScopes (JSON array, single-sourced via ExternalLoginGrants), ConsentedAt, ConsentVersion (OAuthConsentVersions.Current = 1). Additive migration 20261009184414_AddExternalLoginScopeConsent (no child table needed — the link row is the consent record, the cleaner EF shape for the existing model).
  • Recording actual granted scopes: OAuthService.ResolveAuthorizationScopes single-sources the exact scope list authorization URLs embed (defaults per provider, configured scopes override, Microsoft unions required OIDC scopes). Link handlers (Google/Discord), the GIS sign-in path, and every OAuth sign-in auto-link path persist it with a consent stamp; unchanged re-authorizations preserve the first-consent stamp, changed scope sets re-record consent.
  • Surfacing: HEAD /v1/auth/external-logins now also emits X-Granted-Scopes: provider=iso-consent-timestamp|consent-version|url-encoded-scopes. New GET /auth/external-logins/{provider}/link-preview (authorization-time consent screen data). Typed client regenerated; web connected-accounts card shows consent date/terms version/scope chips (en-US/pt-BR strings, parity tests green).
  • Revocation: POST /auth/external-logins/{provider}/scopes:revoke — per-scope, idempotent, keeps the first-consent stamp, audited via structured log. Whole-provider DELETE /auth/external-logins/{provider} is documented as the full revocation (endpoint description + UI copy).

CI repair commit (vs #760's first run)

  • Handlers test: ResolveAuthorizationScopes mock setups now match provider case-insensitively (link-preview passes the caller's casing; exact-match setup returned Moq's empty-array default for "Discord").
  • Repository round-trip test: removed a stray prose string leaked into the expected list; the input now includes a real third scope token so order preservation is genuinely asserted.
  • connected-accounts-card: async onClick handlers wrapped in void arrows (@typescript-eslint/no-misused-promises, 2 errors).

Tests

  • GameGuild.Identity.Authentication.UnitTests: +82 tests across controller (22), handlers (29), repository (13), service (18). First CI run: 2630/2632 passed — the 2 failures were the test defects fixed above (production code unchanged).
  • Web: external-login-consent.test.ts 7/7 + locale-parity suite green locally; full web suite had zero failures across all executed files.
  • Local: gated full solution build clean (0 warnings / 0 errors). Full API/web suites gated by CI here (local dotnet test runner is on environment hold).

Note on Codacy Static Code Analysis: standing external analysis (non-required) that also failed on previously merged PRs #743/#750 — documented per #740 precedent.

- ExternalLogin gains GrantedScopes (JSON array), ConsentedAt and
  ConsentVersion (OAuthConsentVersions.Current = 1); additive migration.
- OAuthService.ResolveAuthorizationScopes single-sources the exact scope
  list authorization URLs embed; link (Google/Discord), callback and
  sign-in auto-link paths persist it with a consent stamp, re-recording
  consent only when the scope set changed (first-consent preserved).
- HEAD /v1/auth/external-logins now also emits X-Granted-Scopes:
  'provider=iso-consent-timestamp|consent-version|url-encoded-scopes'.
- New GET /auth/external-logins/{provider}/link-preview (authorization-
  time consent screen data) and POST .../scopes:revoke (per-scope
  revocation, idempotent, audited via structured log; unlink remains the
  documented whole-provider full revocation).
- Typed client regenerated; web connected-accounts card shows the consent
  record (date, terms version, scope chips) with per-scope revoke wired
  to a server action; en-US/pt-BR strings added.
- Tests: persistence round-trip, callback/link recording, list shape,
  revocation (handler/controller/service/repository), header shape, and
  web consent-header parser unit tests.
…goal-20261009

# Conflicts:
#	packages/infrastructure/client/src/generated/.metadata.json
- Handlers test: ResolveAuthorizationScopes mock setups now match the
  provider case-insensitively, mirroring the real OAuthService (the
  link-preview handler passes the caller's casing through); the previous
  exact-match setup returned Moq's empty-array default for 'Discord'.
- Repository round-trip test: expected list contained a stray prose
  string ('JSON list must round-trip in order') leaked into the
  assertion; replaced with a real third scope token included in the
  serialized input so order preservation is genuinely asserted.
- connected-accounts-card: wrap async onClick handlers in void arrows
  to satisfy @typescript-eslint/no-misused-promises (2 errors).
Copilot AI balanced review requested due to automatic review settings October 10, 2026 02:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codacy-production

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 7 medium

Alerts:
⚠ 7 issues (≤ 0 issues of at least minor severity)

Results:
7 new issues

Category Results
BestPractice 7 medium

View in Codacy

🟢 Metrics 146 complexity · 6 duplication

Metric Results
Complexity 146
Duplication 6

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

…goal-20261009

# Conflicts:
#	packages/infrastructure/client/src/generated/.metadata.json
After merging develop (billing webhook + compliance endpoints landed while
this branch was in review), the OpenAPI capture was re-run locally against
the merged tree (Release publish + swagger capture + pnpm generate --force).
The generated .gen.ts outputs are byte-identical to the merge result; only
the capture hash/timestamp in .metadata.json changed.
The connected-accounts-card async onClick handlers are now wrapped in void
arrows, so the file no longer triggers the rule and ESLint's unused-
suppression check (pass-on-unpruned-suppressions off) fails the lint gate.
…goal-20261009

# Conflicts:
#	packages/infrastructure/client/src/generated/.metadata.json
Develop moved again during CI (#758 remember-me regenerated the surface);
re-ran the local Release publish + swagger capture + pnpm generate --force
against the merged tree. Only the capture hash and the remember-me types
from develop's side carry over.
@mathrmartins
mathrmartins merged commit e0a6e74 into develop Oct 10, 2026
22 of 23 checks passed
@mathrmartins
mathrmartins deleted the feature/issue-250-oauth-scopes-20261010 branch October 10, 2026 05:07
tolstenko pushed a commit that referenced this pull request Oct 10, 2026
…pes-20261010

feat(auth): fine-grained OAuth scope grants and consent records (#250)
mathrmartins added a commit that referenced this pull request Oct 11, 2026
…pes-20261010

feat(auth): fine-grained OAuth scope grants and consent records (#250)
mathrmartins added a commit that referenced this pull request Oct 11, 2026
…pes-20261010

feat(auth): fine-grained OAuth scope grants and consent records (#250)
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.

[Enhancement]: Fine-Grained OAuth Scope & Consent Screen Management

2 participants