package service import ( "context" "errors" "fmt" "log" "github.com/user-management-system/internal/auth" "github.com/user-management-system/internal/domain" ) func (s *AuthService) SetEmailActivationService(svc *EmailActivationService) { s.emailActivationSvc = svc } func (s *AuthService) SetEmailCodeService(svc *EmailCodeService) { s.emailCodeSvc = svc } func (s *AuthService) RegisterWithActivation(ctx context.Context, req *RegisterRequest) (*UserInfo, error) { if err := s.validatePassword(req.Password); err != nil { return nil, err } if err := s.verifyPhoneRegistration(ctx, req); err != nil { return nil, err } exists, err := s.userRepo.ExistsByUsername(ctx, req.Username) if err != nil { return nil, err } if exists { return nil, errors.New("username already exists") } if req.Email != "" { exists, err = s.userRepo.ExistsByEmail(ctx, req.Email) if err != nil { return nil, err } if exists { return nil, errors.New("email already exists") } } if req.Phone != "" { exists, err = s.userRepo.ExistsByPhone(ctx, req.Phone) if err != nil { return nil, err } if exists { return nil, errors.New("phone already exists") } } hashedPassword, err := auth.HashPassword(req.Password) if err != nil { return nil, err } initialStatus := domain.UserStatusActive if s.emailActivationSvc != nil && req.Email != "" { initialStatus = domain.UserStatusInactive } user := &domain.User{ Username: req.Username, Email: domain.StrPtr(req.Email), Phone: domain.StrPtr(req.Phone), Password: hashedPassword, Nickname: req.Nickname, Status: initialStatus, } if err := s.userRepo.Create(ctx, user); err != nil { return nil, err } s.bestEffortAssignDefaultRoles(ctx, user.ID, "register_with_activation") if s.emailActivationSvc != nil && req.Email != "" { nickname := req.Nickname if nickname == "" { nickname = req.Username } go func() { if err := s.emailActivationSvc.SendActivationEmail(ctx, user.ID, req.Email, nickname); err != nil { log.Printf("auth: send activation email failed, user_id=%d email=%s err=%v", user.ID, req.Email, err) } }() } userInfo := s.buildUserInfo(user) s.publishEvent(ctx, domain.EventUserRegistered, userInfo) return userInfo, nil } func (s *AuthService) ActivateEmail(ctx context.Context, token string) error { if s.emailActivationSvc == nil { return errors.New("email activation service is not configured") } userID, err := s.emailActivationSvc.ValidateActivationToken(ctx, token) if err != nil { return err } user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return fmt.Errorf("user not found: %w", err) } if user.Status == domain.UserStatusActive { return errors.New("account already activated") } if user.Status != domain.UserStatusInactive { return errors.New("account status does not allow activation") } return s.userRepo.UpdateStatus(ctx, userID, domain.UserStatusActive) } func (s *AuthService) ResendActivationEmail(ctx context.Context, email string) error { if s.emailActivationSvc == nil { return errors.New("email activation service is not configured") } user, err := s.userRepo.GetByEmail(ctx, email) if err != nil { if isUserNotFoundError(err) { return nil } return err } if user.Status == domain.UserStatusActive { return nil } if user.Status != domain.UserStatusInactive { return errors.New("account status does not allow activation") } nickname := user.Nickname if nickname == "" { nickname = user.Username } return s.emailActivationSvc.SendActivationEmail(ctx, user.ID, email, nickname) } func (s *AuthService) SendEmailLoginCode(ctx context.Context, email string) error { if s.emailCodeSvc == nil { return errors.New("email code service is not configured") } _, err := s.userRepo.GetByEmail(ctx, email) if err != nil { if isUserNotFoundError(err) { return nil } return err } return s.emailCodeSvc.SendEmailCode(ctx, email, "login") } func (s *AuthService) LoginByEmailCode(ctx context.Context, email, code, ip string) (*LoginResponse, error) { if s.emailCodeSvc == nil { return nil, errors.New("email code login is disabled") } if err := s.emailCodeSvc.VerifyEmailCode(ctx, email, "login", code); err != nil { s.writeLoginLog(ctx, nil, domain.LoginTypeEmailCode, ip, false, err.Error()) return nil, err } user, err := s.userRepo.GetByEmail(ctx, email) if err != nil { if isUserNotFoundError(err) { s.writeLoginLog(ctx, nil, domain.LoginTypeEmailCode, ip, false, "email not registered") return nil, errors.New("email not registered") } s.writeLoginLog(ctx, nil, domain.LoginTypeEmailCode, ip, false, err.Error()) return nil, err } if err := s.ensureUserActive(user); err != nil { s.writeLoginLog(ctx, &user.ID, domain.LoginTypeEmailCode, ip, false, err.Error()) s.recordLoginAnomaly(ctx, &user.ID, ip, "", "", false) return nil, err } s.bestEffortUpdateLastLogin(ctx, user.ID, ip, "email_code") s.writeLoginLog(ctx, &user.ID, domain.LoginTypeEmailCode, ip, true, "") s.recordLoginAnomaly(ctx, &user.ID, ip, "", "", true) s.publishEvent(ctx, domain.EventUserLogin, map[string]interface{}{ "user_id": user.ID, "username": user.Username, "ip": ip, "method": "email_code", }) return s.generateLoginResponseWithoutRemember(ctx, user) }