mirror of
https://github.com/teamhanko/hanko.git
synced 2025-10-29 15:49:41 +08:00
110 lines
3.3 KiB
Go
110 lines
3.3 KiB
Go
package session
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/lestrrat-go/jwx/v2/jwt"
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/teamhanko/hanko/backend/v2/dto"
|
|
)
|
|
|
|
// JWTTemplateData holds the data available for template processing
|
|
type JWTTemplateData struct {
|
|
User *dto.UserJWT
|
|
}
|
|
|
|
// ProcessJWTTemplate processes a map of claims using the provided user data and sets them on the token
|
|
func ProcessJWTTemplate(token jwt.Token, claims map[string]interface{}, user dto.UserJWT) error {
|
|
claimTemplateData := JWTTemplateData{
|
|
User: &user,
|
|
}
|
|
for key, value := range claims {
|
|
processedValue, err := processClaimTemplate(value, claimTemplateData)
|
|
if err != nil {
|
|
log.Warn().Err(err).Str("session", key).Msgf("failed to process custom JWT claim template: %+v", value)
|
|
continue
|
|
}
|
|
err = token.Set(key, processedValue)
|
|
if err != nil {
|
|
log.Warn().Err(err).Str("session", key).Msgf("failed to set processed JWT claim %+v to token", value)
|
|
continue
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// processClaimTemplate processes a claim value, handling both string templates and nested structures
|
|
func processClaimTemplate(value interface{}, data JWTTemplateData) (interface{}, error) {
|
|
switch v := value.(type) {
|
|
case string:
|
|
return parseClaimTemplateValue(v, data)
|
|
case map[string]interface{}:
|
|
result := make(map[string]interface{})
|
|
for key, val := range v {
|
|
processed, err := processClaimTemplate(val, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result[key] = processed
|
|
}
|
|
return result, nil
|
|
case []interface{}:
|
|
result := make([]interface{}, len(v))
|
|
for i, val := range v {
|
|
processed, err := processClaimTemplate(val, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result[i] = processed
|
|
}
|
|
return result, nil
|
|
default:
|
|
return value, nil
|
|
}
|
|
}
|
|
|
|
// parseClaimTemplateValue parses and executes a template string using the provided data
|
|
func parseClaimTemplateValue(tmplStr string, data JWTTemplateData) (interface{}, error) {
|
|
tmpl, err := template.New("").Parse(tmplStr)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to parse template: %w", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err = tmpl.Execute(&buf, data); err != nil {
|
|
return "", fmt.Errorf("failed to execute template: %w", err)
|
|
}
|
|
|
|
resultString := buf.String()
|
|
|
|
// "Workaround"/"hack" for when the template expression evaluates to a boolean string, i.e. "true"
|
|
// or "false". This converts it to a bool for consistency's sake (i.e. to prevent that both boolean
|
|
// values and boolean strings are eventually set in the JWT).
|
|
if resultString == "true" || resultString == "false" {
|
|
b, err := strconv.ParseBool(resultString)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse string as bool: %w", err)
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// Another workaround for JSON objects and arrays. Somewhere along the way, representations
|
|
// of JSON objects and arrays end up with multiple escape characters. This was the only way
|
|
// to properly get rid of them.
|
|
looksLikeObject := strings.HasPrefix(resultString, "{") && strings.HasSuffix(resultString, "}")
|
|
looksLikeArray := strings.HasPrefix(resultString, "[") && strings.HasSuffix(resultString, "]")
|
|
if looksLikeObject || looksLikeArray {
|
|
var result interface{}
|
|
if err := json.Unmarshal([]byte(resultString), &result); err == nil {
|
|
return result, nil
|
|
}
|
|
}
|
|
|
|
return resultString, nil
|
|
}
|