Assist_Design/docs/plans/2026-03-03-signup-flow-simplification-design.md

243 lines
12 KiB
Markdown
Raw Normal View History

# Auth Flow Simplification Design
**Date**: 2026-03-03
**Status**: Approved
## Problem
The auth system has accumulated duplication and dead code:
1. **Duplicate signup workflows**: `SfCompletionWorkflow` and `NewCustomerSignupWorkflow` are 85% identical (~300 lines each). The only difference is eligibility case creation and a welcome email.
2. **Confusing names**: `SfCompletionWorkflow` doesn't describe what it does (it creates full accounts, not just "completes SF").
3. **Missing SF fields**: `Birthdate` and `Sex__c` are not set on Salesforce PersonContact during account creation. The only code that called `createContact()` lived in dead legacy code.
4. **Dead code**: `signup/` directory (6 files), `WhmcsLinkWorkflowService` — all unreferenced or superseded.
5. **Legacy duplicate**: `WhmcsLinkWorkflowService` (password-based migration via `POST /auth/migrate`) is superseded by `WhmcsMigrationWorkflowService` (OTP-based migration via get-started).
6. **Scattered shared services**: `SignupUserCreationService` and `SignupWhmcsService` live in `signup/` but are used by other workflows via `CreatePortalUserStep`. They should live alongside the steps they serve.
## Approach — 3 Phases (can stop after any phase)
### Phase 1: Merge signup workflows + cleanup
Merge the two near-duplicate workflows. Move shared services. Delete dead code. Fix Birthdate/Sex\_\_c.
### Phase 2: Remove legacy migrate endpoint
Delete `WhmcsLinkWorkflowService` and `POST /auth/migrate`. All WHMCS migration goes through the get-started OTP-based flow.
### Phase 3: Clean up OTP infrastructure
Extract shared OTP email-sending pattern. Login and signup OTP stay as separate workflows (they serve different purposes: 2FA vs email verification). Keep separate sessions (different data models, TTLs, lifecycle). Improve the PORTAL_EXISTS redirect UX (prefill email on login page).
**Why login and signup stay separate**: In the ISP/telecom industry, login (password → OTP as 2FA) and signup (OTP as email verification → form) serve fundamentally different security purposes. Forcing them into one flow either adds friction for existing users or weakens the security model. Major ISP portals keep these flows separate.
---
## Current Structure
```
workflows/
├── verification-workflow.service.ts # OTP + account status detection
├── guest-eligibility-workflow.service.ts # Guest ISP check (no account created)
├── sf-completion-workflow.service.ts # Creates account (misleading name, 85% overlap)
├── new-customer-signup-workflow.service.ts # Creates account + eligibility (85% overlap)
├── whmcs-migration-workflow.service.ts # OTP-based WHMCS migration (active)
├── whmcs-link-workflow.service.ts # Password-based WHMCS migration (legacy, superseded)
├── login-otp-workflow.service.ts # Login OTP 2FA
├── password-workflow.service.ts # Password operations
├── get-started-coordinator.service.ts # Get-started routing
├── workflow-error.util.ts # Error classification
├── signup/ # Mixed: some active, some dead
│ ├── signup-account-resolver.service.ts # Dead (no active caller)
│ ├── signup-validation.service.ts # Dead (no active caller)
│ ├── signup-whmcs.service.ts # Active (markClientForCleanup used by CreatePortalUserStep)
│ ├── signup-user-creation.service.ts # Active (used by CreatePortalUserStep + WhmcsMigration)
│ ├── signup.types.ts # Dead (only used by dead validation service)
│ └── index.ts # Barrel
└── steps/ # Shared step services
├── resolve-salesforce-account.step.ts
├── create-whmcs-client.step.ts
├── create-portal-user.step.ts # Delegates to SignupUserCreationService
├── create-eligibility-case.step.ts
├── update-salesforce-flags.step.ts
├── generate-auth-result.step.ts
└── index.ts
```
## New Structure (after all 3 phases)
```
workflows/
├── verification-workflow.service.ts # OTP + account detection (unchanged)
├── guest-eligibility-workflow.service.ts # Guest ISP check (unchanged)
├── account-creation-workflow.service.ts # MERGED: replaces sf-completion + new-customer-signup
├── account-migration-workflow.service.ts # RENAMED: from whmcs-migration
├── login-otp-workflow.service.ts # Login OTP 2FA (Phase 3: uses shared OTP orchestration)
├── password-workflow.service.ts # Unchanged
├── get-started-coordinator.service.ts # Updated imports
├── workflow-error.util.ts # Unchanged
└── steps/ # Shared steps + moved services
├── resolve-salesforce-account.step.ts
├── create-whmcs-client.step.ts
├── create-portal-user.step.ts
├── create-eligibility-case.step.ts
├── update-salesforce-flags.step.ts
├── generate-auth-result.step.ts
├── portal-user-creation.service.ts # MOVED from signup/ (renamed)
├── whmcs-cleanup.service.ts # MOVED from signup/ (renamed, trimmed)
└── index.ts
```
**Deleted (all phases combined):**
- `sf-completion-workflow.service.ts` (merged, Phase 1)
- `new-customer-signup-workflow.service.ts` (merged, Phase 1)
- `whmcs-migration-workflow.service.ts` (renamed, Phase 1)
- `whmcs-link-workflow.service.ts` (removed, Phase 2)
- `signup/` entire directory (Phase 1)
---
## Phase 1: Merge signup workflows + cleanup
### Merged Workflow: AccountCreationWorkflowService
**API:**
```typescript
class AccountCreationWorkflowService {
execute(
request: CompleteAccountRequest | SignupWithEligibilityRequest,
options?: { withEligibility?: boolean }
): Promise<AuthResultInternal | SignupWithEligibilityResult>;
}
```
**Routing:**
- `POST /auth/get-started/complete-account``execute(request)` (no eligibility)
- `POST /auth/get-started/signup-with-eligibility``execute(request, { withEligibility: true })`
**Step Sequence:**
```
1. Validate + check existing accounts
2. Hash password
3. Resolve address + names (from request or session prefill)
4. Step 1: Resolve SF Account (CRITICAL)
5. Step 1.5: Write SF Address (DEGRADABLE)
6. Step 1.6: Create SF Contact — Birthdate + Sex__c (DEGRADABLE) ← NEW, fixes the bug
7. [if withEligibility] Step 2: Create Eligibility Case (DEGRADABLE)
8. Step 3: Create WHMCS Client (CRITICAL, rollback)
9. Step 4: Create Portal User (CRITICAL, rollback WHMCS on fail)
10. Step 5: Update SF Flags (DEGRADABLE)
11. Step 6: Generate Auth Result
12. [if withEligibility] Send welcome email with eligibility ref (DEGRADABLE)
13. Invalidate session
```
**Error handling difference between paths:**
- `completeAccount` path: throws exceptions directly (current sf-completion behavior)
- `signupWithEligibility` path: wraps in try/catch, returns `{ success: false, message, errorCategory }` using `classifyError()`
**Birthdate/Sex\_\_c Fix:**
After resolving the SF account, call `SalesforceAccountService.createContact()` on the PersonContact:
- `Birthdate``dateOfBirth` (YYYY-MM-DD)
- `Sex__c` → mapped from `gender` (`male``男性`, `female``女性`, `other``Other`)
This is degradable — account creation continues if it fails.
### Rename: AccountMigrationWorkflowService
Rename `WhmcsMigrationWorkflowService``AccountMigrationWorkflowService`. Refactor to use `CreatePortalUserStep` instead of calling `SignupUserCreationService` directly (reuse existing step, same as other workflows).
### Move signup/ services into steps/
| Old | New | Changes |
| ---------------------------------------- | --------------------------------------- | ------------------------------------------------------------------- |
| `signup/signup-user-creation.service.ts` | `steps/portal-user-creation.service.ts` | Rename class to `PortalUserCreationService` |
| `signup/signup-whmcs.service.ts` | `steps/whmcs-cleanup.service.ts` | Rename to `WhmcsCleanupService`, keep only `markClientForCleanup()` |
### Delete dead code
| File | Reason |
| ------------------------------------------- | ------------------------------------ |
| `signup/signup-account-resolver.service.ts` | No active caller |
| `signup/signup-validation.service.ts` | No active caller |
| `signup/signup.types.ts` | Only used by dead validation service |
| `signup/index.ts` | Directory being removed |
---
## Phase 2: Remove legacy migrate endpoint
### Delete WhmcsLinkWorkflowService
`WhmcsLinkWorkflowService` (`POST /auth/migrate`) is superseded by `AccountMigrationWorkflowService` (`POST /auth/get-started/migrate-whmcs-account`). The new flow uses OTP-based identity verification instead of WHMCS password validation — strictly more secure.
**Files to modify:**
- `apps/bff/src/modules/auth/auth.module.ts` — remove provider + import
- `apps/bff/src/modules/auth/application/auth-orchestrator.service.ts` — remove `linkWhmcsUser()` method + dependency
- `apps/bff/src/modules/auth/presentation/http/auth.controller.ts` — remove `POST /auth/migrate` endpoint
**File to delete:**
- `apps/bff/src/modules/auth/infra/workflows/whmcs-link-workflow.service.ts`
---
## Phase 3: Clean up OTP infrastructure
### Extract shared OTP email-sending pattern
Both `LoginOtpWorkflowService` and `VerificationWorkflowService` implement the same pattern:
1. Look up email template ID from config
2. If template exists, send via template with dynamic data
3. If no template, send fallback HTML
Extract this into a shared `OtpEmailService` (or method on existing `OtpService`).
### Improve PORTAL_EXISTS redirect UX
When get-started verification detects `PORTAL_EXISTS`, pass the verified email to the login page so it's prefilled. This reduces friction for users who accidentally start on the wrong page.
### What stays separate (and why)
- **Login OTP workflow**: OTP serves as 2FA after password validation
- **Verification OTP workflow**: OTP serves as email ownership proof before signup
- **LoginSessionService**: Simple (5 fields, 10min TTL, no prefill)
- **GetStartedSessionService**: Complex (10+ fields, 1hr TTL, prefill data, handoff tokens, idempotency locking)
These serve fundamentally different security purposes and have different data models. Forcing unification would create a confusing abstraction.
---
## Module Updates (all phases)
### GetStartedModule (Phase 1)
- Remove: `NewCustomerSignupWorkflowService`, `SfCompletionWorkflowService`, `WhmcsMigrationWorkflowService`
- Remove: `SignupAccountResolverService`, `SignupValidationService`, `SignupWhmcsService`, `SignupUserCreationService`
- Add: `AccountCreationWorkflowService`, `AccountMigrationWorkflowService`
- Add: `PortalUserCreationService`, `WhmcsCleanupService`
### GetStartedCoordinator (Phase 1)
- `completeAccount()``accountCreation.execute(request)`
- `signupWithEligibility()``accountCreation.execute(request, { withEligibility: true })`
- `migrateWhmcsAccount()``accountMigration.execute(request)`
### AuthModule (Phase 2)
- Remove: `WhmcsLinkWorkflowService` from providers and imports
### AuthOrchestrator (Phase 2)
- Remove: `linkWhmcsUser()` method and `WhmcsLinkWorkflowService` dependency
### AuthController (Phase 2)
- Remove: `POST /auth/migrate` endpoint