Files
enkly-ai/services/iam/internal/application/password_reset.go
enkly 79ce8dc4c0
Some checks failed
Go CI Pipeline / build_and_test (push) Failing after 22m0s
Init Project
2026-10-30 18:06:55 +01:00

70 lines
2.0 KiB
Go

package application
import (
"context"
"errors"
"net/url"
"strings"
"time"
"enkly/pkg/authsupport"
"enkly/pkg/eventbus"
"enkly/pkg/eventbus/events"
"enkly/services/iam/internal/domain"
)
// absURL builds BaseURL + path + ?key=value, replacing
// Handlers.absURL.
func (s *Service) absURL(path, key, value string) string {
return strings.TrimRight(s.BaseURL, "/") + path + "?" + url.Values{key: {value}}.Encode()
}
// ForgotPassword replaces Handlers.ForgotPassword. Always succeeds from
// the caller's point of view — whether or not the address is registered
// is never revealed.
func (s *Service) ForgotPassword(ctx context.Context, email, clientIP string) error {
if !throttle(ctx, s.ResendLimiter, "forgot:ip:"+clientIP, "forgot:email:"+email) {
return ctx.Err()
}
u, err := s.Users.GetByEmail(ctx, email)
if err != nil {
return nil // unknown address — say nothing
}
raw, hash, err := authsupport.NewToken()
if err != nil {
return err
}
expiresAt := time.Now().Add(resetTokenTTL)
if err := s.PasswordReset.Create(ctx, u.ID, hash, expiresAt); err != nil {
return err
}
s.Events.PublishBestEffort(ctx, eventbus.RoutingKeyPasswordResetRequested, events.PasswordResetRequested{
UserID: u.ID, Email: u.Email, ResetLink: s.absURL("/reset-password", "token", raw), ExpiresAt: expiresAt,
})
return nil
}
// CheckPasswordResetToken replaces the GET-time validity check in
// Handlers.ResetPasswordPage.
func (s *Service) CheckPasswordResetToken(ctx context.Context, token string) (bool, error) {
return s.PasswordReset.Valid(ctx, authsupport.HashToken(token))
}
// ResetPassword replaces Handlers.ResetPassword.
func (s *Service) ResetPassword(ctx context.Context, token, password string) (bool, error) {
hash, err := authsupport.HashPassword(password)
if err != nil {
return false, err
}
_, err = s.PasswordReset.ConsumeAndResetPassword(ctx, authsupport.HashToken(token), hash)
switch {
case err == nil:
return true, nil
case errors.Is(err, domain.ErrInvalidToken):
return false, nil
default:
return false, err
}
}