140 lines
2.3 KiB
Go
140 lines
2.3 KiB
Go
package jwt
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"loveuer/utodo/pkg/logger"
|
|
"reflect"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
type PayloadFn[T any] func(data T) map[string]any
|
|
|
|
type Option[T any] func(*option[T])
|
|
|
|
type option[T any] struct {
|
|
secret string
|
|
payloadFn PayloadFn[T]
|
|
}
|
|
|
|
func WithSecret[T any](secret string) Option[T] {
|
|
return func(o *option[T]) {
|
|
if secret != "" {
|
|
o.secret = secret
|
|
}
|
|
}
|
|
}
|
|
|
|
func WithPayloadFn[T any](payloadFn PayloadFn[T]) Option[T] {
|
|
return func(o *option[T]) {
|
|
if payloadFn != nil {
|
|
o.payloadFn = payloadFn
|
|
}
|
|
}
|
|
}
|
|
|
|
type app[T any] struct {
|
|
secret string
|
|
payloadFn func(data T) map[string]any
|
|
}
|
|
|
|
func (a *app[T]) Generate(data T) (string, error) {
|
|
|
|
type MyClaims struct {
|
|
jwt.RegisteredClaims
|
|
User T
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, MyClaims{User: data})
|
|
|
|
return token.SignedString([]byte(a.secret))
|
|
}
|
|
|
|
func (a *app[T]) Parse(token string) (T, error) {
|
|
type MyClaims struct {
|
|
jwt.RegisteredClaims
|
|
User T
|
|
}
|
|
|
|
var (
|
|
err error
|
|
val T
|
|
claims *jwt.Token
|
|
)
|
|
|
|
if claims, err = jwt.ParseWithClaims(token, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
return []byte(a.secret), nil
|
|
}); err != nil {
|
|
return val, err
|
|
}
|
|
|
|
if !claims.Valid {
|
|
return val, ErrTokenInvalid
|
|
}
|
|
|
|
cv, ok := claims.Claims.(*MyClaims)
|
|
if !ok {
|
|
return val, ErrTokenInvalid
|
|
}
|
|
|
|
return cv.User, nil
|
|
}
|
|
|
|
var (
|
|
ErrTokenInvalid = errors.New("token is invalid")
|
|
defaultPayloadFn PayloadFn[any] = func(data any) map[string]any {
|
|
ref := reflect.ValueOf(data)
|
|
|
|
if ref.Kind() == reflect.Pointer {
|
|
ref = ref.Elem()
|
|
}
|
|
|
|
if ref.Kind() != reflect.Struct {
|
|
logger.WarnCtx(context.TODO(), "data is not a struct")
|
|
return nil
|
|
}
|
|
|
|
typ := ref.Type()
|
|
num := typ.NumField()
|
|
|
|
payload := make(map[string]any, num)
|
|
|
|
for i := 0; i < num; i++ {
|
|
field := typ.Field(i)
|
|
tag := field.Tag.Get("json")
|
|
if tag == "-" {
|
|
continue
|
|
}
|
|
|
|
if tag == "" {
|
|
tag = field.Name
|
|
}
|
|
|
|
payload[tag] = ref.Field(i).Interface()
|
|
}
|
|
|
|
return payload
|
|
}
|
|
)
|
|
|
|
const defaultSecret = "{<Z]h/J>[5-F?s#D;~HGpxuBWi=ezNmb"
|
|
|
|
func New[T any](opts ...Option[T]) *app[T] {
|
|
opt := &option[T]{
|
|
secret: defaultSecret,
|
|
payloadFn: func(data T) map[string]any {
|
|
return defaultPayloadFn(data)
|
|
},
|
|
}
|
|
|
|
for _, o := range opts {
|
|
o(opt)
|
|
}
|
|
|
|
return &app[T]{
|
|
secret: opt.secret,
|
|
payloadFn: opt.payloadFn,
|
|
}
|
|
}
|