initial commit

This commit is contained in:
m.zare
2026-04-10 18:25:21 +03:30
commit 77ca6c34a3
263 changed files with 34470 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
package auth
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"base/internal/domain/auth"
"base/internal/dto"
"base/internal/pkg/oauth"
"base/pkg/email"
"base/pkg/hashids"
"base/pkg/jwt"
)
// SendResetPasswordEmail sends a password reset email
func (s *service) SendResetPasswordEmail(ctx context.Context, request dto.SendResetPasswordEmailRequest) error {
user, err := s.userRepo.FindByEmail(ctx, request.Email)
if err != nil {
// Don't reveal if user exists or not for security
return err
}
// Generate reset code
code := hashids.GenerateCode(int64(user.ID.Time()))
key := fmt.Sprintf("reset_password:%s", user.ID.String())
// Store code in cache (15 minutes TTL)
if storeErr := s.resetPasswordStore.Set(ctx, key, code, 15*time.Minute); storeErr != nil {
return fmt.Errorf("failed to store reset password code: %w", storeErr)
}
// Send email
emailData := map[string]interface{}{
"Code": code,
"Name": user.FirstName,
}
emailMsg := email.Request{
To: user.Email,
Subject: "Reset Your Password",
Template: email.TemplateData{
EmailTemplateName: email.TemplatePasswordReset,
Data: emailData,
},
}
if _, sendEmailErr := s.emailService.Send(ctx, emailMsg); sendEmailErr != nil {
return fmt.Errorf("failed to send reset password email: %w", sendEmailErr)
}
return nil
}
// ResetPassword resets a user's password with the provided code
func (s *service) ResetPassword(ctx context.Context, request dto.ResetPasswordRequest) (*dto.TokenResponse, error) {
user, err := s.userRepo.FindByEmail(ctx, request.Email, auth.WithAccounts())
if err != nil {
return nil, ErrUserNotFound
}
// Get code from cache
key := fmt.Sprintf("reset_password:%s", user.ID.String())
storedCode, found, getErr := s.resetPasswordStore.Get(ctx, key)
if getErr != nil || !found {
return nil, ErrInvalidVerificationCode
}
if storedCode != request.Code {
return nil, ErrInvalidVerificationCode
}
// Find credentials account
var credentialsAccount *auth.Account
for _, acc := range user.Accounts {
if acc.Provider == oauth.Credentials {
credentialsAccount = &acc
break
}
}
// Hash new password
hashedPassword, genHashPassErr := bcrypt.GenerateFromPassword([]byte(request.Password), bcrypt.DefaultCost)
if genHashPassErr != nil {
return nil, fmt.Errorf("failed to hash password: %w", genHashPassErr)
}
hashedPasswordStr := string(hashedPassword)
if credentialsAccount != nil {
// Update existing account
credentialsAccount.Password = &hashedPasswordStr
credentialsAccount.UpdatedAt = time.Now()
if err := s.accountRepo.Update(ctx, credentialsAccount); err != nil {
return nil, fmt.Errorf("failed to update account: %w", err)
}
} else {
// Create new credentials account
account := &auth.Account{
ID: uuid.New(),
UserID: user.ID,
Provider: oauth.Credentials,
Password: &hashedPasswordStr,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := s.accountRepo.Create(ctx, account); err != nil {
return nil, fmt.Errorf("failed to create account: %w", err)
}
}
// Delete reset code from cache
_ = s.resetPasswordStore.Delete(ctx, key)
// Generate tokens
tokens, err := s.jwtService.GenerateAccessRefreshTokenPair(ctx, &jwt.TokenData{Sub: user.ID.String()})
if err != nil {
return nil, fmt.Errorf("failed to generate tokens: %w", err)
}
return &dto.TokenResponse{
AccessToken: tokens.AccessToken,
RefreshToken: tokens.RefreshToken,
}, nil
}