package auth import ( "context" "crypto/rand" "encoding/hex" "errors" "fmt" "os" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) var ErrInvalidToken = errors.New("invalid token") type Claims struct { UserID string `json:"user_id"` Email string `json:"email"` Roles []string `json:"roles"` Permissions []string `json:"permissions"` jwt.RegisteredClaims } func HashPassword(password string) (string, error) { b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", fmt.Errorf("bcrypt: %w", err) } return string(b), nil } func CheckPassword(password, hash string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil } func GenerateToken(userID, email string, roles, permissions []string) (string, error) { now := time.Now() claims := Claims{ UserID: userID, Email: email, Roles: roles, Permissions: permissions, RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(24 * time.Hour)), Issuer: "homelab", }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(secret())) } func ValidateToken(raw string) (*Claims, error) { token, err := jwt.ParseWithClaims(raw, &Claims{}, func(t *jwt.Token) (any, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) } return []byte(secret()), nil }) if err != nil { return nil, ErrInvalidToken } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, ErrInvalidToken } return claims, nil } func GenerateCode() string { b := make([]byte, 4) rand.Read(b) return hex.EncodeToString(b) } func secret() string { s := os.Getenv("JWT_SECRET") if s == "" { s = "dev-secret-do-not-use-in-production" } return s } type ctxKey string const ClaimsKey ctxKey = "auth" func WithClaims(ctx context.Context, claims *Claims) context.Context { return context.WithValue(ctx, ClaimsKey, claims) } func FromContext(ctx context.Context) *Claims { c, _ := ctx.Value(ClaimsKey).(*Claims) return c }