Authentication & Account Lifecycle
Status: 🟢 Production verified · Last validated: 2026-09-06
Signup, login, OTP, password, device management, 2FA, biometric, logout — validated against classs/auth_classes/AuthHandler.php, classs/AuthModule.php, forms/doaction_auth.php, and the live kv_auth_* / customer_master schema.
Architecture (two layers)
AuthHandler(classs/auth_classes/AuthHandler.php, ~2,900 lines) — the real engine.
Holds every method: handleSignup :95, handleVerifyOTP :163, handleResendOTP :214, handleLogin :254, handleForgotPassword :403, handleDeviceResetRequest/Verify :435/:444, handleTwoFactorSend/Verify :453/:470, handleResetPassword :488, handleChangePassword :513, handleCompleteProfile :129, handleOnboardingSave :1110, handleToggleSubProfile :618, handleBiometricLogin :2203, handleLogout :2262, handleLogoutDevice :2316, handleLogoutAll :2341, plus session/history getters (getActiveSessions :2391, getLoginHistory :2404, getPasswordHistory :2417).
AuthModule extends AuthHandler(classs/AuthModule.php) — a thin wrapper that calls
parent::handleX() then adds two cross-cutting concerns:
- DPDP audit logging — every auth event is logged via `kvDpdpLogAuthEvent(eventType,
userId, meta, riskScore)` with an explicit per-event risk score (e.g. login success 28, login failed 68, OTP verify success 18).
- Auto-follow seeds — on login success,
FriendsModule::applyAutoFollowSeeds($userId)
connects the user with the superadmin (AuthModule.php:60-70).
Dispatch: the front-end POSTs to forms/doaction_auth.php with TYPEACTION (aliases: typeAction/typeaction/action, GET or POST). It instantiates AuthModule and routes by action.
Response contract (every handler returns this shape): activi_status (1 = success), return_messagealert (user message), redirect_url, activity_return_type. The logged-in user id is $_SESSION['auth_customer_id'].
Entry points (validated pages)
| Purpose | Page(s) |
|---|---|
| Signup | signup.php, register.php |
| Login | login.php |
| OTP | otpverification.php |
| Onboarding (post-OTP) | onboarding.php?signup=1 |
| Forgot / reset password | forgot-password.php, forgotpassword.php, reset-password.php, reset-password_new.php |
| Change password | changepassword.php, password_history.php |
| Social signup/login | google_signup.php, google_signup_complete.php, google_oauth_signup.php, api/google_oauth_init.php |
| Logout | logout.php |
| Module-specific verify/OTP | kyc_otp.php, claim_otp.php, dating_verify.php, propertyadd_otp.php, careerlab_verify.php |
Dispatcher actions (doaction_auth.php): SIGNUP, VERIFY_OTP, RESEND_OTP, LOGIN, COMPLETE_PROFILE, PROFILE_QUICK_UPDATE, BIOMETRIC_LOGIN, FORGOT_PASSWORD, RESET_PASSWORD, CHANGE_PASSWORD, AUTH_DEVICE_RESET_REQUEST, AUTH_DEVICE_RESET_VERIFY, AUTH_DEVICE_PICK_LOGOUT, TWOFA_SEND, TWOFA_VERIFY, plus notification actions (GET_NOTIFICATIONS, MARK_NOTIFICATION_READ, MARK_ALL_NOTIFICATIONS_READ).
Data model (validated)
customer_master (119 rows, 2026-09-06 — corrected same-day recount, was 110; this is an
actively-written dev DB) — the identity table One row per profile, not per person. Key columns:
rowId(PK),customer_uid,parent_user_id(0/self = top-level person; >0 = a sub-profile's
parent), profile_type enum(social,professional,dating), user_type, company_id.
- Credentials:
email,mobile,password_hash. Verification:is_email_verified,
is_mobile_verified, email_verified_at, mobile_verified_at.
- KYC:
kyc_status,kyc_doc_type,kyc_otp,kyc_otp_timestamp. - Account:
account_status,account_status_message,activestatus,age_verified,
preferred_language.
Column drift to know (validated):customer_mastercarries legacy duplicates —password(text) alongsidepassword_hash;customer_email/emailidalongsidecustomer_mobilealongsidemobile;profile_type_old,parent_user_id_old. Write/readpassword_hash,mobile— treat theold/customer/emailidcolumns as legacy unless a specific code path proves otherwise.
The kv_auth_* family (the auth store)
| Table | Rows | Role |
|---|---|---|
kv_auth_sessions | 64 | active/revoked sessions — polymorphic actor |
kv_auth_login_attempts | 53 | login attempt log (rate-limit / lockout source) |
kv_auth_login_challenges | 49 | in-flight OTP/login challenges |
kv_auth_trusted_devices | 12 | remembered devices (device policy) |
kv_auth_password_resets | 5 | password reset requests |
password_reset_tokens | 6 | reset tokens |
device_reset_tokens | 0 | device-reset tokens |
notification_otp_codes | 25 | issued OTP codes |
otp_attempts_log / otp_verifications | 0 / 0 | (dormant — OTP flows use notification_otp_codes + kv_auth_login_challenges) |
auth_session_log | 128 (recounted; was 121) | session audit |
login_activity_log | 248 (recounted; was 238) | login history (getLoginHistory) |
oauth_clients | 0 | OIDC clients (see Developer Platform / SSO) |
referral_codes / user_referrals | 13 / 0 | referral attribution (see below) |
kv_auth_sessions columns (validated): session_uid (char36), actor_type + actor_id (polymorphic — a session belongs to a customer, merchant, partner, or padmin), partner_id, actor_role, php_session_id, device_uid, device_fingerprint_hash, ip_address, user_agent, geo_json, risk_score, status, created_at, last_seen_at, logged_out_at, revoked_at, revoked_by_padmin_id, revoke_reason, meta_json. Session lifecycle is managed by classs/KvSessionDevicePolicy.php, KvSessionGuard.php, KvSessionHeartbeat.php (multi-device limits, eviction, heartbeat/last-seen).
Flows
Signup → OTP → onboarding
signup.php ──POST TYPEACTION=SIGNUP──▶ doaction_auth.php ▶ AuthModule::handleSignup (AuthHandler:95)
accept_policies=1 required · creates customer_master row (unverified) · issues OTP
▶ notification_otp_codes (code) · challenge in kv_auth_login_challenges
â–¶ DPDP log 'auth_signup*'
otpverification.php ─POST VERIFY_OTP─▶ handleVerifyOTP (AuthHandler:163)
on success: is_mobile_verified/is_email_verified set · redirect_url='onboarding.php?signup=1'
â–¶ DPDP log 'auth_verify_otp_success' (risk 18)
onboarding.php ─POST─▶ handleOnboardingSave (:1110) (profile completion, defaults)
Login
login.php ─POST LOGIN (accept_policies=1)─▶ AuthModule::handleLogin
â–¶ parent AuthHandler::handleLogin (:254): verify password_hash, rate-limit via kv_auth_login_attempts,
create kv_auth_sessions row (device fingerprint, risk_score), apply device policy (evict per limits)
on success: FriendsModule::applyAutoFollowSeeds(); DPDP 'auth_login_success' (risk 28)
on failure: DPDP 'auth_login_failed' (risk 68)
Multi-device policy (validated in memory + KvSessionDevicePolicy): koovira.com native allows 5 devices; each Koovira-OAuth partner app = 1 active session (2nd login evicts that app's session).
Forgot / reset / change password
FORGOT_PASSWORD → handleForgotPassword (:403) issues a reset (→ kv_auth_password_resets / password_reset_tokens, OTP/token) → RESET_PASSWORD → handleResetPassword (:488) → CHANGE_PASSWORD → handleChangePassword (:513, keeps password_history).
Device reset / 2FA / biometric
AUTH_DEVICE_RESET_REQUEST/VERIFY (:435/:444, device_reset_tokens), TWOFA_SEND/VERIFY (:453/:470), BIOMETRIC_LOGIN (:2203).
Logout
handleLogout (:2262) ends the current session; handleLogoutDevice (:2316) one device; handleLogoutAll (:2341) revokes all — all update kv_auth_sessions.status/logged_out_at.
Sub-profile toggle (identity)
handleToggleSubProfile (:618) activates/deactivates a professional/dating sub-profile row (the canonical TOGGLE_SUB_PROFILE path — do not build a new endpoint). Writes customer_master, professional_profile, dating_profiles.
Social login
Google via google_oauth_signup.php / api/google_oauth_init.php / google_signup_complete.php. OIDC provider side (Koovira as IdP for partner apps) uses oauth_clients + api/v1/auth/* — see the Developer Platform / SSO docs.
Referral attribution
Signup can carry a referral code → referral_codes (13) / user_referrals / referral_tracking; rewards ledger referral_reward_ledger (referral rewards were switched to wallet credit — see ../40-financial/ and memory referral-rewards-wallet-switch).
Business rules (validated)
BR-AUTH-001— signup and login requireaccept_policies=1(doaction_auth.php:84,286).BR-AUTH-002— every auth event is DPDP-logged with a risk score (AuthModule::kvDpdpLogAuthEvent).BR-AUTH-003— a session is polymorphic (actor_type+actor_id); never assume "user" — a
session may be a merchant/partner/padmin actor.
BR-AUTH-004— multi-device limits are enforced byKvSessionDevicePolicy; exceeding evicts the
oldest per bucket (5 for native Koovira; 1 per OAuth partner app).
Errors (prefix AUTH-E)
| ID | Trigger | Detection | Resolution |
|---|---|---|---|
AUTH-E001 | wrong password | handleLogin returns activi_status!=1; row in kv_auth_login_attempts | user retries / reset |
AUTH-E002 | OTP invalid/expired | handleVerifyOTP fail; check notification_otp_codes timestamp + kv_auth_login_challenges | RESEND_OTP |
AUTH-E003 | account locked (too many attempts) | kv_auth_login_attempts count; account_status | wait / device reset |
AUTH-E004 | device limit reached | session evicted; kv_auth_sessions status | expected; AUTH_DEVICE_PICK_LOGOUT |
AUTH-E005 | signup without policy accept | doaction_auth.php:84 | UI must send accept_policies=1 |
AUTH-E006 | "signup not working" = OTP shown as on-screen image vs emailed | env flag (see memory signup-otp-captcha-bypass) | set flag 0 after confirming email delivery |
Change-impact
Touches: sessions (device policy), the DPDP audit ledger, auto-follow (social graph), sub-profiles, referral rewards (wallet). Re-test: login/OTP happy path, device eviction, DPDP rows written, password_hash (not legacy password) is the column read/written.