From b6751f7ebce43ef115ca583374b2e9c8d3d2dc28 Mon Sep 17 00:00:00 2001 From: IanShaw027 Date: Mon, 20 Apr 2026 13:47:00 +0800 Subject: [PATCH] docs: add auth identity implementation plan --- ...-04-20-auth-identity-payment-foundation.md | 476 ++++++++++++++++++ 1 file changed, 476 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-20-auth-identity-payment-foundation.md diff --git a/docs/superpowers/plans/2026-04-20-auth-identity-payment-foundation.md b/docs/superpowers/plans/2026-04-20-auth-identity-payment-foundation.md new file mode 100644 index 00000000..e8fde9c0 --- /dev/null +++ b/docs/superpowers/plans/2026-04-20-auth-identity-payment-foundation.md @@ -0,0 +1,476 @@ +# 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 branch’s 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 order’s 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. +