feat: add database-refreshed identity authorization

This commit is contained in:
2026-08-13 11:56:36 +08:00
parent 716a8031b1
commit c849077591
12 changed files with 1809 additions and 15 deletions

View File

@@ -0,0 +1,73 @@
# Task: Implement Go identity authorization vertical slice
## Identity
- Task ID: 20260813-go-identity-vertical-c4e91a72
- Mode: Feature
- Branch: codex/20260813-go-identity-vertical-c4e91a72-go-identity-vertical
- Worktree: /Users/brother7/Documents/AI/NianAIGC-go-identity-c4e91a72
- Base commit: 716a8031b1f0b322470e180e45dea2fe12fdead3
- Owner: codex
- Status: Ready for Integration
## Scope
- Add a language-neutral platform-session authorization matrix that fixes the per-request account, organization, role, client, and `sessionVersion` contract shared by TypeScript and Go.
- Add a deep Go Identity Module whose external seam resolves a signed legacy Cookie into a database-refreshed current session or a typed unauthenticated rejection.
- Add a PostgreSQL Adapter that loads the complete authorization snapshot in one parameterized account/organization query.
- Keep login, password changes, HTTP identity routes, middleware replacement, Docker/ACK manifests, and production traffic ownership outside this slice.
## Intent And Constraints
- Follow vertical red-green TDD at the existing signed-session/Identity seam and PostgreSQL Adapter seam.
- Treat Cookie data only as authenticated input; database account role, profile, status, organization state, and session version are authoritative for every resolved request.
- Preserve current legacy compatibility where an absent or zero `sessionVersion` is accepted, while any nonzero mismatch is rejected; the refreshed result always carries the current database version.
- Require every non-super-admin account to belong to an active existing organization; allow an unbound super-admin as the current billing/administration model requires.
- Collapse authentication denials for callers while retaining typed internal rejection reasons for tests and diagnostics; propagate database failures separately.
- Do not expose or route a Go identity endpoint and do not change ACK-001 production behavior in this task.
## Outcome
- Added `contracts/auth/platform-session-authorization-v1.json`, a language-neutral 14-case matrix covering database-authoritative claim refresh, platform-client enforcement, active/disabled accounts, exact platform roles, legacy missing/zero `sessionVersion`, nonzero version mismatch, rejection precedence, organization requirements, and super-admin organization compatibility.
- Added the Go Identity `Resolver` as one deep external seam from a signed legacy Cookie to a database-refreshed `Session`. Authentication denials collapse through `ErrUnauthenticated` while retaining stable internal rejection reasons; persistence failures remain distinct and propagate to the caller.
- Added a Go PostgreSQL authorization-snapshot Adapter that implements the Resolver's single persistence Interface with one explicit-column, parameterized `LEFT JOIN` over `public.platform_users` and `public.platform_organizations`. It deliberately returns disabled/missing organization state to the Identity Module instead of hiding policy in SQL.
- Added the equivalent TypeScript authorization seam and PostgreSQL snapshot store, then changed `getOptionalAuthSession` to use them. PostgreSQL request revalidation now uses one joined query rather than separate account and organization reads; the local development path projects the same narrow snapshot without exposing password storage fields.
- Closed the existing authorization gap that accepted non-super-admin accounts with no organization. All non-super-admin accounts now require a matching, active organization; an unbound super-admin remains valid.
- Kept login, password changes, Middleware, Go identity HTTP routes, Docker/ACK manifests, Ingress paths, and production traffic ownership unchanged. The Go Resolver is implemented and tested but remains intentionally unrouted.
## Verification
- Vertical TDD RED evidence was captured independently before the Go Resolver, Go PostgreSQL Adapter, TypeScript authorization seam, and TypeScript snapshot store implementations existed. Intermediate RED assertions also exposed missing organization-admin claim reconstruction and local snapshot leakage before those paths reached GREEN.
- `npm test -- --reporter=dot`: 36 test files and 135 tests passed, including both consumers of the shared authorization matrix and the PostgreSQL/local snapshot-store contract.
- `npx tsc --noEmit --incremental false`: passed.
- `npm run build`: Next.js 15.5.18 production build completed for all current routes; the pre-existing multiple-lockfile workspace-root warning remains.
- `npm run go:test`: all five Go packages passed.
- `npm run go:vet`: passed.
- `npm run go:build`: built `cmd/zhinian-api` successfully through the repository's cross-platform runner.
- `npm run deploy:check`: all 8 ACK manifest assertions passed, confirming that production routing and workload ownership were not changed.
- `git diff --check` and `gofmt -d` over all changed Go files: passed with no output.
- The independent Sol review found one cross-language composite-failure ordering mismatch. A fixture case was strengthened to combine an unknown database role with a nonzero version mismatch, producing the expected TypeScript RED (`invalid_role` versus `session_version_mismatch`); TypeScript was then aligned with Go so version mismatch has stable precedence. A second review caught that this temporarily removed standalone `invalid_role` coverage, so a redundant standalone version-mismatch case was converted to preserve both reasons while retaining all organization cases and the 14-case total. Both focused contracts and all verification above passed again.
## Follow-ups
- Add the first Go current-user/authentication HTTP Adapter only after its response, 401/403/500 mapping, Cookie transport, and black-box parity contracts are frozen; this task does not claim `/api/auth/me` or another identity path.
- Move or duplicate database-refreshed authorization at the routing boundary before any protected route cutover. The current Next.js Middleware still performs signed-Cookie-only gating, while server Route Handlers perform the authoritative database refresh.
- Add a Go local-development authorization snapshot Adapter before expecting the unrouted Go process to serve identity flows with `ZHINIAN_DATA_BACKEND=local`.
- Exercise the joined identity query against the production-like RDS role, verified-CA TLS, schema privileges, disabled accounts, deleted organizations, and concurrent account mutations before cutover.
- Migrate login/logout/password and lockout mutation flows as separate vertical slices so session-version invalidation and single-writer ownership can be reviewed independently.
## Promotion Candidates
- Target: `.project-docs/30-worklog/current-state.md`, `.project-docs/20-architecture/module-map.md`, and `.project-docs/20-architecture/data-flow.md`.
Proposal: after integration, record the implemented but unrouted Go Identity Resolver, its single PostgreSQL authorization-snapshot seam, and the shared TypeScript/Go authorization contract.
Evidence: the 14-case shared fixture, both language implementations, exact joined-query tests, and complete Node/Go/build/ACK verification in this task.
Future impact: later HTTP slices can consume one current-session result instead of reimplementing Cookie parsing, account/organization lookup order, or role/session-version rules.
Semantic conflicts: canonical documents still describe the Go backend as a foundation with no identity authorization slice; they should change only when this feature is integrated. ACK-001 and Next.js route ownership remain unchanged.
Human confirmation required: no new architecture direction is required; promotion still belongs to the serialized Integration Gate.
- Target: `.project-docs/40-domain/business-rules.md`.
Proposal: record that every active non-super-admin platform account must reference the same active organization returned by the authorization snapshot, while a super-admin may be unbound; missing/zero legacy session versions are temporarily accepted and refreshed, while nonzero mismatches revoke the session.
Evidence: the shared authorization matrix and both TypeScript/Go Resolver contract suites.
Future impact: account deletion, organization archival, role changes, session invalidation, and future authorization Adapters must preserve this rule.
Semantic conflicts: this deliberately closes a prior TypeScript conditional gap and aligns runtime authorization with the existing account mutation/domain constraints.
Human confirmation required: no; the task scope explicitly selected this security rule and no production route ownership changed.