mirror of
https://github.com/mattermost/mattermost.git
synced 2026-05-28 04:35:04 -04:00
[MM-67791] Use atomic token consumption for guest magic links (#35489)
Some checks are pending
API / build (push) Waiting to run
Server CI / Compute Go Version (push) Waiting to run
Server CI / Check mocks (push) Blocked by required conditions
Server CI / Check go mod tidy (push) Blocked by required conditions
Server CI / check-style (push) Blocked by required conditions
Server CI / Check serialization methods for hot structs (push) Blocked by required conditions
Server CI / Vet API (push) Blocked by required conditions
Server CI / Check migration files (push) Blocked by required conditions
Server CI / Generate email templates (push) Blocked by required conditions
Server CI / Check store layers (push) Blocked by required conditions
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres with binary parameters (push) Blocked by required conditions
Server CI / Postgres (push) Blocked by required conditions
Server CI / Postgres (FIPS) (push) Blocked by required conditions
Server CI / Generate Test Coverage (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
Some checks are pending
API / build (push) Waiting to run
Server CI / Compute Go Version (push) Waiting to run
Server CI / Check mocks (push) Blocked by required conditions
Server CI / Check go mod tidy (push) Blocked by required conditions
Server CI / check-style (push) Blocked by required conditions
Server CI / Check serialization methods for hot structs (push) Blocked by required conditions
Server CI / Vet API (push) Blocked by required conditions
Server CI / Check migration files (push) Blocked by required conditions
Server CI / Generate email templates (push) Blocked by required conditions
Server CI / Check store layers (push) Blocked by required conditions
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres with binary parameters (push) Blocked by required conditions
Server CI / Postgres (push) Blocked by required conditions
Server CI / Postgres (FIPS) (push) Blocked by required conditions
Server CI / Generate Test Coverage (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
#### 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. ```
This commit is contained in:
parent
56f51d7df2
commit
f542d7ca18
3 changed files with 14 additions and 20 deletions
|
|
@ -125,20 +125,15 @@ func (a *App) CreateUserWithToken(rctx request.CTX, user *model.User, token *mod
|
|||
// This function handles the passwordless "guest magic link" flow where clicking an email link logs the user in.
|
||||
// Follows the same pattern as SAML/OAuth SSO by creating the user then calling AddUserToTeamByToken.
|
||||
func (a *App) AuthenticateUserForGuestMagicLink(rctx request.CTX, tokenString string) (*model.User, *model.AppError) {
|
||||
// Get and validate token type and expiry
|
||||
token, err := a.Srv().Store().Token().GetByToken(tokenString)
|
||||
// Atomically consume the token to prevent race conditions where concurrent
|
||||
// requests could reuse the same single-use token to create multiple sessions.
|
||||
// Try both valid token types for guest magic links.
|
||||
token, err := a.ConsumeTokenOnce(model.TokenTypeGuestMagicLinkInvitation, tokenString)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("AuthenticateUserForGuestMagicLink", "api.user.guest_magic_link.invalid_token.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
}
|
||||
|
||||
if token.Type != model.TokenTypeGuestMagicLinkInvitation && token.Type != model.TokenTypeGuestMagicLink {
|
||||
return nil, model.NewAppError("AuthenticateUserForGuestMagicLink", "api.user.guest_magic_link.invalid_token_type.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// We have the token we were looking for, so remove it from the database ASAP
|
||||
err = a.Srv().Store().Token().Delete(tokenString)
|
||||
if err != nil {
|
||||
rctx.Logger().Warn("Error while deleting token", mlog.Err(err))
|
||||
token, err = a.ConsumeTokenOnce(model.TokenTypeGuestMagicLink, tokenString)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("AuthenticateUserForGuestMagicLink", "api.user.guest_magic_link.invalid_token.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
if token.IsExpired() {
|
||||
|
|
|
|||
|
|
@ -2687,14 +2687,17 @@ func TestAuthenticateUserForGuestMagicLink(t *testing.T) {
|
|||
)
|
||||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||||
defer func() {
|
||||
appErr := th.App.DeleteToken(token)
|
||||
require.Nil(t, appErr)
|
||||
_ = th.App.Srv().Store().Token().Delete(token.Token)
|
||||
}()
|
||||
|
||||
user, err := th.App.AuthenticateUserForGuestMagicLink(th.Context, token.Token)
|
||||
require.NotNil(t, err, "Should fail on wrong token type")
|
||||
require.Nil(t, user)
|
||||
assert.Equal(t, "api.user.guest_magic_link.invalid_token_type.app_error", err.Id)
|
||||
assert.Equal(t, "api.user.guest_magic_link.invalid_token.app_error", err.Id)
|
||||
|
||||
// Verify token was NOT consumed (wrong type should not delete it)
|
||||
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
|
||||
require.NoError(t, nErr, "Token with wrong type should still exist")
|
||||
})
|
||||
|
||||
t.Run("user already exists returns generic error", func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -4446,10 +4446,6 @@
|
|||
"id": "api.user.guest_magic_link.invalid_token.app_error",
|
||||
"translation": "Invalid invitation link."
|
||||
},
|
||||
{
|
||||
"id": "api.user.guest_magic_link.invalid_token_type.app_error",
|
||||
"translation": "Invalid invitation link type."
|
||||
},
|
||||
{
|
||||
"id": "api.user.guest_magic_link.missing_token.app_error",
|
||||
"translation": "Missing invitation token."
|
||||
|
|
|
|||
Loading…
Reference in a new issue