imc-vibe/backend/internal/auth/jwt.go
Christoph Haas 68285d861a Add comprehensive human-readable comments to all Go files
- Comments explain what each function does and why
- Fixed unused strconv import in queue.go
- Better documentation for SMTP email service
- Explained journalctl log parsing in logs.go
- Clarified queue operations in queue.go
2026-03-22 22:43:41 +01:00

127 lines
4.8 KiB
Go

// Package auth handles authentication and authorization for the application.
// It provides JWT token management and password hashing.
package auth
import (
"errors" // standard errors package
"time" // time handling
"github.com/golang-jwt/jwt/v5" // JWT library for token handling
"golang.org/x/crypto/bcrypt" // bcrypt for secure password hashing
)
// Custom error types for JWT operations.
var (
ErrInvalidToken = errors.New("invalid token") // Token is malformed or has invalid signature
ErrExpiredToken = errors.New("token has expired") // Token has expired
)
// Claims represents the data stored in a JWT token.
// JWT tokens are signed payloads that can be verified but not tampered with.
type Claims struct {
UserID uint `json:"userId"` // ID of the logged-in user
Username string `json:"username"` // Username for display
Role string `json:"role"` // User's role ("admin" or "user")
jwt.RegisteredClaims // Standard JWT claims (expiration, issuer, etc.)
}
// JWTManager handles creating and validating JWT tokens.
type JWTManager struct {
secretKey []byte // Secret key for signing/verifying tokens (keep this secret!)
issuer string // Token issuer (used to verify token source)
}
// NewJWTManager creates a new JWT manager.
// secretKey: the secret key for signing tokens. Should be long and random.
// issuer: the token issuer string (usually the app name).
func NewJWTManager(secretKey, issuer string) *JWTManager {
return &JWTManager{
secretKey: []byte(secretKey), // Convert string to bytes for signing
issuer: issuer,
}
}
// GenerateToken creates a new JWT token for a user.
// userID: the user's ID to include in the token.
// username: the username to include for display.
// role: the user's role ("admin" or "user").
// duration: how long the token should be valid.
// Returns: the signed token string and any error.
func (j *JWTManager) GenerateToken(userID uint, username, role string, duration time.Duration) (string, error) {
// Create the claims (payload) for the token.
claims := &Claims{
UserID: userID,
Username: username,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(duration)), // Token expires in "duration"
IssuedAt: jwt.NewNumericDate(time.Now()), // When the token was created
Issuer: j.issuer, // Who issued the token
},
}
// Create a new token with HMAC-SHA256 signing.
// HMAC is a symmetric algorithm - same key for signing and verifying.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Sign the token with our secret key and return the string representation.
return token.SignedString(j.secretKey)
}
// ValidateToken checks if a token is valid and returns its claims.
// tokenString: the JWT token string to validate.
// Returns: the claims if valid, or an error if invalid.
func (j *JWTManager) ValidateToken(tokenString string) (*Claims, error) {
// Parse and validate the token.
// The callback function is called to get the signing key.
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
// Verify the signing method is HMAC (not RSA, ECDSA, etc.).
// This prevents algorithm confusion attacks.
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, ErrInvalidToken
}
return j.secretKey, nil // Return our secret key for verification
})
// Handle parsing errors.
if err != nil {
// Check specifically for expiration.
if errors.Is(err, jwt.ErrTokenExpired) {
return nil, ErrExpiredToken
}
return nil, ErrInvalidToken
}
// Extract and validate claims.
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid { // token.Valid is false if signature verification failed
return nil, ErrInvalidToken
}
return claims, nil
}
// HashPassword creates a bcrypt hash of a password.
// password: the plain-text password to hash.
// Returns: the bcrypt hash and any error.
// Uses bcrypt's default cost factor (currently 10).
func HashPassword(password string) (string, error) {
// GenerateFromPassword creates a bcrypt hash.
// bcrypt is designed to be slow to resist brute-force attacks.
// DefaultCost is a good balance between security and performance.
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hash), nil // Convert bytes to string
}
// CheckPassword verifies a password against a bcrypt hash.
// password: the plain-text password to check.
// hash: the bcrypt hash to verify against.
// Returns: true if the password matches, false otherwise.
func CheckPassword(password, hash string) bool {
// CompareHashAndPassword returns nil if they match, error otherwise.
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}