Email verification token CRUD backed by SQLite (migration 003).
Encapsulates all verification_tokens queries so route handlers never
write raw SQL — per AGENT.md: "Do not write raw SQL in route handlers."
Methods
(static) claim(token) → {Object|null}
Atomically claim a token — marks it as used only if it is still unused and
not expired. Returns the token row on success, or null if the token was
missing, already used, or expired.
This eliminates the TOCTOU race between SELECT and UPDATE by performing a
single atomic UPDATE and checking changes > 0.
Parameters:
| Name | Type | Description |
|---|---|---|
token |
string | The verification token to claim. |
Returns:
The token row (with usedAt now set), or null.
- Type
- Object | null
(static) create(token, userId, email, expiresAt)
Create a new verification token, invalidating any existing unused tokens for the same user first (only the latest token should be valid).
Parameters:
| Name | Type | Description |
|---|---|---|
token |
string | Cryptographically random base64url string. |
userId |
string | The user requesting verification. |
email |
string | The email address to verify. |
expiresAt |
string | ISO 8601 expiry timestamp. |
(static) deleteExpired() → {number}
Delete all expired tokens (both used and unused) — called by the periodic cleanup job so the table stays small.
Returns:
Number of deleted rows.
- Type
- number
(static) deleteUnusedByUserId(userId) → {number}
Delete all unused tokens for a user (called after successful verification to invalidate any other outstanding verification links).
Parameters:
| Name | Type | Description |
|---|---|---|
userId |
string |
Returns:
Number of deleted rows.
- Type
- number
(static) getUnusedByUserId(userId) → {Object|undefined}
Get the most recent unused verification token for a user. Used to check if a resend is needed or if a token is still pending.
Parameters:
| Name | Type | Description |
|---|---|---|
userId |
string |
Returns:
- Type
- Object | undefined