Skip to content

Commit f542d7c

Browse files
authored
[MM-67791] Use atomic token consumption for guest magic links (mattermost#35489)
#### Summary Use the atomic `ConsumeOnce` pattern for guest magic link token consumption, consistent with how SSO code exchange tokens are already handled. #### Ticket Link https://mattermost.atlassian.net/browse/MM-67791 #### Release Note ```release-note Improved token handling in the guest magic link authentication flow. ```
1 parent 56f51d7 commit f542d7c

3 files changed

Lines changed: 14 additions & 20 deletions

File tree

server/channels/app/user.go

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,15 @@ func (a *App) CreateUserWithToken(rctx request.CTX, user *model.User, token *mod
125125
// This function handles the passwordless "guest magic link" flow where clicking an email link logs the user in.
126126
// Follows the same pattern as SAML/OAuth SSO by creating the user then calling AddUserToTeamByToken.
127127
func (a *App) AuthenticateUserForGuestMagicLink(rctx request.CTX, tokenString string) (*model.User, *model.AppError) {
128-
// Get and validate token type and expiry
129-
token, err := a.Srv().Store().Token().GetByToken(tokenString)
128+
// Atomically consume the token to prevent race conditions where concurrent
129+
// requests could reuse the same single-use token to create multiple sessions.
130+
// Try both valid token types for guest magic links.
131+
token, err := a.ConsumeTokenOnce(model.TokenTypeGuestMagicLinkInvitation, tokenString)
130132
if err != nil {
131-
return nil, model.NewAppError("AuthenticateUserForGuestMagicLink", "api.user.guest_magic_link.invalid_token.app_error", nil, "", http.StatusBadRequest).Wrap(err)
132-
}
133-
134-
if token.Type != model.TokenTypeGuestMagicLinkInvitation && token.Type != model.TokenTypeGuestMagicLink {
135-
return nil, model.NewAppError("AuthenticateUserForGuestMagicLink", "api.user.guest_magic_link.invalid_token_type.app_error", nil, "", http.StatusBadRequest)
136-
}
137-
138-
// We have the token we were looking for, so remove it from the database ASAP
139-
err = a.Srv().Store().Token().Delete(tokenString)
140-
if err != nil {
141-
rctx.Logger().Warn("Error while deleting token", mlog.Err(err))
133+
token, err = a.ConsumeTokenOnce(model.TokenTypeGuestMagicLink, tokenString)
134+
if err != nil {
135+
return nil, model.NewAppError("AuthenticateUserForGuestMagicLink", "api.user.guest_magic_link.invalid_token.app_error", nil, "", http.StatusBadRequest).Wrap(err)
136+
}
142137
}
143138

144139
if token.IsExpired() {

server/channels/app/user_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,14 +2687,17 @@ func TestAuthenticateUserForGuestMagicLink(t *testing.T) {
26872687
)
26882688
require.NoError(t, th.App.Srv().Store().Token().Save(token))
26892689
defer func() {
2690-
appErr := th.App.DeleteToken(token)
2691-
require.Nil(t, appErr)
2690+
_ = th.App.Srv().Store().Token().Delete(token.Token)
26922691
}()
26932692

26942693
user, err := th.App.AuthenticateUserForGuestMagicLink(th.Context, token.Token)
26952694
require.NotNil(t, err, "Should fail on wrong token type")
26962695
require.Nil(t, user)
2697-
assert.Equal(t, "api.user.guest_magic_link.invalid_token_type.app_error", err.Id)
2696+
assert.Equal(t, "api.user.guest_magic_link.invalid_token.app_error", err.Id)
2697+
2698+
// Verify token was NOT consumed (wrong type should not delete it)
2699+
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
2700+
require.NoError(t, nErr, "Token with wrong type should still exist")
26982701
})
26992702

27002703
t.Run("user already exists returns generic error", func(t *testing.T) {

server/i18n/en.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4446,10 +4446,6 @@
44464446
"id": "api.user.guest_magic_link.invalid_token.app_error",
44474447
"translation": "Invalid invitation link."
44484448
},
4449-
{
4450-
"id": "api.user.guest_magic_link.invalid_token_type.app_error",
4451-
"translation": "Invalid invitation link type."
4452-
},
44534449
{
44544450
"id": "api.user.guest_magic_link.missing_token.app_error",
44554451
"translation": "Missing invitation token."

0 commit comments

Comments
 (0)