Complete verification links and preserve verified login email (#60)

This commit is contained in:
Stephan D committed 2026-10-19 14:34:00 +02:00
1 parent 30f06c2f8f
commit 5f3b443e7b
25 files changed
+270 -32

No files matched your search

+12 -4
View File
@@ -179,10 +179,11 @@ func (s *Service) sendActivation(ctx context.Context, a *customer.Account, key s
err = s.Sender.Send(ctx, notifications.Message{Key: secretKey, UserID: a.ID.Hex(), Email: a.Login, Locale: locale, Template: "auth.activation", ExpiresAt: result.ExpiresAt, Parameters: map[string]string{"url": u.String()}})
return result.ExpiresAt, err
}
func (s *Service) Activate(ctx context.Context, raw string) error {
func (s *Service) Activate(ctx context.Context, raw string) (string, error) {
if len(raw) != 43 {
return verification.ErorrTokenNotFound()
return "", verification.ErorrTokenNotFound()
}
var email string
_, err := s.Transactions.CreateTransaction().Execute(ctx, func(tx context.Context) (any, error) {
token, err := s.Tokens.Consume(tx, bson.NilObjectID, verificationmodel.PurposeAccountActivation, "", raw)
if err != nil {
@@ -200,7 +201,14 @@ func (s *Service) Activate(ctx context.Context, raw string) error {
return nil, err
}
}
return nil, s.Accounts.Patch(tx, a.ID, repository.Patch().Set(repository.Field("status"), customer.AccountActive))
if err := s.Accounts.Patch(tx, a.ID, repository.Patch().Set(repository.Field("status"), customer.AccountActive)); err != nil {
return nil, err
}
email = a.Login
return nil, nil
})
return err
if err != nil {
return "", err
}
return email, nil
}
@@ -139,11 +139,15 @@ func (pr *PublicRouter) activate(r *http.Request) http.HandlerFunc {
if err := decodeFlow(r, &req); err != nil {
return pr.flowError(err)
}
if err := pr.flow.Activate(r.Context(), req.Token); err != nil {
email, err := pr.flow.Activate(r.Context(), req.Token)
if err != nil {
return pr.flowError(err)
}
pr.logger.Info("Account activation completed")
return response.Accepted(pr.logger, map[string]bool{"verified": true})
return response.Accepted(pr.logger, struct {
Verified bool `json:"verified"`
Email string `json:"email"`
}{Verified: true, Email: email})
}
func challengeScope(raw string) string {
h := sha256.Sum256([]byte(raw))
@@ -152,17 +152,33 @@ func TestSignupActivationAndLoginEmailFlow(t *testing.T) {
require.NoError(t, service.Facts.Ready(ctx, a.ID))
login := map[string]any{"login": map[string]string{"login": "user@example.com", "password": " correct password "}, "clientId": "web", "deviceId": "browser"}
require.Equal(t, 403, post("login", login).Code)
require.Equal(t, 400, post("verification/confirm", map[string]string{"token": "invalid"}).Code)
invalid := post("verification/confirm", map[string]string{"token": "invalid"})
require.Equal(t, 400, invalid.Code)
require.NotContains(t, invalid.Body.String(), a.Login)
guard := &activationGuard{SignupFacts: service.Facts, block: true}
service.Facts = guard
require.Equal(t, 503, post("verification/confirm", map[string]string{"token": activation}).Code)
blocked := post("verification/confirm", map[string]string{"token": activation})
require.Equal(t, 503, blocked.Code)
require.NotContains(t, blocked.Body.String(), a.Login)
a, err = stores.Accounts.GetByEmail(ctx, a.Login)
require.NoError(t, err)
require.False(t, a.IsActive())
guard.block = false
w = post("verification/confirm", map[string]string{"token": activation})
require.Equal(t, 202, w.Code, w.Body.String())
require.Equal(t, 400, post("verification/confirm", map[string]string{"token": activation}).Code)
require.Equal(t, "no-store", w.Header().Get("Cache-Control"))
var confirmed struct {
Data struct {
Verified bool `json:"verified"`
Email string `json:"email"`
} `json:"data"`
}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &confirmed))
require.True(t, confirmed.Data.Verified)
require.Equal(t, a.Login, confirmed.Data.Email)
used := post("verification/confirm", map[string]string{"token": activation})
require.Equal(t, 400, used.Code)
require.NotContains(t, used.Body.String(), a.Login)
a, err = stores.Accounts.GetByEmail(ctx, a.Login)
require.NoError(t, err)
require.Equal(t, customer.AccountActive, a.Status)