// services/auth/service.go package auth import ( "api-service/internal/config" models "api-service/internal/models/auth" "errors" "os" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) // AuthService handles authentication logic type AuthService struct { config *config.Config users map[string]*models.User // In-memory user store for demo jwtSecret []byte } // NewAuthService creates a new authentication service func NewAuthService(cfg *config.Config) *AuthService { // Initialize with demo users users := make(map[string]*models.User) // Add demo users users["admin"] = &models.User{ ID: "1", Username: "admin", Email: "admin@example.com", Password: "$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", // password Role: "admin", } users["user"] = &models.User{ ID: "2", Username: "user", Email: "user@example.com", Password: "$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", // password Role: "user", } // Get JWT secret from environment or use default jwtSecret := []byte(os.Getenv("JWT_SECRET")) if len(jwtSecret) == 0 { jwtSecret = []byte("your-secret-key-change-this-in-production") } return &AuthService{ config: cfg, users: users, jwtSecret: jwtSecret, } } // Login authenticates user and generates JWT token func (s *AuthService) Login(username, password string) (*models.TokenResponse, error) { user, exists := s.users[username] if !exists { return nil, errors.New("invalid credentials") } // Verify password err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) if err != nil { return nil, errors.New("invalid credentials") } // Generate JWT token token, expiresIn, err := s.generateToken(user) if err != nil { return nil, err } // Generate refresh token refreshToken, err := s.generateRefreshToken(user) if err != nil { return nil, err } return &models.TokenResponse{ AccessToken: token, RefreshToken: refreshToken, TokenType: "Bearer", ExpiresIn: expiresIn, }, nil } // RefreshToken generates a new access token using a valid refresh token func (s *AuthService) RefreshToken(refreshTokenString string) (*models.TokenResponse, error) { // Parse and validate the refresh token token, err := jwt.Parse(refreshTokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("unexpected signing method") } return s.jwtSecret, nil }) if err != nil { return nil, errors.New("invalid refresh token") } if !token.Valid { return nil, errors.New("invalid refresh token") } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return nil, errors.New("invalid token claims") } // Check if it's a refresh token tokenType, ok := claims["type"].(string) if !ok || tokenType != "refresh" { return nil, errors.New("not a refresh token") } // Get user ID from claims userID, ok := claims["user_id"].(string) if !ok { return nil, errors.New("invalid user ID in token") } // Find user var user *models.User for _, u := range s.users { if u.ID == userID { user = u break } } if user == nil { return nil, errors.New("user not found") } // Generate new access token accessToken, expiresIn, err := s.generateToken(user) if err != nil { return nil, err } // Generate new refresh token newRefreshToken, err := s.generateRefreshToken(user) if err != nil { return nil, err } return &models.TokenResponse{ AccessToken: accessToken, RefreshToken: newRefreshToken, TokenType: "Bearer", ExpiresIn: expiresIn, }, nil } // generateToken creates a new JWT access token for the user func (s *AuthService) generateToken(user *models.User) (string, int64, error) { // Create claims now := time.Now() expiresAt := now.Add(time.Hour * 1) // 1 hour expiration claims := jwt.MapClaims{ "user_id": user.ID, "username": user.Username, "email": user.Email, "role": user.Role, "type": "access", "exp": expiresAt.Unix(), "iat": now.Unix(), } // Create token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // Sign token with secret key tokenString, err := token.SignedString(s.jwtSecret) if err != nil { return "", 0, err } return tokenString, int64(time.Hour.Seconds()), nil } // generateRefreshToken creates a new JWT refresh token for the user func (s *AuthService) generateRefreshToken(user *models.User) (string, error) { // Create claims now := time.Now() expiresAt := now.Add(time.Hour * 24 * 7) // 7 days expiration claims := jwt.MapClaims{ "user_id": user.ID, "type": "refresh", "exp": expiresAt.Unix(), "iat": now.Unix(), } // Create token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // Sign token with secret key return token.SignedString(s.jwtSecret) } // ValidateToken validates the JWT access token func (s *AuthService) ValidateToken(tokenString string) (*models.JWTClaims, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("unexpected signing method") } return s.jwtSecret, nil }) if err != nil { return nil, err } if !token.Valid { return nil, errors.New("invalid token") } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return nil, errors.New("invalid claims") } // Check if it's an access token tokenType, ok := claims["type"].(string) if !ok || tokenType != "access" { return nil, errors.New("not an access token") } return &models.JWTClaims{ UserID: claims["user_id"].(string), Username: claims["username"].(string), Email: claims["email"].(string), Role: claims["role"].(string), }, nil } // RegisterUser registers a new user (for demo purposes) func (s *AuthService) RegisterUser(username, email, password, role string) error { if _, exists := s.users[username]; exists { return errors.New("username already exists") } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return err } s.users[username] = &models.User{ ID: string(rune(len(s.users) + 1)), Username: username, Email: email, Password: string(hashedPassword), Role: role, } return nil } // GenerateToken generates a JWT token for the given user data (public method) func (s *AuthService) GenerateToken(userID, username, email, role string) (*models.TokenResponse, error) { user := &models.User{ ID: userID, Username: username, Email: email, Role: role, } // Generate access token token, expiresIn, err := s.generateToken(user) if err != nil { return nil, err } // Generate refresh token refreshToken, err := s.generateRefreshToken(user) if err != nil { return nil, err } return &models.TokenResponse{ AccessToken: token, RefreshToken: refreshToken, TokenType: "Bearer", ExpiresIn: expiresIn, }, nil } // GenerateTokenDirect generates a JWT token directly for the given user data (public method) func (s *AuthService) GenerateTokenDirect(userID, username, email, role string) (*models.TokenResponse, error) { return s.GenerateToken(userID, username, email, role) }