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 }