# 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<AuthResultInternal | SignupWithEligibilityResult>
```

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<void> {
    // 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.
