- Enhanced UnifiedExceptionFilter to handle ZodValidationException, extracting field errors for better user feedback. - Updated WhmcsRequestQueueService and WhmcsHttpClientService to use logger.warn for non-critical errors, improving log clarity. - Introduced Redis-backed caching in JapanPostFacade for postal code lookups, reducing redundant API calls. - Refactored address handling in AddressWriterService to deduplicate concurrent Japan Post lookups, optimizing API usage. - Improved error parsing in various forms and hooks to provide clearer error messages and field-specific feedback.
509 lines
17 KiB
Markdown
509 lines
17 KiB
Markdown
# 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.
|