-
Notifications
You must be signed in to change notification settings - Fork 755
feat(otp): add one_time_tokens query helpers #2797
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: annabaker/auth-1572-start-writing-to-one_time_tokensexpiresat
Are you sure you want to change the base?
Changes from all commits
662b693
5398348
f9b4aa7
fc7f405
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,6 +92,8 @@ func (t *OneTimeTokenType) Scan(src interface{}) error { | |
| return nil | ||
| } | ||
|
|
||
| const PKCEPrefix = "pkce_" | ||
|
|
||
| type OneTimeTokenNotFoundError struct { | ||
| } | ||
|
|
||
|
|
@@ -163,21 +165,48 @@ func CreateOneTimeToken( | |
| } | ||
|
|
||
| func FindOneTimeToken(tx *storage.Connection, tokenHash string, tokenTypes ...OneTimeTokenType) (*OneTimeToken, error) { | ||
| return findOneTimeToken(tx, tokenHash, false, tokenTypes...) | ||
| } | ||
|
|
||
| // FindOneTimeTokenWithPKCEFallback finds the one time token of the given | ||
| // types whose hash is either tokenHash or tokenHash with the "pkce_" prefix, | ||
| // in a single query. An exact match is preferred over a prefixed one. | ||
| // It returns OneTimeTokenNotFoundError when no row exists. | ||
| func FindOneTimeTokenWithPKCEFallback(tx *storage.Connection, tokenHash string, tokenTypes ...OneTimeTokenType) (*OneTimeToken, error) { | ||
| return findOneTimeToken(tx, tokenHash, true, tokenTypes...) | ||
| } | ||
|
|
||
| // findOneTimeToken finds the one time token of the given types by tokenHash. | ||
| // With pkceFallback it also accepts PKCEPrefix+tokenHash and prefers the | ||
| // exact match. It returns OneTimeTokenNotFoundError when no row exists. | ||
| func findOneTimeToken(tx *storage.Connection, tokenHash string, pkceFallback bool, tokenTypes ...OneTimeTokenType) (*OneTimeToken, error) { | ||
| oneTimeToken := &OneTimeToken{} | ||
|
|
||
| query := tx.Eager().Q() | ||
|
|
||
| hashClause, hashArgs := "token_hash = ?", []interface{}{tokenHash} | ||
| if pkceFallback { | ||
| hashClause, hashArgs = "token_hash in (?, ?)", []interface{}{tokenHash, PKCEPrefix + tokenHash} | ||
| } | ||
|
|
||
| switch len(tokenTypes) { | ||
| case 2: | ||
| query = query.Where("(token_type = ? or token_type = ?) and token_hash = ?", tokenTypes[0], tokenTypes[1], tokenHash) // #nosec G602 | ||
| args := append([]interface{}{tokenTypes[0], tokenTypes[1]}, hashArgs...) // #nosec G602 | ||
| query = query.Where("(token_type = ? or token_type = ?) and "+hashClause, args...) | ||
|
|
||
| case 1: | ||
| query = query.Where("token_type = ? and token_hash = ?", tokenTypes[0], tokenHash) | ||
| args := append([]interface{}{tokenTypes[0]}, hashArgs...) | ||
| query = query.Where("token_type = ? and "+hashClause, args...) | ||
|
|
||
| default: | ||
| panic("at most 2 token types are accepted") | ||
| } | ||
|
|
||
| if pkceFallback { | ||
| // true sorts before false in descending order, so this allows us to prefer an exact match | ||
| query = query.Order("token_hash = ? desc", tokenHash) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. praise: I didn't know that you could do this. |
||
| } | ||
|
|
||
| if err := query.First(oneTimeToken); err != nil { | ||
| if errors.Cause(err) == sql.ErrNoRows { | ||
| return nil, OneTimeTokenNotFoundError{} | ||
|
|
@@ -189,6 +218,30 @@ func FindOneTimeToken(tx *storage.Connection, tokenHash string, tokenTypes ...On | |
| return oneTimeToken, nil | ||
| } | ||
|
|
||
| // FindOneTimeTokenByRelatesTo finds the newest one time token of the given | ||
| // token type by the relatesTo field. | ||
| // | ||
| // relates_to is not unique across users. For PhoneChangeToken in particular, | ||
| // two users can hold rows for the same phone number, so the returned row does | ||
| // not identify a user on its own. Callers must check the user against the | ||
| // request before they trust the result. | ||
| // | ||
| // It returns OneTimeTokenNotFoundError when no row exists. | ||
| func FindOneTimeTokenByRelatesTo(tx *storage.Connection, relatesTo string, tokenType OneTimeTokenType) (*OneTimeToken, error) { | ||
| oneTimeToken := &OneTimeToken{} | ||
|
|
||
| err := tx.Eager().Q(). | ||
| Where("token_type = ? and relates_to = ?", tokenType, strings.ToLower(relatesTo)). | ||
| Order("created_at desc"). | ||
| First(oneTimeToken) | ||
| if errors.Cause(err) == sql.ErrNoRows { | ||
| return nil, OneTimeTokenNotFoundError{} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thought(non-blocking): It looks like we have an |
||
| } else if err != nil { | ||
| return nil, errors.Wrap(err, "error finding one time token") | ||
| } | ||
| return oneTimeToken, nil | ||
| } | ||
|
|
||
| // FindUserByOneTimeToken finds the user holding the one-time token matching | ||
| // tokenHash for any of the given token types. | ||
| func FindUserByOneTimeToken(tx *storage.Connection, tokenHash string, tokenTypes ...OneTimeTokenType) (*User, error) { | ||
|
|
@@ -208,7 +261,7 @@ func FindUserByEmailChangeCurrentAndAudience(tx *storage.Connection, email, toke | |
| } | ||
|
|
||
| if ott == nil { | ||
| ott, err = FindOneTimeToken(tx, "pkce_"+token, EmailChangeTokenCurrent) | ||
| ott, err = FindOneTimeToken(tx, PKCEPrefix+token, EmailChangeTokenCurrent) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
@@ -237,7 +290,7 @@ func FindUserByEmailChangeNewAndAudience(tx *storage.Connection, email, token, a | |
| } | ||
|
|
||
| if ott == nil { | ||
| ott, err = FindOneTimeToken(tx, "pkce_"+token, EmailChangeTokenNew) | ||
| ott, err = FindOneTimeToken(tx, PKCEPrefix+token, EmailChangeTokenNew) | ||
| if err != nil && !IsNotFoundError(err) { | ||
| return nil, err | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question(non-blocking): would it be easier to ready to chain the where clauses together? I'm not sure if pop supports something like
query.Where(...).And(..)?