feat(auth): mobile-only register/login with OTP verification
- Register and login by mobile number; email is now an optional contact field only (never used to authenticate) - After registration, the phone is verified via a 6-digit SMS code - Login supports both password and one-time-code (OTP) methods - Phone OTP delivered via Kavenegar (verify/lookup); API key in env - Account page: edit name/optional email, change password, and change mobile number with OTP re-verification - Codes are hashed, expire in 5m, capped at 5 attempts, rate-limited - Seed gives the admin a verified phone so mobile login still works Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -120,11 +120,20 @@ const en: Dictionary = {
|
||||
backHome: 'Back to home',
|
||||
login: {
|
||||
title: 'Welcome back',
|
||||
subtitle: 'Sign in to your Abrban account',
|
||||
subtitle: 'Sign in to your Abrban account with your mobile',
|
||||
altPrompt: 'Don’t have an account yet?',
|
||||
altLabel: 'Sign up',
|
||||
email: 'Email',
|
||||
tabPassword: 'With password',
|
||||
tabOtp: 'With one-time code',
|
||||
phone: 'Mobile number',
|
||||
password: 'Password',
|
||||
otpHint: 'A verification code will be texted to your mobile.',
|
||||
sendCode: 'Send code',
|
||||
sending: 'Sending…',
|
||||
codeSent: 'Verification code sent',
|
||||
verifyNeeded: 'Verify your mobile number to continue.',
|
||||
otpTitle: 'Verify mobile number',
|
||||
otpSubtitle: 'Enter the 6-digit code we texted you',
|
||||
submit: 'Sign in',
|
||||
submitting: 'Signing in…',
|
||||
success: 'Signed in successfully!',
|
||||
@@ -139,13 +148,25 @@ const en: Dictionary = {
|
||||
firstNamePlaceholder: 'e.g. Ali',
|
||||
lastName: 'Last name',
|
||||
lastNamePlaceholder: 'e.g. Rezaei',
|
||||
email: 'Email',
|
||||
phone: 'Mobile number',
|
||||
emailOptional: 'Email (optional)',
|
||||
password: 'Password',
|
||||
passwordPlaceholder: 'At least 8 characters',
|
||||
submit: 'Create account',
|
||||
submitting: 'Creating account…',
|
||||
success: 'Account created successfully!',
|
||||
codeSent: 'A verification code was texted to your mobile',
|
||||
success: 'Account created and verified!',
|
||||
error: 'Sign up failed',
|
||||
verifyError: 'Code verification failed',
|
||||
},
|
||||
otp: {
|
||||
sentTo: 'A verification code was texted to:',
|
||||
codeLabel: 'Verification code',
|
||||
verify: 'Verify and continue',
|
||||
verifying: 'Verifying…',
|
||||
back: 'Back',
|
||||
resend: 'Resend code',
|
||||
resendIn: 'Resend in {s}s',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -391,6 +412,7 @@ const en: Dictionary = {
|
||||
wallet: 'Wallet',
|
||||
invoices: 'Invoices',
|
||||
tickets: 'Tickets',
|
||||
account: 'My Account',
|
||||
users: 'Users',
|
||||
allApplications: 'All Applications',
|
||||
billingPlans: 'Billing Plans',
|
||||
@@ -402,6 +424,48 @@ const en: Dictionary = {
|
||||
},
|
||||
|
||||
dashboard: {
|
||||
account: {
|
||||
title: 'My Account',
|
||||
subtitle: 'Manage your account details, mobile number and password.',
|
||||
personalInfo: 'Personal information',
|
||||
firstName: 'First name',
|
||||
lastName: 'Last name',
|
||||
emailOptional: 'Email (optional)',
|
||||
emailHint: 'Email is for contact only and is not used to sign in.',
|
||||
save: 'Save',
|
||||
saving: 'Saving…',
|
||||
savedName: 'Profile saved successfully',
|
||||
verified: 'Verified',
|
||||
unverified: 'Unverified',
|
||||
notSet: 'Not set',
|
||||
verifyNow: 'Verify',
|
||||
currentPassword: 'Current password',
|
||||
passwordReason: 'Your current password is required to change your mobile number.',
|
||||
sendCode: 'Send code',
|
||||
sending: 'Sending…',
|
||||
codeSent: 'Verification code sent',
|
||||
codeSentTo: 'A verification code was texted to:',
|
||||
enterCode: 'Verification code',
|
||||
confirm: 'Confirm',
|
||||
verifying: 'Verifying…',
|
||||
resend: 'Resend code',
|
||||
cancel: 'Cancel',
|
||||
phoneUpdated: 'Mobile number updated successfully',
|
||||
changePassword: 'Change password',
|
||||
newPassword: 'New password',
|
||||
confirmPassword: 'Confirm new password',
|
||||
passwordMismatch: 'New password and confirmation do not match',
|
||||
passwordChanged: 'Password changed successfully',
|
||||
passwordHint: 'At least 8 characters',
|
||||
errorGeneric: 'Operation failed',
|
||||
phone: {
|
||||
label: 'Mobile number',
|
||||
empty: 'No number set',
|
||||
add: 'Add number',
|
||||
change: 'Change number',
|
||||
newLabel: 'New mobile number',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
running: 'Running',
|
||||
pending: 'Pending',
|
||||
@@ -679,6 +743,9 @@ const en: Dictionary = {
|
||||
notAvailableMessage: 'Central Elasticsearch is not deployed on the cluster. Enable the logging addon when deploying an app, and ask an administrator to deploy the logging stack.',
|
||||
checkingConnection: 'Checking connection…',
|
||||
retryingAuto: 'Retrying automatically every few seconds.',
|
||||
elasticDisabledTitle: 'Elasticsearch logging is off',
|
||||
elasticDisabledMessage: 'You did not enable Elasticsearch for this application when creating it, so centralized logs are unavailable. Enable the Elasticsearch addon when deploying a new app to see its logs here.',
|
||||
elasticDisabledMessageAll: 'None of your applications have Elasticsearch enabled, so centralized logs are unavailable. Enable the Elasticsearch addon when deploying a new app to see its logs here.',
|
||||
},
|
||||
users: {
|
||||
title: 'User Management',
|
||||
|
||||
Reference in New Issue
Block a user