Skip to content

Harden email OTP verification#1582

Merged
riderx merged 5 commits intomainfrom
riderx/security-report
Feb 5, 2026
Merged

Harden email OTP verification#1582
riderx merged 5 commits intomainfrom
riderx/security-report

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Feb 5, 2026

Summary (AI generated)\n\n- Route email OTP verification through a private function and return the verified timestamp\n\n## Motivation (AI generated)\n\n- Require an OTP-authenticated session before recording verification\n\n## Business Impact (AI generated)\n\n- Reduces risk of spoofed OTP verification records\n\n## Test plan (AI generated)\n\n- [x] bun run lint:backend\n- [x] bun run lint\n\n## Screenshots (AI generated)\n\n- N/A\n\n## Checklist (AI generated)\n\n- [x] My code follows the code style of this project and passes\n .\n- [ ] My change requires a change to the documentation.\n- [ ] I have updated the documentation\n accordingly.\n- [ ] My change has adequate E2E test coverage.\n- [ ] I have tested my code manually, and I have provided steps how to reproduce\n my tests\n\nGenerated with AI\n

Summary by CodeRabbit

  • New Features

    • OTP email verification now uses a secure server-side verification endpoint for stronger integrity.
  • Bug Fixes

    • Improved validation and error handling for OTP submission to reduce false failures and mismatched-user cases.
    • Successful OTP verifications are now reliably recorded and reflected in account settings.
  • Tests

    • Added end-to-end tests covering OTP success, invalid/expired tokens, missing auth, and user mismatch scenarios.

Copilot AI review requested due to automatic review settings February 5, 2026 04:14
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 5, 2026

Warning

Rate limit exceeded

@riderx has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 0 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Replaces client-side OTP verification and separate RPC recording with a single serverless endpoint (/private/verify_email_otp) that verifies the OTP using JWT-authenticated context, records verification via a Postgres RPC, and returns a verified_at timestamp to the client.

Changes

Cohort / File(s) Summary
Frontend
src/pages/settings/account/index.vue
Switched client verification to call functions.invoke('private/verify_email_otp'); removed direct RPC recording and adjusted success/error handling to use data.verified_at.
Serverless function
supabase/functions/_backend/private/verify_email_otp.ts, supabase/functions/private/index.ts, cloudflare_workers/api/index.ts
Added Hono POST /verify_email_otp handler with body validation, JWT auth extraction, Supabase OTP verification, owner-mismatch checks, and call to record_email_otp_verified; route registered and exposed via Cloudflare worker.
Database migration
supabase/migrations/20260205031305_mfa_email_otp_hardening.sql
New record_email_otp_verified() PL/pgSQL function that enforces OTP-based auth (amr check), upserts user_security.email_otp_verified_at, returns timestamp, and grants EXECUTE to relevant roles.
Tests
tests/verify-email-otp.test.ts
New test suite for /private/verify_email_otp covering success, DB persistence, and failure cases (invalid body, missing auth, invalid/expired OTP, OTP-user mismatch).
Manifest
package.json
Minor package manifest edits (small line additions/removals).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant CF as Cloudflare Worker
  participant Fn as verify_email_otp (Hono)
  participant Auth as Supabase Auth
  participant DB as Postgres (record_email_otp_verified)

  Client->>CF: POST /private/verify_email_otp { token } + Authorization
  CF->>Fn: forward request
  Fn->>Auth: verifyOtp(email from JWT, token)
  Auth-->>Fn: verification result (owner info)
  alt OTP verified and owner matches JWT
    Fn->>DB: call record_email_otp_verified()
    DB-->>Fn: returns verified_at
    Fn-->>Client: 200 { verified_at }
  else verification fails or mismatch
    Fn-->>Client: 4xx error (invalid_otp / otp_user_mismatch / auth error)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

💰 Rewarded

Poem

🐰 I hopped with a token held snug and small,
Through cloud-lit tunnels and a serverless hall,
One whisper to functions, one stamp on the ground,
A verified heartbeat — secure and sound.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Harden email OTP verification' directly aligns with the main change: implementing enhanced security for email OTP verification through a private function with JWT authentication and RPC validation.
Description check ✅ Passed The description covers summary, motivation, and business impact clearly, with a test plan provided. However, it lacks detail in the manual test steps section, and the E2E test coverage checkbox is unchecked despite a new test suite being added.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch riderx/security-report

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a security hardening for email OTP verification used in the MFA enrollment flow. The change moves OTP verification from client-side to a new backend endpoint that validates the OTP token and enforces that only OTP-authenticated sessions can record verification timestamps.

Changes:

  • Adds a new database function that validates OTP authentication before recording verification timestamps
  • Creates a new backend endpoint /private/verify_email_otp to handle OTP verification securely
  • Updates the frontend to call the new backend endpoint instead of direct Supabase auth calls

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
supabase/migrations/20260205031305_mfa_email_otp_hardening.sql Replaces the record_email_otp_verified function to add OTP authentication requirement by checking JWT 'amr' claim for 'otp' method
supabase/functions/_backend/private/verify_email_otp.ts New backend endpoint that validates OTP tokens, verifies user identity, obtains OTP-authenticated session, and calls the database function
supabase/functions/private/index.ts Adds routing for the new verify_email_otp endpoint in Supabase Edge Functions deployment
src/pages/settings/account/index.vue Updates frontend to use the new backend endpoint instead of direct Supabase auth calls

Comment on lines +16 to +66
app.post('/', middlewareAuth, async (c) => {
const rawBody = await parseBody<{ token?: string }>(c)
const token = rawBody.token?.replaceAll(' ', '') ?? ''

const validation = bodySchema.safeParse({ token })
if (!validation.success) {
throw simpleError('invalid_body', 'Invalid request body', { errors: z.prettifyError(validation.error) })
}

const auth = c.get('auth')
if (!auth?.userId) {
return quickError(401, 'not_authenticated', 'Not authenticated')
}

const authorization = c.get('authorization')
if (!authorization) {
return quickError(401, 'no_authorization', 'No authorization header provided')
}

const claims = getClaimsFromJWT(authorization)
if (!claims?.email) {
return quickError(400, 'missing_email', 'Email is required to verify OTP')
}

const supabase = emptySupabase(c)
const { data: verifyData, error: verifyError } = await supabase.auth.verifyOtp({
email: claims.email,
token,
type: 'email',
})

if (verifyError || !verifyData?.session?.access_token) {
cloudlog({ requestId: c.get('requestId'), context: 'verify_email_otp - verifyOtp failed', error: verifyError?.message })
return quickError(401, 'invalid_otp', 'Invalid or expired OTP')
}

if (verifyData.user?.id && verifyData.user.id !== auth.userId) {
return quickError(403, 'otp_user_mismatch', 'OTP does not match current user')
}

const otpSupabase = supabaseClient(c, `Bearer ${verifyData.session.access_token}`)
const { data: verifiedAt, error: recordError } = await otpSupabase
.rpc('record_email_otp_verified')

if (recordError || !verifiedAt) {
cloudlog({ requestId: c.get('requestId'), context: 'verify_email_otp - record failed', error: recordError?.message })
return quickError(500, 'record_failed', 'Failed to record OTP verification')
}

return c.json({ verified_at: verifiedAt })
})
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new security-critical endpoint lacks test coverage. Given the comprehensive test suite in the tests/ directory (e.g., tests/private-error-cases.test.ts, tests/password-policy.test.ts), this endpoint should have tests covering:

  1. Successful OTP verification flow
  2. Invalid token format validation
  3. Missing authentication header
  4. Email mismatch scenarios
  5. Invalid/expired OTP codes
  6. User ID mismatch between token and session
  7. Integration with the database function

The tests should verify both the Supabase Edge Functions and Cloudflare Workers deployments (using USE_CLOUDFLARE_WORKERS=true).

Copilot uses AI. Check for mistakes.
import { app as stripe_portal } from '../_backend/private/stripe_portal.ts'
import { app as upload_link } from '../_backend/private/upload_link.ts'
import { app as validate_password_compliance } from '../_backend/private/validate_password_compliance.ts'
import { app as verify_email_otp } from '../_backend/private/verify_email_otp.ts'
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new verify_email_otp endpoint needs to be imported and routed in the Cloudflare Workers API file. The endpoint is correctly added to the Supabase private/index.ts, but it's missing from cloudflare_workers/api/index.ts.

You need to:

  1. Add the import at the top of cloudflare_workers/api/index.ts
  2. Add the route to the appPrivate router (around line 86)

This is critical because according to the project architecture, 99% of production traffic runs on Cloudflare Workers (ports 8787/8788/8789 locally), and the backend code must be deployed to all platforms.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@supabase/functions/_backend/private/verify_email_otp.ts`:
- Around line 56-59: The CI failure is due to the new RPC
"record_email_otp_verified" not being present in the generated Supabase types,
causing the call otpSupabase.rpc('record_email_otp_verified') to fail
type-checking; regenerate or update ~/types/supabase.types (and any backend type
files that import it) so Database['public']['Functions'] includes
"record_email_otp_verified", then re-run type generation and ensure the function
name is recognized where supabaseClient(...) and otpSupabase.rpc(...) are used.
- Around line 1-13: The file initializes the app with new Hono(), which bypasses
shared backend setup; replace that with the shared factory by importing and
using createHono instead. Update the top imports to include createHono from
'../utils/hono.ts' (alongside existing imports like middlewareAuth, parseBody)
and change the exported app instantiation from new
Hono<MiddlewareKeyVariables>() to createHono<MiddlewareKeyVariables>() so this
endpoint uses the common middleware/error handling.
- Around line 25-33: The code currently trusts c.get('auth')?.userId without
verifying authType; update the guard in verify_email_otp handler to check
c.get('auth')?.authType === 'jwt' (in addition to presence of auth.userId) and
return quickError(401, 'not_authenticated', 'Not authenticated') for non-jwt
auth (e.g., apikey or missing authType), so only JWT-authenticated requests
proceed; apply this check where auth is read (the const auth = c.get('auth')
block) and adjust the subsequent authorization flow accordingly.

Comment on lines +56 to +59
const otpSupabase = supabaseClient(c, `Bearer ${verifyData.session.access_token}`)
const { data: verifiedAt, error: recordError } = await otpSupabase
.rpc('record_email_otp_verified')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

CI failure: record_email_otp_verified missing from generated RPC types.

The pipeline error indicates the RPC name isn’t in the Database['public']['Functions'] union, so rpc() fails type-checking. Regenerate/update ~/types/supabase.types (and any backend types that import it) to include the new function.

🧰 Tools
🪛 GitHub Actions: Run tests

[error] 58-58: Command failed: vue-tsc --noEmit. TS2345: Argument of type '"record_email_otp_verified"' is not assignable to parameter of type '"accept_invitation_to_org" | "apply_usage_overage" | "calculate_credit_cost" | "check_min_rights" | "check_min_rights_legacy" | "check_org_encrypted_bundle_enforcement" | ... 256 more ... | "verify_mfa"'.

🤖 Prompt for AI Agents
In `@supabase/functions/_backend/private/verify_email_otp.ts` around lines 56 -
59, The CI failure is due to the new RPC "record_email_otp_verified" not being
present in the generated Supabase types, causing the call
otpSupabase.rpc('record_email_otp_verified') to fail type-checking; regenerate
or update ~/types/supabase.types (and any backend type files that import it) so
Database['public']['Functions'] includes "record_email_otp_verified", then
re-run type generation and ensure the function name is recognized where
supabaseClient(...) and otpSupabase.rpc(...) are used.

@riderx riderx force-pushed the riderx/security-report branch 2 times, most recently from fd7819b to 6c678b0 Compare February 5, 2026 05:26
@riderx riderx force-pushed the riderx/security-report branch from 2f34287 to cfce68d Compare February 5, 2026 19:06
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@tests/verify-email-otp.test.ts`:
- Around line 4-6: Replace the generic OTHER_USER_EMAIL constant with a
file-prefixed unique address to avoid cross-test collisions; update the
OTHER_USER_EMAIL declaration in tests/verify-email-otp.test.ts (near
OTP_ENDPOINT and OTHER_USER_EMAIL) to use a name that includes the test file
identifier, e.g., "verify_email_otp_test_user@capgo.app", and ensure any tests
referencing OTHER_USER_EMAIL continue to use that new value.
- Around line 1-3: Change the non-concurrent test to run in parallel by
replacing the use of it() with it.concurrent() for the test in this file; also
make the shared test user email unique to this file by updating the
OTHER_USER_EMAIL constant value to include the test file prefix (e.g.,
"test_verify_email_otp_<original_local>@capgo.app") so the mutations to
user_security for OTHER_USER_EMAIL won't collide across files; locate and update
the OTHER_USER_EMAIL constant and the test declaration (it -> it.concurrent) in
tests/verify-email-otp.test.ts to apply these fixes.
- Around line 18-49: The test mutates shared user_security state and is not
concurrent-safe; create a dedicated test user (constants like
USER_EMAIL_VERIFY_OTP and USER_ID_VERIFY_OTP) in test-utils.ts mirroring the
existing USER_ID_EMAIL_PREFS/USER_ID_STATS patterns, add corresponding seed rows
in supabase/seed.sql (including a user_security row for that user), then update
the test in verify-email-otp.test.ts to reference
USER_EMAIL_VERIFY_OTP/USER_ID_VERIFY_OTP (instead of USER_EMAIL/USER_ID), call
generateEmailOtp(USER_EMAIL_VERIFY_OTP) and getAuthHeaders() for that user, and
change the spec to it.concurrent('verifies OTP and records verification
timestamp', async () => ...) so it can run in parallel safely while still
asserting the DB email_otp_verified_at via getSupabaseClient().

Comment on lines +1 to +3
import { beforeAll, describe, expect, it } from 'vitest'
import { getAuthHeaders, getEndpointUrl, getSupabaseClient, USER_EMAIL, USER_ID } from './test-utils.ts'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for USE_CLOUDFLARE_WORKERS configuration in the repository
rg -n 'USE_CLOUDFLARE_WORKERS' -S --type ts --type js --type json

Repository: Cap-go/capgo

Length of output: 668


🏁 Script executed:

# Find and view the test file
fd 'verify-email-otp.test.ts' --type f

Repository: Cap-go/capgo

Length of output: 86


🏁 Script executed:

# Check vitest configuration
fd -e 'vitest.config' -e 'vite.config' -e 'vitest.setup' --type f

Repository: Cap-go/capgo

Length of output: 38


🏁 Script executed:

# View test-utils.ts to understand helper setup
fd 'test-utils.ts' --type f --exec cat -n {} \;

Repository: Cap-go/capgo

Length of output: 27095


🏁 Script executed:

cat -n tests/verify-email-otp.test.ts

Repository: Cap-go/capgo

Length of output: 4120


Use it.concurrent() and file-prefixed unique test data for shared resources.

The USE_CLOUDFLARE_WORKERS flag is already configured in vitest.config.cloudflare.ts and cloudflare-plugin.ts, so your getEndpointUrl() calls will route correctly to Cloudflare Workers.

However, the test has two design issues:

  1. Line 25 uses it() instead of it.concurrent(). Per guidelines, all tests should use it.concurrent() to run in parallel within the same file.
  2. Line 5's OTHER_USER_EMAIL = 'test2@capgo.app' should be prefixed with the test file name (e.g., 'test_verify_email_otp_test2@capgo.app'). The first test (lines 25–49) mutates the user_security table for this user; without unique file-prefixed naming, parallel test execution risks interference with other test files using the same email.
🤖 Prompt for AI Agents
In `@tests/verify-email-otp.test.ts` around lines 1 - 3, Change the non-concurrent
test to run in parallel by replacing the use of it() with it.concurrent() for
the test in this file; also make the shared test user email unique to this file
by updating the OTHER_USER_EMAIL constant value to include the test file prefix
(e.g., "test_verify_email_otp_<original_local>@capgo.app") so the mutations to
user_security for OTHER_USER_EMAIL won't collide across files; locate and update
the OTHER_USER_EMAIL constant and the test declaration (it -> it.concurrent) in
tests/verify-email-otp.test.ts to apply these fixes.

Comment on lines +4 to +6
const OTP_ENDPOINT = '/private/verify_email_otp'
const OTHER_USER_EMAIL = 'test2@capgo.app'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use a file-prefixed unique email for test users.

OTHER_USER_EMAIL = 'test2@capgo.app' is generic and can collide across tests. Please switch to a unique, file-prefixed address.

🔧 Suggested update
-const OTHER_USER_EMAIL = 'test2@capgo.app'
+const OTHER_USER_EMAIL = 'verify_email_otp_test2@capgo.app'
Based on learnings, When tests interact with shared resources (users, apps, orgs, devices, channels, bundles), use unique naming conventions prefixed with the test file name (e.g., `test_my_feature_user@capgo.app`).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const OTP_ENDPOINT = '/private/verify_email_otp'
const OTHER_USER_EMAIL = 'test2@capgo.app'
const OTP_ENDPOINT = '/private/verify_email_otp'
const OTHER_USER_EMAIL = 'verify_email_otp_test2@capgo.app'
🤖 Prompt for AI Agents
In `@tests/verify-email-otp.test.ts` around lines 4 - 6, Replace the generic
OTHER_USER_EMAIL constant with a file-prefixed unique address to avoid
cross-test collisions; update the OTHER_USER_EMAIL declaration in
tests/verify-email-otp.test.ts (near OTP_ENDPOINT and OTHER_USER_EMAIL) to use a
name that includes the test file identifier, e.g.,
"verify_email_otp_test_user@capgo.app", and ensure any tests referencing
OTHER_USER_EMAIL continue to use that new value.

Comment on lines +18 to +49
describe('[POST] /private/verify_email_otp', () => {
let authHeaders: Record<string, string>

beforeAll(async () => {
authHeaders = await getAuthHeaders()
})

it('verifies OTP and records verification timestamp', async () => {
const otp = await generateEmailOtp(USER_EMAIL)

const response = await fetch(getEndpointUrl(OTP_ENDPOINT), {
method: 'POST',
headers: authHeaders,
body: JSON.stringify({ token: otp, type: 'magiclink' }),
})

expect(response.status).toBe(200)
const data = await response.json() as { verified_at?: string }
expect(data.verified_at).toBeTruthy()

const { data: securityRow, error } = await getSupabaseClient()
.from('user_security')
.select('email_otp_verified_at')
.eq('user_id', USER_ID)
.single()

// Use the email match to locate the user record (auth headers already tied to USER_EMAIL)
if (error) {
throw error
}
expect(securityRow?.email_otp_verified_at).toBeTruthy()
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n tests/verify-email-otp.test.ts

Repository: Cap-go/capgo

Length of output: 4120


🏁 Script executed:

cat -n tests/test-utils.ts | head -100

Repository: Cap-go/capgo

Length of output: 7322


🏁 Script executed:

cat -n tests/test-utils.ts | grep -A 5 -B 5 "USER_EMAIL"

Repository: Cap-go/capgo

Length of output: 2708


🏁 Script executed:

cat -n tests/test-utils.ts | sed -n '130,160p'

Repository: Cap-go/capgo

Length of output: 1128


🏁 Script executed:

rg "USER_EMAIL|USER_ID" tests/ -t ts --max-count=50 | head -60

Repository: Cap-go/capgo

Length of output: 5170


🏁 Script executed:

rg "verify.*email|email.*otp" tests/ -t ts -i

Repository: Cap-go/capgo

Length of output: 1210


🏁 Script executed:

rg "generateEmailOtp|email_otp|verify_email_otp" tests/ -t ts

Repository: Cap-go/capgo

Length of output: 791


🏁 Script executed:

head -200 supabase/seed.sql | grep -A 5 -B 5 "user_security\|email_otp"

Repository: Cap-go/capgo

Length of output: 38


🏁 Script executed:

find . -name "seed.sql" -o -name "*.sql" | grep -E "seed|init" | head -10

Repository: Cap-go/capgo

Length of output: 194


🏁 Script executed:

cat supabase/seed.sql | grep -A 10 -B 5 "email_otp\|test@capgo.app" | head -80

Repository: Cap-go/capgo

Length of output: 10860


🏁 Script executed:

grep -n "user_security\|email_otp_verified" supabase/seed.sql

Repository: Cap-go/capgo

Length of output: 38


🏁 Script executed:

grep -n "CREATE TABLE.*user_security\|INSERT INTO.*user_security" supabase/migrations/*.sql | head -20

Repository: Cap-go/capgo

Length of output: 487


🏁 Script executed:

grep -A 20 "CREATE TABLE.*user_security" supabase/migrations/20260204103000_mfa_email_otp_guard.sql

Repository: Cap-go/capgo

Length of output: 891


Switch to concurrent execution and isolate user seed data.

This test mutates user_security for shared USER_EMAIL/USER_ID and uses it() instead of it.concurrent(). Create dedicated user constants in test-utils.ts (following the pattern of USER_ID_EMAIL_PREFS, USER_ID_STATS, etc.) and seed them in supabase/seed.sql, then update the test to use it.concurrent().

♻️ Suggested change (after isolation)
-  it('verifies OTP and records verification timestamp', async () => {
+  it.concurrent('verifies OTP and records verification timestamp', async () => {
🤖 Prompt for AI Agents
In `@tests/verify-email-otp.test.ts` around lines 18 - 49, The test mutates shared
user_security state and is not concurrent-safe; create a dedicated test user
(constants like USER_EMAIL_VERIFY_OTP and USER_ID_VERIFY_OTP) in test-utils.ts
mirroring the existing USER_ID_EMAIL_PREFS/USER_ID_STATS patterns, add
corresponding seed rows in supabase/seed.sql (including a user_security row for
that user), then update the test in verify-email-otp.test.ts to reference
USER_EMAIL_VERIFY_OTP/USER_ID_VERIFY_OTP (instead of USER_EMAIL/USER_ID), call
generateEmailOtp(USER_EMAIL_VERIFY_OTP) and getAuthHeaders() for that user, and
change the spec to it.concurrent('verifies OTP and records verification
timestamp', async () => ...) so it can run in parallel safely while still
asserting the DB email_otp_verified_at via getSupabaseClient().

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Feb 5, 2026

@riderx riderx merged commit 9cd88a1 into main Feb 5, 2026
11 checks passed
@riderx riderx deleted the riderx/security-report branch February 5, 2026 21:38
@riderx
Copy link
Copy Markdown
Member Author

riderx commented Feb 5, 2026

/tip $250 @nancyhunter191

@algora-pbc
Copy link
Copy Markdown

algora-pbc bot commented Feb 5, 2026

🎉🎈 @nancyhunter191 has been awarded $250 by Capgo! 🎈🎊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants