# Auth Flow Simplification Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Simplify auth flows: merge duplicate signup workflows, fix missing SF Birthdate/Sex\_\_c, remove legacy migrate endpoint, clean up OTP infrastructure, delete dead code. **Architecture:** 3 phases (can stop after any phase). Phase 1 merges signup workflows + cleans up. Phase 2 removes legacy WHMCS migration. Phase 3 extracts shared OTP pattern. **Tech Stack:** NestJS 11, TypeScript, Prisma, Salesforce jsforce, WHMCS API, argon2, BullMQ **Design doc:** `docs/plans/2026-03-03-signup-flow-simplification-design.md` --- # Phase 1: Merge signup workflows + cleanup ## Task 1: Move signup shared services into steps/ Move `SignupUserCreationService` and `SignupWhmcsService` from `signup/` into `steps/`, renaming to remove the "Signup" prefix. Update all imports. **Files:** - Create: `apps/bff/src/modules/auth/infra/workflows/steps/portal-user-creation.service.ts` - Create: `apps/bff/src/modules/auth/infra/workflows/steps/whmcs-cleanup.service.ts` - Modify: `apps/bff/src/modules/auth/infra/workflows/steps/create-portal-user.step.ts` - Modify: `apps/bff/src/modules/auth/infra/workflows/steps/index.ts` - Modify: `apps/bff/src/modules/auth/infra/workflows/whmcs-migration-workflow.service.ts` - Modify: `apps/bff/src/modules/auth/get-started/get-started.module.ts` **Step 1: Create `portal-user-creation.service.ts`** Copy `signup/signup-user-creation.service.ts` to `steps/portal-user-creation.service.ts`: - Rename class: `SignupUserCreationService` → `PortalUserCreationService` - Update import: `SignupWhmcsService` → `WhmcsCleanupService` from `./whmcs-cleanup.service.js` **Step 2: Create `whmcs-cleanup.service.ts`** Copy `signup/signup-whmcs.service.ts` to `steps/whmcs-cleanup.service.ts`: - Rename class: `SignupWhmcsService` → `WhmcsCleanupService` - Keep only `markClientForCleanup()` method - Delete: `createClient()`, `checkExistingClient()`, `validateAddressData()` (unused by active code) - Remove unused imports: `SignupRequest`, `serializeWhmcsKeyValueMap`, `DomainHttpException`, `ErrorCode`, `ConfigService`, `MappingsService`, `WhmcsAccountDiscoveryService` - Constructor only needs: `WhmcsClientService` + `Logger` **Step 3: Update `create-portal-user.step.ts` import** ``` Line 3 — FROM: import { SignupUserCreationService } from "../signup/signup-user-creation.service.js"; Line 3 — TO: import { PortalUserCreationService } from "./portal-user-creation.service.js"; ``` Update constructor param: `signupUserCreation` → `portalUserCreation`, update usage on line 36. **Step 4: Update `steps/index.ts` barrel** Add at end: ```typescript export { PortalUserCreationService } from "./portal-user-creation.service.js"; export type { UserCreationParams, CreatedUserResult } from "./portal-user-creation.service.js"; export { WhmcsCleanupService } from "./whmcs-cleanup.service.js"; ``` **Step 5: Update `whmcs-migration-workflow.service.ts` import** ``` Line 24 — FROM: import { SignupUserCreationService } from "./signup/signup-user-creation.service.js"; Line 24 — TO: import { PortalUserCreationService } from "./steps/portal-user-creation.service.js"; ``` Update constructor param and usage accordingly. (Task 3 will refactor this further to use `CreatePortalUserStep`.) **Step 6: Update `get-started.module.ts`** - Remove imports: `SignupAccountResolverService`, `SignupValidationService`, `SignupWhmcsService`, `SignupUserCreationService` - Add imports: `PortalUserCreationService`, `WhmcsCleanupService` from `../infra/workflows/steps/index.js` - Remove from providers: `SignupAccountResolverService`, `SignupValidationService`, `SignupWhmcsService`, `SignupUserCreationService` - Add to providers: `PortalUserCreationService`, `WhmcsCleanupService` **Step 7: Run type-check** Run: `pnpm type-check` Expected: PASS **Step 8: Commit** ```bash git add apps/bff/src/modules/auth/infra/workflows/steps/portal-user-creation.service.ts \ apps/bff/src/modules/auth/infra/workflows/steps/whmcs-cleanup.service.ts \ apps/bff/src/modules/auth/infra/workflows/steps/create-portal-user.step.ts \ apps/bff/src/modules/auth/infra/workflows/steps/index.ts \ apps/bff/src/modules/auth/infra/workflows/whmcs-migration-workflow.service.ts \ apps/bff/src/modules/auth/get-started/get-started.module.ts git commit -m "refactor: move signup shared services into steps directory" ``` --- ## Task 2: Create merged AccountCreationWorkflowService Merge `SfCompletionWorkflowService` + `NewCustomerSignupWorkflowService` into one workflow. Fix Birthdate/Sex\_\_c gap. **Files:** - Create: `apps/bff/src/modules/auth/infra/workflows/account-creation-workflow.service.ts` **Step 1: Create the merged workflow** Constructor dependencies (union of both old workflows): - `ConfigService` (email template IDs, APP_BASE_URL) - `GetStartedSessionService` - `DistributedLockService` - `UsersService` - `EmailService` (welcome email in eligibility path) - `SalesforceAccountService` (for `createContact()` — Birthdate/Sex\_\_c) - `AddressWriterService` - `ResolveSalesforceAccountStep` - `CreateEligibilityCaseStep` - `CreateWhmcsClientStep` - `CreatePortalUserStep` - `UpdateSalesforceFlagsStep` - `GenerateAuthResultStep` - `Logger` Public API: ```typescript execute( request: CompleteAccountRequest | SignupWithEligibilityRequest, options?: { withEligibility?: boolean } ): Promise ``` Step sequence in `executeCreation()`: 1. Check existing accounts (Portal user duplicate check) 2. Hash password (argon2) 3. Resolve address + names (from request or session prefill — port `resolveAddress()` and `resolveNames()` from sf-completion) 4. **Step 1: Resolve SF Account** (CRITICAL) — `source` depends on `withEligibility` 5. **Step 1.5: Write SF Address** (DEGRADABLE via `safeOperation`) 6. **Step 1.6: Create SF Contact** (DEGRADABLE via `safeOperation`) — **NEW: fixes Birthdate/Sex\_\_c** - Call `salesforceAccountService.createContact({ accountId, firstName, lastName, email, phone, gender, dateOfBirth })` 7. **Step 2** (conditional): Create Eligibility Case (DEGRADABLE, only if `withEligibility`) 8. **Step 3: Create WHMCS Client** (CRITICAL) 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. (conditional) Send welcome email (DEGRADABLE, only if `withEligibility`) — port `sendWelcomeWithEligibilityEmail()` from new-customer-signup 13. Invalidate session Error handling per path: - `withEligibility=true`: wrap in try/catch, return `{ success: false, errorCategory, message }` using `classifyError()` (from `workflow-error.util.ts`) - `withEligibility=false` (default): let exceptions propagate directly (matching current sf-completion behavior) Session handling: - Use `acquireAndMarkAsUsed(sessionToken, withEligibility ? "signup_with_eligibility" : "complete_account")` - `isNewCustomer = !session.sfAccountId` (same logic as sf-completion) - For `isNewCustomer`: require firstName, lastName, address from request - For SF_UNMAPPED: use session prefill data as fallback Port private helpers from sf-completion: - `validateRequest()` — checks isNewCustomer needs firstName/lastName/address - `ensureNoExistingAccounts()` — Portal user duplicate check - `resolveAddress()` — from request or session - `resolveNames()` — from request or session **Step 2: Run type-check** Run: `pnpm type-check` Expected: PASS **Step 3: Commit** ```bash git add apps/bff/src/modules/auth/infra/workflows/account-creation-workflow.service.ts git commit -m "feat: add merged AccountCreationWorkflowService with Birthdate/Sex__c fix" ``` --- ## Task 3: Rename and refactor AccountMigrationWorkflowService Rename `WhmcsMigrationWorkflowService` → `AccountMigrationWorkflowService`. Refactor to use `CreatePortalUserStep` instead of `SignupUserCreationService` directly. **Files:** - Create: `apps/bff/src/modules/auth/infra/workflows/account-migration-workflow.service.ts` **Step 1: Create renamed + refactored file** Copy `whmcs-migration-workflow.service.ts` → `account-migration-workflow.service.ts`: - Rename class: `WhmcsMigrationWorkflowService` → `AccountMigrationWorkflowService` - Replace `PortalUserCreationService` (or `SignupUserCreationService` if Task 1 import not yet updated) with `CreatePortalUserStep` - Constructor: remove `userCreation: PortalUserCreationService`, add `portalUserStep: CreatePortalUserStep` - In `executeMigration()`: replace `this.userCreation.createUserWithMapping({...})` with `this.portalUserStep.execute({...})` - Update imports accordingly **Step 2: Run type-check** Run: `pnpm type-check` Expected: PASS **Step 3: Commit** ```bash git add apps/bff/src/modules/auth/infra/workflows/account-migration-workflow.service.ts git commit -m "refactor: rename WhmcsMigration to AccountMigration, use CreatePortalUserStep" ``` --- ## Task 4: Update coordinator and module Wire new services, remove old references. **Files:** - Modify: `apps/bff/src/modules/auth/infra/workflows/get-started-coordinator.service.ts` - Modify: `apps/bff/src/modules/auth/get-started/get-started.module.ts` **Step 1: Update coordinator** Imports: - Remove: `NewCustomerSignupWorkflowService`, `SfCompletionWorkflowService`, `WhmcsMigrationWorkflowService` - Add: `AccountCreationWorkflowService`, `AccountMigrationWorkflowService` Constructor: - Remove: `newCustomerSignup`, `sfCompletion`, `whmcsMigration` - Add: `accountCreation: AccountCreationWorkflowService`, `accountMigration: AccountMigrationWorkflowService` Methods: - `completeAccount()`: `return this.accountCreation.execute(request);` - `signupWithEligibility()`: `return this.accountCreation.execute(request, { withEligibility: true });` - `migrateWhmcsAccount()`: `return this.accountMigration.execute(request);` **Step 2: Update module providers** Imports: - Remove: `NewCustomerSignupWorkflowService`, `SfCompletionWorkflowService`, `WhmcsMigrationWorkflowService` - Add: `AccountCreationWorkflowService`, `AccountMigrationWorkflowService` Providers: - Remove: `NewCustomerSignupWorkflowService`, `SfCompletionWorkflowService`, `WhmcsMigrationWorkflowService` - Add: `AccountCreationWorkflowService`, `AccountMigrationWorkflowService` **Step 3: Run type-check** Run: `pnpm type-check` Expected: PASS **Step 4: Commit** ```bash git add apps/bff/src/modules/auth/infra/workflows/get-started-coordinator.service.ts \ apps/bff/src/modules/auth/get-started/get-started.module.ts git commit -m "refactor: wire AccountCreation and AccountMigration into coordinator and module" ``` --- ## Task 5: Delete old files **Step 1: Delete old workflow files** ```bash rm apps/bff/src/modules/auth/infra/workflows/sf-completion-workflow.service.ts rm apps/bff/src/modules/auth/infra/workflows/new-customer-signup-workflow.service.ts rm apps/bff/src/modules/auth/infra/workflows/whmcs-migration-workflow.service.ts ``` **Step 2: Delete entire signup/ directory** ```bash rm -rf apps/bff/src/modules/auth/infra/workflows/signup/ ``` **Step 3: Run type-check and tests** Run: `pnpm type-check && pnpm --filter @customer-portal/bff test` Expected: PASS **Step 4: Commit** ```bash git add -A git commit -m "chore: delete merged/deprecated signup workflow files and signup/ directory" ``` --- ## Task 6: Verify no stale references (Phase 1 checkpoint) **Step 1: Search for stale references in source code** Search `apps/` and `packages/` for: - `SfCompletionWorkflow`, `NewCustomerSignupWorkflow`, `WhmcsMigrationWorkflow` - `SignupAccountResolver`, `SignupValidation`, `SignupWhmcsService`, `SignupUserCreation` - `signup-account-resolver`, `signup-validation`, `signup-whmcs`, `signup-user-creation` Expected: No matches in `apps/` or `packages/` (docs/ matches are fine) **Step 2: Fix any remaining references** **Step 3: Run full type-check and tests** Run: `pnpm type-check && pnpm --filter @customer-portal/bff test` Expected: PASS **Step 4: Commit if fixes were needed** ```bash git add -A git commit -m "fix: update remaining references to old workflow service names" ``` --- # Phase 2: Remove legacy migrate endpoint ## Task 7: Remove WhmcsLinkWorkflowService and /auth/migrate Remove the legacy password-based WHMCS migration. All WHMCS migration now goes through get-started OTP-based flow. **Files:** - Delete: `apps/bff/src/modules/auth/infra/workflows/whmcs-link-workflow.service.ts` - Modify: `apps/bff/src/modules/auth/auth.module.ts` (remove provider + import) - Modify: `apps/bff/src/modules/auth/application/auth-orchestrator.service.ts` (remove `linkWhmcsUser()` + dependency) - Modify: `apps/bff/src/modules/auth/presentation/http/auth.controller.ts` (remove `POST /auth/migrate` endpoint) **Step 1: Update AuthOrchestrator** - Remove `linkWhmcsUser()` method - Remove `WhmcsLinkWorkflowService` from constructor injection - Remove import **Step 2: Update AuthController** - Remove the `POST /auth/migrate` endpoint handler - Remove related imports (request DTO, etc.) **Step 3: Update AuthModule** - Remove `WhmcsLinkWorkflowService` from providers - Remove import **Step 4: Delete the workflow file** ```bash rm apps/bff/src/modules/auth/infra/workflows/whmcs-link-workflow.service.ts ``` **Step 5: Search for stale references** Search for `WhmcsLinkWorkflow`, `linkWhmcsUser`, `/auth/migrate` in source code. **Step 6: Run type-check and tests** Run: `pnpm type-check && pnpm --filter @customer-portal/bff test` Expected: PASS **Step 7: Commit** ```bash git add -A git commit -m "refactor: remove legacy /auth/migrate endpoint and WhmcsLinkWorkflowService" ``` --- # Phase 3: Clean up OTP infrastructure ## Task 8: Extract shared OTP email-sending pattern Both `LoginOtpWorkflowService.sendOtpEmail()` and `VerificationWorkflowService.sendOtpEmail()` implement the same pattern: look up template ID from config, send via template or fallback HTML. **Files:** - Create: `apps/bff/src/modules/auth/infra/otp/otp-email.service.ts` - Modify: `apps/bff/src/modules/auth/infra/workflows/login-otp-workflow.service.ts` - Modify: `apps/bff/src/modules/auth/infra/workflows/verification-workflow.service.ts` - Modify: `apps/bff/src/modules/auth/otp/otp.module.ts` (add provider) **Step 1: Create `OtpEmailService`** Extract the shared email-sending pattern: ```typescript @Injectable() export class OtpEmailService { constructor( private readonly config: ConfigService, private readonly emailService: EmailService ) {} async sendOtpCode(email: string, code: string, context: "login" | "verification"): Promise { // Look up template ID based on context // Send via template or fallback HTML // Both current implementations do the same thing with different template IDs } } ``` **Step 2: Refactor LoginOtpWorkflowService** Replace inline `sendOtpEmail()` with `otpEmailService.sendOtpCode(email, code, 'login')`. **Step 3: Refactor VerificationWorkflowService** Replace inline `sendOtpEmail()` with `otpEmailService.sendOtpCode(email, code, 'verification')`. **Step 4: Update OtpModule** Add `OtpEmailService` to providers and exports. **Step 5: Run type-check and tests** Run: `pnpm type-check && pnpm --filter @customer-portal/bff test` Expected: PASS **Step 6: Commit** ```bash git add -A git commit -m "refactor: extract shared OTP email service from login and verification workflows" ``` --- ## Task 9: Improve PORTAL_EXISTS redirect UX When get-started verification detects `PORTAL_EXISTS`, the frontend redirects to login without context. Improve this by passing the verified email so it can be prefilled. **Files:** - Modify: `apps/portal/src/features/get-started/machines/get-started.machine.ts` - Potentially: login page component (to accept prefilled email from query param or state) **Step 1: Update get-started machine** When `accountStatus === PORTAL_EXISTS`, redirect to login with email as query param: ```typescript // Instead of: router.push('/login') // Do: router.push(`/login?email=${encodeURIComponent(verifiedEmail)}`) ``` **Step 2: Update login page to accept prefilled email** Read `email` from URL query params and prefill the email input field. **Step 3: Test manually** 1. Go to get-started with an existing account email 2. Verify via OTP 3. Should redirect to login with email prefilled **Step 4: Commit** ```bash git add -A git commit -m "feat: prefill email on login page when redirected from get-started" ``` --- ## Notes ### Error handling difference between merged paths The `completeAccount` endpoint throws exceptions directly. The `signupWithEligibility` endpoint returns `{ success: false, message, errorCategory }`. The merged workflow preserves this: ```typescript if (options?.withEligibility) { try { return await this.executeCreation(...); } catch (error) { const classified = classifyError(error); return { success: false, errorCategory: classified.errorCategory, message: classified.message }; } } else { return await this.executeCreation(...); // exceptions propagate } ``` ### createContact() for Birthdate/Sex\_\_c New degradable step using existing `SalesforceAccountService.createContact()`. Gender mapping: - `male` → `男性` - `female` → `女性` - `other` → `Other` ### Sessions stay separate Login sessions (`LoginSessionService`) and get-started sessions (`GetStartedSessionService`) have fundamentally different data models, TTLs, and lifecycle requirements. Only the OTP email-sending pattern is worth extracting.