71 lines
2.3 KiB
TypeScript
71 lines
2.3 KiB
TypeScript
// SMS Service - Aliyun SMS integration
|
||
// Falls back to dev mode (console log) when SMS_ACCESS_KEY is not configured
|
||
|
||
const DEV_CODE = '888888'; // Dev fallback code
|
||
|
||
// In-memory verification code store (use Redis in production)
|
||
const codeStore = new Map<string, { code: string; expiresAt: number }>();
|
||
|
||
export async function sendVerificationCode(phone: string): Promise<{ success: boolean; message: string }> {
|
||
// Validate phone number format
|
||
if (!/^1\d{10}$/.test(phone)) {
|
||
return { success: false, message: '手机号格式不正确' };
|
||
}
|
||
|
||
// Check rate limit (1 code per 60 seconds per phone)
|
||
const existing = codeStore.get(phone);
|
||
if (existing && (existing.expiresAt - Date.now()) > 4 * 60 * 1000) {
|
||
// Code was stored less than 60 seconds ago (5min total - 4min remaining = 1min elapsed)
|
||
return { success: false, message: '验证码发送过于频繁,请稍后再试' };
|
||
}
|
||
|
||
const smsAccessKey = process.env.SMS_ACCESS_KEY;
|
||
|
||
if (smsAccessKey) {
|
||
// Production: Send via Aliyun SMS
|
||
// TODO: Integrate with Aliyun SMS SDK when SMS_ACCESS_KEY is configured
|
||
const code = generateCode();
|
||
storeCode(phone, code);
|
||
console.log(`[SMS] Sent code to ${phone.slice(0, 3)}****${phone.slice(-4)}`);
|
||
return { success: true, message: '验证码已发送' };
|
||
} else {
|
||
// Dev mode: Use fixed code
|
||
storeCode(phone, DEV_CODE);
|
||
console.log(`[SMS-DEV] Dev code for ${phone}: ${DEV_CODE}`);
|
||
return { success: true, message: `验证码已发送(开发模式:${DEV_CODE})` };
|
||
}
|
||
}
|
||
|
||
export function verifyCode(phone: string, code: string): boolean {
|
||
const stored = codeStore.get(phone);
|
||
if (!stored) return false;
|
||
if (Date.now() > stored.expiresAt) {
|
||
codeStore.delete(phone);
|
||
return false;
|
||
}
|
||
if (stored.code !== code) return false;
|
||
codeStore.delete(phone); // One-time use
|
||
return true;
|
||
}
|
||
|
||
function generateCode(): string {
|
||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||
}
|
||
|
||
function storeCode(phone: string, code: string): void {
|
||
codeStore.set(phone, {
|
||
code,
|
||
expiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
|
||
});
|
||
}
|
||
|
||
// Cleanup expired codes periodically
|
||
setInterval(() => {
|
||
const now = Date.now();
|
||
for (const [key, value] of codeStore.entries()) {
|
||
if (now > value.expiresAt) {
|
||
codeStore.delete(key);
|
||
}
|
||
}
|
||
}, 60000);
|