81 lines
2.4 KiB
Go
81 lines
2.4 KiB
Go
// Package auth provides local JWT-based authentication replacing Keycloak OIDC.
|
|
// Tokens are signed with HMAC-SHA256 using a secret configured via VEYLANT_AUTH_JWT_SECRET.
|
|
package auth
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
|
|
"github.com/veylant/ia-gateway/internal/middleware"
|
|
)
|
|
|
|
// LocalJWTVerifier implements middleware.TokenVerifier using self-signed HS256 tokens.
|
|
type LocalJWTVerifier struct {
|
|
secret []byte
|
|
}
|
|
|
|
// NewLocalJWTVerifier creates a verifier that validates tokens signed with secret.
|
|
func NewLocalJWTVerifier(secret string) *LocalJWTVerifier {
|
|
return &LocalJWTVerifier{secret: []byte(secret)}
|
|
}
|
|
|
|
// veylantClaims is the JWT payload structure for tokens we issue.
|
|
type veylantClaims struct {
|
|
Email string `json:"email"`
|
|
TenantID string `json:"tenant_id"`
|
|
Roles []string `json:"roles"`
|
|
Department string `json:"department,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
jwt.RegisteredClaims
|
|
}
|
|
|
|
// Verify implements middleware.TokenVerifier.
|
|
func (v *LocalJWTVerifier) Verify(_ context.Context, rawToken string) (*middleware.UserClaims, error) {
|
|
token, err := jwt.ParseWithClaims(rawToken, &veylantClaims{}, func(t *jwt.Token) (interface{}, error) {
|
|
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
|
}
|
|
return v.secret, nil
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("token verification failed: %w", err)
|
|
}
|
|
|
|
claims, ok := token.Claims.(*veylantClaims)
|
|
if !ok || !token.Valid {
|
|
return nil, errors.New("invalid token claims")
|
|
}
|
|
|
|
return &middleware.UserClaims{
|
|
UserID: claims.Subject,
|
|
TenantID: claims.TenantID,
|
|
Email: claims.Email,
|
|
Roles: claims.Roles,
|
|
Department: claims.Department,
|
|
}, nil
|
|
}
|
|
|
|
// GenerateToken creates a signed JWT for the given claims.
|
|
func GenerateToken(claims *middleware.UserClaims, name, secret string, ttlHours int) (string, error) {
|
|
now := time.Now()
|
|
jwtClaims := veylantClaims{
|
|
Email: claims.Email,
|
|
TenantID: claims.TenantID,
|
|
Roles: claims.Roles,
|
|
Department: claims.Department,
|
|
Name: name,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
Subject: claims.UserID,
|
|
IssuedAt: jwt.NewNumericDate(now),
|
|
ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(ttlHours) * time.Hour)),
|
|
},
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims)
|
|
return token.SignedString([]byte(secret))
|
|
}
|