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

17 KiB

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: SignupUserCreationServicePortalUserCreationService
  • Update import: SignupWhmcsServiceWhmcsCleanupService 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: SignupWhmcsServiceWhmcsCleanupService
  • 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: signupUserCreationportalUserCreation, update usage on line 36.

Step 4: Update steps/index.ts barrel

Add at end:

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

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:

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

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 WhmcsMigrationWorkflowServiceAccountMigrationWorkflowService. 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.tsaccount-migration-workflow.service.ts:

  • Rename class: WhmcsMigrationWorkflowServiceAccountMigrationWorkflowService
  • 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

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

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

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

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

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

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

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

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:

@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

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:

// 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

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:

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女性
  • otherOther

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.