FloorVisualizer/internal/service/auth_service.go

121 lines
2.9 KiB
Go
Raw Normal View History

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
}