feat: mobile OTP login for Indian phone numbers#5
Merged
Conversation
Phone-number-based sign-up and login via 6-digit SMS OTP (MSG91), with dedicated phone_otp_codes table and post-verify name collection for new users.
Address architect, critic, and QA lead review feedback: - Specify User changeset split (phone_registration_changeset) - Add phone to Jason.Encoder derive list - Wrap verify in transaction with SELECT FOR UPDATE - Invalidate old OTPs on resend - Add SMS behaviour + test/dev/prod adapters - India-only phone validation with normalization module - Handle MSG91 delivery failure (503) - Acknowledge duplicate account limitation - Expand test plan from 2 files to comprehensive coverage - Add frontend error handling per 429 variant - Add phone to User type in domain.ts
10-task TDD plan covering migration, phone normalization, PhoneOtpCode schema, User schema changes, SMS provider behaviour, Auth context OTP functions, controller endpoints, and Vue frontend.
Pure Moth.Auth.Phone.normalize/1 that strips formatting characters and
returns E.164 (+91XXXXXXXXXX) for Indian mobile numbers (6-9 prefix),
rejecting invalid/non-Indian input with {:error, :invalid_phone}.
- Make User.email nullable and add optional phone field - Add requestOtp and verifyOtp methods to api.auth - Replace Auth.vue with phone/email tab switcher: phone entry → OTP entry (with 30s resend cooldown and attempt-count errors) → name capture for new users; email tab retains magic link + Google OAuth
- Wrap verify_phone_otp in a single Repo.transaction with FOR UPDATE locking to eliminate TOCTOU race on concurrent verify requests - Use Plug.Crypto.secure_compare for timing-safe hash comparison - Add global attempt counter (sum across all OTP rows in 10-min window, cap at 5) to prevent attempt-count reset via resend abuse - Handle concurrent-insert race in find_or_create_phone_user by catching unique constraint violation and retrying lookup - Delete OTP row on SMS delivery failure so it doesn't count against the rate limit - Add fallback clauses for request_otp/verify_otp to return 422 instead of 500 when phone/code params are missing
hashd
added a commit
that referenced
this pull request
Apr 13, 2026
- Move token from localStorage to sessionStorage to limit exposure (#5) - Enforce requiresHost route guard with server-side check (#20) - Compensate for client/server clock skew in countdown timer (#34)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
phone_otp_codestable with hashed OTP storage, attempt limiting (3 per OTP, 5 global per 10min), and rate limiting (3 requests per 10min)SMSProviderbehaviour with swappable adapters: MSG91 (prod), Log (dev), Test (tests)Security
Plug.Crypto.secure_compareSELECT ... FOR UPDATEin transaction prevents TOCTOU race on concurrent verifyTest plan
MSG91_AUTH_KEYandMSG91_TEMPLATE_IDenv vars