Skip to content

Commit 44bce49

Browse files
ralyodioclaude
andcommitted
Release 0.2.1: say how long to wait between SMS codes
Pressing Send Code again inside Supabase's 60s resend window showed "Failed to send verification code. Please try again later.", which read as SMS being broken again. It now answers 429 "A code was just sent. Please wait N seconds..." with Retry-After. Also ships #275 (codes via Telnyx Verify, since carriers refuse our long code with 40010). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 8926ffd commit 44bce49

3 files changed

Lines changed: 32 additions & 1 deletion

File tree

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "qryptchat-web",
33
"private": true,
4-
"version": "0.2.0",
4+
"version": "0.2.1",
55
"type": "module",
66
"bin": {
77
"qc": "./bin/qc.js",

‎src/app/api/auth/send-sms/route.js‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,21 @@ export async function POST(request, { params } = {}) {
160160
logs: logger.getLogsAsString()
161161
});
162162

163+
// GoTrue's resend window ("...only request this after 33 seconds"): say
164+
// how long to wait instead of a failure that sounds like a broken service.
165+
const wait = /after (\d+) seconds?/i.exec(String(smsError.message || ''));
166+
if (wait) {
167+
const seconds = Number(wait[1]);
168+
return NextResponse.json(
169+
{
170+
error: `A code was just sent. Please wait ${seconds} second${seconds === 1 ? '' : 's'} before requesting another.`,
171+
code: 'SMS_RESEND_TOO_SOON',
172+
retryAfter: seconds
173+
},
174+
{ status: 429, headers: { 'Retry-After': String(seconds) } }
175+
);
176+
}
177+
163178
// Determine HTTP status code
164179
let statusCode = 500;
165180
if (smsError.status === 429) {

‎src/app/api/auth/send-sms/route.test.js‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,20 @@ describe('POST /api/auth/send-sms', () => {
6464
expect(mocks.checkDestination).not.toHaveBeenCalled();
6565
expect(mocks.createSupabaseServerClient).not.toHaveBeenCalled();
6666
});
67+
68+
it("says how long to wait when Supabase's resend window refuses a second code", async () => {
69+
const error = Object.assign(new Error('For security purposes, you can only request this after 33 seconds.'), { status: 429 });
70+
mocks.createSupabaseServerClient.mockResolvedValue({ auth: { signInWithOtp: vi.fn().mockResolvedValue({ error }) } });
71+
const { POST } = await import('./route.js');
72+
const response = await POST({
73+
headers: new Headers(),
74+
json: vi.fn().mockResolvedValue({ phoneNumber: '+14155550123' })
75+
});
76+
const body = await response.json();
77+
78+
expect(response.status).toBe(429);
79+
expect(response.headers.get('Retry-After')).toBe('33');
80+
expect(body).toMatchObject({ code: 'SMS_RESEND_TOO_SOON', retryAfter: 33 });
81+
expect(body.error).toBe('A code was just sent. Please wait 33 seconds before requesting another.');
82+
});
6783
});

0 commit comments

Comments
 (0)