package service import ( "crypto/rand" "crypto/sha256" "database/sql" "encoding/hex" "errors" "fmt" "strings" "time" "github.com/golang-jwt/jwt/v5" "floorvisualizer/internal/model" "floorvisualizer/internal/repository" ) type AuthService struct { secret []byte ttl time.Duration } func NewAuthService(secret string) *AuthService { if secret == "" { b := make([]byte, 32) rand.Read(b) secret = hex.EncodeToString(b) } return &AuthService{secret: []byte(secret), ttl: 72 * time.Hour} } func HashPassword(password string) string { h := sha256.Sum256([]byte(password)) return hex.EncodeToString(h[:]) } type Claims struct { UserID string `json:"uid"` Username string `json:"uname"` jwt.RegisteredClaims } func (s *AuthService) GenerateToken(user *model.User) (string, error) { claims := Claims{ UserID: user.ID, Username: user.Username, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.ttl)), IssuedAt: jwt.NewNumericDate(time.Now()), Issuer: "floorvisualizer", }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(s.secret) } func (s *AuthService) ParseToken(tokenStr string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method") } return s.secret, nil }) if err != nil { return nil, err } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, errors.New("invalid token") } return claims, nil } type RegisterInput struct { Username string `json:"username"` Password string `json:"password"` Name string `json:"name"` } type LoginInput struct { Username string `json:"username"` Password string `json:"password"` } type AuthResponse struct { Token string `json:"token"` User model.User `json:"user"` } func (s *AuthService) Register(db *sql.DB, input RegisterInput) (*AuthResponse, error) { if strings.TrimSpace(input.Username) == "" || strings.TrimSpace(input.Password) == "" { return nil, errors.New("username and password are required") } user, err := repository.CreateUser(db, input.Username, HashPassword(input.Password), input.Name) if err != nil { return nil, fmt.Errorf("register: %w", err) } token, err := s.GenerateToken(user) if err != nil { return nil, err } return &AuthResponse{Token: token, User: *user}, nil } func (s *AuthService) Login(db *sql.DB, input LoginInput) (*AuthResponse, error) { user, err := repository.GetUserByUsername(db, input.Username) if err != nil { return nil, errors.New("invalid username or password") } if HashPassword(input.Password) != user.PasswordHash { return nil, errors.New("invalid username or password") } token, err := s.GenerateToken(user) if err != nil { return nil, err } return &AuthResponse{Token: token, User: *user}, nil }