User Registration
Module: Authentication & Authorization
Document Type: Business & Technical Specification
Version: 1.0
Last Updated: June 2025
Table of Contents
- Business Requirements
- Business Workflow
- Technical Workflow
- Data Model Relationships
- Business Rules & Constraints
- Frontend State Management
- Technical Considerations
- Testing Strategy
- Monitoring & Analytics
- 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
- User lands on registration page
- User selects country/dial code from dropdown
- User enters mobile number
- System validates number format
- User clicks "Send OTP" button
- System sends SMS OTP to mobile number
- 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
- User enters 6-digit OTP received via SMS
- System validates OTP in real-time
- User clicks "Verify" button
- System confirms OTP validity
- 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
- User enters full name
- System validates name format
- User clicks "Complete Registration"
- System creates user account
- User receives welcome message
- 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
- Initial State: Record created with
MOBILE_NUMBER_ENTERED
- OTP Dispatch: Updated to
OTP_SENT
after SMS sent - OTP Success: Updated to
OTP_VERIFIED
after validation - 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:
- Validate input parameters (dialCode, mobileNumber)
- Check for existing UserRegistration with same mobile
- Validate send attempts (max 5 per day)
- Create/Update UserRegistration record
- Set stage:
MOBILE_NUMBER_ENTERED
- Increment
otpSendAttempts
- Set
lastOtpSentAt
timestamp
- Set stage:
- Generate 6-digit numeric OTP
- Send SMS via gateway integration
- Update record with OTP details
- Set stage:
OTP_SENT
- Set
lastOtpExpiresAt
(now + 15 minutes) - Set
otpDeliveryMethod
: SMS - Set
otpDeliveryStatus
: SENT/PENDING
- Set stage:
- 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:
- Find UserRegistration by dialCode + mobileNumber
- Validate current stage is
OTP_SENT
- Check OTP expiry (lastOtpExpiresAt > now)
- Validate verification attempts (max 5 per OTP session)
- Compare provided OTP with stored value
- Update verification tracking
- Increment
otpVerifyAttempts
- Set
lastOtpVerifiedAt
(if successful)
- Increment
- Update stage to
OTP_VERIFIED
(if successful) - 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:
- Find UserRegistration by dialCode + mobileNumber
- Validate current stage is
OTP_VERIFIED
- Validate name format (1-100 characters, required)
- Begin database transaction
- Create User record
- Generate publicId (CUID)
- Set name and nickname
- Set timestamps
- Create UserContact record
- Link to User via userId
- Set contactType: MOBILE
- Set isPrimary: true, isVerified: true
- Copy dialCode and mobileNumber
- Update UserRegistration
- Link to User via userId
- Set stage:
USER_CREATED
- Set enteredName
- Commit transaction
- 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
- Business Requirements
- Business Workflow
- Technical Workflow
- Data Model Relationships
- Business Rules & Constraints
- Frontend State Management
- Technical Considerations
- Testing Strategy
- Monitoring & Analytics
- 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
- User lands on registration page
- User selects country/dial code from dropdown
- User enters mobile number
- System validates number format
- User clicks "Send OTP" button
- System sends SMS OTP to mobile number
- 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
- User enters 6-digit OTP received via SMS
- System validates OTP in real-time
- User clicks "Verify" button
- System confirms OTP validity
- 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
- User enters full name
- System validates name format
- User clicks "Complete Registration"
- System creates user account
- User receives welcome message
- 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
- Initial State: Record created with
MOBILE_NUMBER_ENTERED
- OTP Dispatch: Updated to
OTP_SENT
after SMS sent - OTP Success: Updated to
OTP_VERIFIED
after validation - 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:
- Validate input parameters (dialCode, mobileNumber)
- Check for existing UserRegistration with same mobile
- Validate send attempts (max 5 per day)
- Create/Update UserRegistration record
- Set stage:
MOBILE_NUMBER_ENTERED
- Increment
otpSendAttempts
- Set
lastOtpSentAt
timestamp
- Set stage:
- Generate 6-digit numeric OTP
- Send SMS via gateway integration
- Update record with OTP details
- Set stage:
OTP_SENT
- Set
lastOtpExpiresAt
(now + 15 minutes) - Set
otpDeliveryMethod
: SMS - Set
otpDeliveryStatus
: SENT/PENDING
- Set stage:
- 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:
- Find UserRegistration by dialCode + mobileNumber
- Validate current stage is
OTP_SENT
- Check OTP expiry (lastOtpExpiresAt > now)
- Validate verification attempts (max 5 per OTP session)
- Compare provided OTP with stored value
- Update verification tracking
- Increment
otpVerifyAttempts
- Set
lastOtpVerifiedAt
(if successful)
- Increment
- Update stage to
OTP_VERIFIED
(if successful) - 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:
- Find UserRegistration by dialCode + mobileNumber
- Validate current stage is
OTP_VERIFIED
- Validate name format (1-100 characters, required)
- Begin database transaction
- Create User record
- Generate publicId (CUID)
- Set name and nickname
- Set timestamps
- Create UserContact record
- Link to User via userId
- Set contactType: MOBILE
- Set isPrimary: true, isVerified: true
- Copy dialCode and mobileNumber
- Update UserRegistration
- Link to User via userId
- Set stage:
USER_CREATED
- Set enteredName
- Commit transaction
- 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