feat(auth): GitHub OAuth login + SMS one-time-code login
Some checks failed
Deploy to Production / deploy (push) Failing after 1m8s
Some checks failed
Deploy to Production / deploy (push) Failing after 1m8s
GitHub: /v1/auth/github + /callback — authorization-code flow, fetches the verified primary email via /user/emails, reuses upsertOAuthLogin. SMS: phone is now a first-class login identity. - schema: users.email nullable, users.phone added, new sms_codes table. - @bmm/auth: issueSmsCode / consumeSmsCode — 6-digit code, hashed at rest, 10-min TTL, per-phone rate limit, 5-attempt cap, get-or-create user by phone. - apps/api: /v1/auth/sms/request + /verify, Twilio REST send (no SDK), per-IP throttle. /v1/auth/providers now reports google/github/sms. - login UI: Google + GitHub buttons, Email|Phone toggle, two-step SMS (number -> 6-digit code with one-time-code autofill). SMS link was rejected in favour of an OTP code — carrier link-scanners consume magic-link tokens before the user taps them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
30
apps/api/src/lib/sms.ts
Normal file
30
apps/api/src/lib/sms.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { config } from '../config.js';
|
||||
|
||||
/** True when Twilio credentials + a sender number are all configured. */
|
||||
export function smsConfigured(): boolean {
|
||||
return Boolean(config.TWILIO_ACCOUNT_SID && config.TWILIO_AUTH_TOKEN && config.TWILIO_SMS_FROM);
|
||||
}
|
||||
|
||||
/** Send an SMS via the Twilio REST API (no SDK — a single authenticated POST). */
|
||||
export async function sendSms(to: string, body: string): Promise<void> {
|
||||
const { TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_SMS_FROM } = config;
|
||||
if (!TWILIO_ACCOUNT_SID || !TWILIO_AUTH_TOKEN || !TWILIO_SMS_FROM) {
|
||||
throw new Error('sms_not_configured');
|
||||
}
|
||||
const auth = Buffer.from(`${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}`).toString('base64');
|
||||
const res = await fetch(
|
||||
`https://api.twilio.com/2010-04-01/Accounts/${TWILIO_ACCOUNT_SID}/Messages.json`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
authorization: `Basic ${auth}`,
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({ To: to, From: TWILIO_SMS_FROM, Body: body }),
|
||||
},
|
||||
);
|
||||
if (!res.ok) {
|
||||
const detail = await res.text().catch(() => '');
|
||||
throw new Error(`twilio_${res.status}: ${detail.slice(0, 180)}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user