Skip to content

feat(otp): add one_time_tokens query helpers - #2797

Merged
annabkr merged 12 commits into
masterfrom
annabaker/auth-1553-ott-query-helpers
Sep 18, 2026
Merged

annabkr merged 12 commits into
masterfrom
annabaker/auth-1553-ott-query-helpers

Conversation

@annabkr

@annabkr annabkr commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Feature

What is the current behavior?

There is no single-query lookup for a token hash and its pkce_-prefixed form, and no lookup by relates_to.

Callers such as FindUserByEmailChangeCurrentAndAudience run two FindOneTimeToken queries to cover the prefix, which is inefficient.

What is the new behavior?

Adds two parent query methods used by the upcoming OTT-as-source-of-truth work:

  • FindOneTimeTokenWithPKCEFallback: single-query lookup that accepts either an exact token hash or its pkce_-prefixed form, preferring the exact match.
  • FindOneTimeTokenByRelatesTo: looks up the newest token row for a given relates_to value (e.g. phone number) and token type.

Along with:

  • PKCEPrefix is now a constant in models that we reference vs. string literals
  • FindOneTimeToken and FindOneTimeTokenWithPKCEFallback now share findOneTimeToken, guided by a pkceFallback flag.

Adds tests for those methods. Not updating existing callers for now to reduce the radius of this change.

Additional context

Contributes to AUTH-1553 but does not close.

annabkr and others added 7 commits September 2, 2026 09:56
CreateOneTimeToken now takes the validity window and stores
expires_at on insert. Call sites pass Mailer.OtpExp for email
flows and Sms.OtpExp for phone flows, matching the windows the
verify path infers from users.*_sent_at today. Reads are
unchanged. Adds OtpExpAsDuration helpers on the mailer and SMS
configurations. Existing tests are updated for the new signature.
Add a models suite covering persistence, past windows, and resend
replacement, and an api suite asserting expires_at equals
sent_at + OtpExp per channel. The api suite pins Mailer.OtpExp
and Sms.OtpExp to different values so a swapped config field
fails the test.
The test came in from master via merge after this branch added the
validityDuration parameter, so the models package did not compile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@annabkr
annabkr requested a review from a team as a code owner September 9, 2026 16:14
@annabkr
annabkr marked this pull request as draft September 9, 2026 16:15
@annabkr
annabkr force-pushed the annabaker/auth-1553-ott-query-helpers branch from fa21e86 to 7577774 Compare September 9, 2026 19:08
…yRelatesTo

Two lookups for the OTT-as-source-of-truth work.

FindOneTimeTokenWithPKCEFallback accepts a token hash or its pkce_-prefixed
form in a single query and prefers the exact match.

FindOneTimeTokenByRelatesTo returns the newest row for a relates_to value
and token type. relates_to is not unique for PhoneChangeToken, so the doc
comment tells callers to check the user against the request.
models cannot import api, so the constant moves down and api.PKCEPrefix
points at it. Replaces the bare "pkce_" literals in the one_time_tokens
finders with the constant.
FindOneTimeToken and FindOneTimeTokenWithPKCEFallback delegate to
findOneTimeToken, which takes a pkceFallback flag. With the flag off it
builds the statement FindOneTimeToken built before: same WHERE string,
same args in the same order, same Eager connection, no ORDER BY. The flag
only switches the token_hash clause and adds the ORDER BY.
Pin the exact-hash path: no pkce_ match, the type filter applies, and
either of two types is found. seedToken replaces the truncate, create
user, insert row boilerplate in the hash lookup tests.
@annabkr
annabkr force-pushed the annabaker/auth-1553-ott-query-helpers branch from 7577774 to fc7f405 Compare September 9, 2026 19:14
@annabkr
annabkr marked this pull request as ready for review September 9, 2026 19:15

@xlgmokha xlgmokha left a comment

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.

The changes LGTM.

Note: I don't have a lot of experience with this ORM so I think it would be helpful to me to see the SQL queries that are generated and the explain plan for them.

Comment thread internal/models/one_time_token.go
Comment thread internal/models/one_time_token.go
Comment thread internal/models/one_time_token.go
Base automatically changed from annabaker/auth-1572-start-writing-to-one_time_tokensexpiresat to master September 18, 2026 00:52
…-ott-query-helpers

# Conflicts:
#	internal/api/admin_test.go
#	internal/api/external_test.go
#	internal/api/invite_test.go
#	internal/api/mail.go
#	internal/api/one_time_token_expiry_test.go
#	internal/api/phone.go
#	internal/api/resend_test.go
#	internal/api/signup_test.go
#	internal/api/verify_test.go
#	internal/models/one_time_token.go
#	internal/models/one_time_token_test.go
#	internal/models/user_test.go
@annabkr

annabkr commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

The changes LGTM.

Note: I don't have a lot of experience with this ORM so I think it would be helpful to me to see the SQL queries that are generated and the explain plan for them.

I agree. I actually thought about adding unit tests for the queries, which is a pattern I've appreciated before. However, it would have required a large diversion from established pattens in this repo, I decided to hold off because of that and the anticipated discussion.

The queries are pretty simple:

-- FindOneTimeToken:
SELECT ... FROM one_time_tokens WHERE token_type = $1 and token_hash = $2 LIMIT 1
-- FindOneTimeTokenWithPKCEFallback: 
SELECT ... WHERE token_type = $1 and token_hash in ($2, $3) ORDER BY token_hash = $4 desc LIMIT 1
-- FindOneTimeTokenByRelatesTo: 
SELECT ... WHERE token_type = $1 and relates_to = $2 ORDER BY created_at desc LIMIT 1

I'll need to look into how to run EXPLAIN ANALYZE on production data. With the small size of my local tables, the planner picks a sequential scan every time, but one_time_tokens has hash indexes on token_hash and relates_to, as well as a unique btree index on (user_id, token_type).

@annabkr
annabkr merged commit f8520b4 into master Sep 18, 2026
11 checks passed
@annabkr
annabkr deleted the annabaker/auth-1553-ott-query-helpers branch September 18, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants