package auth import ( "context" "fmt" "time" "a301_server/pkg/config" "github.com/golang-jwt/jwt/v5" "github.com/redis/go-redis/v9" "golang.org/x/crypto/bcrypt" ) type Claims struct { UserID uint `json:"user_id"` Username string `json:"username"` Role string `json:"role"` jwt.RegisteredClaims } type Service struct { repo *Repository rdb *redis.Client } func NewService(repo *Repository, rdb *redis.Client) *Service { return &Service{repo: repo, rdb: rdb} } func (s *Service) Login(username, password string) (string, *User, error) { user, err := s.repo.FindByUsername(username) if err != nil { return "", nil, fmt.Errorf("아이디 또는 비밀번호가 올바르지 않습니다") } if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil { return "", nil, fmt.Errorf("아이디 또는 비밀번호가 올바르지 않습니다") } expiry := time.Duration(config.C.JWTExpiryHours) * time.Hour claims := &Claims{ UserID: user.ID, Username: user.Username, Role: string(user.Role), RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiry)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenStr, err := token.SignedString([]byte(config.C.JWTSecret)) if err != nil { return "", nil, fmt.Errorf("토큰 생성에 실패했습니다") } // Redis에 세션 저장 (1계정 1세션) key := fmt.Sprintf("session:%d", user.ID) s.rdb.Set(context.Background(), key, tokenStr, expiry) return tokenStr, user, nil } func (s *Service) Logout(userID uint) { key := fmt.Sprintf("session:%d", userID) s.rdb.Del(context.Background(), key) } func (s *Service) GetAllUsers() ([]User, error) { return s.repo.FindAll() } func (s *Service) UpdateRole(id string, role Role) error { return s.repo.UpdateRole(id, role) } func (s *Service) DeleteUser(id string) error { return s.repo.Delete(id) } func (s *Service) Register(username, password string) error { if _, err := s.repo.FindByUsername(username); err == nil { return fmt.Errorf("이미 사용 중인 아이디입니다") } hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return fmt.Errorf("비밀번호 처리에 실패했습니다") } return s.repo.Create(&User{ Username: username, PasswordHash: string(hash), Role: RoleUser, }) } func (s *Service) EnsureAdmin(username, password string) error { if _, err := s.repo.FindByUsername(username); err == nil { return nil // 이미 존재하면 스킵 } hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return err } return s.repo.Create(&User{ Username: username, PasswordHash: string(hash), Role: RoleAdmin, }) }