221 lines
6.6 KiB
Go
221 lines
6.6 KiB
Go
package services
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"net/mail"
|
|
"strings"
|
|
|
|
"goyco/internal/config"
|
|
"goyco/internal/database"
|
|
)
|
|
|
|
const (
|
|
defaultTokenExpirationHours = 24
|
|
verificationTokenBytes = 32
|
|
deletionTokenExpirationHours = 24
|
|
)
|
|
|
|
var (
|
|
ErrInvalidCredentials = errors.New("invalid credentials")
|
|
ErrInvalidToken = errors.New("invalid or expired token")
|
|
ErrUsernameTaken = errors.New("username already exists")
|
|
ErrEmailTaken = errors.New("email already exists")
|
|
ErrInvalidEmail = errors.New("invalid email address")
|
|
ErrPasswordTooShort = errors.New("password too short")
|
|
ErrEmailNotVerified = errors.New("email not verified")
|
|
ErrAccountLocked = errors.New("account is locked")
|
|
ErrInvalidVerificationToken = errors.New("invalid verification token")
|
|
ErrEmailSenderUnavailable = errors.New("email sender not configured")
|
|
ErrDeletionEmailFailed = errors.New("account deletion email failed")
|
|
ErrInvalidDeletionToken = errors.New("invalid account deletion token")
|
|
ErrUserNotFound = errors.New("user not found")
|
|
ErrDeletionRequestNotFound = errors.New("deletion request not found")
|
|
)
|
|
|
|
type AuthResult struct {
|
|
AccessToken string `json:"access_token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
|
|
RefreshToken string `json:"refresh_token" example:"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"`
|
|
User *database.User `json:"user"`
|
|
}
|
|
|
|
type RegistrationResult struct {
|
|
User *database.User `json:"user"`
|
|
VerificationSent bool `json:"verification_sent"`
|
|
}
|
|
|
|
func normalizeEmail(email string) (string, error) {
|
|
trimmed := strings.TrimSpace(email)
|
|
if trimmed == "" {
|
|
return "", fmt.Errorf("email is required")
|
|
}
|
|
|
|
parsed, err := mail.ParseAddress(trimmed)
|
|
if err != nil {
|
|
return "", ErrInvalidEmail
|
|
}
|
|
|
|
return strings.ToLower(parsed.Address), nil
|
|
}
|
|
|
|
func generateVerificationToken() (string, string, error) {
|
|
buf := make([]byte, verificationTokenBytes)
|
|
if _, err := rand.Read(buf); err != nil {
|
|
return "", "", fmt.Errorf("generate verification token: %w", err)
|
|
}
|
|
|
|
token := hex.EncodeToString(buf)
|
|
hashed := HashVerificationToken(token)
|
|
return token, hashed, nil
|
|
}
|
|
|
|
func HashVerificationToken(token string) string {
|
|
sum := sha256.Sum256([]byte(token))
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
func sanitizeUser(user *database.User) *database.User {
|
|
if user == nil {
|
|
return nil
|
|
}
|
|
|
|
copy := *user
|
|
copy.Password = ""
|
|
copy.EmailVerificationToken = ""
|
|
return ©
|
|
}
|
|
|
|
type AuthFacade struct {
|
|
registrationService *RegistrationService
|
|
passwordResetService *PasswordResetService
|
|
deletionService *AccountDeletionService
|
|
sessionService *SessionService
|
|
userManagementService *UserManagementService
|
|
config *config.Config
|
|
}
|
|
|
|
func NewAuthFacade(
|
|
registrationService *RegistrationService,
|
|
passwordResetService *PasswordResetService,
|
|
deletionService *AccountDeletionService,
|
|
sessionService *SessionService,
|
|
userManagementService *UserManagementService,
|
|
config *config.Config,
|
|
) *AuthFacade {
|
|
return &AuthFacade{
|
|
registrationService: registrationService,
|
|
passwordResetService: passwordResetService,
|
|
deletionService: deletionService,
|
|
sessionService: sessionService,
|
|
userManagementService: userManagementService,
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
func (f *AuthFacade) Register(username, email, password string) (*RegistrationResult, error) {
|
|
return f.registrationService.Register(username, email, password)
|
|
}
|
|
|
|
func (f *AuthFacade) Login(username, password string) (*AuthResult, error) {
|
|
return f.sessionService.Login(username, password)
|
|
}
|
|
|
|
func (f *AuthFacade) VerifyToken(tokenString string) (uint, error) {
|
|
return f.sessionService.VerifyToken(tokenString)
|
|
}
|
|
|
|
func (f *AuthFacade) ConfirmEmail(token string) (*database.User, error) {
|
|
return f.registrationService.ConfirmEmail(token)
|
|
}
|
|
|
|
func (f *AuthFacade) ResendVerificationEmail(email string) error {
|
|
return f.registrationService.ResendVerificationEmail(email)
|
|
}
|
|
|
|
func (f *AuthFacade) RequestPasswordReset(usernameOrEmail string) error {
|
|
return f.passwordResetService.RequestPasswordReset(usernameOrEmail)
|
|
}
|
|
|
|
func (f *AuthFacade) ResetPassword(token, newPassword string) error {
|
|
user, err := f.passwordResetService.GetUserByResetToken(token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := f.passwordResetService.ResetPassword(token, newPassword); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := f.sessionService.InvalidateAllSessions(user.ID); err != nil {
|
|
return fmt.Errorf("invalidate sessions: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *AuthFacade) UpdateUsername(userID uint, username string) (*database.User, error) {
|
|
return f.userManagementService.UpdateUsername(userID, username)
|
|
}
|
|
|
|
func (f *AuthFacade) UpdateEmail(userID uint, email string) (*database.User, error) {
|
|
return f.userManagementService.UpdateEmail(userID, email)
|
|
}
|
|
|
|
func (f *AuthFacade) UpdatePassword(userID uint, currentPassword, newPassword string) (*database.User, error) {
|
|
user, err := f.userManagementService.UpdatePassword(userID, currentPassword, newPassword)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := f.sessionService.InvalidateAllSessions(userID); err != nil {
|
|
return nil, fmt.Errorf("invalidate sessions: %w", err)
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func (f *AuthFacade) RequestAccountDeletion(userID uint) error {
|
|
return f.deletionService.RequestAccountDeletion(userID)
|
|
}
|
|
|
|
func (f *AuthFacade) ConfirmAccountDeletion(token string) error {
|
|
return f.deletionService.ConfirmAccountDeletion(token)
|
|
}
|
|
|
|
func (f *AuthFacade) ConfirmAccountDeletionWithPosts(token string, deletePosts bool) error {
|
|
return f.deletionService.ConfirmAccountDeletionWithPosts(token, deletePosts)
|
|
}
|
|
|
|
func (f *AuthFacade) GetUserIDFromDeletionToken(token string) (uint, error) {
|
|
return f.deletionService.GetUserIDFromDeletionToken(token)
|
|
}
|
|
|
|
func (f *AuthFacade) RefreshAccessToken(refreshToken string) (*AuthResult, error) {
|
|
return f.sessionService.RefreshAccessToken(refreshToken)
|
|
}
|
|
|
|
func (f *AuthFacade) RevokeRefreshToken(refreshToken string) error {
|
|
return f.sessionService.RevokeRefreshToken(refreshToken)
|
|
}
|
|
|
|
func (f *AuthFacade) RevokeAllUserTokens(userID uint) error {
|
|
return f.sessionService.RevokeAllUserTokens(userID)
|
|
}
|
|
|
|
func (f *AuthFacade) InvalidateAllSessions(userID uint) error {
|
|
return f.sessionService.InvalidateAllSessions(userID)
|
|
}
|
|
|
|
func (f *AuthFacade) CleanupExpiredTokens() error {
|
|
return f.sessionService.CleanupExpiredTokens()
|
|
}
|
|
|
|
func (f *AuthFacade) GetAdminEmail() string {
|
|
return f.config.App.AdminEmail
|
|
}
|
|
|
|
func (f *AuthFacade) UserHasPosts(userID uint) (bool, int64, error) {
|
|
return f.userManagementService.UserHasPosts(userID)
|
|
}
|