homelab/pkg/auth/auth.go
Gonçalo Rodrigues 13b7149614 First Commit
2026-06-13 11:25:23 +01:00

100 lines
2.2 KiB
Go

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
}