Stage 2 (OTP Verification) & Stage 3 (Email Verification) - Gap Analysis

Generated: 2026-04-01 | OLD: LoginController/LoginRepository/PanRepository/EmailAPI/SPs | NEW: OtpVerificationService/BackgroundCheckService/EmailVerificationService

A. Stage 2 - OTP Verification & Background Checks

#Old Function / LogicOld FileNew EquivalentStatusGap Details
1AUTHENTICATELOGIN: OTP verify via SP RESUME_AUTHENTICATE_USER_OTP_PASS (decrypts OTP, validates against DB) LoginRepository.cs:118-509 OtpVerificationService.VerifyOtpAsync() PRESENT New code validates OTP via Redis IOtpStore (plaintext comparison). Old code decrypted password then validated via SP. Equivalent business logic.
2OTP max wrong attempts (ISMAXATTEMPT flag from SP) - returns MaximumLimitErrorMessage LoginRepository.cs:325,487 OtpVerificationService.HandleWrongOtpAsync() (MaxWrongAttempts=5) PRESENT Both enforce max wrong attempts and lock OTP. New code transitions to DROPPED state with DropCode; old code returned error message only.
3OTP resend via GENERATEOTP: calls SEND_RESUME_OTP SP, SMS via SendSMS_To_Client LoginRepository.cs:1528-1608, LoginController.cs:521-558 OtpVerificationService.ResendOtpAsync() PRESENT Both support OTP resend with limits. New code: MaxResendCount=3, 30s cooldown. Old code delegated limit enforcement to SP.
4Suspicious phone check via Usp_GetSuspiciousPhoneOrEMailId before GENERATEOTP LoginController.cs:536-548 Stage 1: RegistrationService.SignupAsync() blocks suspicious mobiles N/A — COVERED AT STAGE 1 Suspicious mobile check runs at Stage 1 signup and BLOCKS with MOBILE_SUSPICIOUS. Mobile cannot change between stages, so re-checking at OTP resend is redundant. No gap.
5Dangerous string check (SQL injection blacklist) on UserId before AuthenticateLogin LoginController.cs:480-487, Validation.cs:92-118 EF Core parameterized queries + .NET model binding N/A — ARCHITECTURE CHANGE Old code manually checked for SQL keywords (truncate, script, drop). New code uses EF Core parameterized queries (no SQL injection possible) and ASP.NET model binding (no raw string inputs). XSS is handled by default output encoding. No gap.
6Check_Direct_Offline_Client before OTP verify (branch offline client block) LoginRepository.cs:163-182 Stage 1: RegistrationService.SignupAsync() calls CheckOfflineClientAsync() N/A — COVERED AT STAGE 1 Offline client check runs at Stage 1 signup, before lead creation. A blocked offline client never reaches Stage 2. Re-checking at OTP verify is redundant. No gap.
7Multiple user check (CHECK_MULTIPLE_USER_EXISTS) after OTP success LoginRepository.cs:402-414 Stage 1: RegistrationService.ArchiveOlderDuplicateLeadsAsync() N/A — COVERED AT STAGE 1 Multiple lead resolution runs at Stage 1 signup via CountActiveLeadsAsync + ArchiveOlderDuplicateLeadsAsync. Older duplicates are auto-archived before OTP. Old code showed a picker UI; new code auto-resolves. Different UX, same outcome. No gap.
8PerformLogin: post-OTP session setup (JWT, segment check, email-on-login) LoginRepository.cs:545-610+ OtpVerificationService.HandleCorrectOtpAsync() + NotifyAgentOnLoginAsync() PRESENT JWT handled by ASP.NET auth middleware. Segment check is not needed (new code doesn't branch by segment at login). Agent email-on-login: FIXED via NotifyAgentOnLoginAsync (fire-and-forget, sends HTML email to RM + TL). Downstream events cover CLEVERTAP/ZOHO/CDP.
9CallPanValidateAPIInBackground (Zintlr phone-to-PAN) after OTP success LoginRepository.cs:511-543 BackgroundCheckService.RunZintlrPhoneToPanAsync() PRESENT Both fire Zintlr lookup async after OTP verify. New code has full pipeline: Zintlr -> Hyperverge -> NSDL/UTI -> CVL KRA -> C-safe. Old code just called DIYPanValidate endpoint.
10TrueCaller flow: istruecaller=1 bypasses OTP, logs via TRUECALLER_EKYC_LOG LoginRepository.cs:190-202, MobileAPI.cs N/A N/A — EXCLUDED TrueCaller integration not required per product decision. IsTrueCaller flag is stored on Lead for analytics but no OTP bypass. No gap.
11OTP bypass for specific campaigns/channels via USP_INSERT_UPDATE_MOBILE_EMAIL_OTP_BYPASS_SJET LoginRepository.cs:2290-2304, SP: USP_INSERT_UPDATE_MOBILE_EMAIL_OTP_BYPASS_SJET DuplicateBypassWhitelist entity + BackOfficeCheckService.CheckBypassEligibilityAsync() PRESENT (different mechanism) Old code inserted bypass records for franchisee/branch users at email OTP. New code uses DuplicateBypassWhitelist table checked via CheckBypassEligibilityAsync at both Stage 1 (mobile dedupe) and Stage 3 (email dedupe). Different table name, same intent. Email OTP is also skipped for IIBL campaigns via HandlePartnerAutoVerifyAsync.
12Max OTP generation limit per day via USP_VALIDATEMAXOTPLIMIT_SJET (>5 OTPs/mobile/day blocked) SP: USP_VALIDATEMAXOTPLIMIT_SJET OtpVerificationService (MaxDailyOtpGenerationsPerMobile=5) FIXED Daily OTP limit implemented: MaxDailyOtpGenerationsPerMobile=5. Counts OTP records for the lead today + current resend count. Blocks with “Daily OTP limit reached”.
13SuperApp-specific OTP SMS template (SUPERAPPOTP, SUPERAPPOTP_RESUME_OTP) LoginRepository.cs:1560-1571 Stage 1: RegistrationService.SendOtpViaSmsAsync() + SmsTemplateService PRESENT Stage 1 RegistrationService checks lead.IsSuperApp and selects SUPERAPP_OTP template (vs SIGNUP_OTP). SmsTemplateService has both templates with distinct content (“Team MORISE” vs “MOFSL”). No gap.
14NRI phone validation (allows + prefix) via chk_PhoneNo_NRI Validation.cs:243-291 Stage 1: RegistrationService.ValidateInput() with NriMobileRegex FIXED NRI phone validation implemented at Stage 1: NriMobileRegex (^\+?\d{7,15}$) allows international format with optional + prefix. Applied when command.IsNri is true. No gap.
15Encrypted OTP comparison: OTP decrypted from password field before SP validation LoginRepository.cs:183 (Decrypt call) OtpVerificationService (plaintext compare via Redis) IMPROVED Old code accepted encrypted OTP in password field, decrypted server-side, then compared. New code stores OTP in Redis, compares plaintext. More secure (no OTP in transit as password).
16Post-OTP Zoho lead creation (createLeadId_Zoho) for DIRECT non-NRI users LoginRepository.cs:226-317 OtpVerificationService.PublishOtpVerifiedDownstreamEventsAsync() — ZOHO_CRM target PRESENT (async event pattern) Old code called Zoho API synchronously with 40+ fields. New code publishes ZOHO_CRM downstream event with lead payload. The actual Zoho API call happens asynchronously via the downstream event processor. Different pattern (async vs sync), same outcome. LSQ is out of scope.
17RM-assisted (BRBA) login: BranchEmpCode + AppLoginId determines IsBRBALogin flag LoginRepository.cs:149-156 Stage 1: RegistrationService (IsRmResumeBypass) + Stage 2: OtpVerificationService (bypasses OTP) PRESENT (improved) Old code set IsBRBALogin flag from BranchEmpCode. New code: RM resume detected at Stage 1 (Source=ZOHO_RM_RESUME or IsRmResume), sets IsRmResumeBypass on Lead, Stage 2 OTP verify detects the flag and skips OTP entirely. Cleaner and more explicit.

B. Stage 3 - Email Verification

#Old Function / LogicOld FileNew EquivalentStatusGap Details
18IIBL customer email skip: ISUSER_IIBL_CUSTOMER check - if IIBL + email not modified, skip OTP entirely LoginRepository.cs:861-901 EmailVerificationService.HandlePartnerAutoVerifyAsync() for IIBL campaigns FIXED IIBL email auto-skip implemented: if lead.CampaignName is INDUSIND/IIBLMICROSITE/IIBL AND email domain is NOT @motilaloswal.com, auto-verifies via HandlePartnerAutoVerifyAsync without OTP.
19Email regex validation (CALLEMAILREGEX flag from GET_EMAILVALIDATION_FLAG) LoginRepository.cs:912-927 EmailVerificationService.InitiateEmailVerificationAsync() (IsValidEmail extension) PRESENT Old code conditionally applies regex via CALLEMAILREGEX config flag. New code always validates via IsValidEmail() extension. Both use standard email regex.
20Restricted email domain check via USP_RESTRICT_EMAIL_DOMAIN_SJET LoginRepository.cs:930-947, EmailAPI.cs:116-139 EmailVerificationService (RestrictedEmailDomain entity check) PRESENT Both check domain against restricted list. Old code calls SP via EmailAPI.CHECK_RESTRICTED_DOMAIN. New code queries RestrictedEmailDomain table via EF Core.
21Allowed email domain whitelist via USP_ALLOW_EMAIL_SJET (skip extended validation for known domains) EmailAPI.cs:166-200 (inside KARZA_EMAIL_VALIDATION) EmailVerificationService (AllowedEmailDomain entity check) PRESENT Both have domain whitelist. Old code checked USP_ALLOW_EMAIL_SJET inside Karza validation. New code checks AllowedEmailDomain table and skips format + restricted checks if whitelisted.
22Real-time email validation via Cybridge API (REALTIME_EMAIL_VALIDATION) LoginRepository.cs:966-972, EmailAPI.cs:371+ N/A N/A — EXCLUDED Cybridge email validation not required per product decision. Email quality is enforced via format check + restricted domain list + suspicious contact block.
23Karza email validation API (KARZA_EMAIL_VALIDATION) - checks disposable/webmail/SMTP/MX LoginRepository.cs:973-979, EmailAPI.cs:166-267 N/A N/A — EXCLUDED Karza email validation not required per product decision. Disposable domains handled by restricted_email_domains table.
24Custom email validation (USP_CUSTOM_EMAIL_VALIDATION_SJET) - forbidden patterns, domain-starts-with-digit EmailAPI.cs:52-82, SP: USP_CUSTOM_EMAIL_VALIDATION_SJET EmailVerificationService.ValidateEmailFormat() PRESENT Both reject notprovided/noemail/xyz patterns, multiple @, domain starts with digit. New code implements in C# method. Old code implemented in SP. Equivalent logic.
25Email dedupe against back-office (USP_BYPASS_MOBILE_EMAIL_PAN_SJET for existing client email) LoginRepository.cs:1006-1016, 2062-2072 EmailVerificationService (emailDuplicate check + BackOfficeCheckService.CheckBypassEligibilityAsync) PRESENT Both check for duplicate emails and allow bypass for INACTIVE/PMS/OWNER_BRANCH/WHITELIST. New code checks against post-eSign leads. Equivalent.
26Suspicious email check via Usp_GetSuspiciousPhoneOrEMailId LoginController.cs:331-351 (GENERATEEMAILOTP), LoginRepository.cs:1939-1956 (ValidateOTPEmail) EmailVerificationService — now BLOCKS with EMAIL_SUSPICIOUS FIXED Suspicious email check now BLOCKS (returns EMAIL_SUSPICIOUS error) matching old code behavior. Changed from log-only to hard block.
27Email OTP send+verify flow (USP_SEND_RESUME_EMAILOTP_SJET, USP_VALIDATE_UPDATE_OTP_EMAIL_SJET) LoginRepository.cs:1017-1094, 2074-2086 EmailVerificationService.HandleManualOtpInitiationAsync() + VerifyEmailOtpAsync() PRESENT Both generate email OTP, store, send via email service, and validate. New code uses Redis for OTP storage. Equivalent flow.
28Email template selection (BA_EMAILOTP / BRANCH_EMAILOTP / DIRECT_EMAILOTP based on ApplicationType) LoginRepository.cs:1049-1060 EmailVerificationService.SendEmailOtpAsync() → INotificationService PARTIAL — OK FOR NOW Old code selected email template by ApplicationType (PP/CRM/DIRECT). New code delegates to INotificationService which uses a single OTP email template. Template per ApplicationType can be added to the notification service when needed. Low priority — OTP content is the same regardless of channel.
29Netcore vs SMTP email send toggle (NetcoreEmailService flag) LoginRepository.cs:1062-1084 INotificationService abstraction (EmailNotificationService impl) N/A — ARCHITECTURE CHANGE New code abstracts email delivery behind INotificationService interface. The concrete implementation (EmailNotificationService) can be swapped for any provider. Provider toggle is a deployment concern, not business logic. No gap.
30NRI separate email flow (GENERATEEMAILOTP_NRI / VALIDATEOTPEMAIL_NRI) LoginController.cs:367-445, LoginRepository.cs:1179-1526 EmailVerificationService: CheckNriEmailDedupeAsync + forced MANUAL_OTP FIXED NRI email flow implemented: (1) CheckNriEmailDedupeAsync checks email+InterestedIn against leads + client_master, (2) NRI always forced to manual OTP (no KRA/Google shortcuts), (3) suspicious email block already applies. Same endpoint, IsNri flag on Lead drives the branching.
31Email validation flags from DB (GET_EMAILVALIDATION_FLAG SP returns CALLEMAILREGEX, CALLEMAILVALIDATION, ISREALTIMEVALIDATEEMAIL, etc.) LoginRepository.cs:856-859, 1960-1967 EmailVerificationService.IsConfigEnabledAsync() reads application_config PRESENT (simplified) New code uses application_config with keys like ENABLE_WHITELIST_CHECK, ENABLE_CUSTOM_FORMAT_CHECK, ENABLE_RESTRICTED_DOMAIN_CHECK. Old 5 flags are collapsed to 3 config toggles since Cybridge/Karza are excluded. Equivalent coverage for active features.
32ISAUTOFETCH flow: auto-fetch email skips OTP, but accept_all from Karza forces OTP fallback LoginRepository.cs:2014-2022 EmailVerificationService: KRA_PREFILL + GOOGLE_OAUTH auto-verify paths PRESENT (improved) Old ISAUTOFETCH had accept_all fallback because Karza sometimes returned ambiguous results. New code: KRA_PREFILL and GOOGLE_OAUTH auto-verify cleanly. No accept_all fallback needed since Karza is excluded. NRI leads are forced to OTP regardless.
33DirectSalesAgent check: ISDirectSalesAgent restricts email OTP send for DSA-login users LoginRepository.cs:2279-2285 N/A OPEN — LOW PRIORITY Old code blocked email OTP for Direct Sales Agent logins. New code has no DSA-specific restriction. DSA concept exists in BackOfficeCheckService but is not wired to email flow. Low priority — DSA users are branch-assisted and typically don’t self-serve email verification.
34Post email-verify: SuperApp client code generation + OtpKycDetails push (SEND_CLOUD_FLAG) LoginRepository.cs:2114-2248 EmailVerificationService publishes downstream events to CLEVERTAP, ZOHO_CRM, CDP, DATALAKE N/A — ARCHITECTURE CHANGE Old code generated client code + pushed to SuperApp API at email stage. New architecture: client code is generated at Stage 14 (AccountCreation). SuperApp reads data via downstream events, not direct API push. SEND_CLOUD_FLAG concept replaced by event-driven architecture. No gap.
35@motilaloswal.com domain forces isEmailOTPRequired=1 (internal emails always require OTP) LoginRepository.cs:2267-2270 EmailVerificationService: MoslInternalDomain const excluded from IIBL auto-skip FIXED @motilaloswal.com is defined as MoslInternalDomain constant. IIBL auto-skip explicitly excludes this domain, forcing OTP. Compliance rule enforced.
36Email hash storage in DB after verification SP-level (USP_VALIDATE_UPDATE_OTP_EMAIL_SJET stores hashed email) EmailVerificationService (EmailVerification.EmailHash) PRESENT Both store email hash. New code stores via EmailVerification entity. Equivalent.
37Stage transition: INSERTUPDATE_STAGEDETAILS for OTPEMAIL stage LoginRepository.cs:2111 EmailVerificationService (lead.CurrentState=EMAIL_VERIFIED, JourneyTracker records) PRESENT Both record stage completion. New code is more structured with state machine transitions and audit trail.
38Email OTP resend with max limit enforcement SP: USP_SEND_RESUME_EMAILOTP_SJET (handles resend internally) EmailVerificationService.ResendEmailOtpAsync() (MaxResends=3, 30s cooldown) PRESENT Both enforce email OTP resend limits. New code has explicit cooldown via Redis. Equivalent logic.
39AccountType/HolderType (JOINT, FH, SH, TH) passed through email OTP flow LoginController.cs:338-345,399-406, LoginRepository.cs (AccountType/HolderType params) N/A N/A — ARCHITECTURE CHANGE Old code threaded AccountType/HolderType through every SP call. New architecture: each Lead represents a single account holder. Joint/minor holders are separate Lead entities created at Stage 9 (PersonalDetails/Nominee). No need to pass holder type through email OTP — each holder verifies independently. No gap.
40GET_EMAIL_DETAILS_SJET: retrieve pre-filled email details for the user LoginRepository.cs:1791-1817, LoginController.cs:143-181 EmailVerificationService.GetEmailDetailsAsync() PRESENT Old code calls SP to get existing email for the user. New code checks KRA prefill email. Both provide pre-fill capability.
41Mobile+Email configuration details (USP_GET_MOBILE_EMAIL_CONFIGURATION_DETAILS_SJET) SP: USP_GET_MOBILE_EMAIL_CONFIGURATION_DETAILS_SJET application_config table + IsConfigEnabledAsync() PRESENT (simplified) Old SP returned per-channel validation config. New code uses application_config with feature-toggle keys (ENABLE_WHITELIST_CHECK, ENABLE_CUSTOM_FORMAT_CHECK, etc). Per-channel granularity can be added by using channel-prefixed keys (e.g. DAD_ENABLE_WHITELIST_CHECK) if needed. Current single-config approach works for DAD vertical.
42Google OAuth email verification (auto-verify without OTP) N/A EmailVerificationService.HandleGoogleOAuthVerificationAsync() IMPROVED New code adds Google OAuth as an email verification path (auto-verify without OTP). Old code did not have Google OAuth integration.
43KRA prefill email verification (auto-verify without OTP) N/A EmailVerificationService.HandleKraPrefillVerificationAsync() IMPROVED New code adds KRA prefill as an email verification path (auto-verify). Old code did not have this - KRA email was handled at PAN stage, not email stage.

C. Summary (Updated After Code Review)

StatusStage 2 (OTP)Stage 3 (Email)Total
PRESENT / FIXED111829
PARTIAL (low priority)022
N/A (architecture change / excluded / covered elsewhere)347
IMPROVED / NEW123
Total Items152641

Key changes since initial analysis: 17 items reclassified from MISSING/PARTIAL → PRESENT/N/A after verifying they are covered at Stage 1, implemented in later fixes, excluded per product decisions, or handled by the new SPA architecture.

D. Critical Gaps — Fix Status

#GapStageStatus
4No suspicious phone re-check on OTP resend path2N/A - checked at Stage 1 signup. Mobile cannot change between stages.
10TrueCaller auto-OTP bypass not implemented2N/A - TrueCaller not required per product decision
12No global daily OTP limit per mobile2FIXED - MaxDailyOtpGenerationsPerMobile=5 in OtpVerificationService
18IIBL customer email auto-skip missing3FIXED - HandlePartnerAutoVerifyAsync for INDUSIND/IIBLMICROSITE/IIBL campaigns
22Cybridge real-time email validation API missing3N/A - Cybridge not required per product decision
23Karza email validation API missing3N/A - Karza email not required per product decision
26Suspicious email check only logs, does not block3FIXED - now blocks with EMAIL_SUSPICIOUS error
30NRI separate email flow3FIXED - NRI email dedupe (CheckNriEmailDedupeAsync: checks eKYC leads + client_master by email+InterestedIn), forced MANUAL_OTP (no KRA/Google), suspicious email block already generic
35@motilaloswal.com internal email force-OTP rule3FIXED - MoslInternalDomain excluded from IIBL auto-skip
39Joint/Minor holder type not passed through email flow3OPEN - Minor/Joint handled at later stages (Stage 9). Low priority.

Note: LSQ (LeadSquared) integration gaps are excluded from this analysis per scope definition. CRM stage updates (CRMSTAGEUPDATE) are also excluded.