Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ JWT_SECRET="your-random-secret-here"
PORT=3000
NODE_ENV=development
JWT_REFRESH_SECRET=your-refresh-secret-here

# Add these lines to apps/api/.env.example
MSG91_AUTH_KEY=your_msg91_auth_key
MSG91_SENDER_ID=VAASTIO
MSG91_TEMPLATE_ID=your_template_id
5 changes: 1 addition & 4 deletions apps/api/src/routes/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,7 @@ router.post('/request-otp', otpRateLimit, async (req: AuthRequest, res: Response
})

// 5. send SMS
const smsResult = await sendOtp(normalizedPhone, otp)
if (!smsResult.success) {
return sendError(res, 'sms_failed', 500)
}
await sendOtp(normalizedPhone, otp)

// 6. respond — never return OTP in response
return sendSuccess(res, {
Expand Down
68 changes: 52 additions & 16 deletions apps/api/src/utils/sms.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,59 @@
interface SmsResult {
success: boolean
error?: string
}
// ─────────────────────────────────────────────
// SMS Utility
// Development: logs OTP to console
// Production: sends real SMS via MSG91
// ─────────────────────────────────────────────

const MSG91_AUTH_KEY = process.env.MSG91_AUTH_KEY
const MSG91_SENDER_ID = process.env.MSG91_SENDER_ID || 'VAASTIO'
const MSG91_TEMPLATE_ID = process.env.MSG91_TEMPLATE_ID

export const sendOtp = async (
phone: string,
message: string
): Promise<SmsResult> => {
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
console.log(`\n--------------------------`)
console.log(`SMS for ${phone}: ${message}`)
console.log(`--------------------------\n`)
return { success: true }
otp: string
): Promise<void> => {
if (process.env.NODE_ENV !== 'production') {
// Development and test — never use real SMS credits
console.log('--------------------------')
console.log(`SMS for ${phone}: ${otp}`)
console.log('--------------------------')
return
}

// Production: wire in SMS provider here
// Example: MSG91, Fast2SMS, Twilio
// const response = await msg91.send(phone, otp)
// return { success: true }
// Production — send real SMS via MSG91
try {
const mobile = phone.replace('+', '')

const payload = {
template_id: MSG91_TEMPLATE_ID,
short_url: '0',
realTimeResponse: '1',
recipients: [
{
mobiles: mobile,
OTP: otp
}
]
}

const response = await fetch('https://control.msg91.com/api/v5/flow/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'authkey': MSG91_AUTH_KEY || ''
},
body: JSON.stringify(payload)
})

return { success: false, error: 'sms_provider_not_configured' }
const data = await response.json()

if (!response.ok || data.type === 'error') {
console.error('MSG91 error:', data)
}

} catch (error) {
// Never throw — SMS failure must not crash main flow
console.error('SMS send failed:', error)
}
}
Loading