Skip to content

Instantly share code, notes, and snippets.

@jaytaylor
Created March 10, 2026 21:55
Show Gist options
  • Select an option

  • Save jaytaylor/d02f1214f26993c25e0c12344645e8e8 to your computer and use it in GitHub Desktop.

Select an option

Save jaytaylor/d02f1214f26993c25e0c12344645e8e8 to your computer and use it in GitHub Desktop.

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.

Sprint #047 – DTU Google OAuth Emulator (Okta-backed)

Objective

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.

Context & Problem

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.

Approach (Plan 1 Recap)

  • 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.

Current State Snapshot (git review)

  • a0cf42111 docs(sprint-047): log phase1 progress updates this sprint ledger without touching code, so implementation status still hinges on earlier adapter work.
  • 544279b50 docs(googleoauth): document device + admin flows extends the how-to guide for device + admin APIs, but the UI/admin stack is still unimplemented.
  • 77de4e99d feat(googleoauth): expand oauth surface delivers large additions to internal/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 validation tightens 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 docs refreshes this plan baseline; outstanding checklist items are still open until evidence + code land.

Implementation Plan Refresh (2025-12-04)

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.

Track A – Registry Wiring & Service Skeleton (Prep-2, P0-2)

  • A1 – Deterministic bootstrap + adapter audit

    • Scope: review cmd/dtu/serve.go, internal/app/bootstrap/bootstrap.go, internal/infra/memory/service_catalog.go, and dtu.example.yml to 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.yml is rejected via timeout 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.
  • A2 – Router/config completion + deterministic client secrets

    • Scope: finish router scaffolding + config structs in internal/services/googleoauth and internal/defs, add auto-generation for client_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.

Track B – Contract Harness & Probes (P1-5 + acceptance gaps)

  • 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.py that 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, and code id_token flows, validates ID tokens via JWKS, and hits /userinfo plus /oauth2/v3/tokeninfo.
      • Go script also exercises the device authorization happy path to align with the documented /device/code behavior.
    • Negative tests:
      • Re-run scripts with intentionally invalid client IDs or blocked redirects to confirm Google-style invalid_client / redirect_uri_mismatch errors.
      • Force missing PKCE verifier to confirm invalid_grant responses alongside structured logs.
    • 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.
  • B2 – Negative matrix + telemetry probes

    • Scope: create .scratch/tests/google-auth-negatives.sh that 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 TestAuthEndpointHybridMatrix ensures 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 tokeninfo rejects requests missing both access_token and id_token.
    • 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)

Track C – Okta Integration, Consent, Claims, Sessions (Phase 2)

  • 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) drives TestOktaAuthorizationCodeExchange to 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 assert access_denied propagation.
      • ./.scratch/tests/okta-downtime.sh (exit 0; .scratch/verification/SPRINT-047/track-c/okta/okta-downtime.log) exercises the new TestOktaAuthorizationCodeExchangeDowntime to 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 original state.
      • 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) exercises TestConsentPromptFlow, TestConsentPromptNoneRequiresGrant, and TestConsentDenial to 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 returns access_denied with 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/audit to 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.
  • 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) plus perl -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-wide timeout 180 make -j10 build/test runs archived in the same directory.

Track D – Admin UI, CRUD, Metrics/Ops (Phase 3 UI + Ops)

  • 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.md exercises 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).
    • 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 happy
      • timeout 40 .scratch/tests/admin_api_e2e.sh --scenario revoked-secret
      • timeout 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.md plus 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 expand docs/operators/google-oauth.md with 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 /healthz states 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 emits dtu_googleoauth_auth_requests_total after 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 new docs/operators/google-oauth.md runbook to ensure all required sections exist and no banned placeholders remain.
      • Manual smoke instructions updated in docs/operators/google-oauth.md reference .scratch/google_oauth_smoke.sh and 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 is docs/operators/google-oauth.md.

Track E – End-to-End Automation, Release, Acceptance Closure (Phase 3 final items)

  • E1 – Go e2e suite + smoke script (P3-4 + API-driven matrix)

    • Scope: add e2e/google_oauth_e2e_test.go that covers PKCE happy path, refresh rotation, revoke, blocked redirect, revoked client secret, and deleted app scenarios; add .scratch/google_oauth_smoke.sh for 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_mismatch and revoked secret returns invalid_client.
      • Smoke script validates revoked access token is denied at /userinfo.
    • 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)
  • E2 – Release artifacts, docs, and acceptance sign-off (P3-5 + Phase 1/2/3 acceptance)

    • Scope: update dtu.*.yml samples with multiple app entries, embed admin UI screenshots, extend docs/howto/google-oauth.md + docs/operators/google-oauth.md with env overrides + runbooks, ensure every acceptance criterion replaces {placeholder ...} with verification narratives and exit codes, and link to .scratch evidence.
    • 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/goimports banned, 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.

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.

Implementation Execution Plan

Goal: sequence remaining work so each phase produces verifiable increments with evidence captured beneath every checklist item.

Cross-cutting prep

  • Prep-1 – Work envelopes + evidence scaffolding
    • Implementation: .scratch/notes/sprint-047-baseline.md holds scope/compat targets + Okta prerequisites, .scratch/verification/SPRINT-047/{phase0..phase3}/README.md index evidence drops, and .scratch/google_oauth_helpers.md documents 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).
  • Prep-2 – Deterministic registry + service wiring audit
    • Implementation: review cmd/dtu/serve.go, internal/app/bootstrap/bootstrap.go, and internal/infra/memory/service_catalog.go to confirm the google-oauth adapter 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 .scratch fixture 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)

Phase 0 execution plan (Foundations & Config)

  • P0-1 – Baseline scope + compatibility notes
    • Implementation: .scratch/notes/sprint-047-baseline.md now 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).
  • P0-2 – Service skeleton completion + config structs
    • Implementation: finish fleshing out internal/services/googleoauth router + handler scaffolding (response_mode handling, error enums, structured logging hooks), ensure config structs live in internal/defs and internal/services/googleoauth/config.go (if split) with auto-generation of missing client_id/client_secret.
    • Tests (positive): perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestAuthTokenUserInfoFlow, and perl -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)
  • 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.yml includes sample google-oauth stanzas 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).
  • P0-4 – Signing keys, JWKS rotation, docs
    • Implementation: internal/services/googleoauth now rotates RSA keys (retains overlapping kids plus dtu_created/dtu_expires metadata), JWKS handler streams the set, and docs/howto/google-oauth.md captures 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 + oidc notes stored under .scratch/verification/SPRINT-047/phase0/.
  • P0-5 – Helper probes + diagrams
    • Implementation: .scratch/google_oauth_helpers.md now includes PKCE/tokeninfo walkthroughs and .scratch/diagram-renders/sprint-047/*.png hosts 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.log captures the negative helper lookup (exit_code=1) scenario.

Phase 1 execution plan (OAuth surface & token service)

  • P1-1 – /o/oauth2/v2/auth parity
    • Implementation: implement full response_type matrix, response_mode handling, 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 .scratch probe hitting each response_type + response_mode combination, verifying redirects and fragments; include acceptance tests for access_type=offline returning refresh tokens.
    • Tests (negative): invalid redirect/source, unsupported combination (token without implicit mode), repeated state, missing PKCE, unsupported prompt; add table-driven tests inside internal/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) and timeout 180 make -j10 build (exit 0; .scratch/verification/SPRINT-047/phase1/timeout-make-build.log).
  • 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 .scratch script that requests code, 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) plus timeout 180 make -j10 test (exit 0; .scratch/verification/SPRINT-047/phase1/timeout-make-test.log).
  • P1-3 – /device/code + polling semantics
    • Implementation: create device session store, emit device_code/user_code, enforce polling interval, throttle, support approval/denial/expiry states, and align error payloads with Google.
    • Tests (positive): integration test approving within interval and verifying /token returns tokens when grant_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 in TestDeviceAuthorizationFlow.
  • P1-4 – /userinfo, discovery, JWKS, /oauth2/v3/tokeninfo parity
    • Implementation: add aud validation in userinfo, tokeninfo endpoint supporting access_token or id_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 updated docs/howto/google-oauth.md.
  • 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.py to 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)

Phase 2 execution plan (Okta integration, consent UX, claim fidelity)

  • 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.log hitting 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_denied error redirected to client; missing redirect blocked; stale session fails gracefully.
    • Verification commands: .scratch/ui/consent_evidence.md with 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, hd from email domain, fallback pictures), persist audit events (grant, deny, revoke) and expose to admin UI/log dumps.
    • Tests (positive): fixture validating deterministic sub and hd derivation, verifying email_verified aligns with Okta.
    • Tests (negative): user missing email -> fail with explicit error, mismatch between Okta aud and 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)
  • 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)

Phase 3 execution plan (Admin UI, registry CRUD, ops, e2e)

  • 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) exercises TestAdminAPICRUD, plus timeout 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.md and backend parity proven via timeout 40 ./.scratch/tests/admin_api_e2e.sh --scenario happy / revoked-secret / deleted-app (logs in .scratch/verification/SPRINT-047/phase3/admin-api/).
  • 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/) plus timeout 40 ./.scratch/tests/operators_runbook.sh --scenario happy / --scenario lint (logs in .scratch/verification/SPRINT-047/phase3/hardening/).
  • P3-4 – E2E automation + smoke scripts
    • Implementation: e2e/google_oauth_e2e_test.go plus .scratch/google_oauth_smoke.sh.
    • Verification: timeout 40 go test ./e2e -run TestGoogleOAuthEndToEnd -v and timeout 40 ./.scratch/google_oauth_smoke.sh (logs inside .scratch/verification/SPRINT-047/phase3/e2e/).
  • P3-5 – Release artifacts
    • Implementation: refreshed docs/howto, published docs/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/.

Evidence + verification logging plan

  • 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-047 or .scratch/verification/SPRINT-047/track-d/ui.
  • Store all scratch probes/scripts in .scratch/tests or .scratch/verification (no scripts/ additions per programming practices) and mention them when closing checklist items.

Phase 0 – Foundations & Config (Day 0–1)

  • 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/googleoauth plus cmd/dtu wiring for google-oauth service registration with YAML parsing of apps stanza (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.yml and other fixtures) with a default google-oauth service 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 in docs/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)

Acceptance Criteria – Phase 0

  • 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)

Phase 1 – OAuth Surface & Token Service (Auth, Token, Revoke, UserInfo)

  • Implement Google-shaped auth endpoint (/o/oauth2/v2/auth) with PKCE, state, prompt/approval params, offline/online access types, redirect/source allowlist enforcement, full response_type matrix (code, token, id_token, code token, code id_token, token id_token, code token id_token, none), and support for response_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 every response_type × response_mode combination 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.js and 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) 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 issues device_code/user_code, enforces polling interval, honors scope, 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 /token until approval, observing authorization_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 aud matches 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/tokeninfo accepts 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, and perl -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 /userinfo and /oauth2/v3/tokeninfo, then validate ID tokens against the JWKS to confirm aud and sub fidelity.
  • Add discovery docs + JWKS publishing consistent with Google metadata, including token_endpoint_auth_methods_supported and 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/certs payloads 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/tokeninfo validation, and Prometheus counter increments after each negative.

API Definitions – Google OAuth Surface Parity

GET /o/oauth2/v2/auth

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"
}

POST /device/code

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"
}

POST /token

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.

POST /revoke

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"
}

GET /userinfo

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.

GET /.well-known/openid-configuration

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"]
}

GET /oauth2/v3/certs

Returns JWKS set:

{
  "keys": [
    {
      "kid": "kid-1",
      "kty": "RSA",
      "alg": "RS256",
      "use": "sig",
      "n": "...",
      "e": "AQAB"
    }
  ]
}

GET /oauth2/v3/tokeninfo

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"
}

Acceptance Criteria – Phase 1

  • 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 + /token polling while respecting the returned interval and emitting authorization_pending, slow_down, access_denied, and expired_token errors 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) and response_mode=form_post are 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 .scratch scripts.

    • 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 /token responses.
    • 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-style invalid_grant failures.
  • 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-discovery and jwt decoding 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)

Phase 2 – Okta Integration, Consent UX, and Claim Fidelity

  • 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) drives TestOktaAuthorizationCodeExchange end-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 returns access_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 resulting server_error response plus structured logs.
  • Map Okta userinfo to Google claims with deterministic sub (stable per user), hd derived 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 deterministic sub.
    • 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/audit for 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) exercises TestConsentPromptFlow, TestConsentPromptNoneRequiresGrant, and TestConsentDenial to 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 -v and timeout 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 return invalid_grant and append audit entries, while timeout 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.ts and .../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.

Acceptance Criteria – Phase 2

  • 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 documents access_denied behaviour.
  • 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.sh and timeout 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-style error=access_denied redirects.
    • 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 with invalid_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/audit after 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.

Phase 3 – Admin UI, Registry CRUD, Ops, and E2E

  • 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 for system apps, 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.md documents the system-app warning badge behaviour (triangle icon rendered only when app.system true).

  • 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.md captures 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.
  • Add operational hooks: metrics for auth/token/revoke outcomes, structured audit logs, health checks, and snapshot/load for registry + sessions.

    • /metrics//healthz coverage recorded via timeout 40 ./.scratch/tests/health_probe.sh and timeout 40 ./.scratch/tests/metrics_failure.sh (logs under .scratch/verification/SPRINT-047/phase3/ops/) plus the new docs/operators/google-oauth.md runbook (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-secret and --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 script timeout 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.
  • Release artifacts: sample dtu.*.yml snippet 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.md and ls 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 (no timeout N, required headings present).
    • .scratch/google_oauth_smoke.sh already referenced in Phase 3 E2E evidence, and screenshots/manual notes live under .scratch/verification/SPRINT-047/track-d/ui/.

API Definitions (Admin App Registry) – Requests & Responses

  • POST /a/google-oauth/apps Body (JSON):
    {
      "name": "sample-app",
      "allowed_redirect_urls": ["http://127.0.0.1:11222/oauth/login/callback"],
      "allowed_source_urls": ["http://127.0.0.1:11222"]
    }
    Success: 201 JSON with generated secrets
    {
      "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"
    }
    Errors: 400 JSON { "error": "invalid_redirect_uri" }.
  • GET /a/google-oauth/apps Success: 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 when rotate_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 with invalid_client.
  • POST /_digital_twin_instance/googleoauth/apps/sync (bootstrap registry loader) Body: {"apps":[{...GoogleOAuthApp...}]} with deterministic client_id/secret allowed. Success: 200 JSON { "ok": true, "apps": 3 }; errors return 400 with { "error": "allowed_redirect_urls required" }.

Acceptance Criteria – Phase 3

  • 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.md plus .scratch/verification/SPRINT-047/phase3/admin-api/admin-api-happy.log, admin-api-revoked-secret.log, and admin-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.log and .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/.

Test coverage to implement (Phase 3 UI/admin):

Positive:

  • 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.

Negative:

  • 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.

Remainder Implementation Plan (2025-12-06 Refresh)

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.

Track C – Okta, Consent, Claims, Sessions (Phase 2 Critical Path)

  • C1 – Okta authorize/token client wiring

    • Implementation tasks:
      1. Create internal/services/googleoauth/okta client with typed models, configurable HTTP transport, sensible context deadlines, and structured logging of Okta error payloads.
      2. Extend internal/defs plus cmd/dtu bootstrap so every tenant definition must supply Okta issuer, client credentials, and audience mappings; fail fast with descriptive errors when the stanza is missing.
      3. 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.
    • Positive tests:
      • perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestOktaAuthorizationCodeExchange covering success path with PKCE, offline access type, and stored session metadata.
      • perl -e 'alarm 40; exec @ARGV' bash ./.scratch/tests/okta_happy.sh hitting the Okta sandbox (sanitized env vars) and recording HTTP traces under .scratch/verification/SPRINT-047/track-c/okta/okta-happy.log.
    • Negative tests:
      • Extend TestOktaAuthorizationCodeExchange tables for stale codes, nonce mismatch, invalid state, MFA-required accounts, policy-blocked apps, and Okta network timeouts to prove Google-style invalid_grant, access_denied, or server_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.
    • 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)
  • 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/.

Track D – Admin UI + Ops (Phase 3 UI/Ops foundation)

Track D – Admin UI + Ops (Phase 3 UI/Ops foundation)

  • 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 /healthz handler, 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) plus timeout 40 bash ./.scratch/tests/auth_matrix.sh (exit 0; .scratch/verification/SPRINT-047/phase1/auth/auth-matrix-script.log) and timeout 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 with timeout 40 bash ./.scratch/device_flow_probe.sh (exit 0; .scratch/verification/SPRINT-047/phase1/device/device_flow_probe.log) and timeout 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 plus slow_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) and timeout 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, while timeout 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), and 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) 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), and timeout 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), and timeout 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 suite 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) and 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) 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), and timeout 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), and timeout 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; the timeout 180 make -j10 build / timeout 180 make -j10 test runs 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.
  • 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), and timeout 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) and timeout 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), and timeout 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.

Appendix – Diagrams (rendered with mmdc)

  • 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
Loading
  • 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
  }
Loading
  • 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)
Loading
  • 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
Loading
  • 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
Loading

Remaining Work Implementation Plan (2025-12-04 Refresh #2)

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.

Phase 0 – Documentation Closeout

  • R0.1 – Discovery + sample config coverage (maps to Phase 0 bullet “Document discovery surface…”)
    • Implementation:
      • Expanded docs/howto/google-oauth.md with 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/*.png and added the evidence pipeline section.
      • Updated dtu.example.yml, dtu.min.gmail.yml, and dtu.variant.yml with multi-app google-oauth stanzas (web, desktop, and service account clients) so admin UI columns map 1:1 with YAML.
      • Added .scratch/tests/docs_lint.sh to enforce the new documentation rules (no timeout <n> or goimports, required discovery headings).
    • 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.

Phase 1 – OAuth Surface & Contract Harness Completion

  • R1.1 – Auth endpoint parity (Phase 1 “Implement Google-shaped auth endpoint”)

    • Implementation:
      • Renamed the hybrid matrix test to TestAuthEndpointHybridMatrix, added TestAuthEndpointInvalidCases, and tightened the auth handler to cover disallowed redirects, prompt=none combos, illegal response_mode selections, and unsupported response types.
      • Added .scratch/tests/auth_matrix.sh + .scratch/tests/auth_matrix_negatives.sh backed by .scratch/tests/auth_matrix/main.go so we can spin up the standalone emulator and exercise every response_type × response_mode pair plus failure matrix (blocked origin, invalid redirect, etc.).
      • Rebuilt .scratch/bin/google_oauth_standalone so the new flows are available to shell harnesses and cross-language probes.
    • 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=1
      • timeout 40 go test ./internal/services/googleoauth -run TestAuthEndpointInvalidCases -count=1
      • timeout 40 bash ./.scratch/tests/auth_matrix.sh
      • timeout 40 bash ./.scratch/tests/auth_matrix_negatives.sh
    • Evidence: .scratch/verification/SPRINT-047/phase1/auth.
  • R1.2 – Token endpoint + revocation (Phase 1 “Implement token endpoint…”)

    • Implementation:
      • Added TestTokenGrants/TestTokenGrantFailures to cover authorization_code, refresh rotation + reuse rejection, device slow_down/approval, and JWT-bearer happy + error paths.
      • Extended .scratch/tests/google_auth_go/main.go with rotation and token-negative scenarios 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.
    • 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=1
      • timeout 40 go test ./internal/services/googleoauth -run TestTokenGrantFailures -count=1
      • timeout 40 bash ./.scratch/tests/token_rotation.sh
      • timeout 40 bash ./.scratch/tests/token_negative.sh
    • Evidence: .scratch/verification/SPRINT-047/phase1/token.
  • 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) covers authorization_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 /token until 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/tokeninfo enforces mutually exclusive access_token/id_token inputs 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, while timeout 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) and 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) 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), and 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) drive PKCE, hybrid (code token, code id_token), offline refresh, /userinfo, and /oauth2/v3/tokeninfo validation 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 inside TestTokenGrantFailures (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.go with 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.sh to hit each negative scenario against a running emulator and confirm /metrics counters increment.
    • Positive tests:
      • perl -e 'alarm 40; exec @ARGV' go test ./internal/services/googleoauth -run TestNegativeMatrix to ensure instrumentation remains stable when positives run.
    • Negative tests:
      • bash ./.scratch/tests/google-auth-negatives.sh collects HTTP status/JSON payloads and verifies /metrics increments 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.

Phase 2 – Okta Integration, Consent, Claims, Sessions

  • 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) and timeout 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), while timeout 40 bash ./.scratch/tests/okta-mfa.sh (exit 0; .scratch/verification/SPRINT-047/phase2/okta/okta-mfa.log) and timeout 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, while 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) records denial returning error=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 deterministic sub, 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/audit output; 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/snapshot export/import endpoint that serializes apps, sessions, audit history, and signing keys.
      • Added Go coverage (TestSessionIsolation, TestSessionReplay, TestSessionReplayFailure) plus new .scratch/tests/session_snapshot.sh and .scratch/tests/session_replay.sh harnesses that capture artifacts under .scratch/verification/SPRINT-047/phase2/sessions/.
    • 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 via TestSessionReplay.
      • 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 return invalid_grant.
    • Evidence: .scratch/verification/SPRINT-047/phase2/sessions.
  • R2.5 – Okta-backed integration scenarios (Phase 2 acceptance bullets)

    • Live end-to-end coverage is provided by the okta-happy, okta-mfa, and okta-downtime harnesses plus the multi-language google-auth scripts. The happy-path run timeout 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, while timeout 40 bash ./.scratch/tests/okta-mfa.sh and timeout 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/.

Phase 3 – Admin UI, Ops, E2E, Release

  • 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.md captures 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/.
  • 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.md include 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/.
  • 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) and timeout 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 /healthz handler + Prometheus counters covered.
    • timeout 40 bash ./.scratch/tests/health_probe.sh (exit 0; .scratch/verification/SPRINT-047/phase3/ops/health-probe.log) and timeout 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, and 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) 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) linted docs/operators/google-oauth.md for 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, and timeout 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/.

Phase 4 – Signed JWT Access Tokens + GDrive Integration

  • Google OAuth issues RS256-signed JWT access tokens containing iss, aud, azp, sub, email, email_verified, scope, iat, and exp, with kid matching 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).}
    

Comprehensive Implementation Plan Refresh (2025-01-09)

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment