Skip to main content

User Registration

Module: Authentication & Authorization
Document Type: Business & Technical Specification
Version: 1.0
Last Updated: June 2025


Table of Contents

  1. Business Requirements
  2. Business Workflow
  3. Technical Workflow
  4. Data Model Relationships
  5. Business Rules & Constraints
  6. Frontend State Management
  7. Technical Considerations
  8. Testing Strategy
  9. Monitoring & Analytics
  10. Future Enhancements

1. Business Requirements

1.1 Overview

The user registration workflow is a secure, 3-step mobile-first onboarding process that enables new users to create accounts using SMS-based OTP verification.

1.2 Business Objectives

  • Security: Verify user identity through mobile number ownership
  • Fraud Prevention: Prevent fake account creation with rate limiting
  • User Experience: Simple, intuitive registration process
  • Data Quality: Collect verified contact information
  • Compliance: Maintain audit trail of registration activities

1.3 User Personas

  • Primary: New users accessing the application for the first time
  • Secondary: Existing users who need to re-register due to account issues

1.4 Success Criteria

  • Registration completion rate > 85%
  • OTP delivery success rate > 95%
  • Average registration time < 3 minutes
  • User drop-off rate < 15% per step

2. Business Workflow

2.1 Step 1: Mobile Number Entry (/send-otp)

User Journey

  1. User lands on registration page
  2. User selects country/dial code from dropdown
  3. User enters mobile number
  4. System validates number format
  5. User clicks "Send OTP" button
  6. System sends SMS OTP to mobile number
  7. User receives confirmation and proceeds to next step

Business Logic

  • Validate mobile number format (numbers only)
  • Check if mobile number already exists in system
  • Create UserRegistration record with stage MOBILE_NUMBER_ENTERED
  • Generate 6-digit OTP with 15-minute expiry
  • Send OTP via SMS gateway
  • Update stage to OTP_SENT
  • Track send attempts (max 5 per day)

Success Criteria

  • Valid mobile number format
  • SMS successfully delivered
  • User proceeds to OTP verification

Error Scenarios

  • Invalid mobile number format
  • Mobile number already registered
  • SMS delivery failure
  • Maximum send attempts exceeded

2.2 Step 2: OTP Verification (/verify-otp)

User Journey

  1. User enters 6-digit OTP received via SMS
  2. System validates OTP in real-time
  3. User clicks "Verify" button
  4. System confirms OTP validity
  5. User proceeds to profile completion

Business Logic

  • Validate OTP against stored value
  • Check OTP expiry (15 minutes from generation)
  • Track verification attempts (max 5 per OTP)
  • Update stage to OTP_VERIFIED upon success
  • Allow OTP resend with new attempts counter

Success Criteria

  • Correct OTP entered within expiry time
  • User identity verified successfully
  • Proceed to profile completion

Error Scenarios

  • Incorrect OTP entered
  • OTP expired (show resend option)
  • Maximum verification attempts exceeded
  • Network connectivity issues

2.3 Step 3: Profile Completion (/user-name)

User Journey

  1. User enters full name
  2. System validates name format
  3. User clicks "Complete Registration"
  4. System creates user account
  5. User receives welcome message
  6. User redirected to main application

Business Logic

  • Validate name (required, 1-100 characters)
  • Create User record in database
  • Create UserContact record (primary, verified mobile)
  • Link UserRegistration to User via userId
  • Update stage to USER_CREATED
  • Complete registration process

Success Criteria

  • Valid name provided
  • User account created successfully
  • Mobile contact marked as primary and verified
  • Registration workflow completed

Error Scenarios

  • Invalid name format
  • Database creation errors
  • System connectivity issues

3. Technical Workflow

3.1 Technology Stack

  • Frontend: React, TanStack Router, TanStack Form, Apollo Client
  • Backend: NestJS, GraphQL, Prisma ORM
  • Database: PostgreSQL
  • SMS Gateway: Third-party SMS service
  • Authentication: JWT tokens (post-registration)

3.2 Database State Machine

UserRegistration States

MOBILE_NUMBER_ENTERED → OTP_SENT → OTP_VERIFIED → USER_CREATED

State Transitions

  1. Initial State: Record created with MOBILE_NUMBER_ENTERED
  2. OTP Dispatch: Updated to OTP_SENT after SMS sent
  3. OTP Success: Updated to OTP_VERIFIED after validation
  4. Account Creation: Updated to USER_CREATED after User record created

3.3 API Operations

Mutation 1: Send OTP

mutation SendOTP($dialCode: String!, $mobileNumber: String!) {
sendOTP(dialCode: $dialCode, mobileNumber: $mobileNumber) {
success
message
registrationId
otpExpiresAt
remainingAttempts
}
}

Backend Process Flow:

  1. Validate input parameters (dialCode, mobileNumber)
  2. Check for existing UserRegistration with same mobile
  3. Validate send attempts (max 5 per day)
  4. Create/Update UserRegistration record
    • Set stage: MOBILE_NUMBER_ENTERED
    • Increment otpSendAttempts
    • Set lastOtpSentAt timestamp
  5. Generate 6-digit numeric OTP
  6. Send SMS via gateway integration
  7. Update record with OTP details
    • Set stage: OTP_SENT
    • Set lastOtpExpiresAt (now + 15 minutes)
    • Set otpDeliveryMethod: SMS
    • Set otpDeliveryStatus: SENT/PENDING
  8. Return success response with expiry time

Database Changes:

-- Insert or Update UserRegistration
INSERT INTO user_registrations (
dial_code, mobile_number, stage, otp_send_attempts,
last_otp_sent_at, last_otp_expires_at, otp_delivery_method
) VALUES (
'+91', '9876543210', 'mobile_number_entered', 1,
NOW(), NOW() + INTERVAL '15 minutes', 'sms'
)
ON CONFLICT (dial_code, mobile_number)
DO UPDATE SET
otp_send_attempts = user_registrations.otp_send_attempts + 1,
last_otp_sent_at = NOW(),
stage = 'otp_sent';

Mutation 2: Verify OTP

mutation VerifyOTP(
$dialCode: String!
$mobileNumber: String!
$otpCode: String!
) {
verifyOTP(
dialCode: $dialCode
mobileNumber: $mobileNumber
otpCode: $otpCode
) {
success
message
isVerified
remainingAttempts
}
}

Backend Process Flow:

  1. Find UserRegistration by dialCode + mobileNumber
  2. Validate current stage is OTP_SENT
  3. Check OTP expiry (lastOtpExpiresAt > now)
  4. Validate verification attempts (max 5 per OTP session)
  5. Compare provided OTP with stored value
  6. Update verification tracking
    • Increment otpVerifyAttempts
    • Set lastOtpVerifiedAt (if successful)
  7. Update stage to OTP_VERIFIED (if successful)
  8. Return verification status

Database Changes:

-- Update UserRegistration after verification
UPDATE user_registrations
SET
otp_verify_attempts = otp_verify_attempts + 1,
last_otp_verified_at = CASE WHEN otp_valid THEN NOW() ELSE NULL END,
stage = CASE WHEN otp_valid THEN 'otp_verified' ELSE stage END
WHERE dial_code = '+91' AND mobile_number = '9876543210';

Mutation 3: Complete Registration

mutation CompleteRegistration(
$dialCode: String!
$mobileNumber: String!
$name: String!
) {
completeRegistration(
dialCode: $dialCode
mobileNumber: $mobileNumber
name: $name
) {
success
message
user {
id
publicId
name
nickname
}
}
}

Backend Process Flow:

  1. Find UserRegistration by dialCode + mobileNumber
  2. Validate current stage is OTP_VERIFIED
  3. Validate name format (1-100 characters, required)
  4. Begin database transaction
  5. Create User record
    • Generate publicId (CUID)
    • Set name and nickname
    • Set timestamps
  6. Create UserContact record
    • Link to User via userId
    • Set contactType: MOBILE
    • Set isPrimary: true, isVerified: true
    • Copy dialCode and mobileNumber
  7. Update UserRegistration
    • Link to User via userId
    • Set stage: USER_CREATED
    • Set enteredName
  8. Commit transaction
  9. Return user details

Database Changes:

BEGIN TRANSACTION;

-- Create User
INSERT INTO users (public_id, name, nickname, created_at, updated_at)
VALUES ('clx123abc', 'John Doe', 'John', NOW(), NOW())
RETURNING id;

-- Create UserContact
INSERT INTO user_contacts (
public_id, user_id, contact_type, dial_code, contact_value,
is_primary, is_verified, created_at, updated_at
) VALUES (
'clx456def', user_id, 'mobile', '+91', '9876543210',
true, true, NOW(), NOW()
);

-- Update UserRegistration
UPDATE user_registrations
SET
user_id = user_id,
entered_name = 'John Doe',
stage = 'user_created',
updated_at = NOW()
WHERE dial_code = '+91' AND mobile_number = '9876543210';

COMMIT;

4. Data Model Relationships

4.1 Registration Flow Data Structure

UserRegistration Table

interface UserRegistration {
id: number;
publicId: string; // External identifier

// Phone Identity
dialCode: string; // "+91"
mobileNumber: string; // "9876543210"

// Registration Stage
stage: RegistrationStage; // State machine value

// OTP Tracking
otpSendAttempts: number; // 0-5
otpVerifyAttempts: number; // 0-5
lastOtpSentAt: Date | null; // Timestamp
lastOtpVerifiedAt: Date | null; // Timestamp
lastOtpExpiresAt: Date | null; // 15 minutes from sent

// OTP Delivery
otpDeliveryMethod: OtpDeliveryMethod; // SMS
otpDeliveryStatus: OtpDeliveryStatus; // SENT/DELIVERED/FAILED

// Profile Data
enteredName: string | null; // "John Doe"
userId: number | null; // Link to User after creation

// Metadata
deviceInfo: JSON | null; // Device fingerprinting
createdAt: Date;
updatedAt: Date;
}

User Table (Created in Step 3)

interface User {
id: number;
publicId: string; // External identifier
name: string; // "John Doe"
nickname: string; // "John"
fatherName: string | null;
careOf: string | null;
dateOfBirth: Date | null;
gender: Gender | null;
createdAt: Date;
updatedAt: Date;
}

UserContact Table (Created in Step 3)

interface UserContact {
id: bigint;
publicId: string;

// Contact Information
contactType: ContactType; // MOBILE
dialCode: string; // "+91"
contactValue: string; // "9876543210"
contactLabel: string | null; // "Personal"

// Status Flags
isPrimary: boolean; // true (first mobile)
isVerified: boolean; // true (from registration)
isBlocked: boolean; // false
isDeleted: boolean; // false

// Verification Tracking
verifiedAt: Date; // Set during registration

// Communication Preferences
allowMarketing: boolean; // false
allowNotifications: boolean; // true
allowTransactional: boolean; // true

// Relationships
userId: number; // Links to User

createdAt: Date;
updatedAt: Date;
}

4.2 Data Flow Diagram

Step 1: Mobile Entry
┌─────────────────┐
│ UserRegistration│
│ stage: ENTERED │
│ mobile: +919876 │
│ attempts: 1 │
└─────────────────┘

Step 2: OTP Sent
┌─────────────────┐
│ UserRegistration│
│ stage: OTP_SENT │
│ expiry: +15 min │
│ delivery: SMS │
└─────────────────┘

Step 3: OTP Verified
┌─────────────────┐
│ UserRegistration│
│ stage: VERIFIED │
│ verified_at: now│
└─────────────────┘

Step 4: User Created
┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐
│ UserRegistration│───▶│ User │◀───│ UserContact │
│ stage: CREATED │ │ name: John │ │ type: MOBILE │
│ user_id: 123 │ │ id: 123 │ │ primary: true │
│ name: John Doe │ └─────────────┘ │ verified: true │
└─────────────────┘ └─────────────────┘

5. Business Rules & Constraints

5.1 Input Validation Rules

Mobile Number Validation

  • Format: Numbers only (no spaces, dashes, or special characters)
  • Length: Varies by country (will be implemented later)
  • Required: Both dial code and mobile number mandatory
  • Uniqueness: One registration per mobile number at a time

OTP Validation

  • Format: Exactly 6 digits (000000-999999)
  • Expiry: 15 minutes from generation
  • Usage: Single use only (new OTP required for retry)
  • Case Sensitivity: Not applicable (numeric only)

Name Validation

  • Length: 1-100 characters
  • Characters: Letters, spaces, hyphens, apostrophes allowed
  • Required: Cannot be empty or whitespace only
  • Format: Proper capitalization recommended but not enforced

5.2 Security & Rate Limiting

OTP Send Limits

  • Per Mobile: Maximum 5 attempts per 24-hour period
  • Per IP: Maximum 10 attempts per hour (DoS protection)
  • Per Session: Maximum 3 consecutive failures before cooldown
  • Cooldown Period: 15 minutes after max attempts exceeded

OTP Verification Limits

  • Per OTP: Maximum 5 verification attempts per OTP session
  • Lockout: Account temporarily locked after 5 failed verifications
  • Expiry: OTP expires 15 minutes after generation
  • Reuse: Each OTP can only be verified once successfully

Session Security

  • Registration Session: 24-hour expiry from first step
  • Data Persistence: Mobile number stored only during active session
  • Cross-Device: Registration must be completed on same device/browser
  • Cleanup: Expired registrations automatically cleaned up

5.3 Business Logic Constraints

Registration State Machine

  • Linear Progression: Must complete steps in order (1→2→3)
  • No Skipping: Cannot skip OTP verification step
  • State Validation: Each API call validates current state
  • Rollback: Failed user creation rolls back to OTP_VERIFIED state

Data Integrity Rules

  • Atomic Operations: User and UserContact creation is transactional
  • Referential Integrity: All foreign keys properly maintained
  • Duplicate Prevention: Unique constraints on mobile numbers
  • Audit Trail: All state changes logged with timestamps

5.4 Error Handling Rules

User-Facing Errors

  • Clear Messages: Non-technical language for all errors
  • Actionable Guidance: Tell user what to do next
  • No Sensitive Data: Never expose internal error details
  • Graceful Degradation: Fallback options when possible

System Error Recovery

  • Retry Logic: Automatic retry for transient failures
  • Circuit Breaker: Stop attempts after repeated failures
  • Logging: All errors logged for debugging
  • Alerting: Critical errors trigger immediate notifications

6. Frontend State Management

6.1 Route Structure & Navigation

Route Definitions

// TanStack Router Configuration
const routes = {
sendOtp: '/send-otp', // Step 1: Mobile number entry
verifyOtp: '/verify-otp', // Step 2: OTP verification
userName: '/user-name' // Step 3: Profile completion
}

// Navigation Flow
/send-otp → /verify-otp → /user-name → /dashboard

Route Parameters & State Transfer

// Method 1: URL Search Params
/verify-otp?dialCode=%2B91&mobile=9876543210

// Method 2: Navigation State (Preferred)
navigate('/verify-otp', {
state: {
dialCode: '+91',
mobileNumber: '9876543210',
otpExpiresAt: '2025-06-16T10:15:00Z'
}
})

// Method 3: Session Storage (Fallback)
sessionStorage.setItem('registrationData', JSON.stringify({
dialCode: '+91',
mobileNumber: '9876543210'
}))

6.2 Form State Management with TanStack Form

Step 1: Mobile Number Form

const sendOtpForm = useForm({
defaultValues: {
dialCode: "+91",
mobileNumber: "",
},
validators: {
mobileNumber: (value) => {
if (!value) return "Mobile number is required";
if (!/^\d+$/.test(value)) return "Only numbers allowed";
if (value.length < 10) return "Mobile number too short";
return undefined;
},
},
onSubmit: async (values) => {
const result = await sendOtpMutation({
variables: values,
});
if (result.data?.sendOTP.success) {
navigate("/verify-otp", { state: values });
}
},
});

Step 2: OTP Verification Form

const verifyOtpForm = useForm({
defaultValues: {
otpCode: "",
},
validators: {
otpCode: (value) => {
if (!value) return "OTP is required";
if (!/^\d{6}$/.test(value)) return "OTP must be 6 digits";
return undefined;
},
},
onSubmit: async (values) => {
const { dialCode, mobileNumber } = getRegistrationState();
const result = await verifyOtpMutation({
variables: { ...values, dialCode, mobileNumber },
});
if (result.data?.verifyOTP.success) {
navigate("/user-name", { state: { dialCode, mobileNumber } });
}
},
});

Step 3: User Name Form

const completeRegistrationForm = useForm({
defaultValues: {
name: "",
},
validators: {
name: (value) => {
if (!value?.trim()) return "Name is required";
if (value.length > 100) return "Name too long";
return undefined;
},
},
onSubmit: async (values) => {
const { dialCode, mobileNumber } = getRegistrationState();
const result = await completeRegistrationMutation({
variables: { ...values, dialCode, mobileNumber },
});
if (result.data?.completeRegistration.success) {
// Clear registration state
clearRegistrationState();
// Redirect to main app
navigate("/dashboard");
}
},
});

6.3 Apollo Client State Management

GraphQL Operations

// Mutation Definitions
const SEND_OTP = gql`
mutation SendOTP($dialCode: String!, $mobileNumber: String!) {
sendOTP(dialCode: $dialCode, mobileNumber: $mobileNumber) {
success
message
registrationId
otpExpiresAt
remainingAttempts
}
}
`;

const VERIFY_OTP = gql`
mutation VerifyOTP(
$dialCode: String!
$mobileNumber: String!
$otpCode: String!
) {
verifyOTP(
dialCode: $dialCode
mobileNumber: $mobileNumber
otpCode: $otpCode
) {
success
message
isVerified
remainingAttempts
}
}
`;

const COMPLETE_REGISTRATION = gql`
mutation CompleteRegistration(
$dialCode: String!
$mobileNumber: String!
$name: String!
) {
completeRegistration(
dialCode: $dialCode
mobileNumber: $mobileNumber
name: $name
) {
success
message
user {
id
publicId
name
nickname
}
}
}
`;

Hook Implementation

// Custom Hooks for Registration
export const useRegistrationFlow = () => {
const [sendOtp, { loading: sendingOtp }] = useMutation(SEND_OTP);
const [verifyOtp, { loading: verifyingOtp }] = useMutation(VERIFY_OTP);
const [completeRegistration, { loading: completing }] = useMutation(
COMPLETE_REGISTRATION
);

const sendOtpHandler = async (dialCode: string, mobileNumber: string) => {
try {
const result = await sendOtp({
variables: { dialCode, mobileNumber },
errorPolicy: "all",
});
return result.data?.sendOTP;
} catch (error) {
console.error("Send OTP error:", error);
throw error;
}
};

return {
sendOtpHandler,
verifyOtpHandler,
completeRegistrationHandler,
loading: sendingOtp || verifyingOtp || completing,
};
};

7. Technical Considerations

7.1 Frontend Architecture

Component Structure

src/features/registration/
├── components/
│ ├── SendOtpForm.tsx # Step 1 component
│ ├── VerifyOtpForm.tsx # Step 2 component
│ ├── UserNameForm.tsx # Step 3 component
│ ├── OtpTimer.tsx # Countdown timer
│ ├── ResendOtpButton.tsx # Resend functionality
│ └── RegistrationProgress.tsx # Progress indicator
├── hooks/
│ ├── useRegistrationFlow.ts # Main registration logic
│ ├── useOtpTimer.ts # Timer management
│ └── useRegistrationState.ts # State persistence
├── pages/
│ ├── SendOtpPage.tsx # Route: /send-otp
│ ├── VerifyOtpPage.tsx # Route: /verify-otp
│ └── UserNamePage.tsx # Route: /user-name
├── utils/
│ ├── validation.ts # Form validation rules
│ ├── formatting.ts # Phone number formatting
│ └── storage.ts # Session state management
└── types/
└── registration.types.ts # TypeScript definitions

Type Safety

// Registration Types
export interface RegistrationState {
dialCode: string;
mobileNumber: string;
otpExpiresAt?: string;
step: "mobile" | "otp" | "profile";
}

export interface SendOtpRequest {
dialCode: string;
mobileNumber: string;
}

export interface SendOtpResponse {
success: boolean;
message: string;
registrationId: string;
otpExpiresAt: string;
remainingAttempts: number;
}

export interface VerifyOtpRequest {
dialCode: string;
mobileNumber: string;
otpCode: string;
}

export interface CompleteRegistrationRequest {
dialCode: string;
mobileNumber: string;
name: string;
}

7.2 Backend Architecture

NestJS Module Structure

src/registration/
├── dto/
│ ├── send-otp.dto.ts # Input validation
│ ├── verify-otp.dto.ts # Input validation
│ └── complete-registration.dto.ts
├── entities/
│ ├── user-registration.entity.ts
│ ├── user.entity.ts
│ └── user-contact.entity.ts
├── services/
│ ├── registration.service.ts # Business logic
│ ├── otp.service.ts # OTP generation/validation
│ └── sms.service.ts # SMS gateway integration
├── resolvers/
│ └── registration.resolver.ts # GraphQL endpoints
├── guards/
│ └── rate-limit.guard.ts # Rate limiting
└── registration.module.ts

Service Implementation

@Injectable()
export class RegistrationService {
constructor(
private prisma: PrismaService,
private otpService: OtpService,
private smsService: SmsService
) {}

async sendOtp(dialCode: string, mobileNumber: string) {
// Validate rate limits
await this.validateSendAttempts(dialCode, mobileNumber);

// Create or update registration record
const registration = await this.createOrUpdateRegistration(
dialCode,
mobileNumber
);

// Generate and send OTP
const otp = this.otpService.generate();
await this.smsService.send(dialCode + mobileNumber, otp);

// Update registration with OTP details
await this.updateRegistrationWithOtp(registration.id, otp);

return {
success: true,
message: "OTP sent successfully",
otpExpiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
remainingAttempts: 5 - registration.otpSendAttempts,
};
}

async verifyOtp(dialCode: string, mobileNumber: string, otpCode: string) {
const registration = await this.findRegistration(dialCode, mobileNumber);

// Validate OTP
const isValid = await this.otpService.verify(registration, otpCode);

if (isValid) {
await this.updateRegistrationStage(registration.id, "OTP_VERIFIED");
return { success: true, message: "OTP verified successfully" };
} else {
await this.incrementVerifyAttempts(registration.id);
return {
success: false,
message: "Invalid OTP",
remainingAttempts: 5 - registration.otpVerifyAttempts - 1,
};
}
}

async completeRegistration(
dialCode: string,
mobileNumber: string,
name: string
) {
return await this.prisma.$transaction(async (prisma) => {
const registration = await this.findVerifiedRegistration(
dialCode,
mobileNumber
);

// Create user
const user = await prisma.user.create({
data: {
name,
nickname: name.split(" ")[0],
publicId: cuid(),
},
});

// Create user contact
await prisma.userContact.create({
data: {
userId: user.id,
contactType: "MOBILE",
dialCode,
contactValue: mobileNumber,
isPrimary: true,
isVerified: true,
publicId: cuid(),
},
});

// Update registration
await prisma.userRegistration.update({
where: { id: registration.id },
data: {
userId: user.id,
enteredName: name,
stage: "USER_CREATED",
},
});

return {
success: true,
message: "Registration completed successfully",
user,
};
});
}
}

7.3 Performance Optimizations

Frontend Optimizations

  • Code Splitting: Lazy load registration pages
  • Form Debouncing: Debounce validation calls
  • Apollo Caching: Configure appropriate cache policies
  • Bundle Optimization: Tree shake unused code

Backend Optimizations

  • Database Indexing: Index on mobile number and timestamps
  • Connection Pooling: Optimize database connections
  • Rate Limiting: Implement at multiple levels
  • Caching: Cache OTP validation results temporarily

User Registration Workflow - Business & Technical Specification

Module: Authentication & Authorization
Document Type: Business & Technical Specification
Version: 1.0
Last Updated: June 2025


Table of Contents

  1. Business Requirements
  2. Business Workflow
  3. Technical Workflow
  4. Data Model Relationships
  5. Business Rules & Constraints
  6. Frontend State Management
  7. Technical Considerations
  8. Testing Strategy
  9. Monitoring & Analytics
  10. Future Enhancements

1. Business Requirements

1.1 Overview

The user registration workflow is a secure, 3-step mobile-first onboarding process that enables new users to create accounts using SMS-based OTP verification.

1.2 Business Objectives

  • Security: Verify user identity through mobile number ownership
  • Fraud Prevention: Prevent fake account creation with rate limiting
  • User Experience: Simple, intuitive registration process
  • Data Quality: Collect verified contact information
  • Compliance: Maintain audit trail of registration activities

1.3 User Personas

  • Primary: New users accessing the application for the first time
  • Secondary: Existing users who need to re-register due to account issues

1.4 Success Criteria

  • Registration completion rate > 85%
  • OTP delivery success rate > 95%
  • Average registration time < 3 minutes
  • User drop-off rate < 15% per step

2. Business Workflow

2.1 Step 1: Mobile Number Entry (/send-otp)

User Journey

  1. User lands on registration page
  2. User selects country/dial code from dropdown
  3. User enters mobile number
  4. System validates number format
  5. User clicks "Send OTP" button
  6. System sends SMS OTP to mobile number
  7. User receives confirmation and proceeds to next step

Business Logic

  • Validate mobile number format (numbers only)
  • Check if mobile number already exists in system
  • Create UserRegistration record with stage MOBILE_NUMBER_ENTERED
  • Generate 6-digit OTP with 15-minute expiry
  • Send OTP via SMS gateway
  • Update stage to OTP_SENT
  • Track send attempts (max 5 per day)

Success Criteria

  • Valid mobile number format
  • SMS successfully delivered
  • User proceeds to OTP verification

Error Scenarios

  • Invalid mobile number format
  • Mobile number already registered
  • SMS delivery failure
  • Maximum send attempts exceeded

2.2 Step 2: OTP Verification (/verify-otp)

User Journey

  1. User enters 6-digit OTP received via SMS
  2. System validates OTP in real-time
  3. User clicks "Verify" button
  4. System confirms OTP validity
  5. User proceeds to profile completion

Business Logic

  • Validate OTP against stored value
  • Check OTP expiry (15 minutes from generation)
  • Track verification attempts (max 5 per OTP)
  • Update stage to OTP_VERIFIED upon success
  • Allow OTP resend with new attempts counter

Success Criteria

  • Correct OTP entered within expiry time
  • User identity verified successfully
  • Proceed to profile completion

Error Scenarios

  • Incorrect OTP entered
  • OTP expired (show resend option)
  • Maximum verification attempts exceeded
  • Network connectivity issues

2.3 Step 3: Profile Completion (/user-name)

User Journey

  1. User enters full name
  2. System validates name format
  3. User clicks "Complete Registration"
  4. System creates user account
  5. User receives welcome message
  6. User redirected to main application

Business Logic

  • Validate name (required, 1-100 characters)
  • Create User record in database
  • Create UserContact record (primary, verified mobile)
  • Link UserRegistration to User via userId
  • Update stage to USER_CREATED
  • Complete registration process

Success Criteria

  • Valid name provided
  • User account created successfully
  • Mobile contact marked as primary and verified
  • Registration workflow completed

Error Scenarios

  • Invalid name format
  • Database creation errors
  • System connectivity issues

3. Technical Workflow

3.1 Technology Stack

  • Frontend: React, TanStack Router, TanStack Form, Apollo Client
  • Backend: NestJS, GraphQL, Prisma ORM
  • Database: PostgreSQL
  • SMS Gateway: Third-party SMS service
  • Authentication: JWT tokens (post-registration)

3.2 Database State Machine

UserRegistration States

MOBILE_NUMBER_ENTERED → OTP_SENT → OTP_VERIFIED → USER_CREATED

State Transitions

  1. Initial State: Record created with MOBILE_NUMBER_ENTERED
  2. OTP Dispatch: Updated to OTP_SENT after SMS sent
  3. OTP Success: Updated to OTP_VERIFIED after validation
  4. Account Creation: Updated to USER_CREATED after User record created

3.3 API Operations

Mutation 1: Send OTP

mutation SendOTP($dialCode: String!, $mobileNumber: String!) {
sendOTP(dialCode: $dialCode, mobileNumber: $mobileNumber) {
success
message
registrationId
otpExpiresAt
remainingAttempts
}
}

Backend Process Flow:

  1. Validate input parameters (dialCode, mobileNumber)
  2. Check for existing UserRegistration with same mobile
  3. Validate send attempts (max 5 per day)
  4. Create/Update UserRegistration record
    • Set stage: MOBILE_NUMBER_ENTERED
    • Increment otpSendAttempts
    • Set lastOtpSentAt timestamp
  5. Generate 6-digit numeric OTP
  6. Send SMS via gateway integration
  7. Update record with OTP details
    • Set stage: OTP_SENT
    • Set lastOtpExpiresAt (now + 15 minutes)
    • Set otpDeliveryMethod: SMS
    • Set otpDeliveryStatus: SENT/PENDING
  8. Return success response with expiry time

Database Changes:

-- Insert or Update UserRegistration
INSERT INTO user_registrations (
dial_code, mobile_number, stage, otp_send_attempts,
last_otp_sent_at, last_otp_expires_at, otp_delivery_method
) VALUES (
'+91', '9876543210', 'mobile_number_entered', 1,
NOW(), NOW() + INTERVAL '15 minutes', 'sms'
)
ON CONFLICT (dial_code, mobile_number)
DO UPDATE SET
otp_send_attempts = user_registrations.otp_send_attempts + 1,
last_otp_sent_at = NOW(),
stage = 'otp_sent';

Mutation 2: Verify OTP

mutation VerifyOTP(
$dialCode: String!
$mobileNumber: String!
$otpCode: String!
) {
verifyOTP(
dialCode: $dialCode
mobileNumber: $mobileNumber
otpCode: $otpCode
) {
success
message
isVerified
remainingAttempts
}
}

Backend Process Flow:

  1. Find UserRegistration by dialCode + mobileNumber
  2. Validate current stage is OTP_SENT
  3. Check OTP expiry (lastOtpExpiresAt > now)
  4. Validate verification attempts (max 5 per OTP session)
  5. Compare provided OTP with stored value
  6. Update verification tracking
    • Increment otpVerifyAttempts
    • Set lastOtpVerifiedAt (if successful)
  7. Update stage to OTP_VERIFIED (if successful)
  8. Return verification status

Database Changes:

-- Update UserRegistration after verification
UPDATE user_registrations
SET
otp_verify_attempts = otp_verify_attempts + 1,
last_otp_verified_at = CASE WHEN otp_valid THEN NOW() ELSE NULL END,
stage = CASE WHEN otp_valid THEN 'otp_verified' ELSE stage END
WHERE dial_code = '+91' AND mobile_number = '9876543210';

Mutation 3: Complete Registration

mutation CompleteRegistration(
$dialCode: String!
$mobileNumber: String!
$name: String!
) {
completeRegistration(
dialCode: $dialCode
mobileNumber: $mobileNumber
name: $name
) {
success
message
user {
id
publicId
name
nickname
}
}
}

Backend Process Flow:

  1. Find UserRegistration by dialCode + mobileNumber
  2. Validate current stage is OTP_VERIFIED
  3. Validate name format (1-100 characters, required)
  4. Begin database transaction
  5. Create User record
    • Generate publicId (CUID)
    • Set name and nickname
    • Set timestamps
  6. Create UserContact record
    • Link to User via userId
    • Set contactType: MOBILE
    • Set isPrimary: true, isVerified: true
    • Copy dialCode and mobileNumber
  7. Update UserRegistration
    • Link to User via userId
    • Set stage: USER_CREATED
    • Set enteredName
  8. Commit transaction
  9. Return user details

Database Changes:

BEGIN TRANSACTION;

-- Create User
INSERT INTO users (public_id, name, nickname, created_at, updated_at)
VALUES ('clx123abc', 'John Doe', 'John', NOW(), NOW())
RETURNING id;

-- Create UserContact
INSERT INTO user_contacts (
public_id, user_id, contact_type, dial_code, contact_value,
is_primary, is_verified, created_at, updated_at
) VALUES (
'clx456def', user_id, 'mobile', '+91', '9876543210',
true, true, NOW(), NOW()
);

-- Update UserRegistration
UPDATE user_registrations
SET
user_id = user_id,
entered_name = 'John Doe',
stage = 'user_created',
updated_at = NOW()
WHERE dial_code = '+91' AND mobile_number = '9876543210';

COMMIT;

4. Data Model Relationships

4.1 Registration Flow Data Structure

UserRegistration Table

interface UserRegistration {
id: number;
publicId: string; // External identifier

// Phone Identity
dialCode: string; // "+91"
mobileNumber: string; // "9876543210"

// Registration Stage
stage: RegistrationStage; // State machine value

// OTP Tracking
otpSendAttempts: number; // 0-5
otpVerifyAttempts: number; // 0-5
lastOtpSentAt: Date | null; // Timestamp
lastOtpVerifiedAt: Date | null; // Timestamp
lastOtpExpiresAt: Date | null; // 15 minutes from sent

// OTP Delivery
otpDeliveryMethod: OtpDeliveryMethod; // SMS
otpDeliveryStatus: OtpDeliveryStatus; // SENT/DELIVERED/FAILED

// Profile Data
enteredName: string | null; // "John Doe"
userId: number | null; // Link to User after creation

// Metadata
deviceInfo: JSON | null; // Device fingerprinting
createdAt: Date;
updatedAt: Date;
}

User Table (Created in Step 3)

interface User {
id: number;
publicId: string; // External identifier
name: string; // "John Doe"
nickname: string; // "John"
fatherName: string | null;
careOf: string | null;
dateOfBirth: Date | null;
gender: Gender | null;
createdAt: Date;
updatedAt: Date;
}

UserContact Table (Created in Step 3)

interface UserContact {
id: bigint;
publicId: string;

// Contact Information
contactType: ContactType; // MOBILE
dialCode: string; // "+91"
contactValue: string; // "9876543210"
contactLabel: string | null; // "Personal"

// Status Flags
isPrimary: boolean; // true (first mobile)
isVerified: boolean; // true (from registration)
isBlocked: boolean; // false
isDeleted: boolean; // false

// Verification Tracking
verifiedAt: Date; // Set during registration

// Communication Preferences
allowMarketing: boolean; // false
allowNotifications: boolean; // true
allowTransactional: boolean; // true

// Relationships
userId: number; // Links to User

createdAt: Date;
updatedAt: Date;
}

4.2 Data Flow Diagram

Step 1: Mobile Entry
┌─────────────────┐
│ UserRegistration│
│ stage: ENTERED │
│ mobile: +919876 │
│ attempts: 1 │
└─────────────────┘

Step 2: OTP Sent
┌─────────────────┐
│ UserRegistration│
│ stage: OTP_SENT │
│ expiry: +15 min │
│ delivery: SMS │
└─────────────────┘

Step 3: OTP Verified
┌─────────────────┐
│ UserRegistration│
│ stage: VERIFIED │
│ verified_at: now│
└─────────────────┘

Step 4: User Created
┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐
│ UserRegistration│───▶│ User │◀───│ UserContact │
│ stage: CREATED │ │ name: John │ │ type: MOBILE │
│ user_id: 123 │ │ id: 123 │ │ primary: true │
│ name: John Doe │ └─────────────┘ │ verified: true │
└─────────────────┘ └─────────────────┘

5. Business Rules & Constraints

5.1 Input Validation Rules

Mobile Number Validation

  • Format: Numbers only (no spaces, dashes, or special characters)
  • Length: Varies by country (will be implemented later)
  • Required: Both dial code and mobile number mandatory
  • Uniqueness: One registration per mobile number at a time

OTP Validation

  • Format: Exactly 6 digits (000000-999999)
  • Expiry: 15 minutes from generation
  • Usage: Single use only (new OTP required for retry)
  • Case Sensitivity: Not applicable (numeric only)

Name Validation

  • Length: 1-100 characters
  • Characters: Letters, spaces, hyphens, apostrophes allowed
  • Required: Cannot be empty or whitespace only
  • Format: Proper capitalization recommended but not enforced

5.2 Security & Rate Limiting

OTP Send Limits

  • Per Mobile: Maximum 5 attempts per 24-hour period
  • Per IP: Maximum 10 attempts per hour (DoS protection)
  • Per Session: Maximum 3 consecutive failures before cooldown
  • Cooldown Period: 15 minutes after max attempts exceeded

OTP Verification Limits

  • Per OTP: Maximum 5 verification attempts per OTP session
  • Lockout: Account temporarily locked after 5 failed verifications
  • Expiry: OTP expires 15 minutes after generation
  • Reuse: Each OTP can only be verified once successfully

Session Security

  • Registration Session: 24-hour expiry from first step
  • Data Persistence: Mobile number stored only during active session
  • Cross-Device: Registration must be completed on same device/browser
  • Cleanup: Expired registrations automatically cleaned up

5.3 Business Logic Constraints

Registration State Machine

  • Linear Progression: Must complete steps in order (1→2→3)
  • No Skipping: Cannot skip OTP verification step
  • State Validation: Each API call validates current state
  • Rollback: Failed user creation rolls back to OTP_VERIFIED state

Data Integrity Rules

  • Atomic Operations: User and UserContact creation is transactional
  • Referential Integrity: All foreign keys properly maintained
  • Duplicate Prevention: Unique constraints on mobile numbers
  • Audit Trail: All state changes logged with timestamps

5.4 Error Handling Rules

User-Facing Errors

  • Clear Messages: Non-technical language for all errors
  • Actionable Guidance: Tell user what to do next
  • No Sensitive Data: Never expose internal error details
  • Graceful Degradation: Fallback options when possible

System Error Recovery

  • Retry Logic: Automatic retry for transient failures
  • Circuit Breaker: Stop attempts after repeated failures
  • Logging: All errors logged for debugging
  • Alerting: Critical errors trigger immediate notifications

6. Frontend State Management

6.1 Route Structure & Navigation

Route Definitions

// TanStack Router Configuration
const routes = {
sendOtp: '/send-otp', // Step 1: Mobile number entry
verifyOtp: '/verify-otp', // Step 2: OTP verification
userName: '/user-name' // Step 3: Profile completion
}

// Navigation Flow
/send-otp → /verify-otp → /user-name → /dashboard

Route Parameters & State Transfer

// Method 1: URL Search Params
/verify-otp?dialCode=%2B91&mobile=9876543210

// Method 2: Navigation State (Preferred)
navigate('/verify-otp', {
state: {
dialCode: '+91',
mobileNumber: '9876543210',
otpExpiresAt: '2025-06-16T10:15:00Z'
}
})

// Method 3: Session Storage (Fallback)
sessionStorage.setItem('registrationData', JSON.stringify({
dialCode: '+91',
mobileNumber: '9876543210'
}))

6.2 Form State Management with TanStack Form

Step 1: Mobile Number Form

const sendOtpForm = useForm({
defaultValues: {
dialCode: "+91",
mobileNumber: "",
},
validators: {
mobileNumber: (value) => {
if (!value) return "Mobile number is required";
if (!/^\d+$/.test(value)) return "Only numbers allowed";
if (value.length < 10) return "Mobile number too short";
return undefined;
},
},
onSubmit: async (values) => {
const result = await sendOtpMutation({
variables: values,
});
if (result.data?.sendOTP.success) {
navigate("/verify-otp", { state: values });
}
},
});

Step 2: OTP Verification Form

const verifyOtpForm = useForm({
defaultValues: {
otpCode: "",
},
validators: {
otpCode: (value) => {
if (!value) return "OTP is required";
if (!/^\d{6}$/.test(value)) return "OTP must be 6 digits";
return undefined;
},
},
onSubmit: async (values) => {
const { dialCode, mobileNumber } = getRegistrationState();
const result = await verifyOtpMutation({
variables: { ...values, dialCode, mobileNumber },
});
if (result.data?.verifyOTP.success) {
navigate("/user-name", { state: { dialCode, mobileNumber } });
}
},
});

Step 3: User Name Form

const completeRegistrationForm = useForm({
defaultValues: {
name: "",
},
validators: {
name: (value) => {
if (!value?.trim()) return "Name is required";
if (value.length > 100) return "Name too long";
return undefined;
},
},
onSubmit: async (values) => {
const { dialCode, mobileNumber } = getRegistrationState();
const result = await completeRegistrationMutation({
variables: { ...values, dialCode, mobileNumber },
});
if (result.data?.completeRegistration.success) {
// Clear registration state
clearRegistrationState();
// Redirect to main app
navigate("/dashboard");
}
},
});

6.3 Apollo Client State Management

GraphQL Operations

// Mutation Definitions
const SEND_OTP = gql`
mutation SendOTP($dialCode: String!, $mobileNumber: String!) {
sendOTP(dialCode: $dialCode, mobileNumber: $mobileNumber) {
success
message
registrationId
otpExpiresAt
remainingAttempts
}
}
`;

const VERIFY_OTP = gql`
mutation VerifyOTP(
$dialCode: String!
$mobileNumber: String!
$otpCode: String!
) {
verifyOTP(
dialCode: $dialCode
mobileNumber: $mobileNumber
otpCode: $otpCode
) {
success
message
isVerified
remainingAttempts
}
}
`;

const COMPLETE_REGISTRATION = gql`
mutation CompleteRegistration(
$dialCode: String!
$mobileNumber: String!
$name: String!
) {
completeRegistration(
dialCode: $dialCode
mobileNumber: $mobileNumber
name: $name
) {
success
message
user {
id
publicId
name
nickname
}
}
}
`;

Hook Implementation

// Custom Hooks for Registration
export const useRegistrationFlow = () => {
const [sendOtp, { loading: sendingOtp }] = useMutation(SEND_OTP);
const [verifyOtp, { loading: verifyingOtp }] = useMutation(VERIFY_OTP);
const [completeRegistration, { loading: completing }] = useMutation(
COMPLETE_REGISTRATION
);

const sendOtpHandler = async (dialCode: string, mobileNumber: string) => {
try {
const result = await sendOtp({
variables: { dialCode, mobileNumber },
errorPolicy: "all",
});
return result.data?.sendOTP;
} catch (error) {
console.error("Send OTP error:", error);
throw error;
}
};

return {
sendOtpHandler,
verifyOtpHandler,
completeRegistrationHandler,
loading: sendingOtp || verifyingOtp || completing,
};
};

7. Technical Considerations

7.1 Frontend Architecture

Component Structure

src/features/registration/
├── components/
│ ├── SendOtpForm.tsx # Step 1 component
│ ├── VerifyOtpForm.tsx # Step 2 component
│ ├── UserNameForm.tsx # Step 3 component
│ ├── OtpTimer.tsx # Countdown timer
│ ├── ResendOtpButton.tsx # Resend functionality
│ └── RegistrationProgress.tsx # Progress indicator
├── hooks/
│ ├── useRegistrationFlow.ts # Main registration logic
│ ├── useOtpTimer.ts # Timer management
│ └── useRegistrationState.ts # State persistence
├── pages/
│ ├── SendOtpPage.tsx # Route: /send-otp
│ ├── VerifyOtpPage.tsx # Route: /verify-otp
│ └── UserNamePage.tsx # Route: /user-name
├── utils/
│ ├── validation.ts # Form validation rules
│ ├── formatting.ts # Phone number formatting
│ └── storage.ts # Session state management
└── types/
└── registration.types.ts # TypeScript definitions

Type Safety

// Registration Types
export interface RegistrationState {
dialCode: string;
mobileNumber: string;
otpExpiresAt?: string;
step: "mobile" | "otp" | "profile";
}

export interface SendOtpRequest {
dialCode: string;
mobileNumber: string;
}

export interface SendOtpResponse {
success: boolean;
message: string;
registrationId: string;
otpExpiresAt: string;
remainingAttempts: number;
}

export interface VerifyOtpRequest {
dialCode: string;
mobileNumber: string;
otpCode: string;
}

export interface CompleteRegistrationRequest {
dialCode: string;
mobileNumber: string;
name: string;
}

7.2 Backend Architecture

NestJS Module Structure

src/registration/
├── dto/
│ ├── send-otp.dto.ts # Input validation
│ ├── verify-otp.dto.ts # Input validation
│ └── complete-registration.dto.ts
├── entities/
│ ├── user-registration.entity.ts
│ ├── user.entity.ts
│ └── user-contact.entity.ts
├── services/
│ ├── registration.service.ts # Business logic
│ ├── otp.service.ts # OTP generation/validation
│ └── sms.service.ts # SMS gateway integration
├── resolvers/
│ └── registration.resolver.ts # GraphQL endpoints
├── guards/
│ └── rate-limit.guard.ts # Rate limiting
└── registration.module.ts

Service Implementation

@Injectable()
export class RegistrationService {
constructor(
private prisma: PrismaService,
private otpService: OtpService,
private smsService: SmsService
) {}

async sendOtp(dialCode: string, mobileNumber: string) {
// Validate rate limits
await this.validateSendAttempts(dialCode, mobileNumber);

// Create or update registration record
const registration = await this.createOrUpdateRegistration(
dialCode,
mobileNumber
);

// Generate and send OTP
const otp = this.otpService.generate();
await this.smsService.send(dialCode + mobileNumber, otp);

// Update registration with OTP details
await this.updateRegistrationWithOtp(registration.id, otp);

return {
success: true,
message: "OTP sent successfully",
otpExpiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
remainingAttempts: 5 - registration.otpSendAttempts,
};
}

async verifyOtp(dialCode: string, mobileNumber: string, otpCode: string) {
const registration = await this.findRegistration(dialCode, mobileNumber);

// Validate OTP
const isValid = await this.otpService.verify(registration, otpCode);

if (isValid) {
await this.updateRegistrationStage(registration.id, "OTP_VERIFIED");
return { success: true, message: "OTP verified successfully" };
} else {
await this.incrementVerifyAttempts(registration.id);
return {
success: false,
message: "Invalid OTP",
remainingAttempts: 5 - registration.otpVerifyAttempts - 1,
};
}
}

async completeRegistration(
dialCode: string,
mobileNumber: string,
name: string
) {
return await this.prisma.$transaction(async (prisma) => {
const registration = await this.findVerifiedRegistration(
dialCode,
mobileNumber
);

// Create user
const user = await prisma.user.create({
data: {
name,
nickname: name.split(" ")[0],
publicId: cuid(),
},
});

// Create user contact
await prisma.userContact.create({
data: {
userId: user.id,
contactType: "MOBILE",
dialCode,
contactValue: mobileNumber,
isPrimary: true,
isVerified: true,
publicId: cuid(),
},
});

// Update registration
await prisma.userRegistration.update({
where: { id: registration.id },
data: {
userId: user.id,
enteredName: name,
stage: "USER_CREATED",
},
});

return {
success: true,
message: "Registration completed successfully",
user,
};
});
}
}

7.3 Performance Optimizations

Frontend Optimizations

  • Code Splitting: Lazy load registration pages
  • Form Debouncing: Debounce validation calls
  • Apollo Caching: Configure appropriate cache policies
  • Bundle Optimization: Tree shake unused code

Backend Optimizations

  • Database Indexing: Index on mobile number and timestamps
  • Connection Pooling: Optimize database connections
  • Rate Limiting: Implement at multiple levels
  • Caching: Cache OTP validation results temporarily

8. Testing Strategy

8.1 Frontend Testing

Unit Tests

// Form Validation Tests
describe("Registration Form Validation", () => {
test("validates mobile number format", () => {
expect(validateMobileNumber("abc123")).toBe("Only numbers allowed");
expect(validateMobileNumber("123")).toBe("Mobile number too short");
expect(validateMobileNumber("9876543210")).toBeUndefined();
});

test("validates OTP format", () => {
expect(validateOtp("12345")).toBe("OTP must be 6 digits");
expect(validateOtp("abcdef")).toBe("OTP must be 6 digits");
expect(validateOtp("123456")).toBeUndefined();
});
});

Integration Tests

// Component Integration Tests
describe("Registration Flow", () => {
test("completes full registration workflow", async () => {
render(<RegistrationApp />);

// Step 1: Enter mobile number
fireEvent.change(screen.getByPlaceholderText("Mobile number"), {
target: { value: "9876543210" },
});
fireEvent.click(screen.getByText("Send OTP"));

// Step 2: Enter OTP
await waitFor(() => screen.getByText("Verify OTP"));
fireEvent.change(screen.getByPlaceholderText("Enter OTP"), {
target: { value: "123456" },
});
fireEvent.click(screen.getByText("Verify"));

// Step 3: Enter name
await waitFor(() => screen.getByText("Complete Registration"));
fireEvent.change(screen.getByPlaceholderText("Full name"), {
target: { value: "John Doe" },
});
fireEvent.click(screen.getByText("Complete Registration"));

// Verify completion
await waitFor(() => screen.getByText("Welcome"));
});
});

E2E Tests

// Cypress E2E Tests
describe("User Registration E2E", () => {
it("should complete registration successfully", () => {
cy.visit("/send-otp");

// Step 1: Mobile number entry
cy.get('[data-testid="country-select"]').select("+91");
cy.get('[data-testid="mobile-input"]').type("9876543210");
cy.get('[data-testid="send-otp-btn"]').click();

// Mock OTP reception
cy.intercept("POST", "/graphql", { fixture: "otp-sent.json" });

// Step 2: OTP verification
cy.url().should("include", "/verify-otp");
cy.get('[data-testid="otp-input"]').type("123456");
cy.get('[data-testid="verify-btn"]').click();

// Step 3: Profile completion
cy.url().should("include", "/user-name");
cy.get('[data-testid="name-input"]').type("John Doe");
cy.get('[data-testid="complete-btn"]').click();

// Verify redirect to dashboard
cy.url().should("include", "/dashboard");
cy.get('[data-testid="welcome-message"]').should("be.visible");
});
});

8.2 Backend Testing

Unit Tests

// Service Unit Tests
describe("RegistrationService", () => {
let service: RegistrationService;
let prisma: PrismaService;
let smsService: SmsService;

beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [RegistrationService, PrismaService, SmsService],
}).compile();

service = module.get<RegistrationService>(RegistrationService);
prisma = module.get<PrismaService>(PrismaService);
smsService = module.get<SmsService>(SmsService);
});

describe("sendOtp", () => {
it("should send OTP successfully for valid mobile number", async () => {
const result = await service.sendOtp("+91", "9876543210");

expect(result.success).toBe(true);
expect(result.message).toBe("OTP sent successfully");
expect(result.otpExpiresAt).toBeDefined();
});

it("should fail when maximum send attempts exceeded", async () => {
// Setup: Create registration with max attempts
await prisma.userRegistration.create({
data: {
dialCode: "+91",
mobileNumber: "9876543210",
otpSendAttempts: 5,
stage: "MOBILE_NUMBER_ENTERED",
},
});

await expect(service.sendOtp("+91", "9876543210")).rejects.toThrow(
"Maximum send attempts exceeded"
);
});
});

describe("verifyOtp", () => {
it("should verify OTP successfully", async () => {
// Setup: Create registration with valid OTP
const registration = await createTestRegistration(
"+91",
"9876543210",
"123456"
);

const result = await service.verifyOtp("+91", "9876543210", "123456");

expect(result.success).toBe(true);
expect(result.message).toBe("OTP verified successfully");
});

it("should fail for invalid OTP", async () => {
const registration = await createTestRegistration(
"+91",
"9876543210",
"123456"
);

const result = await service.verifyOtp("+91", "9876543210", "654321");

expect(result.success).toBe(false);
expect(result.message).toBe("Invalid OTP");
});
});
});

API Tests

// GraphQL Resolver Tests
describe("RegistrationResolver", () => {
let app: INestApplication;
let prisma: PrismaService;

beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();

app = moduleFixture.createNestApplication();
prisma = moduleFixture.get<PrismaService>(PrismaService);
await app.init();
});

describe("sendOTP mutation", () => {
it("should send OTP successfully", async () => {
const mutation = `
mutation {
sendOTP(dialCode: "+91", mobileNumber: "9876543210") {
success
message
otpExpiresAt
remainingAttempts
}
}
`;

const response = await request(app.getHttpServer())
.post("/graphql")
.send({ query: mutation })
.expect(200);

expect(response.body.data.sendOTP.success).toBe(true);
expect(response.body.data.sendOTP.remainingAttempts).toBe(4);
});
});
});

8.3 Performance Testing

Load Testing

// Artillery.js Load Test Configuration
module.exports = {
config: {
target: "http://localhost:3000",
phases: [
{ duration: 60, arrivalRate: 10 }, // Warm-up
{ duration: 300, arrivalRate: 50 }, // Normal load
{ duration: 120, arrivalRate: 100 }, // Peak load
{ duration: 60, arrivalRate: 10 }, // Cool-down
],
},
scenarios: [
{
name: "Complete Registration Flow",
weight: 100,
flow: [
{
post: {
url: "/graphql",
json: {
query: `
mutation SendOTP($dialCode: String!, $mobileNumber: String!) {
sendOTP(dialCode: $dialCode, mobileNumber: $mobileNumber) {
success
otpExpiresAt
}
}
`,
variables: {
dialCode: "+91",
mobileNumber: "{{ $randomInt(9000000000, 9999999999) }}",
},
},
},
},
{
post: {
url: "/graphql",
json: {
query: `
mutation VerifyOTP($dialCode: String!, $mobileNumber: String!, $otpCode: String!) {
verifyOTP(dialCode: $dialCode, mobileNumber: $mobileNumber, otpCode: $otpCode) {
success
}
}
`,
variables: {
dialCode: "+91",
mobileNumber: "{{ mobileNumber }}",
otpCode: "123456",
},
},
},
},
],
},
],
};

8.4 Security Testing

Penetration Testing Checklist

  • Input Validation: Test SQL injection, XSS attacks
  • Rate Limiting: Verify DoS protection mechanisms
  • Authentication: Test session management and token security
  • Data Protection: Verify encryption in transit and at rest
  • OTP Security: Test OTP enumeration and timing attacks

9. Monitoring & Analytics

9.1 Application Performance Monitoring

Key Performance Indicators (KPIs)

  • Response Time: API response times (95th percentile)
  • Throughput: Requests per second
  • Error Rate: Percentage of failed requests
  • Availability: System uptime percentage
  • Database Performance: Query execution times

Monitoring Implementation

// Custom Metrics Service
@Injectable()
export class MetricsService {
private readonly logger = new Logger(MetricsService.name);

trackRegistrationEvent(event: string, metadata?: any) {
// Send to monitoring service (e.g., DataDog, New Relic)
this.sendMetric("registration." + event, {
timestamp: new Date().toISOString(),
...metadata,
});
}

trackApiCall(endpoint: string, duration: number, success: boolean) {
this.sendMetric("api.call", {
endpoint,
duration,
success,
timestamp: new Date().toISOString(),
});
}

trackOtpDelivery(status: "sent" | "delivered" | "failed", provider: string) {
this.sendMetric("otp.delivery", {
status,
provider,
timestamp: new Date().toISOString(),
});
}

private sendMetric(name: string, data: any) {
// Implementation depends on monitoring service
console.log(`Metric: ${name}`, data);
}
}

Dashboard Configuration

# Grafana Dashboard Configuration
dashboard:
title: "User Registration Monitoring"
panels:
- title: "Registration Completion Rate"
type: "stat"
targets:
- expr: "sum(rate(registration_completed[5m])) / sum(rate(registration_started[5m])) * 100"

- title: "OTP Delivery Success Rate"
type: "stat"
targets:
- expr: "sum(rate(otp_delivered[5m])) / sum(rate(otp_sent[5m])) * 100"

- title: "API Response Times"
type: "graph"
targets:
- expr: "histogram_quantile(0.95, rate(api_request_duration_seconds_bucket[5m]))"

- title: "Error Rate by Endpoint"
type: "graph"
targets:
- expr: "sum(rate(api_errors[5m])) by (endpoint)"

9.2 Business Analytics

User Journey Analytics

// Analytics Events Tracking
interface AnalyticsEvent {
userId?: string;
sessionId: string;
event: string;
properties: Record<string, any>;
timestamp: Date;
}

// Track Registration Funnel
export class RegistrationAnalytics {
trackStepEntry(step: string, sessionId: string, properties?: any) {
this.track({
event: "registration_step_entered",
sessionId,
properties: {
step,
...properties,
},
timestamp: new Date(),
});
}

trackStepCompletion(step: string, sessionId: string, duration: number) {
this.track({
event: "registration_step_completed",
sessionId,
properties: {
step,
duration_seconds: duration,
},
timestamp: new Date(),
});
}

trackDropoff(step: string, sessionId: string, reason?: string) {
this.track({
event: "registration_dropoff",
sessionId,
properties: {
step,
reason,
},
timestamp: new Date(),
});
}

trackError(error: string, step: string, sessionId: string) {
this.track({
event: "registration_error",
sessionId,
properties: {
error,
step,
},
timestamp: new Date(),
});
}

private track(event: AnalyticsEvent) {
// Send to analytics platform (Google Analytics, Mixpanel, etc.)
console.log("Analytics Event:", event);
}
}

Business Metrics Dashboard

  • Registration Volume: Daily, weekly, monthly registration counts
  • Conversion Funnel: Step-by-step completion rates
  • Geographic Distribution: Registration by country/region
  • Device Analytics: Mobile vs desktop usage patterns
  • Time-based Patterns: Peak registration hours/days
  • User Demographics: Age distribution, gender analytics

9.3 Error Monitoring and Alerting

Error Tracking Configuration

// Sentry Error Tracking
import * as Sentry from "@sentry/node";

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
beforeSend(event, hint) {
// Filter sensitive data
if (event.request) {
delete event.request.headers?.authorization;
delete event.request.data?.otpCode;
}
return event;
},
});

// Custom Error Handler
export class ErrorMonitoringService {
captureRegistrationError(error: Error, context: any) {
Sentry.withScope((scope) => {
scope.setTag("module", "registration");
scope.setContext("registration_context", context);
Sentry.captureException(error);
});
}

captureOtpError(error: Error, mobile: string, attempt: number) {
Sentry.withScope((scope) => {
scope.setTag("module", "otp");
scope.setContext("otp_context", {
mobile_hash: this.hashMobile(mobile),
attempt_number: attempt,
});
Sentry.captureException(error);
});
}

private hashMobile(mobile: string): string {
// Hash mobile number for privacy
return crypto
.createHash("sha256")
.update(mobile)
.digest("hex")
.substring(0, 8);
}
}

Alert Configuration

# PagerDuty Alert Rules
alerts:
- name: "High Registration Error Rate"
condition: "error_rate > 5% for 5 minutes"
severity: "critical"
channels: ["pagerduty", "slack"]

- name: "OTP Delivery Failure"
condition: "otp_delivery_success_rate < 90% for 10 minutes"
severity: "warning"
channels: ["slack", "email"]

- name: "API Response Time Degradation"
condition: "p95_response_time > 3000ms for 10 minutes"
severity: "warning"
channels: ["slack"]

- name: "Registration Volume Drop"
condition: "registration_rate < 50% of previous week for 30 minutes"
severity: "info"
channels: ["email"]

9.4 A/B Testing Framework

Experiment Configuration

// A/B Testing Service
export class ExperimentService {
private experiments = new Map<string, Experiment>();

constructor() {
this.setupExperiments();
}

private setupExperiments() {
// OTP Input Type Experiment
this.experiments.set("otp_input_type", {
name: "OTP Input Type",
variants: [
{ name: "single_field", weight: 50 },
{ name: "separate_digits", weight: 50 },
],
startDate: new Date("2025-06-16"),
endDate: new Date("2025-07-16"),
});

// Registration Progress Indicator
this.experiments.set("progress_indicator", {
name: "Progress Indicator Style",
variants: [
{ name: "steps", weight: 33 },
{ name: "progress_bar", weight: 33 },
{ name: "none", weight: 34 },
],
startDate: new Date("2025-06-16"),
endDate: new Date("2025-07-16"),
});
}

getVariant(experimentId: string, userId: string): string {
const experiment = this.experiments.get(experimentId);
if (!experiment || !this.isExperimentActive(experiment)) {
return "control";
}

// Consistent hashing for user assignment
const hash = this.hashUser(userId + experimentId);
const bucket = hash % 100;

let cumulativeWeight = 0;
for (const variant of experiment.variants) {
cumulativeWeight += variant.weight;
if (bucket < cumulativeWeight) {
return variant.name;
}
}

return experiment.variants[0].name;
}

trackExperimentEvent(experimentId: string, userId: string, event: string) {
const variant = this.getVariant(experimentId, userId);

// Track experiment event
this.track({
event: "experiment_event",
properties: {
experiment_id: experimentId,
variant,
event_name: event,
user_id: userId,
},
});
}

private isExperimentActive(experiment: Experiment): boolean {
const now = new Date();
return now >= experiment.startDate && now <= experiment.endDate;
}

private hashUser(input: string): number {
let hash = 0;
for (let i = 0; i < input.length; i++) {
const char = input.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash);
}
}

10. Future Enhancements

10.1 Short-term Enhancements (Next 3 months)

Multiple OTP Delivery Methods

  • WhatsApp OTP: Integration with WhatsApp Business API
  • Voice OTP: Phone call delivery for accessibility
  • Email OTP: Fallback option for SMS failures
  • Smart Delivery: Automatic method selection based on user preference
// Enhanced OTP Service
interface OtpDeliveryOptions {
primary: "sms" | "whatsapp" | "voice" | "email";
fallback?: ("sms" | "whatsapp" | "voice" | "email")[];
userPreference?: string;
}

export class EnhancedOtpService {
async sendOtp(
mobile: string,
options: OtpDeliveryOptions
): Promise<OtpResult> {
try {
return await this.deliverViaPrimary(mobile, options.primary);
} catch (error) {
if (options.fallback?.length) {
return await this.deliverViaFallback(mobile, options.fallback);
}
throw error;
}
}

private async deliverViaPrimary(
mobile: string,
method: string
): Promise<OtpResult> {
switch (method) {
case "sms":
return await this.smsService.send(mobile);
case "whatsapp":
return await this.whatsappService.send(mobile);
case "voice":
return await this.voiceService.send(mobile);
case "email":
return await this.emailService.send(mobile); // Requires email lookup
default:
throw new Error("Unsupported delivery method");
}
}
}

Country-Specific Mobile Validation

  • Regional Validation: Country-specific mobile number formats
  • Carrier Validation: Real-time carrier lookup
  • Number Portability: Handle ported numbers correctly
  • International Support: Support for global mobile formats
// Country-Specific Validation Service
export class MobileValidationService {
private validationRules = new Map<string, ValidationRule>();

constructor() {
this.setupValidationRules();
}

private setupValidationRules() {
this.validationRules.set("IN", {
pattern: /^[6-9]\d{9}$/,
length: 10,
carriers: ["airtel", "jio", "vodafone", "bsnl"],
});

this.validationRules.set("US", {
pattern: /^[2-9]\d{2}[2-9]\d{2}\d{4}$/,
length: 10,
carriers: ["verizon", "att", "tmobile", "sprint"],
});
}

async validateMobile(
dialCode: string,
mobile: string
): Promise<ValidationResult> {
const country = this.getCountryFromDialCode(dialCode);
const rule = this.validationRules.get(country);

if (!rule) {
return { valid: true, message: "Basic validation passed" };
}

// Format validation
if (!rule.pattern.test(mobile)) {
return {
valid: false,
message: `Invalid ${country} mobile number format`,
};
}

// Carrier lookup (optional)
if (rule.carriers) {
const carrier = await this.lookupCarrier(dialCode + mobile);
return {
valid: true,
message: "Valid mobile number",
carrier,
country,
};
}

return { valid: true, message: "Valid mobile number" };
}
}

Enhanced Security Features

  • Device Fingerprinting: Track device characteristics
  • Behavioral Analytics: Detect suspicious patterns
  • Risk Scoring: Assign risk scores to registrations
  • CAPTCHA Integration: For high-risk attempts

10.2 Medium-term Enhancements (3-6 months)

Social Login Integration

  • OAuth Providers: Google, Facebook, Apple, LinkedIn
  • Account Linking: Link social accounts to mobile-verified accounts
  • Profile Enrichment: Pre-fill profile data from social providers
  • Consent Management: Granular privacy controls
// Social Login Service
export class SocialLoginService {
async initiateOAuthFlow(
provider: "google" | "facebook" | "apple",
mobile: string
) {
const authUrl = await this.generateOAuthUrl(provider, mobile);
return { authUrl, state: this.generateState(mobile) };
}

async handleCallback(provider: string, code: string, state: string) {
const mobile = this.extractMobileFromState(state);
const socialProfile = await this.exchangeCodeForProfile(provider, code);

// Link to existing registration
const registration = await this.findRegistration(mobile);
await this.linkSocialProfile(registration.userId, socialProfile);

return { user: registration.user, socialProfile };
}

private async linkSocialProfile(userId: number, profile: SocialProfile) {
await this.prisma.userSocialProfile.create({
data: {
userId,
provider: profile.provider,
providerId: profile.id,
email: profile.email,
name: profile.name,
avatar: profile.avatar,
},
});
}
}

Progressive Profiling

  • Gradual Data Collection: Collect additional profile data over time
  • Smart Prompts: Context-aware profile completion prompts
  • Incentivization: Rewards for profile completion
  • Privacy Controls: Granular data sharing preferences

Advanced Analytics

  • Cohort Analysis: Track user behavior by registration cohorts
  • Predictive Analytics: Predict user lifetime value from registration data
  • Machine Learning: Automated fraud detection and user segmentation
  • Real-time Dashboards: Live registration metrics and alerts

10.3 Long-term Enhancements (6+ months)

Biometric Authentication

  • Fingerprint Registration: Optional biometric enrollment
  • Face Recognition: Camera-based identity verification
  • Voice Authentication: Voice print registration
  • Multi-factor Biometrics: Combine multiple biometric factors

Blockchain Integration

  • Decentralized Identity: Self-sovereign identity management
  • Verifiable Credentials: Blockchain-based identity verification
  • Smart Contracts: Automated registration workflows
  • Identity Portability: Cross-platform identity transfer

AI-Powered Features

  • Intelligent Routing: AI-powered OTP delivery optimization
  • Fraud Detection: Machine learning-based fraud prevention
  • Personalization: AI-driven user experience customization
  • Predictive Support: Proactive customer support based on registration patterns

Global Expansion Features

  • Multi-language Support: Localized user interfaces
  • Regional Compliance: GDPR, CCPA, local privacy laws
  • Currency Localization: Regional pricing and payments
  • Cultural Adaptation: Region-specific user experience patterns

10.4 Technical Debt and Infrastructure

Platform Modernization

  • Microservices Architecture: Break down monolithic registration service
  • Event-Driven Architecture: Implement event sourcing and CQRS
  • API Gateway: Centralized API management and rate limiting
  • Service Mesh: Advanced service-to-service communication

Performance Optimization

  • CDN Integration: Global content delivery optimization
  • Edge Computing: Distribute registration processing globally
  • Database Sharding: Horizontal database scaling
  • Caching Layers: Multi-tier caching strategy

DevOps and Reliability

  • Chaos Engineering: Proactive system resilience testing
  • Blue-Green Deployments: Zero-downtime deployment strategy
  • Feature Flags: Dynamic feature control and rollback
  • Automated Scaling: Intelligent auto-scaling based on demand

Document Version Control
Version: 1.0
Last Updated: June 2025
Next Review: July 2025
Approved By: Product Team, Engineering Team
Owner: Authentication & Authorization Module Team