Files
sub2api/docs/superpowers/plans/2026-04-20-auth-identity-payment-foundation.md
2026-04-20 13:47:00 +08:00

477 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Auth Identity Payment Foundation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Rebuild the auth identity, profile binding, payment routing, and OpenAI advanced scheduler foundation on top of a clean `origin/main` branch while preserving historical compatibility for existing email users and historical LinuxDo users.
**Architecture:** A unified identity foundation centered on durable provider subjects (`email`, `linuxdo`, `oidc`, `wechat`) and transactional pending-auth sessions; backend-owned payment source routing behind stable frontend methods (`alipay`, `wxpay`); compatibility-first migration/backfill before feature enablement.
**Tech Stack:** Go, Gin, Ent, PostgreSQL, Redis, Vue 3, Pinia, TypeScript, Vitest, pnpm.
---
## Non-Negotiable Product Rules
- [ ] Preserve login continuity for existing email users and historical LinuxDo users.
- [ ] During migration, backfill historical LinuxDo synthetic-email users into explicit LinuxDo identities before first post-upgrade login.
- [ ] Keep existing email login and add third-party login/bind for `linuxdo`, `oidc`, and `wechat`.
- [ ] On first third-party login:
- identity exists: direct login.
- identity does not exist: start pending-auth flow.
- local email binding is required only when system config says so.
- upstream provider email verification never counts as local email verification.
- [ ] When user-entered and locally verified email already exists:
- offer bind-existing-account after local re-authentication.
- offer change-email-and-create-new-account.
- when email binding is mandatory, do not allow bypass without changing to another email.
- [ ] On first third-party login or first third-party bind, provider nickname/avatar must be presented as independent replace options for the current nickname and avatar. They are not auto-applied.
- [ ] Source-specific initial grants must support per-source defaults for balance, concurrency, and subscriptions.
- [ ] Default grant timing: on successful new-account creation.
- [ ] Optional grant timing: on first successful bind for the configured source.
- [ ] Migration/backfill must never trigger first-bind or first-signup grants retroactively.
- [ ] Avatar profile supports:
- direct URL storage.
- image data URL upload compressed to `<=100KB` before storing in DB.
- explicit delete.
- [ ] Admin user management must expose and sort by `last_login_at` and `last_active_at`.
- [ ] WeChat login rules:
- WeChat environment uses MP login.
- non-WeChat browser uses Open/QR login.
- canonical identity uses `unionid`.
- when `unionid` is unavailable, fail the login/bind flow under the approved option-1 policy.
- [ ] Payment UI rules:
- user-facing methods stay `支付宝` and `微信支付`.
- backend decides whether each method routes to official provider instance or EasyPay.
- at runtime, each visible method may only have one active source.
- [ ] Alipay rules:
- PC: in-page QR.
- mobile browser: jump to Alipay payment.
- [ ] WeChat Pay rules:
- PC: in-page QR.
- WeChat H5: MP/JSAPI first, fallback to H5 pay.
- non-WeChat H5: H5 pay, or prompt to open in WeChat when unavailable.
- [ ] Payment success pages are informational only; actual fulfillment depends on webhook or server-side reconciliation.
- [ ] OpenAI advanced scheduler is available but default-disabled.
## Hard Technical Constraints From Audit
- [ ] Browser-based third-party auth must use Authorization Code + PKCE `S256`.
- [ ] OIDC identity primary key must be `(issuer, subject)`, not email.
- [ ] Email equality must never auto-link accounts.
- [ ] Bind-existing-account must require explicit local re-authentication and TOTP verification when enabled.
- [ ] OAuth redirect URI must be fixed server config, exact-match, and never derived from user input.
- [ ] User-supplied redirect may only choose a normalized same-origin internal route after completion.
- [ ] WeChat canonical identity must be `unionid`; `openid` remains channel/app-scoped support data only.
- [ ] Every payment order must snapshot the selected provider instance and reuse that exact instance for callback verification, reconciliation, refund, and audit.
- [ ] Frontend must not receive first-party bearer tokens through callback URL fragments in the rebuilt flow.
- [ ] Public payment result polling must not expose order data by raw `out_trade_no` alone; use authenticated lookup or signed opaque result token.
## Baseline Notes
- [ ] Current clean branch head when this plan was written: `721d7ab3`.
- [ ] Baseline backend verification on clean `origin/main`: `cd backend && go test ./...` passes.
- [ ] Baseline frontend verification on clean `origin/main`: `cd frontend && pnpm test:run` currently fails in unrelated existing suites. New work must add targeted tests and avoid claiming full frontend green until those baseline failures are addressed separately.
- [ ] Existing migration directory currently ends at `107_*`; this rebuild reserves `108` through `111`.
## Target File Map
### New backend migrations
- [ ] `backend/migrations/108_auth_identity_foundation_core.sql`
- [ ] `backend/migrations/109_auth_identity_compat_backfill.sql`
- [ ] `backend/migrations/110_pending_auth_and_provider_default_grants.sql`
- [ ] `backend/migrations/111_payment_routing_and_scheduler_flags.sql`
### New or rebuilt Ent schema
- [ ] `backend/ent/schema/auth_identity.go`
- [ ] `backend/ent/schema/auth_identity_channel.go`
- [ ] `backend/ent/schema/pending_auth_session.go`
- [ ] `backend/ent/schema/identity_adoption_decision.go`
### New or rebuilt backend repositories/services/handlers
- [ ] `backend/internal/repository/user_profile_identity_repo.go`
- [ ] `backend/internal/repository/user_profile_identity_repo_contract_test.go`
- [ ] `backend/internal/repository/auth_identity_migration_report.go`
- [ ] `backend/internal/service/auth_identity_flow.go`
- [ ] `backend/internal/service/auth_identity_flow_test.go`
- [ ] `backend/internal/service/auth_pending_identity_service.go`
- [ ] `backend/internal/service/auth_pending_identity_service_test.go`
- [ ] `backend/internal/service/payment_config_service.go`
- [ ] `backend/internal/service/payment_order.go`
- [ ] `backend/internal/service/payment_order_lifecycle.go`
- [ ] `backend/internal/service/payment_fulfillment.go`
- [ ] `backend/internal/service/openai_account_scheduler.go`
- [ ] `backend/internal/handler/auth_pending_identity_flow.go`
- [ ] `backend/internal/handler/auth_linuxdo_oauth.go`
- [ ] `backend/internal/handler/auth_oidc_oauth.go`
- [ ] `backend/internal/handler/auth_wechat_oauth.go`
- [ ] `backend/internal/handler/auth_handler.go`
- [ ] `backend/internal/handler/user_handler.go`
- [ ] `backend/internal/handler/payment_handler.go`
- [ ] `backend/internal/handler/payment_webhook_handler.go`
- [ ] `backend/internal/handler/admin/user_handler.go`
- [ ] `backend/internal/handler/admin/setting_handler.go`
### New or rebuilt frontend API/store/views/components
- [ ] `frontend/src/api/auth.ts`
- [ ] `frontend/src/api/user.ts`
- [ ] `frontend/src/api/payment.ts`
- [ ] `frontend/src/api/admin/settings.ts`
- [ ] `frontend/src/api/admin/users.ts`
- [ ] `frontend/src/stores/auth.ts`
- [ ] `frontend/src/stores/payment.ts`
- [ ] `frontend/src/components/auth/ThirdPartyAuthCallbackFlow.vue`
- [ ] `frontend/src/components/auth/LinuxDoOAuthSection.vue`
- [ ] `frontend/src/components/auth/OidcOAuthSection.vue`
- [ ] `frontend/src/components/auth/WechatOAuthSection.vue`
- [ ] `frontend/src/components/user/profile/ProfileAccountBindingsCard.vue`
- [ ] `frontend/src/components/user/profile/ProfileInfoCard.vue`
- [ ] `frontend/src/views/auth/LinuxDoCallbackView.vue`
- [ ] `frontend/src/views/auth/OidcCallbackView.vue`
- [ ] `frontend/src/views/auth/WechatCallbackView.vue`
- [ ] `frontend/src/views/user/ProfileView.vue`
- [ ] `frontend/src/views/user/PaymentView.vue`
- [ ] `frontend/src/views/user/PaymentQRCodeView.vue`
- [ ] `frontend/src/views/user/PaymentResultView.vue`
## Phase 1: Migration And Compatibility Foundation
### Task 1. Create core identity schema migration
- [ ] Implement `backend/migrations/108_auth_identity_foundation_core.sql` with:
- `auth_identities`
- `auth_identity_channels`
- `pending_auth_sessions`
- `identity_adoption_decisions`
- `users.last_login_at`
- `users.last_active_at`
- grant-tracking columns/tables required to prevent double-award
- [ ] Add uniqueness/index rules:
- one canonical identity per `(provider, provider_subject)`
- one channel record per `(provider, provider_channel, provider_app_id, provider_channel_subject)`
- one adoption decision per pending session
- [ ] Preserve null-safe compatibility defaults so historical rows remain readable before backfill finishes.
- [ ] Add explicit rollback blocks only where safe; never repeat the destructive pattern observed in old `112_update_pending_auth_sessions.sql`.
### Task 2. Materialize historical identities before runtime
- [ ] Implement `backend/migrations/109_auth_identity_compat_backfill.sql` to backfill:
- existing email users into `auth_identities(provider=email, provider_subject=normalized_email)`
- historical LinuxDo users into `auth_identities(provider=linuxdo, provider_subject=linuxdo_subject)`
- historical synthetic-email LinuxDo users into explicit LinuxDo identity rows by parsing legacy email mode and legacy provider metadata
- profile/channel rows from historical `user_external_identities`-style data when present in upgraded databases
- [ ] Write migration report output in `backend/internal/repository/auth_identity_migration_report.go` so production can inspect unmatched rows instead of silently skipping them.
- [ ] Set `signup_source` and provider provenance when recoverable from historical data. Do not flatten everything to `email`.
### Task 3. Provider default grant and scheduler config migration
- [ ] Implement `backend/migrations/110_pending_auth_and_provider_default_grants.sql` for:
- provider-specific initial balance/concurrency/subscription defaults
- grant timing flags: `on_signup`, optional `on_first_bind`
- email-required-on-third-party-signup flags
- profile avatar storage columns/settings
- [ ] Implement `backend/migrations/111_payment_routing_and_scheduler_flags.sql` for:
- stable payment method to provider-instance routing
- admin exclusivity flags for `alipay` and `wxpay`
- advanced scheduler enable flag defaulting to disabled
### Task 4. Generate Ent and compile migration-safe model layer
- [ ] Add the schema definitions in:
- `backend/ent/schema/auth_identity.go`
- `backend/ent/schema/auth_identity_channel.go`
- `backend/ent/schema/pending_auth_session.go`
- `backend/ent/schema/identity_adoption_decision.go`
- [ ] Run:
```bash
cd backend
go generate ./ent
```
- [ ] Compile after generation:
```bash
cd backend
go test ./... -run '^$'
```
- [ ] Commit checkpoint:
```bash
git add backend/migrations backend/ent/schema backend/ent
git commit -m "feat: add auth identity foundation schema"
```
## Phase 2: Backend Identity Flow Rebuild
### Task 5. Build a single repository contract for identity lookups and grants
- [ ] Implement `backend/internal/repository/user_profile_identity_repo.go` with transactional helpers for:
- get user by canonical identity
- get user by channel identity
- create canonical + channel identity together
- bind identity to existing user after verified re-auth
- record one-time provider grant award
- record adoption preference decisions
- update `last_login_at` and `last_active_at`
- [ ] Add repository contract coverage in `backend/internal/repository/user_profile_identity_repo_contract_test.go`.
- [ ] Enforce dual-write for email registration/login so `users.email` and `auth_identities(provider=email, ...)` stay consistent from this phase onward.
### Task 6. Rebuild transactional pending-auth service
- [ ] Implement `backend/internal/service/auth_pending_identity_service.go` and tests to own these flows:
- create pending session from third-party callback
- verify local email code
- create new account from pending session with correct `signup_source`
- bind pending identity to existing account after password/TOTP re-auth
- apply configured provider defaults on the correct trigger only once
- store provider nickname/avatar candidates and user opt-in replacement decisions independently
- [ ] Keep pending session payload normalized:
- provider identity fields live in typed columns/JSON structure
- avoid the old branchs mixed `metadata` and `upstream_identity_payload` ambiguity
- [ ] Do not call plain email registration helpers from this flow. The old feature branch bug where pending third-party signup fell back to `RegisterWithVerification` must not reappear.
### Task 7. Rebuild provider callback adapters
- [ ] Refactor these handlers to thin adapters over the shared pending-auth service:
- `backend/internal/handler/auth_linuxdo_oauth.go`
- `backend/internal/handler/auth_oidc_oauth.go`
- `backend/internal/handler/auth_wechat_oauth.go`
- [ ] For OIDC:
- require PKCE `S256`, `state`, and `nonce`
- validate `iss`, `aud`, optional `azp`, `exp`, `nonce`
- persist canonical identity as `(issuer, sub)`
- [ ] For WeChat:
- MP flow in WeChat UA
- Open/QR flow outside WeChat UA
- persist channel identity by `(channel, appid, openid)`
- persist canonical identity by `unionid`
- hard-fail when `unionid` is absent under the approved product policy
- [ ] Replace callback URL fragment token delivery with backend session completion or one-time exchange code consumed by `frontend/src/stores/auth.ts`.
### Task 8. Rebuild auth endpoints and profile binding endpoints
- [ ] Implement `backend/internal/handler/auth_pending_identity_flow.go` for:
- fetch pending session summary
- submit verified email
- choose create-new-account or bind-existing-account
- submit nickname/avatar replacement choices
- [ ] Update `backend/internal/handler/auth_handler.go` and `backend/internal/handler/user_handler.go` to expose:
- current bindings summary
- start-bind endpoints for LinuxDo/OIDC/WeChat
- disconnect endpoints with safety checks
- avatar upload/delete endpoints
- [ ] Avatar handling requirements:
- allow external URL
- allow data URL upload
- compress image payload to `<=100KB`
- store compressed value in DB
- deleting custom avatar must not implicitly resurrect stale provider avatar unless the user explicitly chooses provider avatar again
### Task 9. Add admin visibility and sorting
- [ ] Update `backend/internal/handler/admin/user_handler.go` and supporting query/service code so admin list supports:
- `last_login_at`
- `last_active_at`
- sorting by both
- binding/provider summary columns
- [ ] Update `backend/internal/handler/admin/setting_handler.go` and setting service code for:
- provider initial grant config
- mandatory-email-on-third-party-signup config
- payment source exclusivity config
- advanced scheduler toggle
### Task 10. Backend verification checkpoint
- [ ] Run targeted backend tests:
```bash
cd backend
go test ./internal/repository -run 'TestUserProfileIdentity|TestAuthIdentityMigration'
go test ./internal/service -run 'TestAuthIdentityFlow|TestPendingAuthIdentity|TestOpenAIAccountScheduler'
go test ./internal/handler -run 'TestLinuxDo|TestOidc|TestWechat|TestPaymentWebhook'
go test ./...
```
- [ ] Commit checkpoint:
```bash
git add backend
git commit -m "feat: rebuild auth identity backend flows"
```
## Phase 3: Frontend Third-Party Flow And Profile UX
### Task 11. Rebuild callback flow UI around pending session decisions
- [ ] Rebuild `frontend/src/components/auth/ThirdPartyAuthCallbackFlow.vue` so it:
- loads pending-session summary from backend
- shows provider nickname/avatar candidates
- lets user independently choose nickname replacement and avatar replacement
- handles create-new-account vs bind-existing-account
- enforces verified local email before completion when required
- handles “email already exists” by branching to bind-existing-account or change-email-and-create-new-account
- [ ] Update:
- `frontend/src/views/auth/LinuxDoCallbackView.vue`
- `frontend/src/views/auth/OidcCallbackView.vue`
- `frontend/src/views/auth/WechatCallbackView.vue`
- `frontend/src/api/auth.ts`
- `frontend/src/stores/auth.ts`
- [ ] Replace any token-fragment bootstrap with backend session completion or one-time exchange code flow.
### Task 12. Rebuild profile account binding and avatar UX
- [ ] Rebuild `frontend/src/components/user/profile/ProfileAccountBindingsCard.vue` to:
- show linked LinuxDo/OIDC/WeChat providers
- start bind/unbind flows
- show provider avatars and nicknames as reference only
- prevent unsafe disconnect when it would strand the account
- [ ] Rebuild `frontend/src/components/user/profile/ProfileInfoCard.vue` and `frontend/src/views/user/ProfileView.vue` to:
- support avatar URL entry
- support data URL upload/compression preview
- support avatar delete
- clearly separate current profile nickname/avatar from provider-sourced suggested nickname/avatar
### Task 13. Add frontend tests for rebuilt auth/profile flows
- [ ] Add or update:
- `frontend/src/components/auth/__tests__/ThirdPartyAuthCallbackFlow.spec.ts`
- `frontend/src/components/auth/__tests__/LinuxDoCallbackView.spec.ts`
- `frontend/src/components/auth/__tests__/WechatCallbackView.spec.ts`
- `frontend/src/components/user/profile/__tests__/ProfileAccountBindingsCard.spec.ts`
- `frontend/src/components/user/profile/__tests__/ProfileInfoCard.spec.ts`
- [ ] Cover:
- email-required branch
- email-conflict branch
- bind-existing-account with re-auth prompt
- nickname replacement only
- avatar replacement only
- neither replacement
- avatar delete after prior provider adoption
## Phase 4: Payment Routing Rebuild
### Task 14. Normalize payment routing backend
- [ ] Rebuild `backend/internal/service/payment_config_service.go` to expose a stable method-routing contract:
- frontend visible methods remain `alipay` and `wxpay`
- admin chooses which provider instance serves each method
- runtime validation guarantees only one active source per visible method
- [ ] Rebuild `backend/internal/service/payment_order.go` and `backend/internal/service/payment_order_lifecycle.go` so order creation snapshots:
- visible method
- selected provider instance id
- provider type
- provider capability mode
- [ ] Rebuild `backend/internal/handler/payment_handler.go` for UX rules:
- Alipay PC: QR page
- Alipay mobile: direct jump
- WeChat PC: QR page
- WeChat H5 in WeChat: MP/JSAPI first, fallback to H5
- WeChat H5 outside WeChat: H5 or “open in WeChat” prompt when unavailable
- [ ] Never derive canonical return URL from `Referer`; use configured or signed internal callback targets only.
### Task 15. Make fulfillment and reconciliation provider-instance-safe
- [ ] Rebuild `backend/internal/handler/payment_webhook_handler.go` and `backend/internal/service/payment_fulfillment.go` so:
- verification uses the orders original provider instance
- webhook processing is idempotent by provider event id and internal order id
- missed webhook recovery uses server-side provider query, not frontend success return
- [ ] Harden `frontend/src/views/user/PaymentResultView.vue` and `frontend/src/api/payment.ts` so result polling uses an authenticated order lookup or signed opaque token, not a raw public `out_trade_no` query.
### Task 16. Rebuild payment frontend views
- [ ] Rebuild `frontend/src/views/user/PaymentView.vue`, `frontend/src/views/user/PaymentQRCodeView.vue`, and `frontend/src/stores/payment.ts` so:
- only two buttons are shown to user: `支付宝` and `微信支付`
- frontend does not leak official-vs-EasyPay distinction
- route-specific copy handles QR, jump, MP, H5 fallback correctly
- [ ] Add or update:
- `frontend/src/views/user/__tests__/PaymentView.spec.ts`
- `frontend/src/views/user/__tests__/PaymentResultView.spec.ts`
- backend webhook/payment routing tests
### Task 17. Payment verification checkpoint
- [ ] Run:
```bash
cd backend
go test ./internal/service -run 'TestPayment'
go test ./internal/handler -run 'TestPayment'
cd ../frontend
pnpm test:run src/views/user/__tests__/PaymentView.spec.ts src/views/user/__tests__/PaymentResultView.spec.ts
```
- [ ] Commit checkpoint:
```bash
git add backend frontend
git commit -m "feat: rebuild payment routing foundation"
```
## Phase 5: Scheduler, Rollout, And Final Compatibility Pass
### Task 18. Gate advanced scheduler behind explicit config
- [ ] Update `backend/internal/service/openai_account_scheduler.go` and related admin setting surfaces so:
- advanced scheduler remains compiled and testable
- default runtime state is disabled
- enablement is explicit through admin settings
- legacy scheduling behavior remains default on upgrade
- [ ] Add targeted coverage in `backend/internal/service/openai_account_scheduler_test.go`.
### Task 19. Complete compatibility and rollout safety checks
- [ ] Add migration/repository tests covering:
- historical email-only user login after upgrade
- historical LinuxDo user login after upgrade
- historical synthetic-email LinuxDo user login after upgrade
- no retroactive grant replay during migration
- first-bind grant fires once only when enabled
- email identity dual-write stays consistent
- bind-existing-account requires password and TOTP where configured
- [ ] Add deploy sequencing note to release docs or internal runbook:
1. deploy schema and backfill release.
2. inspect migration report for unmatched rows.
3. deploy backend identity/payment compatibility code.
4. deploy frontend callback/profile/payment UI.
5. enable strict email-required signup or provider bind grants only after metrics are healthy.
### Task 20. Final verification and handoff
- [ ] Run final backend verification:
```bash
cd backend
go test ./...
```
- [ ] Run targeted frontend verification:
```bash
cd frontend
pnpm test:run \
src/components/auth/__tests__/ThirdPartyAuthCallbackFlow.spec.ts \
src/components/auth/__tests__/LinuxDoCallbackView.spec.ts \
src/components/auth/__tests__/WechatCallbackView.spec.ts \
src/components/user/profile/__tests__/ProfileAccountBindingsCard.spec.ts \
src/components/user/profile/__tests__/ProfileInfoCard.spec.ts \
src/views/user/__tests__/PaymentView.spec.ts \
src/views/user/__tests__/PaymentResultView.spec.ts
```
- [ ] Run focused manual smoke checks:
- email login with existing account
- LinuxDo existing-account login after migration
- third-party first login create-new-account path
- third-party first login bind-existing-account path
- first third-party bind with optional nickname/avatar replacement
- PC Alipay QR
- mobile Alipay jump
- PC WeChat QR
- WeChat H5 MP/JSAPI path
- non-WeChat H5 fallback path
- [ ] Commit final checkpoint:
```bash
git add docs backend frontend
git commit -m "feat: rebuild auth identity and payment foundation"
```
## Review Checklist
- [ ] No flow still relies on provider email equality for account linking.
- [ ] No flow still creates third-party users through plain email registration helpers.
- [ ] No callback still returns first-party bearer tokens in URL fragments.
- [ ] No payment result view trusts provider return page as authoritative fulfillment.
- [ ] No webhook verification path selects provider credentials from “currently active config” instead of the order snapshot.
- [ ] Existing email users and historical LinuxDo users are covered by migration tests.
- [ ] Avatar adoption and deletion semantics are explicit and reversible.
- [ ] Grant timing is source-aware and one-time only.