70 lines
2.0 KiB
Go
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
|
|
}
|
|
}
|