searcherside/handlers/cli/token.go

71 lines
1.6 KiB
Go

package cli
import (
"errors"
"fmt"
"log/slog"
"time"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwt"
"code.icb4dc0.de/prskr/searcherside/core/ports"
"code.icb4dc0.de/prskr/searcherside/internal/flags"
)
var (
ErrJwtSecretRequired = errors.New("JWT secret is required")
)
type TokenHandler struct {
Token struct {
Secret flags.HexString `name:"secret" help:"JWT secret"`
Lifetime time.Duration `name:"lifetime" help:"JWT lifetime" default:"24h"`
Subject string `name:"subject" help:"JWT subject" default:"${WHOAMI=nobody}"`
Claims []flags.TokenClaim `name:"claims" help:"JWT claims"`
} `embed:"" prefix:"token."`
}
func (h *TokenHandler) Run(stdout ports.STDOUT, logger *slog.Logger) error {
now := time.Now().UTC()
if len(h.Token.Secret) == 0 {
return ErrJwtSecretRequired
}
if tokenLength := len(h.Token.Secret); tokenLength < jwtSecretLength {
logger.Warn(
"The secret does not have the recommended length",
slog.Int("actual_length", tokenLength),
slog.Int("recommended_length", jwtSecretLength),
)
}
token := jwt.New()
for _, claim := range h.Token.Claims {
if err := token.Set(claim.Key, claim.Value); err != nil {
return err
}
}
if err := token.Set(jwt.SubjectKey, h.Token.Subject); err != nil {
return err
}
if err := token.Set(jwt.NotBeforeKey, now); err != nil {
return err
}
if err := token.Set(jwt.ExpirationKey, now.Add(h.Token.Lifetime)); err != nil {
return err
}
tokenString, err := jwt.Sign(token, jwt.WithKey(jwa.HS256, h.Token.Secret.Raw()))
if err != nil {
return err
}
_, err = fmt.Fprintln(stdout, string(tokenString))
return err
}