Legend: [ ] Incomplete, [X] Complete
Evidence for every completed checklist item must include the exact verification command (wrapped with backticks) plus its exit code and artifacts (logs, screenshots, .scratch transcripts) directly beneath the item when the work is performed.
Deliver a Google-compatible OAuth 2.0 / OIDC provider surface (auth, token, userinfo, revoke, JWKS, discovery) that apps can target via GOOGLE_CLIENT_ID/SECRET/ENDPOINT, while brokering identity through Okta and offering an admin UI + YAML-driven registry for client apps. Success means drop-in behavior with google-auth SDKs, enforceable redirect/source policies, PKCE + offline refresh support, and first-class CRUD for app credentials inside DTU.
Client apps need a Google-like OAuth provider for end-to-end flows without touching real Google. Today there is no DTU-hosted emulator tied to Okta identities, no way to manage client IDs/secrets centrally, and no consent/userinfo surface that mirrors Google claims. We must expose the Google protocol surface, keep compatibility with SDKs, and let operators manage apps via YAML and UI while enforcing redirect/source allowlists.
- Mirror Google endpoints and discovery docs while re-signing tokens locally and mapping Okta claims to Google-shaped payloads (
sub,email,email_verified,hd,picture). - Registry-driven app definitions (YAML + UI CRUD) with auto-generated client IDs/secrets, allowed redirect/source URL enforcement, and JWKS rotation.
- State layer for auth codes, access tokens, and refresh tokens with rotation, revocation, PKCE enforcement, and deterministic fixtures for tests.
- Admin UI for listing/creating/updating/revoking apps, viewing audit logs, and exporting client configs.
a0cf42111 docs(sprint-047): log phase1 progressupdates this sprint ledger without touching code, so implementation status still hinges on earlier adapter work.544279b50 docs(googleoauth): document device + admin flowsextends the how-to guide for device + admin APIs, but the UI/admin stack is still unimplemented.77de4e99d feat(googleoauth): expand oauth surfacedelivers large additions tointernal/services/googleoauth, covering auth/token/device/tokeninfo handlers; contract harness scripts, Okta plumbing, and admin UX remain missing.c5304fa00 Hanging chad detected: phase0 scaffolding, jwks rotation, registry validationtightens docs/tests plus JWKS rotation but explicitly calls out remaining gaps such as deterministic config hydration and stricter loader enforcement.dfef92982 docs(googleoauth): update sprint 47 Google OAuth docsrefreshes this plan baseline; outstanding checklist items are still open until evidence + code land.
This plan targets the unchecked deliverables after commits a0cf42111..dfef92982. Follow the tracks below in order so prerequisites land before dependent UX + Okta work. Each item lists required coverage plus the exact commands to record once complete.
Execution order: Track A → Track B → Track C → Track D → Track E.
-
A1 – Deterministic bootstrap + adapter audit
- Scope: review
cmd/dtu/serve.go,internal/app/bootstrap/bootstrap.go,internal/infra/memory/service_catalog.go, anddtu.example.ymlto guarantee every tenant wires the google-oauth adapter and to fail fast with a descriptive error whenever the stanza is missing; document guardrails in.scratch/notes/sprint-047-baseline.md. - Positive tests:
timeout 40 go test ./internal/app/... -run GoogleOAuth(exit 0;.scratch/verification/SPRINT-047/track-a/bootstrap/go-test-internal-app-googleoauth.log).timeout 40 go test ./internal/infra/memory -run ServiceCatalog(exit 0;.scratch/verification/SPRINT-047/track-a/bootstrap/go-test-internal-infra-memory-servicecatalog.log).
- Negative tests:
.scratch/fixtures/dtu-missing-google-oauth.ymlis rejected viatimeout 40 go run ./cmd/dtu serve --definitions .scratch/fixtures/dtu-missing-google-oauth.yml --listen 127.0.0.1:0(captures non-zero exit;.scratch/verification/SPRINT-047/track-a/bootstrap/bootstrap-failure.log).
- Evidence: guardrails + failure logs stored under
.scratch/verification/SPRINT-047/track-a/bootstrap.
- Scope: review
-
A2 – Router/config completion + deterministic client secrets
- Scope: finish router scaffolding + config structs in
internal/services/googleoauthandinternal/defs, add auto-generation forclient_id/client_secret, enforce redirect/source validation at load, and enable structured logging toggles. - Positive tests:
timeout 40 go test ./internal/defs(exit 0;.scratch/verification/SPRINT-047/track-a/config/go-test-internal-defs.log).timeout 40 go test ./cmd/dtu -run TestApplyDefinitions_OktaSeeded(exit 0;.scratch/verification/SPRINT-047/track-a/config/go-test-cmd-dtu-applydefs.log).
- Negative tests:
.scratch/tests/config_generation.sh(exit 0;.scratch/verification/SPRINT-047/track-a/config/config-generation.log) drives invalid ftp/file origins to prove loader rejection.
- Verification commands:
timeout 180 make -j10 build(exit 0;.scratch/verification/SPRINT-047/track-a/config/make-build.log).timeout 180 make -j10 test(exit 0;.scratch/verification/SPRINT-047/track-a/config/make-test.log).
- Evidence: deterministic config changes + command logs archived under
.scratch/verification/SPRINT-047/track-a/config.
- Scope: finish router scaffolding + config structs in
-
B1 – Multi-language google-auth contract scripts
- Scope: add
.scratch/tests/google-auth-go.sh,.scratch/tests/google-auth-node.js, and.scratch/tests/google-auth-python.pythat drive PKCE + offline refresh + hybrid response types against localhost emulator endpoints; scripts must emit step-by-step artifacts for evidence logs. - Positive tests:
- Each language completes
code+PKCE,code token, andcode id_tokenflows, validates ID tokens via JWKS, and hits/userinfoplus/oauth2/v3/tokeninfo. - Go script also exercises the device authorization happy path to align with the documented
/device/codebehavior.
- Each language completes
- Negative tests:
- Re-run scripts with intentionally invalid client IDs or blocked redirects to confirm Google-style
invalid_client/redirect_uri_mismatcherrors. - Force missing PKCE verifier to confirm
invalid_grantresponses alongside structured logs.
- Re-run scripts with intentionally invalid client IDs or blocked redirects to confirm Google-style
- Verification commands:
perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh perl -e 'alarm 40; exec @ARGV' node ./.scratch/tests/google-auth-node.js perl -e 'alarm 40; exec @ARGV' python3 ./.scratch/tests/google-auth-python.py - Evidence:
perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh(exit 0;.scratch/verification/SPRINT-047/track-b/sdk-contracts/google-auth-go.log)perl -e 'alarm 40; exec @ARGV' node ./.scratch/tests/google-auth-node.js(exit 0;.scratch/verification/SPRINT-047/track-b/sdk-contracts/google-auth-node.log)perl -e 'alarm 40; exec @ARGV' python3 ./.scratch/tests/google-auth-python.py(exit 0;.scratch/verification/SPRINT-047/track-b/sdk-contracts/google-auth-python.log)- Helper binary reused across scripts (
.scratch/bin/google_oauth_standalone) to keep runtimes <10s.
- Scope: add
-
B2 – Negative matrix + telemetry probes
- Scope: create
.scratch/tests/google-auth-negatives.shthat covers blocked redirect/source origins, refresh reuse after rotation, revoked tokens hitting/userinfo, JWT-bearer assertion failures, and tokeninfo parameter validation; wire counters/structured logs so operators can triage failures quickly. - Positive tests:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestAuthEndpointHybridMatrixensures instrumentation preserves positive flows.
- Negative tests:
- Negative script captures HTTP status + JSON error bodies for each scenario and verifies metrics increments via
/metrics. - Table-driven unit tests validate
tokeninforejects requests missing bothaccess_tokenandid_token.
- Negative script captures HTTP status + JSON error bodies for each scenario and verifies metrics increments via
- Verification commands:
perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-negatives.sh perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestAuthEndpointHybridMatrix - Evidence:
perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-negatives.sh(exit 0;.scratch/verification/SPRINT-047/track-b/negatives/google-auth-negatives.log)perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestAuthEndpointHybridMatrix(exit 0;.scratch/verification/SPRINT-047/track-b/negatives/go-test-hybrid-matrix.log)
- Scope: create
-
C1 – Okta authorize/token client wiring
- Scope: exercised the Okta-backed authorize/token handlers end-to-end (code, PKCE, refresh) plus new resilience coverage for unreachable peers while keeping Google-shaped responses wired through the store.
- Positive tests:
./.scratch/tests/okta-happy.sh(exit 0;.scratch/verification/SPRINT-047/track-c/okta/okta-happy.log) drivesTestOktaAuthorizationCodeExchangeto prove Okta profile claims land in ID/userinfo payloads.
- Negative tests:
./.scratch/tests/okta-mfa.sh(exit 0;.scratch/verification/SPRINT-047/track-c/okta/okta-mfa.log) re-runs the harness with a missing Okta principal to simulate MFA/denied login flows and assertaccess_deniedpropagation../.scratch/tests/okta-downtime.sh(exit 0;.scratch/verification/SPRINT-047/track-c/okta/okta-downtime.log) exercises the newTestOktaAuthorizationCodeExchangeDowntimeto confirm transport errors from Okta bubble up as actionable responses.
- Evidence: harness logs for happy path + denial + downtime plus the updated unit suite live under
.scratch/verification/SPRINT-047/track-c/okta.
-
C2 – Consent UI + incremental scope tracking
- Scope: validated the embedded consent screen end-to-end (manual approval, denial, and post-grant reuse) by spinning up a consent-specific emulator with auto-consent disabled via the new harness and driving both the UI and unit coverage.
- Positive tests:
cd playwright && perl -e 'alarm 40; exec @ARGV' npx playwright test ../.scratch/tests/consent_happy.spec.ts --config ../.scratch/tests/playwright.config.ts(exit 0;.scratch/verification/SPRINT-047/track-c/consent/consent-happy-playwright.log) proves the UI renders app metadata and returns an auth code with the originalstate.perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestConsent -count=1 -v(exit 0;.scratch/verification/SPRINT-047/track-c/consent/go-test-consent-suite.log) exercisesTestConsentPromptFlow,TestConsentPromptNoneRequiresGrant, andTestConsentDenialto cover server-side grant persistence.
- Negative tests:
cd playwright && perl -e 'alarm 40; exec @ARGV' npx playwright test ../.scratch/tests/consent_denied.spec.ts --config ../.scratch/tests/playwright.config.ts(exit 0;.scratch/verification/SPRINT-047/track-c/consent/consent-denied-playwright.log) validates the Deny path returnsaccess_deniedwith the original state.perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestConsentPromptFlow -count=1 -v(exit 0;.scratch/verification/SPRINT-047/track-c/consent/go-test-consent-prompt.log) ensures stateful sessions still require CSRF tokens.
- Evidence: Playwright artifacts plus go-test logs stored under
.scratch/verification/SPRINT-047/track-c/consent.
-
C3 – Claim mapping + audit logging
- Scope: normalized user profiles (fallback
sub,email,hd,name,picture) before issuing tokens/userinfo responses and added an in-memory audit log (grant/deny/revoke) exposed via/a/google-oauth/audit. - Positive tests:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestClaimMappingFallbacks -count=1 -v(exit 0;.scratch/verification/SPRINT-047/track-c/claims/go-test-claim-mapping.log) verifies fallback claims appear in both ID tokens and/userinfo../.scratch/tests/audit_log_probe.sh(exit 0;.scratch/verification/SPRINT-047/track-c/claims/audit-log-probe.log) drives consent approve/deny flows and fetches/a/google-oauth/auditto confirm ordered entries.
- Negative tests:
./.scratch/tests/audit_log_negative.sh(exit 0;.scratch/verification/SPRINT-047/track-c/claims/audit-log-negative.log) ensures audit data is rejected without a valid admin token.
- Evidence: claim-mapping go test output plus audit probe logs stored in
.scratch/verification/SPRINT-047/track-c/claims.
- Scope: normalized user profiles (fallback
-
C4 – Session persistence + replay protection
- Scope: persist auth codes + refresh tokens bound to Okta session IDs, PKCE challenge, redirect URI, and expiry; add replay detection, rotation records, and snapshot/restore hooks.
- Positive tests:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestSessionIsolation -count=1 -v(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-isolation.log) covers concurrent issuance + isolation.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/session_snapshot.sh(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-replay.log) exports/imports snapshots and rehydrates pending auth codes.
- Negative tests:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestSessionReplayFailure -count=1 -v(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-replay-failure.log) enforces replay protection and error payloads.perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestAuthorizationCodeSingleUse -count=1 -v(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-auth-code-single-use.log) plusperl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestSessionReplay -count=1 -v(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-replay.log) cover single-use codes + refresh rotation failures.
- Evidence: Session logs and scripts stored under
.scratch/verification/SPRINT-047/phase2/sessions/(auth-code-single-use, refresh reuse, session snapshot/replay, replay failure) plus repo-widetimeout 180 make -j10 build/testruns archived in the same directory.
-
D1 – Admin UI table + forms (P3-2 + UI acceptance criteria)
- Scope: implement Go-embedded UI assets to list/create/update/delete/rotate OAuth apps with pagination, row selection, copy-to-clipboard actions, warning icons, inline validation, and parity with the Google UX described earlier.
- Positive tests:
- Manual walkthrough captured in
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.mdexercises pagination, selection, warning icons, copy, create/update/delete, and secret rotation flows exactly as rendered in/a/google-oauth. timeout 40 .scratch/tests/admin_api_e2e.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-happy.log) verifies backend CRUD/API parity leveraged by the embedded UI (create → auth/token → refresh → revoke/userinfo).
- Manual walkthrough captured in
- Negative tests:
timeout 40 .scratch/tests/admin_api_e2e.sh --scenario revoked-secret(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-revoked-secret.log) rotates client secrets and confirms the old secret is rejected end-to-end.timeout 40 .scratch/tests/admin_api_e2e.sh --scenario deleted-app(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-deleted-app.log) deletes the app and proves subsequent auth/token attempts fail with Google-style errors.
- Verification commands:
timeout 40 .scratch/tests/admin_api_e2e.sh --scenario happytimeout 40 .scratch/tests/admin_api_e2e.sh --scenario revoked-secrettimeout 40 .scratch/tests/admin_api_e2e.sh --scenario deleted-app
- Evidence: manual UI notes under
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.mdplus admin API e2e logs in.scratch/verification/SPRINT-047/phase3/admin-api/.
-
D2 – Metrics, health, and runbooks (P3-3)
- Scope: emit Prometheus counters/histograms for auth/token/revoke outcomes, expose
/metrics+/healthz, add snapshot hooks for registry/session stores, and expanddocs/operators/google-oauth.mdwith troubleshooting + runbooks. - Positive tests:
timeout 40 go test ./internal/services/googleoauth -run TestHealthEndpoint -count=1 -v(exit 0;.scratch/verification/SPRINT-047/phase3/ops/go-test-health-endpoint.log) covers both OK + degraded/healthzstates after the new handler was wired up.timeout 40 ./.scratch/tests/health_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase3/ops/health-probe.log) boots the standalone emulator, curls/healthz, and verifies Prometheus emitsdtu_googleoauth_auth_requests_totalafter exercising/o/oauth2/v2/auth.timeout 40 ./.scratch/tests/operators_runbook.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase3/hardening/operators-runbook-happy.log) lints the newdocs/operators/google-oauth.mdrunbook to ensure all required sections exist and no banned placeholders remain.- Manual smoke instructions updated in
docs/operators/google-oauth.mdreference.scratch/google_oauth_smoke.shand the new health probe so operators can self-serve checks.
- Negative tests:
timeout 40 ./.scratch/tests/metrics_failure.sh(exit 0;.scratch/verification/SPRINT-047/phase3/ops/metrics-failure.log) fetches/metrics, terminates the emulator, and records the expected connection-refused error without panics.timeout 40 ./.scratch/tests/operators_runbook.sh --scenario lint(exit 2 as expected;.scratch/verification/SPRINT-047/phase3/hardening/operators-runbook-lint.log) intentionally checks for a missing heading to prove the lint script fails loudly when the runbook drifts.
- Evidence: health/metrics probe logs live under
.scratch/verification/SPRINT-047/phase3/ops/and the operator runbook plus lint artifacts sit in.scratch/verification/SPRINT-047/phase3/hardening/; the runbook itself isdocs/operators/google-oauth.md.
- Scope: emit Prometheus counters/histograms for auth/token/revoke outcomes, expose
-
E1 – Go e2e suite + smoke script (P3-4 + API-driven matrix)
- Scope: add
e2e/google_oauth_e2e_test.gothat covers PKCE happy path, refresh rotation, revoke, blocked redirect, revoked client secret, and deleted app scenarios; add.scratch/google_oauth_smoke.shfor manual smoke + CI artifact referencing admin API-created apps. - Positive tests:
timeout 40 go test ./e2e -run TestGoogleOAuthEndToEnd -v.- Smoke script drives auth → token → refresh → revoke → userinfo sequence and stores JSON payloads per step.
- Negative tests:
- E2E test ensures mismatched redirect returns
redirect_uri_mismatchand revoked secret returnsinvalid_client. - Smoke script validates revoked access token is denied at
/userinfo.
- E2E test ensures mismatched redirect returns
- Verification:
timeout 40 go test ./e2e -run TestGoogleOAuthEndToEnd -v(exit 0;.scratch/verification/SPRINT-047/phase3/e2e/go-test-google-oauth.log)perl -e 'alarm 40; exec @ARGV' bash ./.scratch/google_oauth_smoke.sh(exit 0;.scratch/verification/SPRINT-047/phase3/e2e/google-oauth-smoke-run.log)
- Scope: add
-
E2 – Release artifacts, docs, and acceptance sign-off (P3-5 + Phase 1/2/3 acceptance)
- Scope: update
dtu.*.ymlsamples with multiple app entries, embed admin UI screenshots, extenddocs/howto/google-oauth.md+docs/operators/google-oauth.mdwith env overrides + runbooks, ensure every acceptance criterion replaces{placeholder ...}with verification narratives and exit codes, and link to.scratchevidence. - Positive tests:
rg GOOGLE_CLIENT_ID docs/howto/google-oauth.md(exit 0;.scratch/verification/SPRINT-047/phase3/release/rg-google-client-id.log) shows the documented env overrides/snippets.ls dtu.*.yml | xargs rg "google-oauth"(exit 0;.scratch/verification/SPRINT-047/phase3/release/rg-sample-configs.log) confirms every sample config contains the google-oauth service stanza.
- Negative tests:
perl -e 'alarm 40; exec @ARGV' rg "T[O]DO" docs/sprints/SPRINT-047-google-oauth.md(exit 1 as expected;.scratch/verification/SPRINT-047/phase3/release/rg-no-todo.log) proves the sprint doc no longer contains T[O]DO/placeholder text.timeout 40 ./.scratch/tests/docs_lint.sh(exit 0;.scratch/verification/SPRINT-047/phase3/release/docs-lint.log) enforces the markdown rules (timeout/goimportsbanned, required headings present).
- Evidence: release verification sits under
.scratch/verification/SPRINT-047/phase3/release/alongside admin UI screenshots (.scratch/verification/SPRINT-047/track-d/ui/) and the operator/runbook evidence mentioned above.
- Scope: update
Evidence expectations: mark items [X] only after attaching the command output (with exit code) plus any screenshots/logs under .scratch/verification/SPRINT-047/<track>/...; include device/consent/Okta screenshots and admin UI captures for UX deliverables.
Goal: sequence remaining work so each phase produces verifiable increments with evidence captured beneath every checklist item.
- Prep-1 – Work envelopes + evidence scaffolding
- Implementation:
.scratch/notes/sprint-047-baseline.mdholds scope/compat targets + Okta prerequisites,.scratch/verification/SPRINT-047/{phase0..phase3}/README.mdindex evidence drops, and.scratch/google_oauth_helpers.mddocuments PKCE/token probes. - Verification:
ls .scratch/notes/sprint-047-baseline.md .scratch/google_oauth_helpers.md(exit 0);bash -lc 'ls .scratch/google_oauth_helpers.md.missing 2>&1 | tee .scratch/verification/SPRINT-047/phase0/missing-helper.log; printf "exit_code=%d\n" ${PIPESTATUS[0]} >> .scratch/verification/SPRINT-047/phase0/missing-helper.log'(exit 0, artifact logs the intentional failure + exit_code=1).
- Implementation:
- Prep-2 – Deterministic registry + service wiring audit
- Implementation: review
cmd/dtu/serve.go,internal/app/bootstrap/bootstrap.go, andinternal/infra/memory/service_catalog.goto confirm thegoogle-oauthadapter is registered for every tenant/service entry; document any gaps and patch wiring before Phase 0 work begins. - Tests (positive):
perl -e 'alarm 40; exec @ARGV' go test ./internal/app/... -run GoogleOAuth(targeted package subset) to ensure bootstrap logic exercises the adapter;perl -e 'alarm 40; exec @ARGV' go test ./internal/infra/memory -run ServiceCatalog. - Tests (negative): intentionally strip a service entry in
.scratchfixture to verify loader rejects missing google-oauth configuration, capturing the error output for later regression tests. - Evidence/logging:
perl -e 'alarm 40; exec @ARGV' go test ./internal/app/... -run GoogleOAuth(exit 0;.scratch/verification/SPRINT-047/phase0/bootstrap/go-test-internal-app-googleoauth.log)perl -e 'alarm 40; exec @ARGV' go test ./internal/infra/memory -run ServiceCatalog(exit 0;.scratch/verification/SPRINT-047/phase0/bootstrap/go-test-internal-infra-memory-servicecatalog.log)perl -e 'alarm 40; exec @ARGV' go run ./cmd/dtu serve --definitions .scratch/fixtures/dtu-missing-google-oauth.yml --listen 127.0.0.1:0(exit 1 as expected;.scratch/verification/SPRINT-047/phase0/bootstrap/bootstrap-failure.log)
- Implementation: review
- P0-1 – Baseline scope + compatibility notes
- Implementation:
.scratch/notes/sprint-047-baseline.mdnow documents google-auth SDK targets, env overrides, Okta tenant prerequisites, scoped risks/assumptions, and helper probe references. - Verification:
rg --files .scratch | rg sprint-047-baseline.md(exit 0),perl -e 'alarm 40; exec @ARGV' rg "Risks" .scratch/notes/sprint-047-baseline.md(exit 0),perl -e 'alarm 40; exec @ARGV' rg "TBD" .scratch/notes/sprint-047-baseline.md(exit 1, expected none found).
- Implementation:
- P0-2 – Service skeleton completion + config structs
- Implementation: finish fleshing out
internal/services/googleoauthrouter + handler scaffolding (response_mode handling, error enums, structured logging hooks), ensure config structs live ininternal/defsandinternal/services/googleoauth/config.go(if split) with auto-generation of missingclient_id/client_secret. - Tests (positive):
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestAuthTokenUserInfoFlow, andperl -e 'alarm 40; exec @ARGV' go test ./internal/defs -run TestGoogleOAuth. - Tests (negative): add table-driven tests covering invalid service entries (empty redirect/source) that must fail during load.
- Evidence/logging:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestAuthTokenUserInfoFlow(exit 0;.scratch/verification/SPRINT-047/phase0/service-skeleton/go-test-internal-services-googleoauth-auth-token-userinfo.log)perl -e 'alarm 40; exec @ARGV' go test ./internal/defs -run TestGoogleOAuth(exit 0;.scratch/verification/SPRINT-047/phase0/service-skeleton/go-test-internal-defs-googleoauth.log)- Sample hydrated app credentials recorded via
rg -n "client_id" dtu.example.yml(output archived at.scratch/verification/SPRINT-047/phase0/service-skeleton/dtu-example-client-ids.txt)
- Implementation: finish fleshing out
- P0-3 – YAML-driven registry auto-hydration
- Implementation: registry normalization now validates redirect/source URLs (http/https only), auto-generates deterministic client IDs/secrets, and bootstrap wiring still posts the hydrated payload to
/_digital_twin_instance/googleoauth/apps/sync;dtu.example.ymlincludes samplegoogle-oauthstanzas for each tenant. - Verification:
rg -n "google-oauth" dtu.example.yml(exit 0),perl -e 'alarm 40; exec @ARGV' go test ./internal/defs -run TestGoogleOAuthAppsInvalid(exit 0),make -j10 build(exit 0, executed via harness timeout),make -j10 test(exit 0, executed via harness timeout).
- Implementation: registry normalization now validates redirect/source URLs (http/https only), auto-generates deterministic client IDs/secrets, and bootstrap wiring still posts the hydrated payload to
- P0-4 – Signing keys, JWKS rotation, docs
- Implementation:
internal/services/googleoauthnow rotates RSA keys (retains overlappingkids plusdtu_created/dtu_expiresmetadata), JWKS handler streams the set, anddocs/howto/google-oauth.mdcaptures discovery/JWKS usage + env overrides. - Verification:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestJWKSRotation(exit 0),make -j10 build(exit 0, executed via harness timeout),make -j10 test(exit 0, executed via harness timeout),ls docs/howto/google-oauth.md(exit 0). JWKS rotation log +oidcnotes stored under.scratch/verification/SPRINT-047/phase0/.
- Implementation:
- P0-5 – Helper probes + diagrams
- Implementation:
.scratch/google_oauth_helpers.mdnow includes PKCE/tokeninfo walkthroughs and.scratch/diagram-renders/sprint-047/*.pnghosts the rendered architecture/dataflow/ER/workflow diagrams referenced in the Appendix. - Verification:
ls .scratch/diagram-renders/sprint-047(exit 0),perl -e 'alarm 40; exec @ARGV' rg "PKCE" .scratch/google_oauth_helpers.md(exit 0),.scratch/verification/SPRINT-047/phase0/missing-helper.logcaptures the negative helper lookup (exit_code=1) scenario.
- Implementation:
- P1-1 –
/o/oauth2/v2/authparity- Implementation: implement full response_type matrix,
response_modehandling, PKCE enforcement, state/prompt/access_type semantics, redirect/source origin enforcement, incremental scope merges, and hybrid response payload marshaling (query/fragment/form_post). - Tests (positive): contract tests using
.scratchprobe hitting each response_type + response_mode combination, verifying redirects and fragments; include acceptance tests foraccess_type=offlinereturning refresh tokens. - Tests (negative): invalid redirect/source, unsupported combination (
tokenwithout implicit mode), repeatedstate, missing PKCE, unsupported prompt; add table-driven tests insideinternal/services/googleoauth/adapter_test.go. - Verification:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth(exit 0;.scratch/verification/SPRINT-047/phase1/unit-googleoauth.log) andtimeout 180 make -j10 build(exit 0;.scratch/verification/SPRINT-047/phase1/timeout-make-build.log).
- Implementation: implement full response_type matrix,
- P1-2 –
/token(authorization_code, refresh, device, JWT-bearer) + revocation logic- Implementation: add grant handlers (authorization_code, refresh_token with rotation + reuse detection, device_code grant, JWT-bearer), Basic auth + body credentials, error semantics, refresh invalidation, and revocation list enforcement shared with
/revoke. - Tests (positive): service-level tests exchanging codes, doing refresh rotation, device code approval flow, JWT-bearer success; e2e
.scratchscript that requestscode, trades for tokens, refreshes, revokes. - Tests (negative): invalid verifier, expired code, mismatched redirect, replayed refresh after rotation, revoked refresh, JWT assertion signature failure/expiration.
- Verification:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth(exit 0;.scratch/verification/SPRINT-047/phase1/unit-googleoauth.log) plustimeout 180 make -j10 test(exit 0;.scratch/verification/SPRINT-047/phase1/timeout-make-test.log).
- Implementation: add grant handlers (authorization_code, refresh_token with rotation + reuse detection, device_code grant, JWT-bearer), Basic auth + body credentials, error semantics, refresh invalidation, and revocation list enforcement shared with
- P1-3 –
/device/code+ polling semantics- Implementation: create device session store, emit
device_code/user_code, enforce pollinginterval, throttle, support approval/denial/expiry states, and align error payloads with Google. - Tests (positive): integration test approving within interval and verifying
/tokenreturns tokens whengrant_type=urn:ietf:params:oauth:grant-type:device_code. - Tests (negative): too-frequent polling ->
slow_down, expired device code, denied request, invalid client. - Verification:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth(exit 0;.scratch/verification/SPRINT-047/phase1/unit-googleoauth.log) and device flow exercised inTestDeviceAuthorizationFlow.
- Implementation: create device session store, emit
- P1-4 –
/userinfo, discovery, JWKS,/oauth2/v3/tokeninfoparity- Implementation: add
audvalidation in userinfo, tokeninfo endpoint supportingaccess_tokenorid_token, discovery doc fields for device/JWT grants, JWKS caching headers. - Tests (positive):
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestUserInfo TokenInfo. - Tests (negative): wrong
aud, expired token, missing params, invalid token type. - Verification:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth(exit 0;.scratch/verification/SPRINT-047/phase1/unit-googleoauth.log) plus discovery + JWKS responses captured via updateddocs/howto/google-oauth.md.
- Implementation: add
- P1-5 – Contract test matrix + probes
- Implementation: create
.scratch/tests/google-auth-go.sh,.scratch/tests/google-auth-node.js,.scratch/tests/google-auth-python.pyto run google-auth SDK samples against emulator (env overrides). - Tests (positive): each script completes PKCE + offline + hybrid flows.
- Tests (negative): misconfigured client_id should fail quickly with documented error.
- Verification:
perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-happy.log)perl -e 'alarm 40; exec @ARGV' node ./.scratch/tests/google-auth-node.js(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-node-happy.log)perl -e 'alarm 40; exec @ARGV' python3 ./.scratch/tests/google-auth-python.py(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-python-happy.log)perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh --scenario invalid-client(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-invalid-client.log)perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh --scenario missing-pkce(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-missing-pkce.log)perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh --scenario redirect-mismatch(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-redirect-mismatch.log)
- Implementation: create
- P2-1 – Okta authorize/token wiring
- Implementation: add Okta client (config-driven tenant details), exchange flows, state/nonce validation, caching Okta session context, fallback messaging on outages.
- Tests (positive):
.scratch/verification/SPRINT-047/phase2/okta/okta-happy.loghitting Okta sandbox, ensuring code exchange returns Okta tokens. - Tests (negative): stale/invalid Okta code, MFA-required user ensures UI surfaces challenge, Okta downtime returning graceful error.
- Verification commands:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestOkta*using mocked Okta client. - Verification:
timeout 40 go test ./internal/services/googleoauth -run TestOktaAuthorizationCodeExchange -v(exit 0;.scratch/verification/SPRINT-047/phase2/okta/go-test-okta-auth.log)
- P2-2 – Consent screen + incremental scope tracking
- Implementation: add DTU-hosted consent UI (templated HTML/Go embed), enforce per-user grants, incremental scopes, record decisions.
- Tests (positive): integration test to request scopes twice; second run should auto-consent if scopes already granted.
- Tests (negative): consent denied ->
access_deniederror redirected to client; missing redirect blocked; stale session fails gracefully. - Verification commands:
.scratch/ui/consent_evidence.mdwith screenshots +go test ./internal/services/googleoauth -run TestConsent. - Verification:
timeout 40 go test ./internal/services/googleoauth -run TestConsent -v(exit 0;.scratch/verification/SPRINT-047/phase2/consent/go-test-consent.log)
- P2-3 – Claim mapping + audit logging
- Implementation: map Okta userinfo to Google claims (stable
sub,hdfrom email domain, fallback pictures), persist audit events (grant, deny, revoke) and expose to admin UI/log dumps. - Tests (positive): fixture validating deterministic
subandhdderivation, verifyingemail_verifiedaligns with Okta. - Tests (negative): user missing email -> fail with explicit error, mismatch between Okta
audand client. - Verification commands:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestClaimMapping Audit. - Verification:
timeout 40 go test ./internal/services/googleoauth -run TestAuthTokenUserInfoFlow -v(exit 0;.scratch/verification/SPRINT-047/phase2/claims/go-test-claims.log)
- Implementation: map Okta userinfo to Google claims (stable
- P2-4 – Session persistence + replay protection
- Implementation: bind auth codes to Okta session IDs + PKCE + redirect URIs, enforce expiry, add storage interface for persistence + snapshot hooks.
- Tests (positive): run concurrency test ensuring multiple sessions isolated.
- Tests (negative): attempt to reuse code should fail; mutated redirect fails; stale session cannot exchange.
- Verification commands:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestSession*. - Verification:
timeout 40 go test ./internal/services/googleoauth -run TestAuthorizationCodeSingleUse -v(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-auth-code-single-use.log)timeout 40 go test ./internal/services/googleoauth -run TestRefreshTokenReuseRejected -v(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-refresh-reuse.log)perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-negatives.sh(exit 0;.scratch/verification/SPRINT-047/phase1/negatives/google-auth-negatives.log)
- P3-1 – Admin API surface (
/a/google-oauth/*)- Implementation: REST handlers for list/create/update/delete/rotate, validation shared with YAML loader, secret rotation invalidates active tokens, structured audit logs emitted.
- Tests (positive):
go test ./internal/services/googleoauth -run TestAdminAPI*covering CRUD + secret rotation. - Tests (negative): invalid URIs, duplicate names, delete system app, unauthorized calls.
- Verification:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth(exit 0;.scratch/verification/SPRINT-047/phase1/unit-googleoauth.log) exercisesTestAdminAPICRUD, plustimeout 180 make -j10 build(exit 0;.scratch/verification/SPRINT-047/phase1/timeout-make-build.log) to ensure handlers compile within the binary.
- P3-2 – Admin UI (table, forms, warning indicators)
- Implementation: Go-embedded UI (
internal/services/googleoauth/adminui) mirroring Google’s OAuth client UX (table, pagination, warning icons, create form). - Verification: manual walkthrough + screenshots under
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.mdand backend parity proven viatimeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario happy/revoked-secret/deleted-app(logs in.scratch/verification/SPRINT-047/phase3/admin-api/).
- Implementation: Go-embedded UI (
- P3-3 – Ops hooks + metrics
- Implementation:
/healthz, Prometheus counters, snapshot hooks, operator runbook. - Verification:
timeout 40 ./.scratch/tests/health_probe.sh+timeout 40 ./.scratch/tests/metrics_failure.sh(logs in.scratch/verification/SPRINT-047/phase3/ops/) plustimeout 40 ./.scratch/tests/operators_runbook.sh --scenario happy/--scenario lint(logs in.scratch/verification/SPRINT-047/phase3/hardening/).
- Implementation:
- P3-4 – E2E automation + smoke scripts
- Implementation:
e2e/google_oauth_e2e_test.goplus.scratch/google_oauth_smoke.sh. - Verification:
timeout 40 go test ./e2e -run TestGoogleOAuthEndToEnd -vandtimeout 40 ./.scratch/google_oauth_smoke.sh(logs inside.scratch/verification/SPRINT-047/phase3/e2e/).
- Implementation:
- P3-5 – Release artifacts
- Implementation: refreshed
docs/howto, publisheddocs/operators/google-oauth.md, UI screenshots + smoke docs, and referenced.scratch/google_oauth_smoke.sh. - Verification:
rg/lint logs stored in.scratch/verification/SPRINT-047/phase3/release/(rg-google-client-id.log,rg-sample-configs.log,rg-no-todo.log,docs-lint.log) alongside manual UI evidence in.scratch/verification/SPRINT-047/track-d/ui/.
- Implementation: refreshed
- Every future
[X]mark must include:- Command used (e.g.,
perl -e 'alarm 40; exec @ARGV' make -j10 precommit) with exact exit code and link to.scratch/verification/...artifact. - Positive + negative test list referencing actual files/scripts, matching the detail above.
- For UI features, attach screenshot thumbnails (PNG/SVG) stored under
.scratch/diagram-renders/sprint-047or.scratch/verification/SPRINT-047/track-d/ui.
- Command used (e.g.,
- Store all scratch probes/scripts in
.scratch/testsor.scratch/verification(noscripts/additions per programming practices) and mention them when closing checklist items.
-
Confirm scope, sample env contract, and compatibility targets (google-auth Go/JS/Python) in
.scratch/notes/sprint-047-baseline.md; capture risks/assumptions and Okta tenant prerequisites.ls .scratch/notes/sprint-047-baseline.md(exit 0;.scratch/verification/SPRINT-047/track-0/helpers/ls-sprint-baseline.log)rg --files .scratch | rg sprint-047-baseline.md(exit 0;.scratch/verification/SPRINT-047/track-0/helpers/rg-sprint-baseline.log)rg "Risks" .scratch/notes/sprint-047-baseline.md(exit 0;.scratch/verification/SPRINT-047/track-0/helpers/rg-risks-baseline.log)rg "TBD" .scratch/notes/sprint-047-baseline.md(expected exit 1;.scratch/verification/SPRINT-047/track-0/helpers/rg-tbd-baseline.log)
-
Define service skeleton (router, handler stubs, config structs) under
internal/services/googleoauthpluscmd/dtuwiring forgoogle-oauthservice registration with YAML parsing ofappsstanza (auto-generate client_id/secret if omitted).timeout 180 make -j10 build(exit 0;.scratch/verification/SPRINT-047/track-1/registry/make-build.log)go test ./cmd/dtu -run TestApplyDefinitions_OktaSeeded(exit 0;.scratch/verification/SPRINT-047/track-1/router/go-test-cmd-dtu-applydefs.log)
-
Seed baseline DTU configs (
dtu.example.ymland other fixtures) with a defaultgoogle-oauthservice stanza per tenant, including sample app entries, and add loader tests to ensure registry hydration from these fixtures.go test ./internal/defs ./internal/services/googleoauth(exit 0;.scratch/verification/SPRINT-047/track-1/registry/go-test-internal-defs-and-services.log)bash ./.scratch/tests/config_generation.sh(exit 0;.scratch/verification/SPRINT-047/track-1/registry/config-generation.log)
-
Establish signing keys + JWKS publisher (Ed/RS) with rotation plan; persist to in-memory store with snapshot hooks; document key IDs and lifetimes.
go test ./internal/services/googleoauth -run TestJWKSRotation(exit 0;.scratch/verification/SPRINT-047/track-1/jwks/go-test-internal-services-googleoauth-jwks.log)timeout 180 make -j10 test(exit 0;.scratch/verification/SPRINT-047/track-1/jwks/make-test.log)
-
Document discovery surface (
/.well-known/openid-configuration, JWKS URL) and sample client config snippets for apps indocs/howto/google-oauth.md.ls docs/howto/google-oauth.md(exit 0;.scratch/verification/SPRINT-047/phase0/docs/howto-presence.log) confirms the how-to exists with discovery + YAML sections.rg -n \"Discovery surface\" -n docs/howto/google-oauth.md(exit 0;.scratch/verification/SPRINT-047/phase0/docs/howto-discovery-rg.log) surfaces the documented endpoints plus sample config block.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/discovery_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/discovery_probe.log) captures live discovery/JWKS payloads referenced in the doc for operator validation.
-
Set up .scratch helpers (PKCE generator, token decode probe, Okta token introspection probe) to validate flows during later phases.
ls .scratch/google_oauth_helpers.md(exit 0;.scratch/verification/SPRINT-047/track-0/helpers/ls-google-oauth-helpers.log)rg "PKCE" .scratch/google_oauth_helpers.md(exit 0;.scratch/verification/SPRINT-047/track-0/helpers/rg-pkce-helper.log)
- YAML-driven registry loads, validates, and auto-populates missing client IDs/secrets; invalid redirect/source URLs are rejected with actionable errors.
perl -e 'alarm 40; exec @ARGV' go test ./internal/defs ./internal/services/googleoauth (exit 0)
make -j10 test (exit 0, executed via harness timeout)
- Baseline fixture configs (e.g.,
dtu.example.yml) include google-oauth service entries per tenant, and registry loader tests confirm they hydrate correctly.
rg -n "google-oauth" dtu.example.yml (exit 0)
make -j10 test (exit 0, executed via harness timeout)
- JWKS endpoint exposes at least one active signing key with kid rotation metadata; mmdc-rendered architecture diagrams stored under
.scratch/diagram-renders/sprint-047/*are referenced from this doc.
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestJWKSRotation (exit 0)
make -j10 test (exit 0, executed via harness timeout)
- Baseline notes include compatibility targets, risks, and Okta prerequisites; probes in .scratch run successfully against the stubbed service.
rg --files .scratch | rg sprint-047-baseline.md (exit 0)
perl -e 'alarm 40; exec @ARGV' rg "Risks" .scratch/notes/sprint-047-baseline.md (exit 0)
perl -e 'alarm 40; exec @ARGV' rg "TBD" .scratch/notes/sprint-047-baseline.md (exit 1, expected)
ls .scratch/notes/sprint-047-baseline.md .scratch/google_oauth_helpers.md (exit 0)
-
Implement Google-shaped auth endpoint (
/o/oauth2/v2/auth) with PKCE, state, prompt/approval params, offline/online access types, redirect/source allowlist enforcement, fullresponse_typematrix (code,token,id_token,code token,code id_token,token id_token,code token id_token,none), and support forresponse_mode=query|fragment|form_post.timeout 40 go test ./internal/services/googleoauth -run TestAuthEndpointHybridMatrix -v(exit 0;.scratch/verification/SPRINT-047/phase1/auth/go-test-hybrid-matrix.log) exercises everyresponse_type×response_modecombination and asserts PKCE/query/fragment/form_post handling.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go.log) runs the Go google-auth SDK sample across PKCE, hybrid, and form_post flows against the emulator.perl -e 'alarm 40; exec @ARGV' node ./.scratch/tests/google-auth-node.jsandperl -e 'alarm 40; exec @ARGV' python3 ./.scratch/tests/google-auth-python.py(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-node.log,.scratch/verification/SPRINT-047/phase1/contracts/google-auth-python.log) prove SDK parity for Node/Python hybrids, validating redirect payloads and JWKS-backed ID token verification.
-
Implement token endpoint (
/token) supporting authorization_code + refresh_token grants, refresh rotation, revocation list, error responses matching Google semantics, and returning fields required for hybrid response types.timeout 40 go test ./internal/services/googleoauth -run TestAuthTokenUserInfoFlow -v(exit 0;.scratch/verification/SPRINT-047/phase1/token/go-test-auth-token-userinfo.log) covers code→token→userinfo exchanges with PKCE verification.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go.log) validates refresh rotation plus hybrid responses end-to-end.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-negatives.sh(exit 0;.scratch/verification/SPRINT-047/phase1/negatives/google-auth-negatives.log) demonstrates error paths for redirect mismatches, missing PKCE verifier, refresh reuse detection, and JWT-bearer failures (all returning Google-aligned JSON).
-
Add device authorization endpoint (
/device/code) that issuesdevice_code/user_code, enforces polling interval, honorsscope, and aligns error semantics (authorization_pending,slow_down,expired_token,access_denied) with Google.timeout 40 go test ./internal/services/googleoauth -run TestDeviceAuthorizationFlow -v(exit 0;.scratch/verification/SPRINT-047/phase1/device/go-test-device-flow.log) drives pending, approval, and denial paths via the unit harness.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/device_flow_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase1/device/device_flow_probe.log) polls/tokenuntil approval, observingauthorization_pending→ success plus interval enforcement logs emitted by the emulator.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go.log) exercises the documented device approval story as part of the SDK contract.
-
Build userinfo endpoint and ID token claims mapper that projects Okta claims into Google payloads (sub, email, email_verified, hd, picture, locale) and ensures
audmatches the requesting client ID.timeout 40 go test ./internal/services/googleoauth -run TestAuthTokenUserInfoFlow -v(exit 0;.scratch/verification/SPRINT-047/phase1/token/go-test-auth-token-userinfo.log) verifies userinfo responses carry Google-shaped claims for the resolved Okta user.timeout 40 go test ./internal/services/googleoauth -run TestTokenInfoEndpoints -v(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/go-test-tokeninfo.log) proves/oauth2/v3/tokeninfoaccepts both access and ID tokens with correct claim projection and audience validation.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh,perl -e 'alarm 40; exec @ARGV' node ./.scratch/tests/google-auth-node.js, andperl -e 'alarm 40; exec @ARGV' python3 ./.scratch/tests/google-auth-python.py(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go.log,.scratch/verification/SPRINT-047/phase1/contracts/google-auth-node.log,.scratch/verification/SPRINT-047/phase1/contracts/google-auth-python.log) call/userinfoand/oauth2/v3/tokeninfo, then validate ID tokens against the JWKS to confirmaudandsubfidelity.
-
Add discovery docs + JWKS publishing consistent with Google metadata, including
token_endpoint_auth_methods_supportedand supported grant types.timeout 40 go test ./internal/services/googleoauth -run TestJWKSRotation -v(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/go-test-jwks-rotation.log) enforces overlapping key rotation and JWKS exposure.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/discovery_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/discovery_probe.log) captures live/.well-known/openid-configuration+/oauth2/v3/certspayloads alongside stored artifacts (oidc_discovery.json,jwks.json).perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go.log) validates JWKS entries by decoding ID tokens and ensuring Google-auth SDKs accept the discovery metadata.
-
Unit + contract tests for positive and negative paths: PKCE success/failure, invalid redirect, mismatched client, replayed code, expired code, refresh reuse/rotation, revoked refresh, userinfo with wrong audience, and revocation endpoint behavior.
timeout 40 go test ./internal/services/googleoauth -count=1(exit 0;.scratch/verification/SPRINT-047/phase1/unit/go-test-internal-services-googleoauth.log) keeps the adapter unit suite green across auth/token/device/consent scenarios.perl -e 'alarm 40; exec @ARGV' node ./.scratch/tests/google-auth-node.js&perl -e 'alarm 40; exec @ARGV' python3 ./.scratch/tests/google-auth-python.py(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-node.log,.scratch/verification/SPRINT-047/phase1/contracts/google-auth-python.log) extend the positive contract matrix to Node/Python SDKs (PKCE, hybrid, JWKS verification).perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-negatives.sh(exit 0;.scratch/verification/SPRINT-047/phase1/negatives/google-auth-negatives.log) documents blocked redirects, origin enforcement, missing PKCE verifier, refresh reuse, revoked tokens hitting/userinfo, JWT-bearer failures,/oauth2/v3/tokeninfovalidation, and Prometheus counter increments after each negative.
| Parameter | Type | Required | Valid Values / Constraints | Description |
|---|---|---|---|---|
client_id |
string | Yes | Registered OAuth client ID (*_apps.googleusercontent.com format) |
Identifies the DTU app making the request. |
redirect_uri |
string (URL) | Yes | Must exactly match an allowlisted HTTPS/HTTP URI | Where the authorization response is sent; enforced against registry allowlist. |
response_type |
string | Yes | One of code, token, id_token, code token, code id_token, token id_token, code token id_token, none |
Determines which credentials are returned in the response. |
scope |
space-delimited string | Yes | Combination of openid, email, profile and DTU-supported Google scopes |
Requested OAuth scopes. |
state |
string | Recommended | 1–1024 UTF-8 chars | Opaque value returned to the client for CSRF mitigation. |
code_challenge |
string | Conditional | Base64URL string, 43–128 chars | PKCE code challenge; required when response_type includes code. |
code_challenge_method |
string | Conditional | S256 or plain |
Method used to transform code_verifier; default plain. |
access_type |
string | Optional | online, offline |
Controls refresh token issuance. |
prompt |
string | Optional | none, consent, select_account |
Forces user interaction semantics. |
include_granted_scopes |
string | Optional | true, false |
Enables incremental auth if true. |
login_hint |
string | Optional | Email address | Suggests the account to pre-select. |
response_mode |
string | Optional | query, fragment, form_post (defaults: query for code; fragment for implicit) |
Determines how parameters are encoded in the response. |
Sample request:
GET /o/oauth2/v2/auth?client_id=6505.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Flocal.app%2Foauth2%2Fcallback&response_type=code%20id_token&scope=openid%20email%20profile&state=abc123&code_challenge=ZL4lU...&code_challenge_method=S256&response_mode=form_post HTTP/1.1
Host: dtu-google-oauth.internal
Sample redirect (form_post body for hybrid response):
{
"code": "4/0AbbA123",
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"state": "abc123",
"scope": "openid email profile"
}Errors return HTTP 400 with JSON payloads such as:
{
"error": "invalid_request",
"error_description": "redirect_uri_not_allowed"
}| Parameter | Type | Required | Valid Values / Constraints | Description |
|---|---|---|---|---|
client_id |
string | Yes | Registered OAuth client ID | Device app identifier. |
scope |
string | Yes | Space-delimited scopes (openid email profile etc.) |
Requested scopes for the device session. |
Request format: application/x-www-form-urlencoded.
Sample request body:
client_id=6505.apps.googleusercontent.com&scope=openid%20email%20profile
Sample response:
{
"device_code": "ZjA1Yk...0x",
"user_code": "ABCD-EFGH",
"verification_url": "https://dtu-google-oauth.internal/device",
"verification_url_complete": "https://dtu-google-oauth.internal/device?user_code=ABCD-EFGH",
"expires_in": 1800,
"interval": 5
}Error responses:
{
"error": "invalid_client",
"error_description": "Client not found"
}| Parameter | Type | Required | Valid Values / Constraints | Description |
|---|---|---|---|---|
grant_type |
string | Yes | authorization_code, refresh_token, urn:ietf:params:oauth:grant-type:device_code, urn:ietf:params:oauth:grant-type:jwt-bearer |
Determines which other parameters are required. |
code |
string | Conditional | Non-empty | Authorization code; required for authorization_code. |
code_verifier |
string | Conditional | 43–128 chars (pkce) | Required with PKCE for authorization_code. |
redirect_uri |
string | Conditional | Exact match to original request | Required when exchanging an auth code. |
refresh_token |
string | Conditional | Non-empty | Required for refresh_token grant. |
device_code |
string | Conditional | Non-empty | Required for device code grant. |
assertion |
string | Conditional | Signed JWT (Base64URL sections) | Required for urn:ietf:params:oauth:grant-type:jwt-bearer; must be signed with a service account key and include iss, sub, aud, scope. |
client_id |
string | Yes | Registered ID | Can be supplied in body or via Basic auth. |
client_secret |
string | Conditional | Present for confidential clients | Required unless using mTLS/Basic equivalent. |
Sample authorization_code request:
grant_type=authorization_code&code=4/0AbbA123&redirect_uri=https%3A%2F%2Flocal.app%2Foauth2%2Fcallback&client_id=6505.apps.googleusercontent.com&client_secret=shhh&code_verifier=Q1w2E...
Sample success response:
{
"access_token": "ya29.a0Af...",
"expires_in": 3600,
"refresh_token": "1//0g...",
"scope": "openid email profile",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIs..."
}Sample device grant error while pending:
{
"error": "authorization_pending"
}Sample JWT-bearer request (service account):
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Sample JWT-bearer success response:
{
"access_token": "ya29.c.ElqB...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "https://www.googleapis.com/auth/cloud-platform"
}JWT-bearer error example:
{
"error": "invalid_grant",
"error_description": "Invalid JWT Signature."
}Other error codes: invalid_grant, invalid_client, invalid_request, slow_down, access_denied, expired_token.
Form data: token={access_or_refresh}. Optional token_type_hint=access_token|refresh_token.
Sample request:
token=ya29.a0Af...&token_type_hint=access_token
Success: HTTP 200 with empty body. Errors:
{
"error": "invalid_token"
}Headers: Authorization: Bearer {access_token}.
Sample response:
{
"sub": "okta|00u123",
"email": "alice@example.com",
"email_verified": true,
"hd": "example.com",
"picture": "https://example.com/avatar.png",
"locale": "en",
"name": "Alice Example",
"given_name": "Alice",
"family_name": "Example"
}Errors use HTTP 401 with WWW-Authenticate: Bearer error="invalid_token" plus JSON body.
Response includes:
{
"issuer": "https://dtu-google-oauth.internal",
"authorization_endpoint": "https://dtu-google-oauth.internal/o/oauth2/v2/auth",
"device_authorization_endpoint": "https://dtu-google-oauth.internal/device/code",
"token_endpoint": "https://dtu-google-oauth.internal/token",
"userinfo_endpoint": "https://dtu-google-oauth.internal/userinfo",
"revocation_endpoint": "https://dtu-google-oauth.internal/revoke",
"jwks_uri": "https://dtu-google-oauth.internal/oauth2/v3/certs",
"response_types_supported": ["code","token","id_token","code token","code id_token","token id_token","code token id_token","none"],
"response_modes_supported": ["query","fragment","form_post"],
"grant_types_supported": ["authorization_code","refresh_token","urn:ietf:params:oauth:grant-type:device_code","urn:ietf:params:oauth:grant-type:jwt-bearer"],
"code_challenge_methods_supported": ["plain","S256"],
"token_endpoint_auth_methods_supported": ["client_secret_post","client_secret_basic"]
}Returns JWKS set:
{
"keys": [
{
"kid": "kid-1",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"n": "...",
"e": "AQAB"
}
]
}| Parameter | Type | Required | Description |
|---|---|---|---|
access_token |
string | Conditional | For validating OAuth access tokens. |
id_token |
string | Conditional | For validating ID tokens; either access_token or id_token must be present. |
Sample response for access_token:
{
"aud": "6505.apps.googleusercontent.com",
"azp": "6505.apps.googleusercontent.com",
"issued_to": "6505.apps.googleusercontent.com",
"scope": "openid email profile",
"expires_in": 3599,
"sub": "user-1",
"email": "user@example.com",
"token_type": "Bearer"
}Sample response for id_token validation:
{
"aud": "6505.apps.googleusercontent.com",
"iss": "https://dtu-google-oauth.internal",
"sub": "user-1",
"exp": 1700000000,
"email": "user@example.com",
"email_verified": true
}Errors return HTTP 400:
{
"error": "invalid_token",
"error_description": "Token expired or malformed"
}-
All endpoints round-trip with google-auth SDKs (Go/Node/Python) using env overrides pointing at
http://127.0.0.1:11111; positive cases include PKCE + offline/online access, hybrid response payloads, and tokens that validate against JWKS.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go.log)perl -e 'alarm 40; exec @ARGV' node ./.scratch/tests/google-auth-node.js(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-node.log)perl -e 'alarm 40; exec @ARGV' python3 ./.scratch/tests/google-auth-python.py(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-python.log)
-
Device authorization grant covers pending, approved, denied, and expired flows via
/device/code+/tokenpolling while respecting the returnedintervaland emittingauthorization_pending,slow_down,access_denied, andexpired_tokenerrors exactly as Google does.timeout 40 go test ./internal/services/googleoauth -run TestDeviceAuthorizationFlow -v(exit 0;.scratch/verification/SPRINT-047/phase1/device/go-test-device-flow.log)perl -e 'alarm 40; exec @ARGV' bash ./.scratch/device_flow_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase1/device/device_flow_probe.log)
-
Hybrid response types (
code token,code id_token,token id_token,code token id_token) andresponse_mode=form_postare validated by .scratch probes to ensure payload shapes and parameter names match Google responses.timeout 40 go test ./internal/services/googleoauth -run TestAuthEndpointHybridMatrix -v(exit 0;.scratch/verification/SPRINT-047/phase1/auth/go-test-hybrid-matrix.log)- Contract harness logs (
.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go.log,.scratch/verification/SPRINT-047/phase1/contracts/google-auth-node.log,.scratch/verification/SPRINT-047/phase1/contracts/google-auth-python.log) capture Go/Node/Python SDKs consuming hybrid fragments and form_post redirects.
-
JWT-bearer (service account) grant exchanges succeed using signed assertions and cover error cases for invalid signatures, expired assertions, and unsupported scopes; verification captured via
.scratchscripts.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/jwt_bearer_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase1/jwt/jwt_bearer_probe.log) issues HS256 assertions and validates/tokenresponses.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-negatives.sh(exit 0;.scratch/verification/SPRINT-047/phase1/negatives/google-auth-negatives.log) replays malformed JWT-bearer payloads to confirm Google-styleinvalid_grantfailures.
-
Negative cases: blocked redirect/source origins, invalid code_verifier, reused auth code, refresh reuse after rotation, revoked token rejected at userinfo, and tokeninfo parameter validation.
perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-negatives.sh(exit 0;.scratch/verification/SPRINT-047/phase1/negatives/google-auth-negatives.log)timeout 40 go test ./internal/services/googleoauth -run TestAuthTokenUserInfoFlow -v(exit 0;.scratch/verification/SPRINT-047/phase1/token/go-test-auth-token-userinfo.log) ensures revoked tokens are rejected at/userinfo.
-
Discovery doc and JWKS validate via
oidc-discoveryandjwtdecoding probes; logs stored in.scratch/verification/SPRINT-047/phase1.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/discovery_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/discovery_probe.log)timeout 40 go test ./internal/services/googleoauth -run TestJWKSRotation -v(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/go-test-jwks-rotation.log)
-
Wire upstream Okta authorize/token calls, exchanging Okta code for id_token/access_token and caching Okta session context; support Okta MFA/consent flows and state/nonce validation.
timeout 40 ./.scratch/tests/okta-happy.sh(exit 0;.scratch/verification/SPRINT-047/track-c/okta/okta-happy.log) drivesTestOktaAuthorizationCodeExchangeend-to-end (PKCE, offline access, Okta profile claims, state/nonce enforcement).timeout 40 ./.scratch/tests/okta-mfa.sh(exit 0;.scratch/verification/SPRINT-047/track-c/okta/okta-mfa.log) simulates MFA/unknown principals and confirms the auth handler returnsaccess_denied.timeout 40 ./.scratch/tests/okta-downtime.sh(exit 0;.scratch/verification/SPRINT-047/track-c/okta/okta-downtime.log) forces the Okta peer offline and captures the resultingserver_errorresponse plus structured logs.
-
Map Okta userinfo to Google claims with deterministic
sub(stable per user),hdderived from email domain, and profile photo fallbacks; ensure email_verified tracks Okta status.timeout 40 go test ./internal/services/googleoauth -run TestClaimMappingFallbacks -count=1 -v(exit 0;.scratch/verification/SPRINT-047/track-c/claims/go-test-claim-mapping.log) covers missing email/photo/name/domain fallbacks plus deterministicsub.timeout 40 ./.scratch/tests/audit_log_probe.sh(exit 0;.scratch/verification/SPRINT-047/track-c/claims/audit-log-probe.log) approves/denies/revokes via the Okta-backed flow and checks/a/google-oauth/auditfor ordered entries that mirror Google-style claims.timeout 40 ./.scratch/tests/audit_log_negative.sh(exit 0;.scratch/verification/SPRINT-047/track-c/claims/audit-log-negative.log) attempts to read the audit log without admin auth and verifies the HTTP 403 + warning log.
-
Implement consent screen (DTU-hosted) that mirrors Google scopes, shows app name/icon from registry, and records grants per user+app for incremental scopes.
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth (exit 0)
-
Evidence:
timeout 40 go test ./internal/services/googleoauth(exit 0;.scratch/verification/SPRINT-047/phase2/consent/go-test-internal-services-googleoauth-consent.log) exercisesTestConsentPromptFlow,TestConsentPromptNoneRequiresGrant, andTestConsentDenialto cover happy/negative consent paths. -
Session persistence for auth codes bound to Okta session + PKCE + redirect URI, with expiry and replay protection; include audit logging for grant/deny events.
timeout 40 go test ./internal/services/googleoauth -run TestSessionIsolation -count=1 -vandtimeout 40 go test ./internal/services/googleoauth -run TestSessionReplay -count=1 -v(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-isolation.log,.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-replay.log) ensure concurrent issuances stay isolated and session snapshots restore cleanly.timeout 40 ./.scratch/tests/session_replay.sh(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-replay-failure.log) proves replay attempts returninvalid_grantand append audit entries, whiletimeout 40 ./.scratch/tests/session_snapshot.sh(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-replay.log) exercises snapshot/export/import flows.
-
Integration tests covering: successful Okta login → code → token → userinfo; MFA-required user; denied consent; stale/invalid Okta code; Okta outage fallback messaging.
timeout 40 ./.scratch/tests/okta-happy.sh/okta-mfa.sh/okta-downtime.sh(exit 0;.scratch/verification/SPRINT-047/track-c/okta/*.log) capture positive flow, MFA/unknown user rejection, and outage handling.cd playwright && timeout 40 npx playwright test ../.scratch/tests/consent_happy.spec.ts --config ../.scratch/tests/playwright.config.tsand.../consent_denied.spec.ts(exit 0;.scratch/verification/SPRINT-047/track-c/consent/consent-happy-playwright.log,.scratch/verification/SPRINT-047/track-c/consent/consent-denied-playwright.log) exercise Okta-backed consent approval/denial and verify redirects preserve state + Google-style errors.
-
Okta-backed login + consent completes and returns Google-shaped tokens usable by SDK samples; MFA and incremental scope prompts surface correctly.
timeout 40 ./.scratch/tests/okta-happy.sh(exit 0;.scratch/verification/SPRINT-047/track-c/okta/okta-happy.log) issues code → token → userinfo and validates Okta claims within the ID token/userinfo payloads.cd playwright && timeout 40 npx playwright test ../.scratch/tests/consent_happy.spec.ts --config ../.scratch/tests/playwright.config.ts(exit 0;.scratch/verification/SPRINT-047/track-c/consent/consent-happy-playwright.log) covers incremental scopes + persisted grants, while the denied-spec log documentsaccess_deniedbehaviour.
-
Negative coverage: consent denied returns Google-style error, invalid/expired Okta code fails token exchange, missing/blocked redirect is rejected, and stale session cannot replay code.
timeout 40 ./.scratch/tests/okta-mfa.shandtimeout 40 ./.scratch/tests/okta-downtime.sh(exit 0;.scratch/verification/SPRINT-047/track-c/okta/okta-mfa.log,.scratch/verification/SPRINT-047/track-c/okta/okta-downtime.log) cover MFA/unknown user rejections plus outage messaging.cd playwright && timeout 40 npx playwright test ../.scratch/tests/consent_denied.spec.ts --config ../.scratch/tests/playwright.config.ts(exit 0;.scratch/verification/SPRINT-047/track-c/consent/consent-denied-playwright.log) verifies Google-styleerror=access_deniedredirects.timeout 40 ./.scratch/tests/session_replay.sh(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-replay-failure.log) demonstrates stale/rotated refresh tokens fail withinvalid_grant.
-
Audit log entries (grant/deny/revoke) appear in admin UI/log dump; userinfo claims match Okta profile with deterministic sub and hd.
timeout 40 ./.scratch/tests/audit_log_probe.sh(exit 0;.scratch/verification/SPRINT-047/track-c/claims/audit-log-probe.log) hits/a/google-oauth/auditafter grant/deny flows to confirm entries mirror Okta claim mapping.timeout 40 ./.scratch/tests/audit_log_negative.sh(exit 0;.scratch/verification/SPRINT-047/track-c/claims/audit-log-negative.log) verifies admin auth is required before audit data is exposed.
-
Build admin UI to list/create/update/revoke OAuth apps, rotate client secrets, export env snippets, and view recent grant/deny events; embed assets via Go
//go:embed. -
Evidence:
timeout 40 go test ./internal/services/googleoauth -run TestAdminUIRequiresAuth(exit 0;.scratch/verification/SPRINT-047/track-d/ui/go-test-internal-services-googleoauth-adminui.log) plus manual smoke notes in.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md. -
Admin UI app list includes columns: Name, Creation date, Type, Client ID, and Actions (view/edit/delete/rotate), matching operator expectations; table sortable on all columns except Actions; Client ID column provides one-click clipboard copy per row.
-
Evidence: Manual verification recorded in
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md(clipboard + sorting + action buttons) after running the embedded UI. -
Table UX parity (per screenshot): row selection checkboxes with header select-all and bulk Delete button (disabled until selection); Create client button above table; row-level warning indicator for apps needing attention; per-row actions include copy Client ID, edit, and delete; creation date shown; type values include Web application / Desktop / Service account client; pagination + rows-per-page control; column sort affordance (e.g., on Creation date).
-
Evidence: Manual workflow documented under
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md(select-all, bulk delete toggle, warning badge forsystemapps, pagination + sortable headers). -
Table iconography and action order: warning indicator is a yellow triangle-with-exclamation at row start; per-row actions (left-to-right) are copy Client ID (clipboard/duplicate icon), edit (pencil), delete (trash); all actions expose tooltips and keyboard focus; Client ID text may be truncated visually with ellipsis but copy grabs full value.
-
Evidence: Manual verification
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md. -
Warning indicator criteria: show warning icon only when DTU flags the app as non-modifiable (if ever applicable). If no such system apps exist, omit warnings; no inactivity warnings.
-
Evidence:
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.mddocuments the system-app warning badge behaviour (triangle icon rendered only whenapp.systemtrue). -
Pagination requirements: rows-per-page selector (e.g., 10/25/50) with display of current range (
1–N of M) and next/previous controls; sorting and filters persist across page changes; selections clear when page changes unless bulk actions are active. -
Evidence:
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md+ interactive replay confirm rows-per-page, summary text, Prev/Next buttons, and selection reset when moving pages. -
Create OAuth client flow requirements (per Google UI parity): form fields for Application type (dropdown: Web application, Desktop, Service account client), Name (required), Authorized JavaScript origins (repeatable, required for Web; inline validation “Invalid Origin: URI must not be empty.”), Authorized redirect URIs (repeatable, required for Web); add/remove URI controls; informational note that domains are added to consent screen authorized domains; buttons Create (primary, disabled until valid) and Cancel; display inline errors on empty/invalid URI; support multiple URI entries with per-row delete; note about propagation delay.
- Manual evidence in
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.mdcaptures the full create/edit/delete UI (validation errors, propagation note, per-row delete, application-type dropdown, disabled Create button until valid). timeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-happy.log) proves the backend create API (which the UI calls) immediately yields working credentials used by the Google-auth contract harness.
- Manual evidence in
-
Add operational hooks: metrics for auth/token/revoke outcomes, structured audit logs, health checks, and snapshot/load for registry + sessions.
/metrics//healthzcoverage recorded viatimeout 40 ./.scratch/tests/health_probe.shandtimeout 40 ./.scratch/tests/metrics_failure.sh(logs under.scratch/verification/SPRINT-047/phase3/ops/) plus the newdocs/operators/google-oauth.mdrunbook (linted via.scratch/tests/operators_runbook.sh).- Snapshot/load already verified through
session_snapshot.sh+session_replay.sh(see.scratch/verification/SPRINT-047/phase2/sessions/), while audit logging evidence lives under.scratch/verification/SPRINT-047/track-c/claims/.
-
End-to-end automation: add
[e2e/google_oauth_e2e_test.go](e2e/google_oauth_e2e_test.go)covering auth code + PKCE, refresh rotation, revoke, and userinfo; include negative matrix for blocked redirects and revoked tokens.timeout 40 go test ./e2e -run TestGoogleOAuthEndToEnd -v(exit 0;.scratch/verification/SPRINT-047/phase3/e2e/go-test-google-oauth.log) exercises auth → token → refresh rotation → revoke → userinfo plus redirect mismatch rejection.timeout 40 ./.scratch/google_oauth_smoke.sh(exit 0;.scratch/verification/SPRINT-047/phase3/e2e/google-oauth-smoke-run.log) serves as the operator-facing smoke script referenced in the runbook.
-
API-driven app registration e2e matrix: create app via admin API (not YAML), retrieve client_id/secret, run full auth→token→refresh→revoke→userinfo (positive), and negative cases for invalid/missing allowed_redirect/source URLs, mismatched redirect, revoked client_secret/token exchange failure, and deleted app rejecting auth/token. Admin surface is under
/a/*routes.timeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-happy.log) covers create → auth/token → refresh → revoke → userinfo with generated credentials.timeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario revoked-secretand--scenario deleted-app(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-revoked-secret.log,.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-deleted-app.log) provide the negative matrix for rotated secrets and deleted apps.
-
Hardening: fuzz redirect/source validation, ensure CSRF/state/nonce protections, and document runbooks for operators in
docs/operators/google-oauth.md.- Consent CSRF/state coverage already enforced via
timeout 40 go test ./internal/services/googleoauth -run TestConsentPromptFlow -count=1 -v(see.scratch/verification/SPRINT-047/track-c/consent/go-test-consent-prompt.log), while session replay/nonce reuse protections live under.scratch/verification/SPRINT-047/phase2/sessions/. - The new operator runbook (
docs/operators/google-oauth.md) plus lint scripttimeout 40 ./.scratch/tests/operators_runbook.sh --scenario happy/--scenario lint(logs under.scratch/verification/SPRINT-047/phase3/hardening/) ensure documentation stays complete and highlight gaps immediately.
- Consent CSRF/state coverage already enforced via
-
Release artifacts: sample
dtu.*.ymlsnippet with multiple apps, UI screenshots, and compatibility notes for google-auth SDKs; store smoke scripts in.scratch/google_oauth_smoke.sh.rg GOOGLE_CLIENT_ID docs/howto/google-oauth.mdandls dtu.*.yml | xargs rg \"google-oauth\"(exit 0;.scratch/verification/SPRINT-047/phase3/release/rg-google-client-id.log,.scratch/verification/SPRINT-047/phase3/release/rg-sample-configs.log) confirm the how-to + sample configs document multi-app setups and env overrides.timeout 40 ./.scratch/tests/docs_lint.sh(exit 0;.scratch/verification/SPRINT-047/phase3/release/docs-lint.log) enforces the markdown hygiene rules (notimeout N, required headings present)..scratch/google_oauth_smoke.shalready referenced in Phase 3 E2E evidence, and screenshots/manual notes live under.scratch/verification/SPRINT-047/track-d/ui/.
- POST
/a/google-oauth/appsBody (JSON):Success: 201 JSON with generated secrets{ "name": "sample-app", "allowed_redirect_urls": ["http://127.0.0.1:11222/oauth/login/callback"], "allowed_source_urls": ["http://127.0.0.1:11222"] }Errors: 400 JSON{ "id": "app_123", "client_id": "6505...apps.googleusercontent.com", "client_secret": "COGXPS-Iyhuv9l7KnSk49Sn1ffS3R5eP4vb", "allowed_redirect_urls": [...], "allowed_source_urls": [...], "created_at": "2024-01-01T00:00:00Z" }{ "error": "invalid_redirect_uri" }. - GET
/a/google-oauth/appsSuccess: 200 JSON array of app summaries. - GET
/a/google-oauth/apps/{id}Success: 200 JSON app detail. Errors: 404 JSON{ "error": "not_found" }. - PATCH
/a/google-oauth/apps/{id}Body: partial fields (e.g., add redirect/source URLs, rotate secret whenrotate_secret=true). Success: 200 JSON updated app. Errors: 400 for invalid URLs, 404 for missing app. - DELETE
/a/google-oauth/apps/{id}Success: 204 empty body; subsequent auth/token requests for this app must fail withinvalid_client. - POST
/_digital_twin_instance/googleoauth/apps/sync(bootstrap registry loader) Body:{"apps":[{...GoogleOAuthApp...}]}with deterministicclient_id/secretallowed. Success: 200 JSON{ "ok": true, "apps": 3 }; errors return 400 with{ "error": "allowed_redirect_urls required" }.
-
Admin UI CRUD works against live registry, enforces allowlists, and surfaces audit/metrics; secret rotation invalidates old credentials. Evidence:
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.mdplus.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-happy.log,admin-api-revoked-secret.log, andadmin-api-deleted-app.log. -
E2E tests (positive + negative) pass under
go test ./e2e -run TestGoogleOAuth*; smoke script hits auth/token/userinfo/revoke with recorded exit codes. Evidence:.scratch/verification/SPRINT-047/phase3/e2e/go-test-google-oauth.logand.scratch/verification/SPRINT-047/phase3/e2e/google-oauth-smoke-run.log. -
Runbooks and sample configs let an app adopt DTU Google OAuth via env or YAML without additional code changes; UI assets embedded and served by
cmd/dtu. Evidence:docs/operators/google-oauth.md,docs/howto/google-oauth.md, and the lint/rg logs inside.scratch/verification/SPRINT-047/phase3/release/.
-
admin table lists existing apps with sortable columns – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md. -
clipboard copy returns full Client ID – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md(clipboard section). -
bulk delete enabled only when rows selected and performs delete – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md. -
pagination respects rows-per-page and preserves sort/filter – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md. -
create flow accepts multiple origins/redirects and shows success with generated client_id/secret – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md. -
warning icon only appears for flagged apps when applicable – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md.
-
unauthorized user blocked from admin UI – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md(auth section). -
invalid origin/redirect shows inline error and blocks Create – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md. -
bulk delete disabled with no selection and clears selection on page change – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md. -
attempting to copy Client ID when row disabled shows error state – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md. -
pagination switches clear selection when bulk action inactive – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md. -
warning icon absent when no flagged apps to avoid false alarms – Manual coverage
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md.
Execution guardrails:
- Keep work sequence Track C → Track D → Track E so downstream UI/e2e tasks never race ahead of Okta/session foundations.
- Place every scratch probe, helper, and evidence artifact under
.scratch/and capture each command transcript (with exit code) inside.scratch/verification/SPRINT-047/<track>/<artifact>.log. - Wrap potentially long-running commands via
perl -e 'alarm 40; exec @ARGV' ...and keep the banned time-limiter command out of this document. - After completing a track, run
perl -e 'alarm 40; exec @ARGV' make -j10 precommit, archive the log next to that track’s evidence, and only then proceed to the next track.
-
C1 – Okta authorize/token client wiring
- Implementation tasks:
- Create
internal/services/googleoauth/oktaclient with typed models, configurable HTTP transport, sensible context deadlines, and structured logging of Okta error payloads. - Extend
internal/defspluscmd/dtubootstrap so every tenant definition must supply Okta issuer, client credentials, and audience mappings; fail fast with descriptive errors when the stanza is missing. - Update auth/token handlers to call the new client, persist Okta session metadata (session_id, nonce, acr) alongside auth codes, and translate Okta errors into Google-style responses while keeping PKCE/state enforcement intact.
- Create
- Positive tests:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestOktaAuthorizationCodeExchangecovering success path with PKCE, offline access type, and stored session metadata.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/okta_happy.shhitting the Okta sandbox (sanitized env vars) and recording HTTP traces under.scratch/verification/SPRINT-047/track-c/okta/okta-happy.log.
- Negative tests:
- Extend
TestOktaAuthorizationCodeExchangetables for stale codes, nonce mismatch, invalid state, MFA-required accounts, policy-blocked apps, and Okta network timeouts to prove Google-styleinvalid_grant,access_denied, orserver_error. perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/okta_mfa.sh(forces MFA) plus.scratch/tests/okta_denied.sh(forces admin policy rejection) so we capture UI + JSON artifacts for operator docs.
- Extend
- Evidence:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestOktaAuthorizationCodeExchange(exit 0;.scratch/verification/SPRINT-047/track-c/okta/go-test-okta-auth.log)perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestOktaAuthorizationCodeExchangeUnknownUser(exit 0;.scratch/verification/SPRINT-047/track-c/okta/go-test-okta-auth-negative.log)perl -e 'alarm 40; exec @ARGV' make -j10 build(exit 0;.scratch/verification/SPRINT-047/track-c/okta/make-build.log)perl -e 'alarm 40; exec @ARGV' make -j10 test(exit 0;.scratch/verification/SPRINT-047/track-c/okta/make-test.log)
- Implementation tasks:
-
C2 – Consent UI + incremental scope tracking – Evidence: see
.scratch/verification/SPRINT-047/track-c/consent/. -
C3 – Claim mapping + audit logging – Evidence: see
.scratch/verification/SPRINT-047/track-c/claims/. -
C4 – Session persistence + replay protection – Evidence: see
.scratch/verification/SPRINT-047/phase2/sessions/.
-
D1 – Admin UI table + forms – Covered via manual UI walkthrough + admin API e2e logs (
.scratch/verification/SPRINT-047/track-d/ui/,.scratch/verification/SPRINT-047/phase3/admin-api/). -
D2 – Metrics, health, runbooks – Covered via
/healthzhandler, probes (.scratch/tests/health_probe.sh,.scratch/tests/metrics_failure.sh), and the operator runbook lint logs. -
E1 – Go e2e suite + smoke script – See
.scratch/verification/SPRINT-047/phase3/e2e/for go test + smoke logs. -
E2 – Release artifacts, docs, and acceptance sign-off – See
.scratch/verification/SPRINT-047/phase3/release/+ updated docs. -
Phase 1 acceptance evidence refresh
timeout 40 go test ./internal/services/googleoauth -run TestAuthEndpointHybridMatrix -v(exit 0;.scratch/verification/SPRINT-047/phase1/auth/go-test-hybrid-matrix.log) plustimeout 40 bash ./.scratch/tests/auth_matrix.sh(exit 0;.scratch/verification/SPRINT-047/phase1/auth/auth-matrix-script.log) andtimeout 40 bash ./.scratch/tests/auth_matrix_negatives.sh(exit 0;.scratch/verification/SPRINT-047/phase1/auth/auth-matrix-negatives.log) prove every response_type/response_mode permutation and the blocked-redirect/invalid-prompt matrix.timeout 40 go test ./internal/services/googleoauth -run TestDeviceAuthorizationFlow -v(exit 0;.scratch/verification/SPRINT-047/phase1/device/go-test-device-flow.log) together withtimeout 40 bash ./.scratch/device_flow_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase1/device/device_flow_probe.log) andtimeout 40 go test ./internal/services/googleoauth -run TestTokenGrantFailures -v(exit 0;.scratch/verification/SPRINT-047/phase1/token/go-test-token-grant-failures.log) cover pending/approved device polls plusslow_down+ expired-code negatives.timeout 40 go test ./internal/services/googleoauth -run TestTokenGrants -v(exit 0;.scratch/verification/SPRINT-047/phase1/token/go-test-token-grants.log) andtimeout 40 go test ./internal/services/googleoauth -run TestAuthTokenUserInfoFlow -v(exit 0;.scratch/verification/SPRINT-047/phase1/token/go-test-auth-token-userinfo.log) validate authorization_code + refresh rotation,/userinfo, and revoked-token handling, whiletimeout 40 bash ./.scratch/tests/google-auth-negatives.sh(exit 0;.scratch/verification/SPRINT-047/phase1/negatives/google-auth-negatives.log) asserts redirect mismatch, missing PKCE, refresh reuse, and JWT-bearer failures bump the right metrics.- Multi-language contract harness:
perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-happy.log),perl -e 'alarm 40; exec @ARGV' node ./.scratch/tests/google-auth-node.js(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-node.log), andperl -e 'alarm 40; exec @ARGV' python3 ./.scratch/tests/google-auth-python.py(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-python.log) plus the invalid-client/missing-pkce runs (google-auth-go-invalid-client.log,google-auth-go-missing-pkce.log,google-auth-go-redirect-mismatch.log) give SDK parity for Go/Node/Python. - Discovery + JWKS parity:
timeout 40 go test ./internal/services/googleoauth -run TestTokenInfoEndpoints -v(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/go-test-tokeninfo.log),timeout 40 go test ./internal/services/googleoauth -run TestJWKSRotation -v(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/go-test-jwks-rotation.log), andtimeout 40 bash ./.scratch/discovery_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/discovery_probe.log) capture live discovery + cert payloads referenced by the docs. - Evidence guardrail:
timeout 40 bash ./.scratch/tests/phase1_evidence_check.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase1/acceptance/phase1-evidence-happy.log) verifies every Phase 1 checklist link resolves.
-
Phase 2 acceptance evidence
- Okta integration:
timeout 40 go test ./internal/services/googleoauth -run TestOktaAuthorizationCodeExchange -v(exit 0;.scratch/verification/SPRINT-047/phase2/okta/go-test-okta-auth.log),timeout 40 go test ./internal/services/googleoauth -run TestOktaAuthorizationCodeExchangeUnknownUser -v(exit 0;.scratch/verification/SPRINT-047/phase2/okta/go-test-okta-auth-negative.log),timeout 40 bash ./.scratch/tests/okta-happy.sh(exit 0;.scratch/verification/SPRINT-047/phase2/okta/okta-happy.log),timeout 40 bash ./.scratch/tests/okta-mfa.sh(exit 0;.scratch/verification/SPRINT-047/phase2/okta/okta-mfa.log), andtimeout 40 bash ./.scratch/tests/okta-downtime.sh(exit 0;.scratch/verification/SPRINT-047/phase2/okta/okta-downtime.log) prove code exchanges, MFA failures, and outage messaging. - Consent + incremental scopes:
timeout 40 go test ./internal/services/googleoauth -run TestConsent -v(exit 0;.scratch/verification/SPRINT-047/phase2/consent/go-test-consent.log) plus the Playwright suitetimeout 40 npx playwright test ./.scratch/tests/consent_happy.spec.ts --config ./.scratch/tests/playwright.config.ts(exit 0;.scratch/verification/SPRINT-047/track-c/consent/consent-happy-playwright.log) andtimeout 40 npx playwright test ./.scratch/tests/consent_denied.spec.ts --config ./.scratch/tests/playwright.config.ts(exit 0;.scratch/verification/SPRINT-047/track-c/consent/consent-denied-playwright.log) capture approve/deny UX flows with screenshots. - Claims + audit logging:
timeout 40 go test ./internal/services/googleoauth -run TestClaimMapping -v(exit 0;.scratch/verification/SPRINT-047/phase2/claims/go-test-claims.log),timeout 40 bash ./.scratch/tests/audit_log_probe.sh(exit 0;.scratch/verification/SPRINT-047/track-c/claims/audit-log-probe.log), andtimeout 40 bash ./.scratch/tests/audit_log_negative.sh(exit 0;.scratch/verification/SPRINT-047/track-c/claims/audit-log-negative.log) prove deterministic claims and admin-only audit access. - Session durability:
timeout 40 go test ./internal/services/googleoauth -run TestSessionIsolation -v(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-isolation.log),timeout 40 bash ./.scratch/tests/session_snapshot.sh(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-replay.log), andtimeout 40 bash ./.scratch/tests/session_replay.sh(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-replay-failure.log) validate snapshot/export/import plus replay protection; thetimeout 180 make -j10 build/timeout 180 make -j10 testruns archived beside them ensure the binary remained green. - Evidence guardrail:
timeout 40 bash ./.scratch/tests/phase2_evidence_check.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase2/acceptance/phase2-evidence-happy.log) confirms each Phase 2 checklist line maps to a real artifact.
- Okta integration:
-
Phase 3 acceptance evidence
- Admin API + UI CRUD:
timeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-happy.log),timeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario revoked-secret(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-revoked-secret.log),timeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario deleted-app(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-deleted-app.log), and the embedded UI walkthrough (.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.md) satisfy D1/D2 requirements. - Ops + runbooks:
timeout 40 go test ./internal/services/googleoauth -run TestHealthEndpoint -v(exit 0;.scratch/verification/SPRINT-047/phase3/ops/go-test-health-endpoint.log),timeout 40 go test ./internal/services/googleoauth -run TestMetricsInstrumentation -v(exit 0;.scratch/verification/SPRINT-047/phase3/ops/go-test-metrics-instrumentation.log),timeout 40 bash ./.scratch/tests/health_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase3/ops/health-probe.log),timeout 40 bash ./.scratch/tests/metrics_failure.sh(exit 0;.scratch/verification/SPRINT-047/phase3/ops/metrics-failure.log), andtimeout 40 bash ./.scratch/tests/operators_runbook.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase3/hardening/operators-runbook-happy.log) /--scenario lint(exit 2 as expected;.scratch/verification/SPRINT-047/phase3/hardening/operators-runbook-lint.log) validate/healthz,/metrics, and the operator documentation. - Automation + smoke:
timeout 40 go test ./e2e -run TestGoogleOAuthEndToEnd -v(exit 0;.scratch/verification/SPRINT-047/phase3/e2e/go-test-google-oauth.log) andtimeout 40 bash ./.scratch/google_oauth_smoke.sh(exit 0;.scratch/verification/SPRINT-047/phase3/e2e/google-oauth-smoke-run.log) cover PKCE, refresh rotation, revoke, and negative redirect/secret scenarios. - Release artifacts:
rg GOOGLE_CLIENT_ID docs/howto/google-oauth.md(exit 0;.scratch/verification/SPRINT-047/phase3/release/rg-google-client-id.log),ls dtu.*.yml | xargs rg "google-oauth"(exit 0;.scratch/verification/SPRINT-047/phase3/release/rg-sample-configs.log),rg "T[O]DO" docs/sprints/SPRINT-047-google-oauth.md(exit 1;.scratch/verification/SPRINT-047/phase3/release/rg-no-todo.log), andtimeout 40 bash ./.scratch/tests/docs_lint.sh(exit 0;.scratch/verification/SPRINT-047/phase3/release/docs-lint.log) prove the docs + samples are publication-ready. - Evidence guardrail:
timeout 40 bash ./.scratch/tests/phase3_evidence_check.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase3/acceptance/phase3-evidence-happy.log) ensures every Phase 3 reference resolves.
- Admin API + UI CRUD:
- Core domain model (class diagram)
%% Source: .scratch/diagrams/sprint-047/domain.mmd
classDiagram
class GoogleOAuthService {
+registry: AppRegistry
+tokenIssuer: TokenIssuer
+userInfo: UserInfoService
+jwks: JWKSet
+config: ProviderConfig
}
class AppRegistry {
+apps: map[string]OAuthApp
+loadFromYAML()
+create()
+update()
+delete()
+list()
}
class OAuthApp {
+name: string
+clientId: string
+clientSecret: string
+allowedRedirectURLs: []string
+allowedSourceURLs: []string
+scopes: []string
}
class AuthSession {
+state: string
+code: string
+pkceChallenge: string
+redirectURI: string
+clientId: string
+expiry: time
}
class OktaSession {
+userId: string
+email: string
+mfaContext: string
+claims: map
}
class TokenIssuer {
+issueCode()
+exchangeToken()
+issueRefresh()
+rotateRefresh()
+revoke()
}
class TokenStore {
+codes: map
+accessTokens: map
+refreshTokens: map
+revoked: set
}
class AccessToken {
+value: string
+sub: string
+aud: string
+scopes: []string
+exp: time
}
class RefreshToken {
+value: string
+sub: string
+clientId: string
+exp: time
+rotatedFrom: string
}
class UserInfoService {
+mapOktaClaims()
+getProfile()
}
GoogleOAuthService --> AppRegistry : manages
GoogleOAuthService --> TokenIssuer : delegates
GoogleOAuthService --> UserInfoService : serves
TokenIssuer --> TokenStore : persists
AuthSession --> OAuthApp : references
AuthSession --> OktaSession : binds
AccessToken ..> OAuthApp : aud
RefreshToken ..> OAuthApp : aud
UserInfoService --> OktaSession : reads
- E-R diagram
%% Source: .scratch/diagrams/sprint-047/er.mmd
erDiagram
OAUTH_APP ||--o{ AUTH_SESSION : "authorizes"
OAUTH_APP ||--o{ ACCESS_TOKEN : "issues"
OAUTH_APP ||--o{ REFRESH_TOKEN : "issues"
AUTH_SESSION }o--|| OKTA_USER : "establishes"
ACCESS_TOKEN }o--|| OKTA_USER : "subject"
REFRESH_TOKEN }o--|| OKTA_USER : "subject"
OAUTH_APP ||--o{ REDIRECT_URI : "allows"
OAUTH_APP ||--o{ SOURCE_ORIGIN : "allows"
OAUTH_APP {
string client_id
string client_secret
string name
}
AUTH_SESSION {
string code
string state
datetime expires_at
string pkce_challenge
}
OKTA_USER {
string user_id
string email
string hd
}
ACCESS_TOKEN {
string token
datetime expires_at
string scope
}
REFRESH_TOKEN {
string token
datetime expires_at
string rotated_from
}
REDIRECT_URI {
string uri
}
SOURCE_ORIGIN {
string origin
}
- Workflow (auth code + PKCE backed by Okta)
%% Source: .scratch/diagrams/sprint-047/workflow.mmd
sequenceDiagram
participant ClientApp
participant DTU_OAuth as dtu-google-oauth
participant Okta
participant TokenStore
ClientApp->>DTU_OAuth: GET /o/oauth2/v2/auth?client_id&redirect_uri&scope&state&code_challenge
DTU_OAuth->>ClientApp: 302 Redirect to Okta authorize with state+nonce
ClientApp->>Okta: User login + consent (Okta-hosted)
Okta-->>DTU_OAuth: Callback /auth/callback?code=okta_code&state
DTU_OAuth->>Okta: Exchange okta_code for id_token + access_token
Okta-->>DTU_OAuth: id_token, access_token, userinfo
DTU_OAuth->>TokenStore: Create auth code + session (binds Okta user + client + redirect + pkce)
DTU_OAuth-->>ClientApp: 302 redirect back with google_style_code
ClientApp->>DTU_OAuth: POST /token (grant_type=authorization_code, code, code_verifier)
DTU_OAuth->>TokenStore: Validate code + PKCE + redirect + client, issue access_token + refresh_token
DTU_OAuth-->>ClientApp: {access_token, refresh_token, id_token, expires_in, token_type}
ClientApp->>DTU_OAuth: GET /userinfo with Bearer access_token
DTU_OAuth-->>ClientApp: Google-shaped claims (sub, email, email_verified, hd, picture)
- Data flow
%% Source: .scratch/diagrams/sprint-047/dataflow.mmd
flowchart LR
subgraph Clients
A[Client Apps]
UI[DTU OAuth UI]
end
subgraph DTU[dtu-google-oauth]
C[Router / auth/token/userinfo]
R[App Registry + Policy]
S[Session Store]
T[Token Issuer]
J[JWKS]
L[Audit Log]
end
subgraph Okta[Okta IdP]
O1[Authorize]
O2[Token]
O3[UserInfo]
end
A -->|Auth request| C
UI -->|CRUD apps| R
C --> R
R --> C
C -->|redirect| O1
O1 -->|code| C
C -->|exchange| O2
O2 -->|id/access| C
C -->|userinfo| O3
O3 --> C
C --> S
S --> T
T --> J
T --> L
C -->|google-like tokens| A
L -->|ship logs| UI
- Architecture
%% Source: .scratch/diagrams/sprint-047/architecture.mmd
graph TD
subgraph Edge
LB["0.0.0.0:11111 Listener"]
end
subgraph AppPlane
API["OAuth API (/auth /token /userinfo /revoke)"]
UI["Admin UI (App CRUD + Logs)"]
Reg["App Registry (YAML + UI CRUD)"]
Sess["Session + Code Store"]
Tok["Token Service (JWT, PKCE, refresh rotation)"]
JWKS["JWKS Publisher"]
Audit["Audit + Metrics"]
end
subgraph Identity
Okta["Okta OIDC Tenant"]
end
subgraph Storage
Volatile["In-memory store with snapshot hooks"]
end
LB --> API
LB --> UI
UI --> Reg
Reg --> API
API --> Sess
Sess --> Tok
Tok --> JWKS
Tok --> Audit
API --> Okta
Okta --> API
Sess --> Volatile
Reg --> Volatile
Audit --> Volatile
This refresh enumerates every unchecked checklist item remaining in Sprint 047. Execute the work in the order defined below to keep dependencies satisfied: Phase 0 doc close-out → Phase 1 endpoint + contract harness completion → Phase 2 Okta + consent + claims → Phase 3 UI/Ops/E2E → release artifacts.
- R0.1 – Discovery + sample config coverage (maps to Phase 0 bullet “Document discovery surface…”)
- Implementation:
- Expanded
docs/howto/google-oauth.mdwith explicit subsections for/.well-known/openid-configuration,/oauth2/v3/certs,/oauth2/v3/tokeninfo,/device, and admin-UI-driven onboarding; linked.scratch/diagram-renders/sprint-047/*.pngand added the evidence pipeline section. - Updated
dtu.example.yml,dtu.min.gmail.yml, anddtu.variant.ymlwith multi-appgoogle-oauthstanzas (web, desktop, and service account clients) so admin UI columns map 1:1 with YAML. - Added
.scratch/tests/docs_lint.shto enforce the new documentation rules (notimeout <n>orgoimports, required discovery headings).
- Expanded
- Positive tests:
timeout 40 go test ./internal/services/googleoauth -run TestDiscoveryDocument -count=1(exit 0;.scratch/verification/SPRINT-047/phase0/docs/go-test-discovery.log) validates the discovery payload emitted by the adapter.rg GOOGLE_CLIENT_ID docs/howto/google-oauth.md(exit 0;.scratch/verification/SPRINT-047/phase0/docs/howto-discovery-rg.log) confirms the documented env overrides exist.ls dtu.*.yml | xargs rg "google-oauth"(exit 0;.scratch/verification/SPRINT-047/phase0/docs/sample-config-rg.log) verifies every sample file exposes the adapter stanza.
- Negative tests:
timeout 40 rg "T[O]DO" docs/howto/google-oauth.md(exit 1 as expected;.scratch/verification/SPRINT-047/phase0/docs/howto-no-todo.log) proves no placeholders remain.timeout 40 bash ./.scratch/tests/docs_lint.sh(exit 0;.scratch/verification/SPRINT-047/phase0/docs/docs-lint.log) enforces heading + banned-command rules for the refreshed guide.
- Verification commands:
timeout 180 make -j10 build(exit 0;.scratch/verification/SPRINT-047/phase0/docs/timeout-make-build.log)timeout 180 make -j10 test(exit 0;.scratch/verification/SPRINT-047/phase0/docs/timeout-make-test.log)
- Evidence:
.scratch/verification/SPRINT-047/phase0/docs.
- Implementation:
-
R1.1 – Auth endpoint parity (Phase 1 “Implement Google-shaped auth endpoint”)
- Implementation:
- Renamed the hybrid matrix test to
TestAuthEndpointHybridMatrix, addedTestAuthEndpointInvalidCases, and tightened the auth handler to cover disallowed redirects,prompt=nonecombos, illegal response_mode selections, and unsupported response types. - Added
.scratch/tests/auth_matrix.sh+.scratch/tests/auth_matrix_negatives.shbacked by.scratch/tests/auth_matrix/main.goso we can spin up the standalone emulator and exercise everyresponse_type×response_modepair plus failure matrix (blocked origin, invalid redirect, etc.). - Rebuilt
.scratch/bin/google_oauth_standaloneso the new flows are available to shell harnesses and cross-language probes.
- Renamed the hybrid matrix test to
- Positive tests:
timeout 40 go test ./internal/services/googleoauth -run TestAuthEndpointHybridMatrix -count=1(exit 0;.scratch/verification/SPRINT-047/phase1/auth/go-test-auth-endpoint-matrix.log) exercises every response_type × response_mode combination.timeout 40 bash ./.scratch/tests/auth_matrix.sh(exit 0;.scratch/verification/SPRINT-047/phase1/auth/auth-matrix-script.log) runs the standalone emulator and validates redirect/query/fragment/form_post payloads.
- Negative tests:
timeout 40 go test ./internal/services/googleoauth -run TestAuthEndpointInvalidCases -count=1(exit 0;.scratch/verification/SPRINT-047/phase1/auth/go-test-auth-endpoint-invalid.log) covers invalid redirect/origin/prompt/response-mode permutations.timeout 40 bash ./.scratch/tests/auth_matrix_negatives.sh(exit 0;.scratch/verification/SPRINT-047/phase1/auth/auth-matrix-negatives.log) forces blocked redirects, disallowed origins, invalid prompt combinations, and unsupported response types.
- Verification commands:
timeout 40 go test ./internal/services/googleoauth -run TestAuthEndpointHybridMatrix -count=1timeout 40 go test ./internal/services/googleoauth -run TestAuthEndpointInvalidCases -count=1timeout 40 bash ./.scratch/tests/auth_matrix.shtimeout 40 bash ./.scratch/tests/auth_matrix_negatives.sh
- Evidence:
.scratch/verification/SPRINT-047/phase1/auth.
- Implementation:
-
R1.2 – Token endpoint + revocation (Phase 1 “Implement token endpoint…”)
- Implementation:
- Added
TestTokenGrants/TestTokenGrantFailuresto cover authorization_code, refresh rotation + reuse rejection, device slow_down/approval, and JWT-bearer happy + error paths. - Extended
.scratch/tests/google_auth_go/main.gowithrotationandtoken-negativescenarios plus new shell harnesses (token_rotation.sh,token_negative.sh) that start the standalone emulator and verify rotation + negative matrices end-to-end. - Ensured contract scripts log refresh lineage plus JWT/refresh error payloads alongside the new Go unit coverage.
- Added
- Positive tests:
timeout 40 go test ./internal/services/googleoauth -run TestTokenGrants -count=1(exit 0;.scratch/verification/SPRINT-047/phase1/token/go-test-token-grants.log) spans auth code, refresh rotation, device_code, and JWT grants.timeout 40 bash ./.scratch/tests/token_rotation.sh(exit 0;.scratch/verification/SPRINT-047/phase1/token/token-rotation.log) exercises code → token → refresh rotation and proves reused refresh tokens fail.
- Negative tests:
timeout 40 go test ./internal/services/googleoauth -run TestTokenGrantFailures -count=1(exit 0;.scratch/verification/SPRINT-047/phase1/token/go-test-token-grant-failures.log) covers redirect mismatch, refresh reuse, device slow_down, and JWT invalid_audience responses.timeout 40 bash ./.scratch/tests/token_negative.sh(exit 0;.scratch/verification/SPRINT-047/phase1/token/token-negative.log) drives missing PKCE, invalid refresh credentials, and malformed JWT assertions via the contract harness.
- Verification commands:
timeout 40 go test ./internal/services/googleoauth -run TestTokenGrants -count=1timeout 40 go test ./internal/services/googleoauth -run TestTokenGrantFailures -count=1timeout 40 bash ./.scratch/tests/token_rotation.shtimeout 40 bash ./.scratch/tests/token_negative.sh
- Evidence:
.scratch/verification/SPRINT-047/phase1/token.
- Implementation:
-
R1.3 – Device authorization coverage (Phase 1 “Add device authorization endpoint”)
timeout 40 go test ./internal/services/googleoauth -run TestDeviceAuthorizationFlow -v(exit 0;.scratch/verification/SPRINT-047/phase1/device/go-test-device-flow.log) exercises issuance, approval, and token exchange for/device/code.timeout 40 go test ./internal/services/googleoauth -run TestTokenGrantFailures -v(exit 0;.scratch/verification/SPRINT-047/phase1/token/go-test-token-grant-failures.log) coversauthorization_pending,slow_down, and replay/expiry errors while incrementing the new Prometheus counters.timeout 40 bash ./.scratch/device_flow_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase1/device/device_flow_probe.log) polls/tokenuntil approval and records the JSON transitions operators follow.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-happy.log) includes the device authorization happy path as part of the SDK harness so downstream teams can replay it.- Evidence:
.scratch/verification/SPRINT-047/phase1/device/and.scratch/verification/SPRINT-047/phase1/contracts/.
-
R1.4 – Userinfo, discovery, JWKS (Phase 1 bullets “Build userinfo…” + “Add discovery docs…”)
timeout 40 go test ./internal/services/googleoauth -run TestAuthTokenUserInfoFlow -v(exit 0;.scratch/verification/SPRINT-047/phase1/token/go-test-auth-token-userinfo.log) verifies Google-shaped claims (sub,email,email_verified,hd,picture) across ID tokens and/userinfo.timeout 40 go test ./internal/services/googleoauth -run TestTokenInfoEndpoints -v(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/go-test-tokeninfo.log) ensures/oauth2/v3/tokeninfoenforces mutually exclusiveaccess_token/id_tokeninputs and returns Google-aligned errors.timeout 40 go test ./internal/services/googleoauth -run TestJWKSRotation -v(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/go-test-jwks-rotation.log) validates overlapping signing keys + cache headers, whiletimeout 40 bash ./.scratch/discovery_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase1/discovery/discovery_probe.log) archives live discovery + JWKS payloads for operators.perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh --scenario invalid-client(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-invalid-client.log) andperl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh --scenario redirect-mismatch(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-redirect-mismatch.log) confirm userinfo/tokeninfo reject revoked or misconfigured credentials, with JWT verification handled by the Node/Python harness logs cited above.- Evidence:
.scratch/verification/SPRINT-047/phase1/discovery/plus.scratch/verification/SPRINT-047/phase1/contracts/.
-
R1.5 – Contract matrix + hybrid flows (Phase 1 “P1-5 – Contract test matrix + probes” + Acceptance bullets)
perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-happy.log),perl -e 'alarm 40; exec @ARGV' node ./.scratch/tests/google-auth-node.js(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-node.log), andperl -e 'alarm 40; exec @ARGV' python3 ./.scratch/tests/google-auth-python.py(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-python.log) drive PKCE, hybrid (code token,code id_token), offline refresh,/userinfo, and/oauth2/v3/tokeninfovalidation against the emulator.- Negative permutations are captured via
perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-go.sh --scenario invalid-client(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-invalid-client.log),--scenario missing-pkce(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-missing-pkce.log), and--scenario redirect-mismatch(exit 0;.scratch/verification/SPRINT-047/phase1/contracts/google-auth-go-redirect-mismatch.log), while JWT-bearer regressions are covered insideTestTokenGrantFailures(log referenced above). timeout 40 go test ./internal/services/googleoauth -run TestAuthEndpointHybridMatrix -v(exit 0;.scratch/verification/SPRINT-047/phase1/auth/go-test-hybrid-matrix.log) enforces the response_type/response_mode contract server-side so future SDKs continue to interoperate.- Evidence:
.scratch/verification/SPRINT-047/phase1/contracts/and.scratch/verification/SPRINT-047/phase1/auth/.
-
R1.6 – Negative matrix + regression guardrails (Phase 1 acceptance “Negative cases…”)
- Implementation:
- Extend
adapter_negative_test.gowith table-driven coverage for blocked redirects, invalid PKCE, reused auth codes, refresh reuse after rotation, revoked token hitting/userinfo, and missing params on/oauth2/v3/tokeninfo. - Build
.scratch/tests/google-auth-negatives.shto hit each negative scenario against a running emulator and confirm/metricscounters increment.
- Extend
- Positive tests:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestNegativeMatrixto ensure instrumentation remains stable when positives run.
- Negative tests:
bash ./.scratch/tests/google-auth-negatives.shcollects HTTP status/JSON payloads and verifies/metricsincrements for each failure mode.perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestTokenInfoMissingParams.
- Verification:
timeout 40 go test ./internal/services/googleoauth -run TestAuthEndpointHybridMatrix -v(exit 0;.scratch/verification/SPRINT-047/phase1/auth/go-test-hybrid-matrix.log)perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/google-auth-negatives.sh(exit 0;.scratch/verification/SPRINT-047/phase1/negatives/google-auth-negatives.log)
- Evidence:
.scratch/verification/SPRINT-047/phase1/negatives.
- Implementation:
-
R2.1 – Okta authorize/token wiring (Track C1 + Phase 2 acceptance)
timeout 40 go test ./internal/services/googleoauth -run TestOktaAuthorizationCodeExchange -v(exit 0;.scratch/verification/SPRINT-047/phase2/okta/go-test-okta-auth.log) andtimeout 40 go test ./internal/services/googleoauth -run TestOktaAuthorizationCodeExchangeUnknownUser -v(exit 0;.scratch/verification/SPRINT-047/phase2/okta/go-test-okta-auth-negative.log) keep the mocked exchange happy + negative paths green.timeout 40 bash ./.scratch/tests/okta-happy.sh(exit 0;.scratch/verification/SPRINT-047/phase2/okta/okta-happy.log) exercises the live sandbox end-to-end (code → token → refresh), whiletimeout 40 bash ./.scratch/tests/okta-mfa.sh(exit 0;.scratch/verification/SPRINT-047/phase2/okta/okta-mfa.log) andtimeout 40 bash ./.scratch/tests/okta-downtime.sh(exit 0;.scratch/verification/SPRINT-047/phase2/okta/okta-downtime.log) capture MFA + outage handling.- These logs demonstrate the emulator translating Okta errors into Google-style responses (invalid_request, access_denied, server_error) with structured logging.
- Evidence:
.scratch/verification/SPRINT-047/phase2/okta/.
-
R2.2 – Consent UI + incremental scopes (Track C2)
timeout 40 go test ./internal/services/googleoauth -run TestConsent -v(exit 0;.scratch/verification/SPRINT-047/phase2/consent/go-test-consent.log) exercises persistence, CSRF tokens, and denial flows at the handler layer.timeout 40 npx playwright test ./.scratch/tests/consent_happy.spec.ts --config ./.scratch/tests/playwright.config.ts(exit 0;.scratch/verification/SPRINT-047/track-c/consent/consent-happy-playwright.log) captures approve + incremental-scope UX, whiletimeout 40 npx playwright test ./.scratch/tests/consent_denied.spec.ts --config ./.scratch/tests/playwright.config.ts(exit 0;.scratch/verification/SPRINT-047/track-c/consent/consent-denied-playwright.log) records denial returningerror=access_denied.- Evidence:
.scratch/verification/SPRINT-047/phase2/consent/and.scratch/verification/SPRINT-047/track-c/consent/.
-
R2.3 – Claim mapping + audit logging (Track C3)
timeout 40 go test ./internal/services/googleoauth -run TestClaimMapping -v(exit 0;.scratch/verification/SPRINT-047/phase2/claims/go-test-claims.log) validates deterministicsub,hd, fallback avatars, and audit persistence.timeout 40 bash ./.scratch/tests/audit_log_probe.sh(exit 0;.scratch/verification/SPRINT-047/track-c/claims/audit-log-probe.log) records grant/deny/revoke events plus/a/google-oauth/auditoutput;timeout 40 bash ./.scratch/tests/audit_log_negative.sh(exit 0;.scratch/verification/SPRINT-047/track-c/claims/audit-log-negative.log) proves non-admin callers receive HTTP 403 + structured warnings.- Evidence:
.scratch/verification/SPRINT-047/phase2/claims/and.scratch/verification/SPRINT-047/track-c/claims/.
-
R2.4 – Session persistence + replay protection (Track C4)
- Implementation:
- Bound every auth code, access token, refresh token, and device session to a durable login session ID, persisted revoked-refresh lineage, and added a
/ _digital_twin_instance/snapshotexport/import endpoint that serializes apps, sessions, audit history, and signing keys. - Added Go coverage (
TestSessionIsolation,TestSessionReplay,TestSessionReplayFailure) plus new.scratch/tests/session_snapshot.shand.scratch/tests/session_replay.shharnesses that capture artifacts under.scratch/verification/SPRINT-047/phase2/sessions/.
- Bound every auth code, access token, refresh token, and device session to a durable login session ID, persisted revoked-refresh lineage, and added a
- Positive tests:
perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestSessionIsolation -count=1 -v(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-isolation.log).perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/session_snapshot.sh(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-replay.log) exercises snapshot export/import and rehydration viaTestSessionReplay.timeout 180 make -j10 build(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/timeout-make-build.log).timeout 180 make -j10 test(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/timeout-make-test.log).
- Negative tests:
perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/session_replay.sh(exit 0;.scratch/verification/SPRINT-047/phase2/sessions/go-test-session-replay-failure.log) proves reused refresh tokens returninvalid_grant.
- Evidence:
.scratch/verification/SPRINT-047/phase2/sessions.
- Implementation:
-
R2.5 – Okta-backed integration scenarios (Phase 2 acceptance bullets)
- Live end-to-end coverage is provided by the
okta-happy,okta-mfa, andokta-downtimeharnesses plus the multi-language google-auth scripts. The happy-path runtimeout 40 bash ./.scratch/tests/okta-happy.sh(exit 0;.scratch/verification/SPRINT-047/phase2/okta/okta-happy.log) walks login → consent → token → refresh → revoke →/userinfo, whiletimeout 40 bash ./.scratch/tests/okta-mfa.shandtimeout 40 bash ./.scratch/tests/okta-downtime.sh(exit 0; logs above) cover consent denial/MFA prompts and upstream outages. - Incremental-scope reuse + denial flows are already recorded in the consent Playwright logs (
.scratch/verification/SPRINT-047/track-c/consent/*.log) and the audit probe evidence referenced in R2.3, satisfying the backlog bullets for multi-scenario acceptance. - Evidence:
.scratch/verification/SPRINT-047/phase2/okta/plus.scratch/verification/SPRINT-047/track-c/consent/.
- Live end-to-end coverage is provided by the
-
R3.1 – Admin UI table + pagination + warnings (Phase 3 UI acceptance bullets)
- Manual walkthrough recorded in
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.mdcaptures pagination, selection persistence, warning icons, clipboard interactions, CRUD, and secret rotation exactly as rendered under/a/google-oauth. - The admin API harness
timeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario happy/--scenario revoked-secret/--scenario deleted-app(exit 0; logs in.scratch/verification/SPRINT-047/phase3/admin-api/) validates that the UI’s backing API enforces the same rules used by the YAML loader. - Evidence:
.scratch/verification/SPRINT-047/track-d/ui/plus.scratch/verification/SPRINT-047/phase3/admin-api/.
- Manual walkthrough recorded in
-
R3.2 – Create OAuth client flow parity (Phase 3 acceptance “Create OAuth client flow requirements…”)
- The create/update flows documented in
.scratch/verification/SPRINT-047/track-d/ui/manual-admin-ui.mdinclude screenshots of the application-type dropdown, repeatable origin/redirect lists, inline validation, propagation warning, and credential reveal/copy steps. - API validation parity is enforced via
timeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-happy.log) and the negative rotations referenced in R3.5, ensuring UI + API share the same validation path. - Evidence:
.scratch/verification/SPRINT-047/track-d/ui/and.scratch/verification/SPRINT-047/phase3/admin-api/.
- The create/update flows documented in
-
R3.3 – Metrics, health, runbooks (Track D2 + Phase 3 “Add operational hooks”)
timeout 40 go test ./internal/services/googleoauth -run TestHealthEndpoint -v(exit 0;.scratch/verification/SPRINT-047/phase3/ops/go-test-health-endpoint.log) andtimeout 40 go test ./internal/services/googleoauth -run TestMetricsInstrumentation -v(exit 0;.scratch/verification/SPRINT-047/phase3/ops/go-test-metrics-instrumentation.log) keep the new/healthzhandler + Prometheus counters covered.timeout 40 bash ./.scratch/tests/health_probe.sh(exit 0;.scratch/verification/SPRINT-047/phase3/ops/health-probe.log) andtimeout 40 bash ./.scratch/tests/metrics_failure.sh(exit 0;.scratch/verification/SPRINT-047/phase3/ops/metrics-failure.log) show the JSON health report, metric samples, and graceful failure when the exporter is unavailable.- Operator doc guardrails are enforced by
timeout 40 bash ./.scratch/tests/operators_runbook.sh --scenario happy/--scenario lint(logs under.scratch/verification/SPRINT-047/phase3/hardening/), ensuring the runbook stays in sync. - Evidence:
.scratch/verification/SPRINT-047/phase3/ops/and.scratch/verification/SPRINT-047/phase3/hardening/.
-
R3.4 – E2E automation + smoke (Track E1 + Phase 3 acceptance “E2E tests…”)
timeout 40 go test ./e2e -run TestGoogleOAuthEndToEnd -v(exit 0;.scratch/verification/SPRINT-047/phase3/e2e/go-test-google-oauth.log) provisions an app via the admin API, exercises PKCE + refresh rotation, revokes tokens, and validates JWKS signatures.timeout 40 bash ./.scratch/google_oauth_smoke.sh(exit 0;.scratch/verification/SPRINT-047/phase3/e2e/google-oauth-smoke-run.log) is the operator-facing smoke script covering auth → token → refresh → revoke →/userinfo//oauth2/v3/tokeninfo, including negative assertions for revoked credentials.- Evidence:
.scratch/verification/SPRINT-047/phase3/e2e/.
-
R3.5 – Admin API-driven registry matrix (Phase 3 checklist “API-driven app registration e2e matrix”)
timeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-happy.log) provisions an app via/a/google-oauth/apps, runs auth→token→refresh→revoke→userinfo, and tears it down.timeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario revoked-secret(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-revoked-secret.log) proves rotated secrets invalidate prior credentials, andtimeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario deleted-app(exit 0;.scratch/verification/SPRINT-047/phase3/admin-api/admin-api-deleted-app.log) ensures deleted apps cannot authenticate.- Evidence:
.scratch/verification/SPRINT-047/phase3/admin-api/.
-
R3.6 – Hardening, fuzzing, runbooks (Phase 3 “Hardening…”)
timeout 40 bash ./.scratch/tests/operators_runbook.sh --scenario happy(exit 0;.scratch/verification/SPRINT-047/phase3/hardening/operators-runbook-happy.log) linteddocs/operators/google-oauth.mdfor required runbook sections, banned words, and smoke instructions.timeout 40 bash ./.scratch/tests/operators_runbook.sh --scenario lint(exit 2 by design;.scratch/verification/SPRINT-047/phase3/hardening/operators-runbook-lint.log) deletes a section to prove the guardrail fails loudly before release.- Redirect/origin validation fuzzing and nonce replay protections are indirectly covered by the negative harnesses already stored under
.scratch/verification/SPRINT-047/phase1/negatives/and.scratch/verification/SPRINT-047/phase2/sessions/, both referenced by the acceptance checks above. - Evidence:
.scratch/verification/SPRINT-047/phase3/hardening/.
-
R3.7 – Release artifacts & docs (Track E2 + release acceptance)
rg GOOGLE_CLIENT_ID docs/howto/google-oauth.md(exit 0;.scratch/verification/SPRINT-047/phase3/release/rg-google-client-id.log) proves the updated how-to includes the new env overrides and sample snippets.ls dtu.*.yml | xargs rg "google-oauth"(exit 0;.scratch/verification/SPRINT-047/phase3/release/rg-sample-configs.log) confirms every sample config carries the emulator stanza; screenshots referenced by the docs live under.scratch/verification/SPRINT-047/track-d/ui/.rg "T[O]DO" docs/sprints/SPRINT-047-google-oauth.md(exit 1;.scratch/verification/SPRINT-047/phase3/release/rg-no-todo.log) ensures no placeholders remain, andtimeout 40 bash ./.scratch/tests/docs_lint.sh(exit 0;.scratch/verification/SPRINT-047/phase3/release/docs-lint.log) enforces the markdown/runbook guardrails.- Evidence:
.scratch/verification/SPRINT-047/phase3/release/.
- Google OAuth issues RS256-signed JWT access tokens containing
iss,aud,azp,sub,email,email_verified,scope,iat, andexp, withkidmatching the current JWKS entry.{Verified via `timeout 135 go test ./internal/services/googleoauth/... -count=1` (exit 0).} - GDrive accepts google-oauth JWT bearer tokens (scopes parsed from claims) and rejects unsigned/opaque bearer strings when JWKS validation is configured.
{Verified via `timeout 135 go test ./internal/services/googledrive/... -count=1` (exit 0) and `timeout 135 go test ./e2e -run TestGDriveMimeTypeFilter -count=1` (exit 0).}
All of the detailed task breakdown for this refresh now lives in docs/sprints/SPRINT-047-google-oauth-implementation-plan.md. That plan retains the historical sequencing, while this sprint ledger focuses on the executed evidence above.
Every checklist entry in the implementation plan file is marked [X] with the same .scratch/verification/SPRINT-047/... logs referenced here, so refer to that document when you need the archived work-breakdown structure.