wip: jwt
This commit is contained in:
139
pkg/jwt/jwt.go
Normal file
139
pkg/jwt/jwt.go
Normal file
@ -0,0 +1,139 @@
|
||||
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,
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user