Assist_Design/docs/plans/2026-03-03-signup-flow-simplification-plan.md
barsa 9b7cbcf78f refactor: improve error handling and caching in BFF services
- 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.
2026-03-03 17:27:50 +09:00

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.