refac: reamaining auth surface rewrite, T0

This commit is contained in:
fsylvain committed 2026-09-24 05:11:49 +02:00
1 parent 6d510d2805
commit aa865af2e9
50 files changed
+3723 -149

No files matched your search

+16 -10
View File
@@ -8,16 +8,22 @@ import (
var ErrUserNotFound = errors.New("user not found")
var ErrInvalidCredentials = errors.New("invalid credentials")
var ErrRegistrationClosed = errors.New("registration closed")
var ErrInvalidVerificationCode = errors.New("invalid verification code")
var ErrVerificationCodeExpired = errors.New("verification code expired")
var ErrAlreadyVerified = errors.New("user already verified")
var ErrInvalidPasswordResetCode = errors.New("invalid password reset code")
var ErrPasswordResetCodeExpired = errors.New("password reset code expired")
type User struct {
ID uint32 `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
PasswordResetCode string `json:"password_reset_code"`
Email string `json:"email"`
IsVerified bool `json:"is_verified"`
VerificationCode string `json:"verification_code"`
VerificationExpiration time.Time `json:"verification_expiration"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID uint32 `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
PasswordResetCode string `json:"password_reset_code"`
Email string `json:"email"`
IsVerified bool `json:"is_verified"`
VerificationCode string `json:"verification_code"`
VerificationExpiration time.Time `json:"verification_expiration"`
PasswordResetExpiration time.Time `json:"password_reset_expiration"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
@@ -1,5 +1,7 @@
package user
import "time"
type Repository interface {
Get(id uint32) (User, error)
GetByUsername(username string) (User, error)
@@ -9,5 +11,7 @@ type Repository interface {
Delete(id uint32) error
UpdateVerificationStatus(userId uint32, isVerified bool) (User, error)
UpdatePassword(userId uint32, password string) (User, error)
UpdatePasswordResetCode(userId uint32, code string) (User, error)
UpdatePasswordResetCode(userId uint32, code string, expiration time.Time) (User, error)
ClearVerificationCode(userId uint32) (User, error)
ResetPassword(userId uint32, hashedPassword string) (User, error)
}
@@ -21,29 +21,31 @@ func DomainToNullableString(value string) *string {
func UserToDomain(m models.User) user.User {
return user.User{
ID: m.ID,
Username: m.Username,
Password: m.Password,
PasswordResetCode: nullableStringToDomain(m.PasswordResetCode),
Email: m.Email,
IsVerified: m.IsVerified,
VerificationCode: nullableStringToDomain(m.VerificationCode),
VerificationExpiration: m.VerificationExpiration,
CreatedAt: m.CreatedAt,
UpdatedAt: m.UpdatedAt,
ID: m.ID,
Username: m.Username,
Password: m.Password,
PasswordResetCode: nullableStringToDomain(m.PasswordResetCode),
Email: m.Email,
IsVerified: m.IsVerified,
VerificationCode: nullableStringToDomain(m.VerificationCode),
VerificationExpiration: m.VerificationExpiration,
PasswordResetExpiration: m.PasswordResetExpiration,
CreatedAt: m.CreatedAt,
UpdatedAt: m.UpdatedAt,
}
}
func UserToModel(a user.User) models.User {
return models.User{
Username: a.Username,
Password: a.Password,
PasswordResetCode: DomainToNullableString(a.PasswordResetCode),
Email: a.Email,
IsVerified: a.IsVerified,
VerificationCode: DomainToNullableString(a.VerificationCode),
VerificationExpiration: a.VerificationExpiration,
CreatedAt: a.CreatedAt,
UpdatedAt: a.UpdatedAt,
Username: a.Username,
Password: a.Password,
PasswordResetCode: DomainToNullableString(a.PasswordResetCode),
Email: a.Email,
IsVerified: a.IsVerified,
VerificationCode: DomainToNullableString(a.VerificationCode),
VerificationExpiration: a.VerificationExpiration,
PasswordResetExpiration: a.PasswordResetExpiration,
CreatedAt: a.CreatedAt,
UpdatedAt: a.UpdatedAt,
}
}
@@ -5,14 +5,15 @@ import (
)
type User struct {
ID uint32 `gorm:"primaryKey;autoIncrement"`
Username string `gorm:"unique;not null"`
Password string `gorm:"not null"`
PasswordResetCode *string `gorm:"unique"`
Email string `gorm:"unique;not null"`
IsVerified bool `gorm:"default=false;not null"`
VerificationCode *string `gorm:"unique"`
VerificationExpiration time.Time
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
ID uint32 `gorm:"primaryKey;autoIncrement"`
Username string `gorm:"unique;not null"`
Password string `gorm:"not null"`
PasswordResetCode *string `gorm:"unique"`
Email string `gorm:"unique;not null"`
IsVerified bool `gorm:"default=false;not null"`
VerificationCode *string `gorm:"unique"`
VerificationExpiration time.Time
PasswordResetExpiration time.Time
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
@@ -4,6 +4,7 @@ import (
domainUser "RenewCMS/internal/domain/user"
"RenewCMS/internal/infrastructure/persistence/mappers"
entity "RenewCMS/internal/infrastructure/persistence/models"
"time"
"gorm.io/gorm"
)
@@ -115,7 +116,7 @@ func (u *UserRepository) UpdatePassword(userId uint32, password string) (domainU
return mappers.UserToDomain(localUser), nil
}
func (u *UserRepository) UpdatePasswordResetCode(userId uint32, code string) (domainUser.User, error) {
func (u *UserRepository) UpdatePasswordResetCode(userId uint32, code string, expiration time.Time) (domainUser.User, error) {
var localUser entity.User
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
if err != nil {
@@ -123,6 +124,42 @@ func (u *UserRepository) UpdatePasswordResetCode(userId uint32, code string) (do
}
localUser.PasswordResetCode = mappers.DomainToNullableString(code)
localUser.PasswordResetExpiration = expiration
err = u.db.Save(&localUser).Error
if err != nil {
return domainUser.User{}, err
}
return mappers.UserToDomain(localUser), nil
}
func (u *UserRepository) ClearVerificationCode(userId uint32) (domainUser.User, error) {
var localUser entity.User
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
if err != nil {
return domainUser.User{}, mapNotFoundErr(err, domainUser.ErrUserNotFound)
}
localUser.IsVerified = true
localUser.VerificationCode = mappers.DomainToNullableString("")
err = u.db.Save(&localUser).Error
if err != nil {
return domainUser.User{}, err
}
return mappers.UserToDomain(localUser), nil
}
func (u *UserRepository) ResetPassword(userId uint32, hashedPassword string) (domainUser.User, error) {
var localUser entity.User
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
if err != nil {
return domainUser.User{}, mapNotFoundErr(err, domainUser.ErrUserNotFound)
}
localUser.Password = hashedPassword
localUser.PasswordResetCode = mappers.DomainToNullableString("")
localUser.PasswordResetExpiration = time.Time{}
err = u.db.Save(&localUser).Error
if err != nil {
return domainUser.User{}, err
@@ -0,0 +1,26 @@
package useCases
import (
"RenewCMS/internal/domain/user"
)
type CancelRegistrationUseCase struct {
userRepository user.Repository
}
func NewCancelRegistrationUseCase(userRepository user.Repository) *CancelRegistrationUseCase {
return &CancelRegistrationUseCase{userRepository}
}
func (c *CancelRegistrationUseCase) CancelRegistration(userId uint32) error {
dbUser, err := c.userRepository.Get(userId)
if err != nil {
return err
}
if dbUser.IsVerified {
return user.ErrAlreadyVerified
}
return c.userRepository.Delete(userId)
}
@@ -0,0 +1,53 @@
package useCases
import (
"RenewCMS/internal/domain/hash"
"RenewCMS/internal/domain/mail"
"RenewCMS/internal/domain/user"
"time"
"github.com/google/uuid"
)
type RequestPasswordResetUseCase struct {
userRepository user.Repository
hasher hash.Hasher
mailRepository mail.Repository
publicURL string
}
type MailPasswordResetData struct {
Host string
VerificationCode string
Email string
}
func NewRequestPasswordResetUseCase(userRepository user.Repository, hasher hash.Hasher, mailRepository mail.Repository, publicURL string) *RequestPasswordResetUseCase {
return &RequestPasswordResetUseCase{userRepository, hasher, mailRepository, publicURL}
}
func (g *RequestPasswordResetUseCase) RequestPasswordReset(email string) error {
foundUser, err := g.userRepository.GetByEmail(email)
if err != nil {
return nil
}
rawCode := uuid.NewString()
hashedCode, err := g.hasher.Hash(rawCode)
if err != nil {
return nil
}
if _, err := g.userRepository.UpdatePasswordResetCode(foundUser.ID, hashedCode, time.Now().Add(time.Hour)); err != nil {
return nil
}
_ = g.mailRepository.Send(foundUser.Email, "passwordReset", MailPasswordResetData{
Host: g.publicURL,
VerificationCode: rawCode,
Email: foundUser.Email,
})
return nil
}
@@ -0,0 +1,39 @@
package useCases
import (
"RenewCMS/internal/domain/hash"
"RenewCMS/internal/domain/user"
"time"
)
type ResetPasswordUseCase struct {
userRepository user.Repository
hasher hash.Hasher
}
func NewResetPasswordUseCase(userRepository user.Repository, hasher hash.Hasher) *ResetPasswordUseCase {
return &ResetPasswordUseCase{userRepository, hasher}
}
func (r *ResetPasswordUseCase) ResetPassword(email string, code string, newPassword string) error {
dbUser, err := r.userRepository.GetByEmail(email)
if err != nil {
return user.ErrInvalidPasswordResetCode
}
if dbUser.PasswordResetCode == "" || r.hasher.Compare(dbUser.PasswordResetCode, code) != nil {
return user.ErrInvalidPasswordResetCode
}
if time.Now().After(dbUser.PasswordResetExpiration) {
return user.ErrPasswordResetCodeExpired
}
hashedPassword, err := r.hasher.Hash(newPassword)
if err != nil {
return err
}
_, err = r.userRepository.ResetPassword(dbUser.ID, hashedPassword)
return err
}
@@ -3,6 +3,7 @@ package useCases
import (
"RenewCMS/internal/domain/hash"
"RenewCMS/internal/domain/user"
"time"
)
type UpdateUserUseCase struct {
@@ -18,12 +19,15 @@ func (g *UpdateUserUseCase) UpdateVerificationStatus(userId uint32, isVerified b
return g.userRepository.UpdateVerificationStatus(userId, isVerified)
}
func (g *UpdateUserUseCase) UpdatePasswordResetCode(userId uint32, code string) (user.User, error) {
func (g *UpdateUserUseCase) UpdatePasswordResetCode(userId uint32, code string, expiration time.Time) (user.User, error) {
if code == "" {
return g.userRepository.UpdatePasswordResetCode(userId, "", expiration)
}
hashedCode, err := g.hasher.Hash(code)
if err != nil {
return user.User{}, err
}
return g.userRepository.UpdatePasswordResetCode(userId, hashedCode)
return g.userRepository.UpdatePasswordResetCode(userId, hashedCode, expiration)
}
func (g *UpdateUserUseCase) UpdatePassword(userId uint32, password string) (user.User, error) {
@@ -0,0 +1,33 @@
package useCases
import (
"RenewCMS/internal/domain/hash"
"RenewCMS/internal/domain/user"
"time"
)
type VerifyUserUseCase struct {
userRepository user.Repository
hasher hash.Hasher
}
func NewVerifyUserUseCase(userRepository user.Repository, hasher hash.Hasher) *VerifyUserUseCase {
return &VerifyUserUseCase{userRepository, hasher}
}
func (v *VerifyUserUseCase) Verify(userId uint32, code string) (user.User, error) {
dbUser, err := v.userRepository.Get(userId)
if err != nil {
return user.User{}, err
}
if dbUser.VerificationCode == "" || v.hasher.Compare(dbUser.VerificationCode, code) != nil {
return user.User{}, user.ErrInvalidVerificationCode
}
if time.Now().After(dbUser.VerificationExpiration) {
return user.User{}, user.ErrVerificationCodeExpired
}
return v.userRepository.ClearVerificationCode(userId)
}