package jwt import ( "clapclap/internal/apperr" "clapclap/internal/ginctx" "errors" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) type Authenticator struct { getToken func(*gin.Context) (string, error) // Specify how to retrive the key used to validate the signature getKeyfunc func(*gin.Context) (jwt.Keyfunc, error) // Jwt specific parsing options getParserOptions func(*gin.Context) ([]jwt.ParserOption, error) // Validate logic for arbitrary jwt claims validate func(*jwt.MapClaims) error } func (a Authenticator) Optional(c *gin.Context) { // create an empty claim object claims := &RequestClaims{} // parse the token into the claims err := a.BindClaimsUnverified(c, claims) if err != nil { c.Next() return } // Extract user UUID from claims and store it in the ginctx uuid, err := claims.GetUUID() if err != nil { c.Next() return } c.Set("userID", uuid) c.Next() } func (a Authenticator) Authenticate(c *gin.Context) { // create an empty claim object claims := &RequestClaims{} claims.validate = a.validate // parse the token into the claims err := a.BindClaims(c, claims) if err != nil { apperr.AbortWithError(c, apperr.NewBadRequestError(err, "Failed to parse authentication token")) return } // Execute the custom validation (e.g., checking the "purpose" claim) if err := claims.Validate(); err != nil { apperr.AbortWithError(c, apperr.NewUnauthorizedError(err, "Invalid token claims")) return } // Extract user UUID from claims and store it in the ginctx uuid, err := claims.GetUUID() if err != nil { apperr.AbortWithError(c, apperr.NewUnauthorizedError(err, "Invalid user UUID in token")) return } c.Set("userID", uuid) // Store the claims in the ginctx too c.Set("claims", claims) c.Next() } func (a Authenticator) Optional2(c *gin.Context) { token, err := a.getToken(c) if err != nil || token == "" { c.Next() return } a.Authenticate(c) } func (a *Authenticator) Default() { a.getToken = func(c *gin.Context) (string, error) { return ginctx.GetAuthorization(c, "Bearer") } a.getKeyfunc = func(c *gin.Context) (jwt.Keyfunc, error) { fn := func(tkn *jwt.Token) (any, error) { return ginctx.GetServerJwtSecret(c) } return fn, nil } a.getParserOptions = func(c *gin.Context) ([]jwt.ParserOption, error) { algos := c.GetStringSlice(SUPPORTED_ALGOS_KEY) opts := []jwt.ParserOption{ jwt.WithExpirationRequired(), jwt.WithValidMethods(algos), } return opts, nil } a.validate = func(*jwt.MapClaims) error { return nil } } func (a *Authenticator) Cookie(name string) { a.Default() a.getToken = func(c *gin.Context) (string, error) { cookie, err := c.Cookie(name) if err != nil { return "", err } return cookie, nil } } func (a *Authenticator) Query(paramName string, expectedPurpose string) { a.Default() a.getToken = func(c *gin.Context) (string, error) { token := c.Query(paramName) if token == "" { return "", errors.New("missing token in query parameter") } return token, nil } a.validate = func(claims *jwt.MapClaims) error { purpose, ok := (*claims)["purpose"].(string) if !ok || purpose != expectedPurpose { return errors.New("invalid token purpose") } return nil } }