feat(otp): add one_time_tokens query helpers - #2797
Conversation
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.
…-start-writing-to-one_time_tokensexpiresat
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>
…-start-writing-to-one_time_tokensexpiresat
fa21e86 to
7577774
Compare
…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.
7577774 to
fc7f405
Compare
xlgmokha
left a comment
There was a problem hiding this comment.
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.
…-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
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 1I'll need to look into how to run |
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 byrelates_to.Callers such as
FindUserByEmailChangeCurrentAndAudiencerun twoFindOneTimeTokenqueries 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 itspkce_-prefixed form, preferring the exact match.FindOneTimeTokenByRelatesTo: looks up the newest token row for a givenrelates_tovalue (e.g. phone number) and token type.Along with:
PKCEPrefixis now a constant inmodelsthat we reference vs. string literalsFindOneTimeTokenandFindOneTimeTokenWithPKCEFallbacknow sharefindOneTimeToken, guided by apkceFallbackflag.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.