100 lines
2.2 KiB
Go
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
|
|
}
|