docs: harden auth identity payment design
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> **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.
|
> **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.
|
**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, existing LinuxDo users, historical LinuxDo/WeChat/OIDC synthetic-email users, and historical WeChat `openid`-only records.
|
||||||
**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.
|
**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.
|
**Tech Stack:** Go, Gin, Ent, PostgreSQL, Redis, Vue 3, Pinia, TypeScript, Vitest, pnpm.
|
||||||
|
|
||||||
@@ -10,8 +10,9 @@
|
|||||||
|
|
||||||
## Non-Negotiable Product Rules
|
## Non-Negotiable Product Rules
|
||||||
|
|
||||||
- [ ] Preserve login continuity for existing email users and historical LinuxDo users.
|
- [ ] Preserve login continuity for existing email users, existing LinuxDo users, and historically migrated third-party users.
|
||||||
- [ ] During migration, backfill historical LinuxDo synthetic-email users into explicit LinuxDo identities before first post-upgrade login.
|
- [ ] During migration, backfill historical LinuxDo/WeChat/OIDC synthetic-email users into explicit third-party identities before first post-upgrade login whenever deterministic recovery is possible.
|
||||||
|
- [ ] During migration, surface historical WeChat `openid`-only records through explicit migration reports and remediation rules; do not silently reinterpret them as valid canonical identities.
|
||||||
- [ ] Keep existing email login and add third-party login/bind for `linuxdo`, `oidc`, and `wechat`.
|
- [ ] Keep existing email login and add third-party login/bind for `linuxdo`, `oidc`, and `wechat`.
|
||||||
- [ ] On first third-party login:
|
- [ ] On first third-party login:
|
||||||
- identity exists: direct login.
|
- identity exists: direct login.
|
||||||
@@ -37,6 +38,11 @@
|
|||||||
- non-WeChat browser uses Open/QR login.
|
- non-WeChat browser uses Open/QR login.
|
||||||
- canonical identity uses `unionid`.
|
- canonical identity uses `unionid`.
|
||||||
- when `unionid` is unavailable, fail the login/bind flow under the approved option-1 policy.
|
- when `unionid` is unavailable, fail the login/bind flow under the approved option-1 policy.
|
||||||
|
- [ ] OIDC rules:
|
||||||
|
- browser authorization-code flow always uses PKCE `S256`.
|
||||||
|
- discovery issuer and ID token `iss` must match exactly.
|
||||||
|
- `userinfo.sub` must match ID token `sub` when UserInfo is used.
|
||||||
|
- upstream `email_verified` does not satisfy local email verification.
|
||||||
- [ ] Payment UI rules:
|
- [ ] Payment UI rules:
|
||||||
- user-facing methods stay `支付宝` and `微信支付`.
|
- user-facing methods stay `支付宝` and `微信支付`.
|
||||||
- backend decides whether each method routes to official provider instance or EasyPay.
|
- backend decides whether each method routes to official provider instance or EasyPay.
|
||||||
@@ -49,20 +55,26 @@
|
|||||||
- WeChat H5: MP/JSAPI first, fallback to H5 pay.
|
- WeChat H5: MP/JSAPI first, fallback to H5 pay.
|
||||||
- non-WeChat H5: H5 pay, or prompt to open in WeChat when unavailable.
|
- 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.
|
- [ ] Payment success pages are informational only; actual fulfillment depends on webhook or server-side reconciliation.
|
||||||
|
- [ ] WeChat in-app payment requiring `openid` must use a dedicated server-backed payment OAuth resume flow rather than frontend-only recovery state.
|
||||||
- [ ] OpenAI advanced scheduler is available but default-disabled.
|
- [ ] OpenAI advanced scheduler is available but default-disabled.
|
||||||
|
|
||||||
## Hard Technical Constraints From Audit
|
## Hard Technical Constraints From Audit
|
||||||
|
|
||||||
- [ ] Browser-based third-party auth must use Authorization Code + PKCE `S256`.
|
- [ ] Browser-based third-party auth must use Authorization Code + PKCE `S256`.
|
||||||
|
- [ ] PKCE must not be admin-configurable off for browser authorization-code providers.
|
||||||
- [ ] OIDC identity primary key must be `(issuer, subject)`, not email.
|
- [ ] OIDC identity primary key must be `(issuer, subject)`, not email.
|
||||||
- [ ] Email equality must never auto-link accounts.
|
- [ ] Email equality must never auto-link accounts.
|
||||||
- [ ] Bind-existing-account must require explicit local re-authentication and TOTP verification when enabled.
|
- [ ] Bind-existing-account must require explicit local re-authentication and TOTP verification when enabled.
|
||||||
|
- [ ] Bind-current-user must originate from an already-authenticated local user and preserve explicit bind intent across callback completion.
|
||||||
- [ ] OAuth redirect URI must be fixed server config, exact-match, and never derived from user input.
|
- [ ] 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.
|
- [ ] 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.
|
- [ ] 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.
|
- [ ] Every canonical identity uniqueness rule must include provider namespace (`provider_key`) consistently.
|
||||||
|
- [ ] Callback completion must use backend session completion or a one-time opaque exchange code that is short-lived, one-time, browser-session-bound, `POST`-redeemed, and unusable as a bearer token.
|
||||||
|
- [ ] Every payment order must snapshot the selected provider instance plus the order-time verification inputs required for callback verification, reconciliation, refund, and audit.
|
||||||
- [ ] Frontend must not receive first-party bearer tokens through callback URL fragments in the rebuilt flow.
|
- [ ] 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.
|
- [ ] Public payment result polling must not expose order data by raw `out_trade_no` alone; use authenticated lookup or signed opaque result token.
|
||||||
|
- [ ] WeChat Pay webhook handling must verify signature, decrypt payload, and compare `appid`, `mchid`, `out_trade_no`, `amount`, `currency`, and provider trade state against the order snapshot before fulfillment.
|
||||||
|
|
||||||
## Baseline Notes
|
## Baseline Notes
|
||||||
|
|
||||||
@@ -100,6 +112,8 @@
|
|||||||
- [ ] `backend/internal/service/payment_order.go`
|
- [ ] `backend/internal/service/payment_order.go`
|
||||||
- [ ] `backend/internal/service/payment_order_lifecycle.go`
|
- [ ] `backend/internal/service/payment_order_lifecycle.go`
|
||||||
- [ ] `backend/internal/service/payment_fulfillment.go`
|
- [ ] `backend/internal/service/payment_fulfillment.go`
|
||||||
|
- [ ] `backend/internal/service/payment_resume_service.go`
|
||||||
|
- [ ] `backend/internal/service/payment_resume_service_test.go`
|
||||||
- [ ] `backend/internal/service/openai_account_scheduler.go`
|
- [ ] `backend/internal/service/openai_account_scheduler.go`
|
||||||
- [ ] `backend/internal/handler/auth_pending_identity_flow.go`
|
- [ ] `backend/internal/handler/auth_pending_identity_flow.go`
|
||||||
- [ ] `backend/internal/handler/auth_linuxdo_oauth.go`
|
- [ ] `backend/internal/handler/auth_linuxdo_oauth.go`
|
||||||
@@ -148,9 +162,10 @@
|
|||||||
- `users.last_active_at`
|
- `users.last_active_at`
|
||||||
- grant-tracking columns/tables required to prevent double-award
|
- grant-tracking columns/tables required to prevent double-award
|
||||||
- [ ] Add uniqueness/index rules:
|
- [ ] Add uniqueness/index rules:
|
||||||
- one canonical identity per `(provider, provider_subject)`
|
- one canonical identity per `(provider, provider_key, provider_subject)`
|
||||||
- one channel record per `(provider, provider_channel, provider_app_id, provider_channel_subject)`
|
- one channel record per `(provider, provider_channel, provider_app_id, provider_channel_subject)`
|
||||||
- one adoption decision per pending session
|
- one adoption decision per pending session
|
||||||
|
- [ ] Model `pending_auth_sessions` so immutable upstream claims and mutable local flow state are stored separately; do not reintroduce a mixed `metadata` catch-all.
|
||||||
- [ ] Preserve null-safe compatibility defaults so historical rows remain readable before backfill finishes.
|
- [ ] 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`.
|
- [ ] Add explicit rollback blocks only where safe; never repeat the destructive pattern observed in old `112_update_pending_auth_sessions.sql`.
|
||||||
|
|
||||||
@@ -160,8 +175,10 @@
|
|||||||
- existing email users into `auth_identities(provider=email, provider_subject=normalized_email)`
|
- 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 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
|
- historical synthetic-email LinuxDo users into explicit LinuxDo identity rows by parsing legacy email mode and legacy provider metadata
|
||||||
|
- historical synthetic-email WeChat users into explicit WeChat identities where `unionid` or equivalent deterministic provider identity is recoverable
|
||||||
|
- historical synthetic-email OIDC users into explicit OIDC identities where deterministic provider identity is recoverable
|
||||||
- profile/channel rows from historical `user_external_identities`-style data when present in upgraded databases
|
- 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.
|
- [ ] Write migration report output in `backend/internal/repository/auth_identity_migration_report.go` so production can inspect unmatched rows, `openid`-only WeChat rows, and non-deterministic synthetic-email rows instead of silently skipping them.
|
||||||
- [ ] Set `signup_source` and provider provenance when recoverable from historical data. Do not flatten everything to `email`.
|
- [ ] 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
|
### Task 3. Provider default grant and scheduler config migration
|
||||||
@@ -173,6 +190,7 @@
|
|||||||
- profile avatar storage columns/settings
|
- profile avatar storage columns/settings
|
||||||
- [ ] Implement `backend/migrations/111_payment_routing_and_scheduler_flags.sql` for:
|
- [ ] Implement `backend/migrations/111_payment_routing_and_scheduler_flags.sql` for:
|
||||||
- stable payment method to provider-instance routing
|
- stable payment method to provider-instance routing
|
||||||
|
- visible-method normalization from historical `supported_types`, `payment_mode`, and legacy aliases such as `wxpay_direct`
|
||||||
- admin exclusivity flags for `alipay` and `wxpay`
|
- admin exclusivity flags for `alipay` and `wxpay`
|
||||||
- advanced scheduler enable flag defaulting to disabled
|
- advanced scheduler enable flag defaulting to disabled
|
||||||
|
|
||||||
@@ -213,6 +231,7 @@
|
|||||||
- update `last_login_at` and `last_active_at`
|
- update `last_login_at` and `last_active_at`
|
||||||
- [ ] Add repository contract coverage in `backend/internal/repository/user_profile_identity_repo_contract_test.go`.
|
- [ ] 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.
|
- [ ] Enforce dual-write for email registration/login so `users.email` and `auth_identities(provider=email, ...)` stay consistent from this phase onward.
|
||||||
|
- [ ] Add repository coverage proving `last_login_at` and `last_active_at` use the required field names and are not silently replaced by derived `last_used_at` logic.
|
||||||
|
|
||||||
### Task 6. Rebuild transactional pending-auth service
|
### Task 6. Rebuild transactional pending-auth service
|
||||||
|
|
||||||
@@ -223,8 +242,15 @@
|
|||||||
- bind pending identity to existing account after password/TOTP re-auth
|
- bind pending identity to existing account after password/TOTP re-auth
|
||||||
- apply configured provider defaults on the correct trigger only once
|
- apply configured provider defaults on the correct trigger only once
|
||||||
- store provider nickname/avatar candidates and user opt-in replacement decisions independently
|
- store provider nickname/avatar candidates and user opt-in replacement decisions independently
|
||||||
|
- [ ] Implement callback completion so pending auth can finish through backend session completion or a one-time exchange code:
|
||||||
|
- short TTL
|
||||||
|
- one-time use
|
||||||
|
- browser-session binding
|
||||||
|
- `POST` redemption only
|
||||||
|
- safe mixed-version bridge to legacy pending-token aliases during rollout
|
||||||
- [ ] Keep pending session payload normalized:
|
- [ ] Keep pending session payload normalized:
|
||||||
- provider identity fields live in typed columns/JSON structure
|
- provider identity fields live in typed columns/JSON structure
|
||||||
|
- mutable local progression lives separately from immutable upstream claims
|
||||||
- avoid the old branch’s mixed `metadata` and `upstream_identity_payload` ambiguity
|
- 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.
|
- [ ] 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.
|
||||||
|
|
||||||
@@ -236,11 +262,13 @@
|
|||||||
- `backend/internal/handler/auth_wechat_oauth.go`
|
- `backend/internal/handler/auth_wechat_oauth.go`
|
||||||
- [ ] For OIDC:
|
- [ ] For OIDC:
|
||||||
- require PKCE `S256`, `state`, and `nonce`
|
- require PKCE `S256`, `state`, and `nonce`
|
||||||
- validate `iss`, `aud`, optional `azp`, `exp`, `nonce`
|
- validate discovery issuer, `iss`, `aud`, optional `azp`, `exp`, and `nonce`
|
||||||
|
- verify `userinfo.sub == id_token.sub` when UserInfo is used
|
||||||
- persist canonical identity as `(issuer, sub)`
|
- persist canonical identity as `(issuer, sub)`
|
||||||
- [ ] For WeChat:
|
- [ ] For WeChat:
|
||||||
- MP flow in WeChat UA
|
- MP flow in WeChat UA
|
||||||
- Open/QR flow outside WeChat UA
|
- Open/QR flow outside WeChat UA
|
||||||
|
- website login uses authorization-code flow and persists channel/app binding
|
||||||
- persist channel identity by `(channel, appid, openid)`
|
- persist channel identity by `(channel, appid, openid)`
|
||||||
- persist canonical identity by `unionid`
|
- persist canonical identity by `unionid`
|
||||||
- hard-fail when `unionid` is absent under the approved product policy
|
- hard-fail when `unionid` is absent under the approved product policy
|
||||||
@@ -253,6 +281,10 @@
|
|||||||
- submit verified email
|
- submit verified email
|
||||||
- choose create-new-account or bind-existing-account
|
- choose create-new-account or bind-existing-account
|
||||||
- submit nickname/avatar replacement choices
|
- submit nickname/avatar replacement choices
|
||||||
|
- [ ] Make bind-existing-account and bind-current-user flows explicit:
|
||||||
|
- no automatic linking on matching email
|
||||||
|
- fresh password/TOTP proof is scoped to the intended target account only
|
||||||
|
- no automatic metadata merge beyond explicitly selected nickname/avatar adoption
|
||||||
- [ ] Update `backend/internal/handler/auth_handler.go` and `backend/internal/handler/user_handler.go` to expose:
|
- [ ] Update `backend/internal/handler/auth_handler.go` and `backend/internal/handler/user_handler.go` to expose:
|
||||||
- current bindings summary
|
- current bindings summary
|
||||||
- start-bind endpoints for LinuxDo/OIDC/WeChat
|
- start-bind endpoints for LinuxDo/OIDC/WeChat
|
||||||
@@ -312,6 +344,7 @@
|
|||||||
- `frontend/src/api/auth.ts`
|
- `frontend/src/api/auth.ts`
|
||||||
- `frontend/src/stores/auth.ts`
|
- `frontend/src/stores/auth.ts`
|
||||||
- [ ] Replace any token-fragment bootstrap with backend session completion or one-time exchange code flow.
|
- [ ] Replace any token-fragment bootstrap with backend session completion or one-time exchange code flow.
|
||||||
|
- [ ] During rollout, keep temporary compatibility readers for legacy pending-token aliases behind a bounded bridge contract and explicit removal step.
|
||||||
|
|
||||||
### Task 12. Rebuild profile account binding and avatar UX
|
### Task 12. Rebuild profile account binding and avatar UX
|
||||||
|
|
||||||
@@ -351,11 +384,17 @@
|
|||||||
- frontend visible methods remain `alipay` and `wxpay`
|
- frontend visible methods remain `alipay` and `wxpay`
|
||||||
- admin chooses which provider instance serves each method
|
- admin chooses which provider instance serves each method
|
||||||
- runtime validation guarantees only one active source per visible method
|
- runtime validation guarantees only one active source per visible method
|
||||||
|
- [ ] Add migration logic and tests to normalize historical provider-instance config:
|
||||||
|
- `supported_types`
|
||||||
|
- `payment_mode`
|
||||||
|
- legacy aliases such as `wxpay_direct`
|
||||||
|
- historical limit config
|
||||||
- [ ] Rebuild `backend/internal/service/payment_order.go` and `backend/internal/service/payment_order_lifecycle.go` so order creation snapshots:
|
- [ ] Rebuild `backend/internal/service/payment_order.go` and `backend/internal/service/payment_order_lifecycle.go` so order creation snapshots:
|
||||||
- visible method
|
- visible method
|
||||||
- selected provider instance id
|
- selected provider instance id
|
||||||
- provider type
|
- provider type
|
||||||
- provider capability mode
|
- provider capability mode
|
||||||
|
- verification-critical provider fields needed for later callback/query/refund validation
|
||||||
- [ ] Rebuild `backend/internal/handler/payment_handler.go` for UX rules:
|
- [ ] Rebuild `backend/internal/handler/payment_handler.go` for UX rules:
|
||||||
- Alipay PC: QR page
|
- Alipay PC: QR page
|
||||||
- Alipay mobile: direct jump
|
- Alipay mobile: direct jump
|
||||||
@@ -363,6 +402,11 @@
|
|||||||
- WeChat H5 in WeChat: MP/JSAPI first, fallback to H5
|
- WeChat H5 in WeChat: MP/JSAPI first, fallback to H5
|
||||||
- WeChat H5 outside WeChat: H5 or “open in WeChat” prompt when unavailable
|
- 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.
|
- [ ] Never derive canonical return URL from `Referer`; use configured or signed internal callback targets only.
|
||||||
|
- [ ] Implement `backend/internal/service/payment_resume_service.go` so WeChat in-app payment OAuth resume is server-backed rather than localStorage-backed:
|
||||||
|
- create `oauth_required` resume context
|
||||||
|
- persist amount/order_type/plan_id/visible method/redirect/state
|
||||||
|
- redeem callback into same-origin internal resume target
|
||||||
|
- expire and consume resume context safely
|
||||||
|
|
||||||
### Task 15. Make fulfillment and reconciliation provider-instance-safe
|
### Task 15. Make fulfillment and reconciliation provider-instance-safe
|
||||||
|
|
||||||
@@ -370,6 +414,12 @@
|
|||||||
- verification uses the order’s original provider instance
|
- verification uses the order’s original provider instance
|
||||||
- webhook processing is idempotent by provider event id and internal order id
|
- webhook processing is idempotent by provider event id and internal order id
|
||||||
- missed webhook recovery uses server-side provider query, not frontend success return
|
- missed webhook recovery uses server-side provider query, not frontend success return
|
||||||
|
- [ ] For WeChat Pay specifically, enforce:
|
||||||
|
- fixed HTTPS `notify_url` with no query params
|
||||||
|
- no dependency on user login state
|
||||||
|
- signature verification before decrypt
|
||||||
|
- APIv3 decrypt before business parsing
|
||||||
|
- comparison of `appid`, `mchid`, `out_trade_no`, `amount`, `currency`, and trade state against the order snapshot
|
||||||
- [ ] 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.
|
- [ ] 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
|
### Task 16. Rebuild payment frontend views
|
||||||
@@ -378,6 +428,10 @@
|
|||||||
- only two buttons are shown to user: `支付宝` and `微信支付`
|
- only two buttons are shown to user: `支付宝` and `微信支付`
|
||||||
- frontend does not leak official-vs-EasyPay distinction
|
- frontend does not leak official-vs-EasyPay distinction
|
||||||
- route-specific copy handles QR, jump, MP, H5 fallback correctly
|
- route-specific copy handles QR, jump, MP, H5 fallback correctly
|
||||||
|
- [ ] Rebuild WeChat in-app payment resume UX around the server-backed resume context:
|
||||||
|
- handle `oauth_required`
|
||||||
|
- continue from same-origin resume target
|
||||||
|
- avoid long-lived localStorage as the source of truth
|
||||||
- [ ] Add or update:
|
- [ ] Add or update:
|
||||||
- `frontend/src/views/user/__tests__/PaymentView.spec.ts`
|
- `frontend/src/views/user/__tests__/PaymentView.spec.ts`
|
||||||
- `frontend/src/views/user/__tests__/PaymentResultView.spec.ts`
|
- `frontend/src/views/user/__tests__/PaymentResultView.spec.ts`
|
||||||
@@ -416,16 +470,22 @@
|
|||||||
- historical email-only user login after upgrade
|
- historical email-only user login after upgrade
|
||||||
- historical LinuxDo user login after upgrade
|
- historical LinuxDo user login after upgrade
|
||||||
- historical synthetic-email LinuxDo user login after upgrade
|
- historical synthetic-email LinuxDo user login after upgrade
|
||||||
|
- historical synthetic-email WeChat user login after upgrade
|
||||||
|
- historical synthetic-email OIDC user login after upgrade
|
||||||
|
- historical WeChat `openid`-only rows are reported or explicitly remediated
|
||||||
- no retroactive grant replay during migration
|
- no retroactive grant replay during migration
|
||||||
- first-bind grant fires once only when enabled
|
- first-bind grant fires once only when enabled
|
||||||
- email identity dual-write stays consistent
|
- email identity dual-write stays consistent
|
||||||
- bind-existing-account requires password and TOTP where configured
|
- bind-existing-account requires password and TOTP where configured
|
||||||
|
- mixed-version callback token bridge works during rollout and is removable afterward
|
||||||
|
- historical payment config is normalized into visible-method routing without refund/query regression
|
||||||
- [ ] Add deploy sequencing note to release docs or internal runbook:
|
- [ ] Add deploy sequencing note to release docs or internal runbook:
|
||||||
1. deploy schema and backfill release.
|
1. deploy schema and backfill release.
|
||||||
2. inspect migration report for unmatched rows.
|
2. inspect migration report for unmatched rows.
|
||||||
3. deploy backend identity/payment compatibility code.
|
3. deploy backend identity/payment compatibility code with exchange bridge and legacy token aliases still enabled.
|
||||||
4. deploy frontend callback/profile/payment UI.
|
4. deploy frontend callback/profile/payment UI using session completion, exchange code, and server-backed WeChat payment resume.
|
||||||
5. enable strict email-required signup or provider bind grants only after metrics are healthy.
|
5. remove legacy callback/token parsing after mixed-version window closes.
|
||||||
|
6. enable strict email-required signup or provider bind grants only after metrics are healthy.
|
||||||
|
|
||||||
### Task 20. Final verification and handoff
|
### Task 20. Final verification and handoff
|
||||||
|
|
||||||
@@ -449,6 +509,8 @@
|
|||||||
- [ ] Run focused manual smoke checks:
|
- [ ] Run focused manual smoke checks:
|
||||||
- email login with existing account
|
- email login with existing account
|
||||||
- LinuxDo existing-account login after migration
|
- LinuxDo existing-account login after migration
|
||||||
|
- WeChat synthetic-email account login after migration
|
||||||
|
- OIDC synthetic-email account login after migration
|
||||||
- third-party first login create-new-account path
|
- third-party first login create-new-account path
|
||||||
- third-party first login bind-existing-account path
|
- third-party first login bind-existing-account path
|
||||||
- first third-party bind with optional nickname/avatar replacement
|
- first third-party bind with optional nickname/avatar replacement
|
||||||
@@ -456,6 +518,7 @@
|
|||||||
- mobile Alipay jump
|
- mobile Alipay jump
|
||||||
- PC WeChat QR
|
- PC WeChat QR
|
||||||
- WeChat H5 MP/JSAPI path
|
- WeChat H5 MP/JSAPI path
|
||||||
|
- WeChat in-app OAuth resume path
|
||||||
- non-WeChat H5 fallback path
|
- non-WeChat H5 fallback path
|
||||||
- [ ] Commit final checkpoint:
|
- [ ] Commit final checkpoint:
|
||||||
```bash
|
```bash
|
||||||
@@ -468,9 +531,9 @@
|
|||||||
- [ ] No flow still relies on provider email equality for account linking.
|
- [ ] No flow still relies on provider email equality for account linking.
|
||||||
- [ ] No flow still creates third-party users through plain email registration helpers.
|
- [ ] No flow still creates third-party users through plain email registration helpers.
|
||||||
- [ ] No callback still returns first-party bearer tokens in URL fragments.
|
- [ ] No callback still returns first-party bearer tokens in URL fragments.
|
||||||
|
- [ ] No callback completion path can be replayed as a bearer token substitute.
|
||||||
- [ ] No payment result view trusts provider return page as authoritative fulfillment.
|
- [ ] 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.
|
- [ ] 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.
|
- [ ] Existing email users, historical LinuxDo/WeChat/OIDC users, and `openid`-only WeChat remediation cases are covered by migration tests.
|
||||||
- [ ] Avatar adoption and deletion semantics are explicit and reversible.
|
- [ ] Avatar adoption and deletion semantics are explicit and reversible.
|
||||||
- [ ] Grant timing is source-aware and one-time only.
|
- [ ] Grant timing is source-aware and one-time only.
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ This design includes:
|
|||||||
- Profile binding management and avatar upload/delete
|
- Profile binding management and avatar upload/delete
|
||||||
- Source-based initial grants for balance, concurrency, and subscriptions
|
- Source-based initial grants for balance, concurrency, and subscriptions
|
||||||
- User management support for `last_login_at` and `last_active_at` sorting
|
- User management support for `last_login_at` and `last_active_at` sorting
|
||||||
- Unified payment display methods (`alipay`, `wechat`) mapped to a single active backend source each
|
- Unified payment display methods (`alipay`, `wxpay`) mapped to a single active backend source each
|
||||||
- Alipay and WeChat UX routing rules across PC, mobile, H5, and WeChat environments
|
- Alipay and WeChat UX routing rules across PC, mobile, H5, and WeChat environments
|
||||||
- Admin settings for auth providers, source defaults, payment sources, and OpenAI advanced scheduling
|
- Admin settings for auth providers, source defaults, payment sources, and OpenAI advanced scheduling
|
||||||
- Incremental migration and compatibility for existing email users and historical LinuxDo synthetic-email users
|
- Incremental migration and compatibility for existing email users, existing LinuxDo users, historical LinuxDo/WeChat/OIDC synthetic-email users, and historical WeChat `openid`-only identity records
|
||||||
|
|
||||||
This design does not treat unrelated upstream merges, docs churn, or license changes from the old branch as required scope.
|
This design does not treat unrelated upstream merges, docs churn, or license changes from the old branch as required scope.
|
||||||
|
|
||||||
@@ -32,9 +32,11 @@ This design does not treat unrelated upstream merges, docs churn, or license cha
|
|||||||
### Auth and identity
|
### Auth and identity
|
||||||
|
|
||||||
- Existing email users remain valid and continue to log in with no manual action.
|
- Existing email users remain valid and continue to log in with no manual action.
|
||||||
|
- Existing LinuxDo, OIDC, and WeChat users represented by historical third-party or synthetic-email data must remain recoverable during migration.
|
||||||
- Third-party first login behavior:
|
- Third-party first login behavior:
|
||||||
- Existing bound identity: direct login
|
- Existing bound identity: direct login
|
||||||
- Missing identity: start first-login flow
|
- Missing identity: start first-login flow
|
||||||
|
- Browser-based third-party authorization-code login always uses PKCE `S256`; this is not an admin-toggleable feature.
|
||||||
- If `force_email_on_third_party_signup` is disabled, a first-login user may create an account without binding an email.
|
- If `force_email_on_third_party_signup` is disabled, a first-login user may create an account without binding an email.
|
||||||
- If `force_email_on_third_party_signup` is enabled, the user must provide an email.
|
- If `force_email_on_third_party_signup` is enabled, the user must provide an email.
|
||||||
- If the provided and verified email already exists:
|
- If the provided and verified email already exists:
|
||||||
@@ -43,11 +45,25 @@ This design does not treat unrelated upstream merges, docs churn, or license cha
|
|||||||
- allow "change email and continue registration"
|
- allow "change email and continue registration"
|
||||||
- do not allow bypassing the email requirement
|
- do not allow bypassing the email requirement
|
||||||
- Upstream provider email verification is not trusted as a local bound email.
|
- Upstream provider email verification is not trusted as a local bound email.
|
||||||
|
- Matching upstream email must never auto-link to an existing local account.
|
||||||
|
- Linking to an existing local account is allowed only when:
|
||||||
|
- the user explicitly chooses that target account
|
||||||
|
- the target account passes fresh local re-authentication
|
||||||
|
- required TOTP verification succeeds
|
||||||
|
- New third-party bind initiated from profile must start from an already logged-in local account and preserve explicit bind intent end-to-end.
|
||||||
|
- `redirect_to` may only represent a normalized same-origin internal route. It must never contain a third-party URL and must never be derived from `Referer`.
|
||||||
|
- OIDC validation rules:
|
||||||
|
- canonical identity key is `issuer + sub`
|
||||||
|
- discovery issuer and ID token `iss` must match exactly
|
||||||
|
- `userinfo.sub` must match ID token `sub` when UserInfo is used
|
||||||
|
- upstream `email_verified` may improve UX copy but does not satisfy local email-binding requirements
|
||||||
- WeChat login chooses channel by environment:
|
- WeChat login chooses channel by environment:
|
||||||
- in WeChat environment: `mp`
|
- in WeChat environment: `mp`
|
||||||
- outside WeChat: `open`
|
- outside WeChat: `open`
|
||||||
- WeChat primary identity key is `unionid`.
|
- WeChat primary identity key is `unionid`.
|
||||||
- If a WeChat login/bind flow cannot produce `unionid`, the flow fails and no fallback `openid` identity is created.
|
- If a WeChat login/bind flow cannot produce `unionid`, the flow fails and no fallback `openid` identity is created.
|
||||||
|
- Historical WeChat records that only contain `openid` are treated as migration-remediation cases, not as a valid long-term canonical identity model.
|
||||||
|
- WeChat website login uses authorization code flow, random `state`, and the provider channel/app binding must be persisted alongside the resolved identity.
|
||||||
|
|
||||||
### Profile adoption
|
### Profile adoption
|
||||||
|
|
||||||
@@ -85,7 +101,7 @@ This design does not treat unrelated upstream merges, docs churn, or license cha
|
|||||||
|
|
||||||
- Frontend shows only two display methods:
|
- Frontend shows only two display methods:
|
||||||
- `alipay`
|
- `alipay`
|
||||||
- `wechat`
|
- `wxpay`
|
||||||
- Users never choose between official providers and EasyPay explicitly.
|
- Users never choose between official providers and EasyPay explicitly.
|
||||||
- Backend allows only one active source per display method at a time.
|
- Backend allows only one active source per display method at a time.
|
||||||
- Alipay UX:
|
- Alipay UX:
|
||||||
@@ -96,6 +112,10 @@ This design does not treat unrelated upstream merges, docs churn, or license cha
|
|||||||
- non-WeChat H5: prefer H5 pay; if unavailable, tell the user to open in WeChat
|
- non-WeChat H5: prefer H5 pay; if unavailable, tell the user to open in WeChat
|
||||||
- WeChat environment: prefer MP/JSAPI pay; if unavailable, fall back to H5 pay
|
- WeChat environment: prefer MP/JSAPI pay; if unavailable, fall back to H5 pay
|
||||||
- Payment success is confirmed by backend order state, webhook, and/or query, not only frontend return.
|
- Payment success is confirmed by backend order state, webhook, and/or query, not only frontend return.
|
||||||
|
- Frontend-visible labels remain `支付宝` and `微信支付`, while internal visible-method identifiers remain `alipay` and `wxpay`.
|
||||||
|
- Public result pages must not verify order state by exposing raw `out_trade_no`; they use authenticated lookup or a signed opaque result token instead.
|
||||||
|
- Payment callback or return URLs must be fixed same-origin internal targets. They must not be inferred from `Referer`.
|
||||||
|
- WeChat payment webhook handling must use a fixed HTTPS `notify_url` with no query parameters and must not depend on user login state.
|
||||||
|
|
||||||
### OpenAI advanced scheduling
|
### OpenAI advanced scheduling
|
||||||
|
|
||||||
@@ -105,9 +125,9 @@ This design does not treat unrelated upstream merges, docs churn, or license cha
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
Keep `users` as the account owner table and move login identities, channel mappings, pending auth state, and first-bind grant idempotency into dedicated tables and services. Keep email login working while progressively introducing unified identity reads and writes.
|
Keep `users` as the account owner table and move login identities, channel mappings, pending auth state, callback completion state, and first-bind grant idempotency into dedicated tables and services. Keep email login working while progressively introducing unified identity reads and writes.
|
||||||
|
|
||||||
Payment uses a similar split between user-visible display methods and backend provider sources. Frontend works only with stable display methods while backend resolves to the currently active source and capability matrix.
|
Payment uses a similar split between user-visible display methods and backend provider sources. Frontend works only with stable display methods while backend resolves to the currently active source and capability matrix, and stores enough order-time snapshot data to survive later provider-config changes.
|
||||||
|
|
||||||
Compatibility is a first-class concern: migrations are additive, reads are compatibility-aware, and rollout must tolerate existing `main` data and short-lived frontend/backend version skew.
|
Compatibility is a first-class concern: migrations are additive, reads are compatibility-aware, and rollout must tolerate existing `main` data and short-lived frontend/backend version skew.
|
||||||
|
|
||||||
@@ -148,9 +168,9 @@ Uniqueness:
|
|||||||
Rules:
|
Rules:
|
||||||
|
|
||||||
- email identity uses canonicalized local email
|
- email identity uses canonicalized local email
|
||||||
- LinuxDo uses stable provider subject
|
- LinuxDo uses stable provider subject under the configured provider namespace
|
||||||
- OIDC uses stable issuer + subject
|
- OIDC uses stable issuer + subject, with issuer namespace represented consistently through `provider_key` and `issuer`
|
||||||
- WeChat uses `unionid` as canonical subject
|
- WeChat uses `unionid` as canonical subject under the configured Open Platform namespace
|
||||||
|
|
||||||
### `auth_identity_channels`
|
### `auth_identity_channels`
|
||||||
|
|
||||||
@@ -189,9 +209,12 @@ Fields:
|
|||||||
- `target_user_id`
|
- `target_user_id`
|
||||||
- `redirect_to`
|
- `redirect_to`
|
||||||
- `resolved_email`
|
- `resolved_email`
|
||||||
- `pending_password_hash`
|
- `registration_password_hash`
|
||||||
- `upstream_identity_payload`
|
- `upstream_identity_claims`
|
||||||
- `metadata`
|
- `local_flow_state`
|
||||||
|
- `browser_session_key`
|
||||||
|
- `completion_code_hash`
|
||||||
|
- `completion_code_expires_at`
|
||||||
- `email_verified_at`
|
- `email_verified_at`
|
||||||
- `password_verified_at`
|
- `password_verified_at`
|
||||||
- `totp_verified_at`
|
- `totp_verified_at`
|
||||||
@@ -205,19 +228,33 @@ Responsibilities:
|
|||||||
- persist nickname/avatar suggestions
|
- persist nickname/avatar suggestions
|
||||||
- persist explicit adoption decisions
|
- persist explicit adoption decisions
|
||||||
- survive navigation between auth pages
|
- survive navigation between auth pages
|
||||||
|
- support mixed-version rollout through short-lived legacy token aliases when required
|
||||||
|
|
||||||
|
Security rules:
|
||||||
|
|
||||||
|
- callback completion uses backend session completion or a one-time exchange code
|
||||||
|
- exchange codes are short-lived, one-time, bound to browser session and pending session, and redeemed via `POST`
|
||||||
|
- exchange codes must not behave as bearer tokens and must not be logged, stored in URL fragments, or reused after redemption
|
||||||
|
- `local_flow_state` stores mutable local progression only; immutable upstream claims remain in `upstream_identity_claims`
|
||||||
|
|
||||||
### `identity_adoption_decisions`
|
### `identity_adoption_decisions`
|
||||||
|
|
||||||
Persists user adoption preference for a specific identity.
|
Persists user adoption preference collected during a pending-auth flow and resolved onto the bound identity.
|
||||||
|
|
||||||
Fields:
|
Fields:
|
||||||
|
|
||||||
|
- `pending_auth_session_id`
|
||||||
- `identity_id`
|
- `identity_id`
|
||||||
- `adopt_display_name`
|
- `adopt_display_name`
|
||||||
- `adopt_avatar`
|
- `adopt_avatar`
|
||||||
- `decided_at`
|
- `decided_at`
|
||||||
- timestamps
|
- timestamps
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- one adoption-decision row exists per pending session
|
||||||
|
- `identity_id` is filled once final account creation or bind succeeds
|
||||||
|
|
||||||
### `user_avatars`
|
### `user_avatars`
|
||||||
|
|
||||||
Stores the currently effective custom avatar.
|
Stores the currently effective custom avatar.
|
||||||
@@ -265,6 +302,7 @@ WeChat-specific rule:
|
|||||||
|
|
||||||
- `openid` never becomes the primary stored identity key
|
- `openid` never becomes the primary stored identity key
|
||||||
- if only `openid` is available, login/bind fails with a configuration/identity error
|
- if only `openid` is available, login/bind fails with a configuration/identity error
|
||||||
|
- historical `openid`-only records must be reported and either remediated during migration or explicitly blocked from silent auto-upgrade
|
||||||
|
|
||||||
## Core Flows
|
## Core Flows
|
||||||
|
|
||||||
@@ -285,6 +323,7 @@ WeChat-specific rule:
|
|||||||
|
|
||||||
- Create `pending_auth_session`
|
- Create `pending_auth_session`
|
||||||
- Frontend callback flow decides next action
|
- Frontend callback flow decides next action
|
||||||
|
- Pending session creation stores immutable upstream claims separately from mutable local progress fields
|
||||||
|
|
||||||
Branches:
|
Branches:
|
||||||
|
|
||||||
@@ -303,6 +342,7 @@ On new account creation:
|
|||||||
|
|
||||||
- create `users` row
|
- create `users` row
|
||||||
- create canonical third-party identity
|
- create canonical third-party identity
|
||||||
|
- create or update canonical email identity when local email binding succeeds
|
||||||
- apply source signup grants
|
- apply source signup grants
|
||||||
- apply adoption choices if selected
|
- apply adoption choices if selected
|
||||||
|
|
||||||
@@ -310,21 +350,34 @@ On new account creation:
|
|||||||
|
|
||||||
- current user starts bind flow
|
- current user starts bind flow
|
||||||
- callback resolves to `bind_current_user`
|
- callback resolves to `bind_current_user`
|
||||||
|
- bind intent is tied to the initiating local user session and cannot be re-targeted by email match
|
||||||
- bind canonical identity to current user
|
- bind canonical identity to current user
|
||||||
- if configured and first bind for that provider, apply first-bind grants
|
- if configured and first bind for that provider, apply first-bind grants
|
||||||
- present nickname/avatar replacement choice
|
- present nickname/avatar replacement choice
|
||||||
|
|
||||||
### Bind existing account during first-login flow
|
### Bind existing account during first-login flow
|
||||||
|
|
||||||
|
- user explicitly selects bind-existing-account
|
||||||
- verify password for existing account
|
- verify password for existing account
|
||||||
- if account requires TOTP, verify TOTP
|
- if account requires TOTP, verify TOTP
|
||||||
- bind canonical identity to target account
|
- bind canonical identity to target account
|
||||||
- optionally apply first-bind grants
|
- optionally apply first-bind grants
|
||||||
- present nickname/avatar replacement choice
|
- present nickname/avatar replacement choice
|
||||||
|
- no automatic profile or metadata merge occurs beyond explicitly selected nickname/avatar replacement
|
||||||
|
|
||||||
|
### Callback completion and exchange flow
|
||||||
|
|
||||||
|
- third-party callback never returns first-party bearer tokens in URL fragments
|
||||||
|
- callback completion uses either:
|
||||||
|
- backend session completion tied to the initiating browser session
|
||||||
|
- one-time opaque exchange code redeemed by `POST`
|
||||||
|
- mixed-version rollout may temporarily emit legacy pending token aliases in addition to the new completion path
|
||||||
|
- legacy alias support is transitional and bounded to rollout windows only
|
||||||
|
|
||||||
### WeChat login and channel mapping
|
### WeChat login and channel mapping
|
||||||
|
|
||||||
- environment chooses `mp` or `open`
|
- environment chooses `mp` or `open`
|
||||||
|
- website login uses authorization-code flow with provider-configured app/channel binding
|
||||||
- callback must resolve to `unionid`
|
- callback must resolve to `unionid`
|
||||||
- channel `openid` is optionally recorded in `auth_identity_channels`
|
- channel `openid` is optionally recorded in `auth_identity_channels`
|
||||||
- failure to obtain `unionid` aborts flow
|
- failure to obtain `unionid` aborts flow
|
||||||
@@ -344,7 +397,7 @@ On new account creation:
|
|||||||
### User-visible methods
|
### User-visible methods
|
||||||
|
|
||||||
- `alipay`
|
- `alipay`
|
||||||
- `wechat`
|
- `wxpay`
|
||||||
|
|
||||||
### Backend source abstraction
|
### Backend source abstraction
|
||||||
|
|
||||||
@@ -357,6 +410,12 @@ Each display method maps to exactly one active configured backend source:
|
|||||||
|
|
||||||
Frontend submits display method only. Backend resolves display method to active source and capability set.
|
Frontend submits display method only. Backend resolves display method to active source and capability set.
|
||||||
|
|
||||||
|
### Legacy payment-config normalization
|
||||||
|
|
||||||
|
- existing provider-instance `supported_types`, legacy aliases such as `wxpay_direct`, and per-type limit structures are migrated into the visible-method model
|
||||||
|
- migration preserves historical payment capability and refund semantics
|
||||||
|
- the system keeps one normalized visible-method mapping per provider instance for rollout and audit
|
||||||
|
|
||||||
### Alipay routing
|
### Alipay routing
|
||||||
|
|
||||||
- PC: create QR-oriented result and show QR in page
|
- PC: create QR-oriented result and show QR in page
|
||||||
@@ -372,11 +431,25 @@ Frontend submits display method only. Backend resolves display method to active
|
|||||||
- prefer MP/JSAPI
|
- prefer MP/JSAPI
|
||||||
- if unavailable, fall back to H5 pay
|
- if unavailable, fall back to H5 pay
|
||||||
|
|
||||||
|
### WeChat payment OAuth recovery
|
||||||
|
|
||||||
|
- if WeChat in-app payment requires `openid` and the current request does not already hold it, backend returns an `oauth_required` response instead of guessing
|
||||||
|
- backend creates a server-backed payment-resume context containing:
|
||||||
|
- target visible method
|
||||||
|
- amount/order type/plan context
|
||||||
|
- redirect target
|
||||||
|
- anti-replay state
|
||||||
|
- backend redirects through a dedicated WeChat payment OAuth start endpoint
|
||||||
|
- callback exchanges the provider code server-side, stores `openid` in the payment-resume context, and returns a same-origin internal resume target
|
||||||
|
- frontend resumes the original order flow through the resume context instead of trusting raw callback query state or long-lived local storage
|
||||||
|
|
||||||
### Payment completion
|
### Payment completion
|
||||||
|
|
||||||
- frontend return restores context and UI state
|
- frontend return restores context and UI state
|
||||||
- backend order state remains source of truth
|
- backend order state remains source of truth
|
||||||
- webhook and/or order query remain authoritative for fulfillment
|
- webhook and/or order query remain authoritative for fulfillment
|
||||||
|
- order fulfillment validates webhook or query payload against order-time snapshot data including provider instance, merchant identifiers, amount, currency, and provider order references
|
||||||
|
- result pages use authenticated lookup or signed opaque result tokens, never raw public `out_trade_no`
|
||||||
|
|
||||||
## Admin Configuration Model
|
## Admin Configuration Model
|
||||||
|
|
||||||
@@ -420,6 +493,9 @@ Compatibility is mandatory, especially for:
|
|||||||
- existing email users
|
- existing email users
|
||||||
- existing LinuxDo users
|
- existing LinuxDo users
|
||||||
- historical LinuxDo synthetic-email accounts
|
- historical LinuxDo synthetic-email accounts
|
||||||
|
- historical WeChat synthetic-email accounts
|
||||||
|
- historical OIDC synthetic-email accounts
|
||||||
|
- historical WeChat `openid`-only records created by older branches
|
||||||
|
|
||||||
### Additive migrations
|
### Additive migrations
|
||||||
|
|
||||||
@@ -431,6 +507,8 @@ Compatibility is mandatory, especially for:
|
|||||||
|
|
||||||
- backfill canonical `email` identities for valid existing email users
|
- backfill canonical `email` identities for valid existing email users
|
||||||
- backfill canonical `linuxdo` identities during migration for historical synthetic-email LinuxDo users
|
- backfill canonical `linuxdo` identities during migration for historical synthetic-email LinuxDo users
|
||||||
|
- backfill canonical `wechat` and `oidc` identities when historical synthetic-email or `user_external_identities` data allows deterministic reconstruction
|
||||||
|
- emit migration reports for historical WeChat `openid`-only records that cannot be safely promoted to canonical `unionid`
|
||||||
- backfill must be idempotent and repeatable
|
- backfill must be idempotent and repeatable
|
||||||
|
|
||||||
### Compatibility reads
|
### Compatibility reads
|
||||||
@@ -438,7 +516,7 @@ Compatibility is mandatory, especially for:
|
|||||||
During rollout:
|
During rollout:
|
||||||
|
|
||||||
- read new identity model first
|
- read new identity model first
|
||||||
- where necessary, retain compatibility logic for existing email and historical LinuxDo synthetic-email recognition
|
- where necessary, retain compatibility logic for existing email and historical LinuxDo/WeChat/OIDC synthetic-email recognition
|
||||||
|
|
||||||
### Grant idempotency
|
### Grant idempotency
|
||||||
|
|
||||||
@@ -453,17 +531,20 @@ Retain transitional support for legacy/new request and response shapes where nee
|
|||||||
- `pending_oauth_token`
|
- `pending_oauth_token`
|
||||||
- old callback parsing expectations
|
- old callback parsing expectations
|
||||||
- historical profile field mappings
|
- historical profile field mappings
|
||||||
|
- legacy callback fragment readers during the bounded rollout window
|
||||||
|
|
||||||
### Settings and payment compatibility
|
### Settings and payment compatibility
|
||||||
|
|
||||||
- preserve existing payment configs and order semantics from `main`
|
- preserve existing payment configs and order semantics from `main`
|
||||||
- add new settings incrementally
|
- add new settings incrementally
|
||||||
- avoid rewriting the entire settings schema in one cutover
|
- avoid rewriting the entire settings schema in one cutover
|
||||||
|
- preserve legacy provider-instance capabilities by explicitly mapping historical `supported_types`, `payment_mode`, and limit config into normalized visible-method routing
|
||||||
|
|
||||||
### Rolling upgrade tolerance
|
### Rolling upgrade tolerance
|
||||||
|
|
||||||
- do not assume simultaneous frontend/backend deployment
|
- do not assume simultaneous frontend/backend deployment
|
||||||
- new backend must tolerate short-lived older frontend request shapes
|
- new backend must tolerate short-lived older frontend request shapes
|
||||||
|
- rollout must define the deployment order and removal point for legacy callback token parsing and legacy payment resume parsing
|
||||||
|
|
||||||
## Testing Strategy
|
## Testing Strategy
|
||||||
|
|
||||||
@@ -509,9 +590,13 @@ Retain transitional support for legacy/new request and response shapes where nee
|
|||||||
|
|
||||||
- existing email users
|
- existing email users
|
||||||
- historical LinuxDo synthetic-email users
|
- historical LinuxDo synthetic-email users
|
||||||
|
- historical WeChat synthetic-email users
|
||||||
|
- historical OIDC synthetic-email users
|
||||||
|
- historical WeChat `openid`-only records reported or remediated correctly
|
||||||
- historical payment config
|
- historical payment config
|
||||||
- legacy auth payload field names
|
- legacy auth payload field names
|
||||||
- historical payment result handling
|
- historical payment result handling
|
||||||
|
- mixed-version callback token bridge behavior
|
||||||
|
|
||||||
## Implementation Phases
|
## Implementation Phases
|
||||||
|
|
||||||
@@ -528,9 +613,12 @@ Retain transitional support for legacy/new request and response shapes where nee
|
|||||||
Implementation must follow current primary-source guidance:
|
Implementation must follow current primary-source guidance:
|
||||||
|
|
||||||
- OAuth 2.0 Security BCP (RFC 9700): strict redirect handling, state protection, mix-up resistant design
|
- OAuth 2.0 Security BCP (RFC 9700): strict redirect handling, state protection, mix-up resistant design
|
||||||
- PKCE (RFC 7636): use on authorization code flows where applicable
|
- PKCE (RFC 7636): require `S256` on browser authorization-code flows
|
||||||
- OpenID Connect Core: stable issuer/subject handling for OIDC identities
|
- OpenID Connect Core: stable issuer/subject handling for OIDC identities
|
||||||
- Account linking best practice: require explicit user confirmation or re-authentication before linking to existing accounts
|
- Account linking best practice: require explicit user confirmation or re-authentication before linking to existing accounts
|
||||||
|
- WeChat UnionID and website-login guidance: treat `unionid` as canonical cross-channel subject and persist channel/app binding with website login responses
|
||||||
|
- WeChat Pay webhook guidance: verify signatures, decrypt payloads, and confirm merchant/order/amount fields against order-time state before fulfillment
|
||||||
|
- Payment success-page guidance: custom success pages are informational and must not be the only fulfillment trigger
|
||||||
|
|
||||||
References:
|
References:
|
||||||
|
|
||||||
@@ -538,6 +626,10 @@ References:
|
|||||||
- RFC 7636: <https://www.rfc-editor.org/rfc/rfc7636>
|
- RFC 7636: <https://www.rfc-editor.org/rfc/rfc7636>
|
||||||
- OpenID Connect Core 1.0: <https://openid.net/specs/openid-connect-core-1_0.html>
|
- OpenID Connect Core 1.0: <https://openid.net/specs/openid-connect-core-1_0.html>
|
||||||
- Auth0 account linking guidance: <https://auth0.com/docs/manage-users/user-accounts/user-account-linking>
|
- Auth0 account linking guidance: <https://auth0.com/docs/manage-users/user-accounts/user-account-linking>
|
||||||
|
- WeChat UnionID guidance: <https://developers.weixin.qq.com/doc/service/guide/product/unionid.html>
|
||||||
|
- WeChat website login guidance: <https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html>
|
||||||
|
- WeChat Pay callback/signature guidance: <https://pay.weixin.qq.com/doc/v3/merchant/4012075249>
|
||||||
|
- Stripe Checkout fulfillment guidance: <https://docs.stripe.com/checkout/fulfillment>
|
||||||
|
|
||||||
## Audit Synthesis
|
## Audit Synthesis
|
||||||
|
|
||||||
@@ -553,7 +645,7 @@ The clean rebuild direction is not to copy either existing branch directly.
|
|||||||
- `personal-dev-branch` has the better real-world closure:
|
- `personal-dev-branch` has the better real-world closure:
|
||||||
- LinuxDo and WeChat callback flows are more operationally complete
|
- LinuxDo and WeChat callback flows are more operationally complete
|
||||||
- profile binding and avatar UX is more complete
|
- profile binding and avatar UX is more complete
|
||||||
- historical synthetic-email users are recognized and recovered in live flows
|
- historical synthetic-email users across multiple providers are recognized and recovered in live flows
|
||||||
- WeChat payment OAuth and recovery behavior is more complete
|
- WeChat payment OAuth and recovery behavior is more complete
|
||||||
- Primary-source guidance supplies hard constraints for OAuth/OIDC, account linking, WeChat identity handling, and payment completion semantics.
|
- Primary-source guidance supplies hard constraints for OAuth/OIDC, account linking, WeChat identity handling, and payment completion semantics.
|
||||||
|
|
||||||
@@ -584,6 +676,7 @@ Keep these operational flow ideas from `personal-dev-branch`:
|
|||||||
- profile bindings UX and “cannot disconnect last usable login method” rule
|
- profile bindings UX and “cannot disconnect last usable login method” rule
|
||||||
- separate WeChat login OAuth and WeChat payment OAuth entry points
|
- separate WeChat login OAuth and WeChat payment OAuth entry points
|
||||||
- historical synthetic-email recognition logic as a migration bridge
|
- historical synthetic-email recognition logic as a migration bridge
|
||||||
|
- explicit WeChat payment OAuth recovery protocol as a product requirement, but reimplemented with server-backed resume state
|
||||||
|
|
||||||
### Adapt
|
### Adapt
|
||||||
|
|
||||||
@@ -595,6 +688,7 @@ These areas must be reimplemented with the same intent but stricter boundaries:
|
|||||||
- WeChat payment recovery state must move from frontend-only storage to server-backed continuation state
|
- WeChat payment recovery state must move from frontend-only storage to server-backed continuation state
|
||||||
- avatar adoption fetches must be security-hardened and failure-visible
|
- avatar adoption fetches must be security-hardened and failure-visible
|
||||||
- pending-auth payload modeling must clearly separate immutable upstream payload from mutable local metadata
|
- pending-auth payload modeling must clearly separate immutable upstream payload from mutable local metadata
|
||||||
|
- callback completion must use a real exchange/session model instead of fragment-delivered bearer tokens
|
||||||
- profile binding/avatar DTOs must be simplified to one authoritative backend contract instead of sprawling frontend fallback parsing
|
- profile binding/avatar DTOs must be simplified to one authoritative backend contract instead of sprawling frontend fallback parsing
|
||||||
- admin settings should preserve capability while reducing duplicated or transitional config branches
|
- admin settings should preserve capability while reducing duplicated or transitional config branches
|
||||||
|
|
||||||
@@ -614,7 +708,7 @@ The audit and source review establish these hard constraints:
|
|||||||
|
|
||||||
### Auth
|
### Auth
|
||||||
|
|
||||||
- all authorization-code providers use PKCE where applicable
|
- all browser authorization-code providers use PKCE `S256` and do not expose an admin-off switch
|
||||||
- callback handling uses strict `redirect_uri` discipline and state validation
|
- callback handling uses strict `redirect_uri` discipline and state validation
|
||||||
- OIDC identity key is `issuer + sub`
|
- OIDC identity key is `issuer + sub`
|
||||||
- existing-account linking after email conflict must require explicit user action plus local-account verification
|
- existing-account linking after email conflict must require explicit user action plus local-account verification
|
||||||
@@ -624,7 +718,8 @@ The audit and source review establish these hard constraints:
|
|||||||
|
|
||||||
- existing email users must continue to work with no manual intervention
|
- existing email users must continue to work with no manual intervention
|
||||||
- existing LinuxDo users must not split into duplicate accounts
|
- existing LinuxDo users must not split into duplicate accounts
|
||||||
- historical LinuxDo synthetic-email users must be backfilled into canonical LinuxDo identities during migration, not only lazily on next login
|
- historical LinuxDo/WeChat/OIDC synthetic-email users must be backfilled into canonical identities during migration when deterministic recovery is possible
|
||||||
|
- historical WeChat `openid`-only records must be surfaced through migration reporting and explicit remediation rules
|
||||||
- migration backfills must not trigger signup or first-bind grants
|
- migration backfills must not trigger signup or first-bind grants
|
||||||
- legacy `pending_auth_token` and `pending_oauth_token` contracts must remain accepted during rollout
|
- legacy `pending_auth_token` and `pending_oauth_token` contracts must remain accepted during rollout
|
||||||
- legacy auth/public setting aliases needed by older frontend builds must remain available during rollout
|
- legacy auth/public setting aliases needed by older frontend builds must remain available during rollout
|
||||||
@@ -634,7 +729,9 @@ The audit and source review establish these hard constraints:
|
|||||||
|
|
||||||
- frontend return pages do not determine final payment success
|
- frontend return pages do not determine final payment success
|
||||||
- backend order state, webhook processing, and/or provider status query remain authoritative
|
- backend order state, webhook processing, and/or provider status query remain authoritative
|
||||||
- each visible method (`alipay`, `wechat`) may have only one active backend source at a time
|
- each visible method (`alipay`, `wxpay`) may have only one active backend source at a time
|
||||||
|
- public result pages must not expose raw `out_trade_no` lookup
|
||||||
|
- WeChat Pay callback handling must verify signature, decrypt payload, and compare order fields against order-time snapshot data
|
||||||
|
|
||||||
## Known Risks To Eliminate In Implementation
|
## Known Risks To Eliminate In Implementation
|
||||||
|
|
||||||
@@ -649,6 +746,7 @@ These are specifically observed problems in the existing branches that the clean
|
|||||||
- WeChat payment recovery in `personal-dev-branch` is frontend-local and not robust across tabs or concurrent attempts
|
- WeChat payment recovery in `personal-dev-branch` is frontend-local and not robust across tabs or concurrent attempts
|
||||||
- the existing pending-auth migration update is too destructive to reuse unchanged in a safer rollout
|
- the existing pending-auth migration update is too destructive to reuse unchanged in a safer rollout
|
||||||
- historical provider provenance should not be permanently flattened to `signup_source = email`
|
- historical provider provenance should not be permanently flattened to `signup_source = email`
|
||||||
|
- design/plan drift can reintroduce ambiguous identity uniqueness or ambiguous adoption-decision ownership if not aligned before implementation
|
||||||
|
|
||||||
## Rollout Gates
|
## Rollout Gates
|
||||||
|
|
||||||
@@ -656,9 +754,10 @@ The rebuild is not ready for rollout until all of these are satisfied:
|
|||||||
|
|
||||||
1. Identity schema and migration chain are linearized and production-safe.
|
1. Identity schema and migration chain are linearized and production-safe.
|
||||||
2. Email identity backfill is complete and idempotent.
|
2. Email identity backfill is complete and idempotent.
|
||||||
3. Historical LinuxDo synthetic-email backfill to canonical LinuxDo identity is complete and idempotent.
|
3. Historical LinuxDo/WeChat/OIDC synthetic-email backfill to canonical identity is complete where deterministic, and non-recoverable rows are reported.
|
||||||
4. `signup_source` backfill is accurate for known historical provider-created users.
|
4. Historical WeChat `openid`-only rows are either remediated or explicitly blocked with operator-visible reporting.
|
||||||
5. Dual token acceptance and required legacy field aliases are present.
|
5. `signup_source` backfill is accurate for known historical provider-created users.
|
||||||
6. Existing payment configs are normalized and verified against current frontend-visible capabilities.
|
6. Dual token acceptance, exchange bridge behavior, and required legacy field aliases are present for the bounded rollout window.
|
||||||
7. New frontend flows are verified against mixed-version backend compatibility windows.
|
7. Existing payment configs are normalized and verified against current frontend-visible capabilities.
|
||||||
8. Duplicate-account creation, first-bind grants, and payment route selection have regression coverage.
|
8. New frontend flows are verified against mixed-version backend compatibility windows.
|
||||||
|
9. Duplicate-account creation, first-bind grants, and payment route selection have regression coverage.
|
||||||
|
|||||||
Reference in New Issue
Block a user