刷新令牌
This commit is contained in:
parent
689e53d177
commit
23892f19c7
@ -97,9 +97,9 @@ var (
|
|||||||
},
|
},
|
||||||
Indexes: []*schema.Index{
|
Indexes: []*schema.Index{
|
||||||
{
|
{
|
||||||
Name: "userprofile_name",
|
Name: "userprofile_uuid",
|
||||||
Unique: false,
|
Unique: false,
|
||||||
Columns: []*schema.Column{UserProfilesColumns[1]},
|
Columns: []*schema.Column{UserProfilesColumns[2]},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,6 @@ func (UserProfile) Edges() []ent.Edge {
|
|||||||
|
|
||||||
func (UserProfile) Indexes() []ent.Index {
|
func (UserProfile) Indexes() []ent.Index {
|
||||||
return []ent.Index{
|
return []ent.Index{
|
||||||
index.Fields("name"),
|
index.Fields("uuid"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,3 +96,26 @@ func (y *Yggdrasil) Invalidate() httprouter.Handle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (y *Yggdrasil) Refresh() httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
cxt := r.Context()
|
||||||
|
a, has := getAnyModel[yggdrasil.RefreshToken](cxt, w, r.Body, y.validate, y.logger)
|
||||||
|
if !has {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t, err := y.yggdrasilService.Refresh(cxt, a)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sutils.ErrTokenInvalid) {
|
||||||
|
y.logger.DebugContext(cxt, err.Error())
|
||||||
|
handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: "Invalid token.", Error: "ForbiddenOperationException"}, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
y.logger.WarnContext(cxt, err.Error())
|
||||||
|
handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: err.Error()}, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(t)
|
||||||
|
w.Write(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package yggdrasil
|
package yggdrasil
|
||||||
|
|
||||||
type Pass struct {
|
type Pass struct {
|
||||||
Username string `json:"username" validate:"required"`
|
// 目前只能是 email
|
||||||
|
Username string `json:"username" validate:"required,email"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,13 +24,14 @@ type Error struct {
|
|||||||
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
AccessToken string `json:"accessToken"`
|
AccessToken string `json:"accessToken"`
|
||||||
AvailableProfiles []TokenProfile `json:"availableProfiles"`
|
AvailableProfiles []TokenProfile `json:"availableProfiles,omitempty"`
|
||||||
ClientToken string `json:"clientToken"`
|
ClientToken string `json:"clientToken"`
|
||||||
SelectedProfile TokenProfile `json:"selectedProfile"`
|
SelectedProfile TokenProfile `json:"selectedProfile,omitempty"`
|
||||||
User TokenUser `json:"user,omitempty"`
|
User TokenUser `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenProfile struct {
|
type TokenProfile struct {
|
||||||
|
// 就是 uuid
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
@ -43,3 +45,9 @@ type ValidateToken struct {
|
|||||||
AccessToken string `json:"accessToken" validate:"required,jwt"`
|
AccessToken string `json:"accessToken" validate:"required,jwt"`
|
||||||
ClientToken string `json:"clientToken"`
|
ClientToken string `json:"clientToken"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RefreshToken struct {
|
||||||
|
ValidateToken
|
||||||
|
RequestUser bool `json:"requestUser"`
|
||||||
|
SelectedProfile TokenProfile `json:"selectedProfile"`
|
||||||
|
}
|
||||||
|
@ -18,7 +18,7 @@ var (
|
|||||||
ErrTokenInvalid = errors.New("token 无效")
|
ErrTokenInvalid = errors.New("token 无效")
|
||||||
)
|
)
|
||||||
|
|
||||||
func Auth(ctx context.Context, t yggdrasil.ValidateToken, client *ent.Client, jwtKey string) (*model.TokenClaims, error) {
|
func Auth(ctx context.Context, t yggdrasil.ValidateToken, client *ent.Client, jwtKey string, tmpInvalid bool) (*model.TokenClaims, error) {
|
||||||
token, err := jwt.ParseWithClaims(t.AccessToken, &model.TokenClaims{}, func(t *jwt.Token) (interface{}, error) {
|
token, err := jwt.ParseWithClaims(t.AccessToken, &model.TokenClaims{}, func(t *jwt.Token) (interface{}, error) {
|
||||||
return []byte(jwtKey), nil
|
return []byte(jwtKey), nil
|
||||||
})
|
})
|
||||||
@ -34,6 +34,7 @@ func Auth(ctx context.Context, t yggdrasil.ValidateToken, client *ent.Client, jw
|
|||||||
return nil, fmt.Errorf("Auth: %w", ErrTokenInvalid)
|
return nil, fmt.Errorf("Auth: %w", ErrTokenInvalid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpInvalid {
|
||||||
it, err := claims.GetIssuedAt()
|
it, err := claims.GetIssuedAt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Auth: %w", err)
|
return nil, fmt.Errorf("Auth: %w", err)
|
||||||
@ -42,10 +43,11 @@ func Auth(ctx context.Context, t yggdrasil.ValidateToken, client *ent.Client, jw
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Auth: %w", err)
|
return nil, fmt.Errorf("Auth: %w", err)
|
||||||
}
|
}
|
||||||
invalidTime := it.Add(et.Time.Sub(it.Time))
|
invalidTime := it.Add(et.Time.Sub(it.Time) / 2)
|
||||||
if time.Now().After(invalidTime) {
|
if time.Now().After(invalidTime) {
|
||||||
return nil, fmt.Errorf("Auth: %w", ErrTokenInvalid)
|
return nil, fmt.Errorf("Auth: %w", ErrTokenInvalid)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ut, err := client.UserToken.Query().Where(usertoken.UUIDEQ(claims.Subject)).First(ctx)
|
ut, err := client.UserToken.Query().Where(usertoken.UUIDEQ(claims.Subject)).First(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -8,12 +8,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/xmdhs/authlib-skin/db/ent"
|
"github.com/xmdhs/authlib-skin/db/ent"
|
||||||
"github.com/xmdhs/authlib-skin/db/ent/user"
|
"github.com/xmdhs/authlib-skin/db/ent/user"
|
||||||
"github.com/xmdhs/authlib-skin/db/ent/usertoken"
|
"github.com/xmdhs/authlib-skin/db/ent/usertoken"
|
||||||
"github.com/xmdhs/authlib-skin/model"
|
|
||||||
"github.com/xmdhs/authlib-skin/model/yggdrasil"
|
"github.com/xmdhs/authlib-skin/model/yggdrasil"
|
||||||
sutils "github.com/xmdhs/authlib-skin/service/utils"
|
sutils "github.com/xmdhs/authlib-skin/service/utils"
|
||||||
"github.com/xmdhs/authlib-skin/utils"
|
"github.com/xmdhs/authlib-skin/utils"
|
||||||
@ -80,26 +78,15 @@ func (y *Yggdrasil) Authenticate(cxt context.Context, auth yggdrasil.Authenticat
|
|||||||
return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err)
|
return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
claims := model.TokenClaims{
|
jwts, err := newJwtToken(y.config.JwtKey, strconv.FormatUint(utoken.TokenID, 10), clientToken, u.Edges.Profile.UUID)
|
||||||
Tid: strconv.FormatUint(utoken.TokenID, 10),
|
|
||||||
CID: clientToken,
|
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * 24 * time.Hour)),
|
|
||||||
Issuer: "authlib-skin",
|
|
||||||
Subject: u.Edges.Profile.UUID,
|
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
||||||
jwts, err := token.SignedString([]byte(y.config.JwtKey))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err)
|
return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := yggdrasil.TokenProfile{
|
p := yggdrasil.TokenProfile{
|
||||||
ID: u.Edges.Profile.UUID,
|
ID: u.Edges.Profile.UUID,
|
||||||
Name: u.Edges.Profile.Name,
|
Name: u.Edges.Profile.Name,
|
||||||
}
|
}
|
||||||
|
|
||||||
return yggdrasil.Token{
|
return yggdrasil.Token{
|
||||||
AccessToken: jwts,
|
AccessToken: jwts,
|
||||||
AvailableProfiles: []yggdrasil.TokenProfile{p},
|
AvailableProfiles: []yggdrasil.TokenProfile{p},
|
||||||
@ -113,7 +100,7 @@ func (y *Yggdrasil) Authenticate(cxt context.Context, auth yggdrasil.Authenticat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (y *Yggdrasil) ValidateToken(ctx context.Context, t yggdrasil.ValidateToken) error {
|
func (y *Yggdrasil) ValidateToken(ctx context.Context, t yggdrasil.ValidateToken) error {
|
||||||
_, err := sutils.Auth(ctx, t, y.client, y.config.JwtKey)
|
_, err := sutils.Auth(ctx, t, y.client, y.config.JwtKey, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ValidateToken: %w", err)
|
return fmt.Errorf("ValidateToken: %w", err)
|
||||||
}
|
}
|
||||||
@ -141,7 +128,7 @@ func (y *Yggdrasil) SignOut(ctx context.Context, t yggdrasil.Pass) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (y *Yggdrasil) Invalidate(ctx context.Context, accessToken string) error {
|
func (y *Yggdrasil) Invalidate(ctx context.Context, accessToken string) error {
|
||||||
t, err := sutils.Auth(ctx, yggdrasil.ValidateToken{AccessToken: accessToken}, y.client, y.config.JwtKey)
|
t, err := sutils.Auth(ctx, yggdrasil.ValidateToken{AccessToken: accessToken}, y.client, y.config.JwtKey, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Invalidate: %w", err)
|
return fmt.Errorf("Invalidate: %w", err)
|
||||||
}
|
}
|
||||||
@ -151,3 +138,21 @@ func (y *Yggdrasil) Invalidate(ctx context.Context, accessToken string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (y *Yggdrasil) Refresh(ctx context.Context, token yggdrasil.RefreshToken) (yggdrasil.Token, error) {
|
||||||
|
t, err := sutils.Auth(ctx, yggdrasil.ValidateToken{AccessToken: token.AccessToken, ClientToken: token.ClientToken}, y.client, y.config.JwtKey, false)
|
||||||
|
if err != nil {
|
||||||
|
return yggdrasil.Token{}, fmt.Errorf("Refresh: %w", err)
|
||||||
|
}
|
||||||
|
jwts, err := newJwtToken(y.config.JwtKey, t.Tid, t.CID, t.Subject)
|
||||||
|
if err != nil {
|
||||||
|
return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err)
|
||||||
|
}
|
||||||
|
return yggdrasil.Token{
|
||||||
|
AccessToken: jwts,
|
||||||
|
ClientToken: t.CID,
|
||||||
|
User: yggdrasil.TokenUser{
|
||||||
|
ID: t.Subject,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -5,9 +5,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/xmdhs/authlib-skin/config"
|
"github.com/xmdhs/authlib-skin/config"
|
||||||
"github.com/xmdhs/authlib-skin/db/cache"
|
"github.com/xmdhs/authlib-skin/db/cache"
|
||||||
"github.com/xmdhs/authlib-skin/db/ent"
|
"github.com/xmdhs/authlib-skin/db/ent"
|
||||||
|
"github.com/xmdhs/authlib-skin/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Yggdrasil struct {
|
type Yggdrasil struct {
|
||||||
@ -57,3 +59,22 @@ func putUint(n uint64, c cache.Cache, key []byte, d time.Duration) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newJwtToken(jwtKey string, tokenID, clientToken, UUID string) (string, error) {
|
||||||
|
claims := model.TokenClaims{
|
||||||
|
Tid: tokenID,
|
||||||
|
CID: clientToken,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * 24 * time.Hour)),
|
||||||
|
Issuer: "authlib-skin",
|
||||||
|
Subject: UUID,
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
jwts, err := token.SignedString([]byte(jwtKey))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("newJwtToken: %w", err)
|
||||||
|
}
|
||||||
|
return jwts, nil
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user