认证提取为 service

This commit is contained in:
xmdhs 2023-11-24 14:51:30 +08:00
parent cd66d3823e
commit 9147dc6b1d
No known key found for this signature in database
GPG Key ID: E809D6D43DEFCC95
13 changed files with 217 additions and 93 deletions

View File

@ -9,7 +9,7 @@ import (
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/service"
sutils "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/service/auth"
)
func (h *Handel) handleErrorService(ctx context.Context, w http.ResponseWriter, err error) {
@ -33,7 +33,7 @@ func (h *Handel) handleErrorService(ctx context.Context, w http.ResponseWriter,
h.handleError(ctx, w, err.Error(), model.ErrPassWord, 401, slog.LevelDebug)
return
}
if errors.Is(err, sutils.ErrUserDisable) {
if errors.Is(err, auth.ErrUserDisable) {
h.handleError(ctx, w, err.Error(), model.ErrUserDisable, 401, slog.LevelDebug)
return
}
@ -41,7 +41,7 @@ func (h *Handel) handleErrorService(ctx context.Context, w http.ResponseWriter,
h.handleError(ctx, w, err.Error(), model.ErrNotAdmin, 401, slog.LevelDebug)
return
}
if errors.Is(err, sutils.ErrTokenInvalid) {
if errors.Is(err, auth.ErrTokenInvalid) {
h.handleError(ctx, w, err.Error(), model.ErrAuth, 401, slog.LevelDebug)
return
}

View File

@ -15,7 +15,7 @@ import (
"github.com/samber/lo"
"github.com/xmdhs/authlib-skin/config"
"github.com/xmdhs/authlib-skin/model/yggdrasil"
utilsS "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/service/auth"
yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil"
"github.com/xmdhs/authlib-skin/utils"
)
@ -111,7 +111,7 @@ func (y *Yggdrasil) Auth(handle http.Handler) http.Handler {
t, err := y.yggdrasilService.Auth(ctx, a)
if err != nil {
if errors.Is(err, utilsS.ErrTokenInvalid) {
if errors.Is(err, auth.ErrTokenInvalid) {
y.logger.DebugContext(ctx, err.Error())
handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "Invalid token.", Error: "ForbiddenOperationException"}, 403)
return

View File

@ -13,6 +13,7 @@ import (
"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/service/auth"
utilsService "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/utils"
)
@ -20,7 +21,7 @@ import (
var ErrNotAdmin = errors.New("无权限")
func (w *WebService) Auth(ctx context.Context, token string) (*model.TokenClaims, error) {
t, err := utilsService.Auth(ctx, yggdrasil.ValidateToken{AccessToken: token}, w.client, w.cache, &w.prikey.PublicKey, false)
t, err := w.authService.Auth(ctx, yggdrasil.ValidateToken{AccessToken: token}, false)
if err != nil {
return nil, fmt.Errorf("WebService.Auth: %w", err)
}
@ -32,7 +33,7 @@ func (w *WebService) IsAdmin(ctx context.Context, t *model.TokenClaims) error {
if err != nil {
return fmt.Errorf("IsAdmin: %w", err)
}
if !utilsService.IsAdmin(u.State) {
if !auth.IsAdmin(u.State) {
return fmt.Errorf("IsAdmin: %w", ErrNotAdmin)
}
return nil
@ -62,12 +63,12 @@ func (w *WebService) ListUser(ctx context.Context, page int, email, name string)
UserInfo: model.UserInfo{
UID: v.ID,
UUID: v.Edges.Profile.UUID,
IsAdmin: utilsService.IsAdmin(v.State),
IsAdmin: auth.IsAdmin(v.State),
},
Email: v.Email,
RegIp: v.RegIP,
Name: v.Edges.Profile.Name,
IsDisable: utilsService.IsDisable(v.State),
IsDisable: auth.IsDisable(v.State),
})
}
@ -131,13 +132,13 @@ func (w *WebService) EditUser(ctx context.Context, u model.EditUser, uid int) er
state := aUser.State
if u.IsAdmin != nil {
state = utilsService.SetAdmin(state, *u.IsAdmin)
state = auth.SetAdmin(state, *u.IsAdmin)
}
if u.IsDisable != nil {
if *u.IsDisable {
changePasswd = true
}
state = utilsService.SetDisable(state, *u.IsDisable)
state = auth.SetDisable(state, *u.IsDisable)
}
if state != aUser.State {
upUser = upUser.SetState(state)

View File

@ -1,4 +1,4 @@
package utils
package auth
import (
"context"
@ -18,14 +18,35 @@ import (
"github.com/xmdhs/authlib-skin/utils"
)
type AuthService struct {
client *ent.Client
c cache.Cache
pub *rsa.PublicKey
pri *rsa.PrivateKey
}
func NewAuthService(
client *ent.Client,
c cache.Cache,
pub *rsa.PublicKey,
pri *rsa.PrivateKey,
) *AuthService {
return &AuthService{
client: client,
c: c,
pub: pub,
pri: pri,
}
}
var (
ErrTokenInvalid = errors.New("token 无效")
ErrUserDisable = errors.New("用户被禁用")
)
func Auth(ctx context.Context, t yggdrasil.ValidateToken, client *ent.Client, c cache.Cache, pubkey *rsa.PublicKey, tmpInvalid bool) (*model.TokenClaims, error) {
func (a *AuthService) Auth(ctx context.Context, t yggdrasil.ValidateToken, tmpInvalid bool) (*model.TokenClaims, error) {
token, err := jwt.ParseWithClaims(t.AccessToken, &model.TokenClaims{}, func(t *jwt.Token) (interface{}, error) {
return pubkey, nil
return a.pub, nil
})
if err != nil {
return nil, fmt.Errorf("Auth: %w", errors.Join(err, ErrTokenInvalid))
@ -54,7 +75,7 @@ func Auth(ctx context.Context, t yggdrasil.ValidateToken, client *ent.Client, c
}
}
tokenID, err := func() (uint64, error) {
c := cache.CacheHelp[uint64]{Cache: c}
c := cache.CacheHelp[uint64]{Cache: a.c}
key := []byte("auth" + strconv.Itoa(claims.UID))
t, err := c.Get(key)
if err != nil {
@ -63,7 +84,7 @@ func Auth(ctx context.Context, t yggdrasil.ValidateToken, client *ent.Client, c
if t != 0 {
return t, nil
}
ut, err := client.UserToken.Query().Where(usertoken.HasUserWith(user.ID(claims.UID))).First(ctx)
ut, err := a.client.UserToken.Query().Where(usertoken.HasUserWith(user.ID(claims.UID))).First(ctx)
if err != nil {
var ne *ent.NotFoundError
if errors.As(err, &ne) {
@ -82,12 +103,12 @@ func Auth(ctx context.Context, t yggdrasil.ValidateToken, client *ent.Client, c
return claims, nil
}
func CreateToken(ctx context.Context, u *ent.User, client *ent.Client, cache cache.Cache, jwtKey *rsa.PrivateKey, clientToken string, uuid string) (string, error) {
func (a *AuthService) CreateToken(ctx context.Context, u *ent.User, clientToken string, uuid string) (string, error) {
if IsDisable(u.State) {
return "", fmt.Errorf("CreateToken: %w", ErrUserDisable)
}
var utoken *ent.UserToken
err := utils.WithTx(ctx, client, func(tx *ent.Tx) error {
err := utils.WithTx(ctx, a.client, func(tx *ent.Tx) error {
var err error
utoken, err = tx.User.QueryToken(u).ForUpdateA().First(ctx)
if err != nil {
@ -108,55 +129,13 @@ func CreateToken(ctx context.Context, u *ent.User, client *ent.Client, cache cac
if err != nil {
return "", fmt.Errorf("CreateToken: %w", err)
}
err = cache.Del([]byte("auth" + strconv.Itoa(u.ID)))
err = a.c.Del([]byte("auth" + strconv.Itoa(u.ID)))
if err != nil {
return "", fmt.Errorf("CreateToken: %w", err)
}
t, err := NewJwtToken(jwtKey, strconv.FormatUint(utoken.TokenID, 10), clientToken, uuid, u.ID)
t, err := NewJwtToken(a.pri, strconv.FormatUint(utoken.TokenID, 10), clientToken, uuid, u.ID)
if err != nil {
return "", fmt.Errorf("CreateToken: %w", err)
}
return t, nil
}
func NewJwtToken(jwtKey *rsa.PrivateKey, tokenID, clientToken, UUID string, userID int) (string, error) {
claims := model.TokenClaims{
Tid: tokenID,
CID: clientToken,
UID: userID,
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.SigningMethodRS256, claims)
jwts, err := token.SignedString(jwtKey)
if err != nil {
return "", fmt.Errorf("newJwtToken: %w", err)
}
return jwts, nil
}
func IsAdmin(state int) bool {
return state&1 == 1
}
func IsDisable(state int) bool {
return state&2 == 2
}
func SetAdmin(state int, is bool) int {
if is {
return state | 1
}
return state & (state ^ 1)
}
func SetDisable(state int, is bool) int {
if is {
return state | 2
}
return state & (state ^ 2)
}

52
service/auth/utils.go Normal file
View File

@ -0,0 +1,52 @@
package auth
import (
"crypto/rsa"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/xmdhs/authlib-skin/model"
)
func IsAdmin(state int) bool {
return state&1 == 1
}
func IsDisable(state int) bool {
return state&2 == 2
}
func SetAdmin(state int, is bool) int {
if is {
return state | 1
}
return state & (state ^ 1)
}
func SetDisable(state int, is bool) int {
if is {
return state | 2
}
return state & (state ^ 2)
}
func NewJwtToken(jwtKey *rsa.PrivateKey, tokenID, clientToken, UUID string, userID int) (string, error) {
claims := model.TokenClaims{
Tid: tokenID,
CID: clientToken,
UID: userID,
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.SigningMethodRS256, claims)
jwts, err := token.SignedString(jwtKey)
if err != nil {
return "", fmt.Errorf("NewJwtToken: %w", err)
}
return jwts, nil
}

View File

@ -0,0 +1,85 @@
package captcha
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"github.com/xmdhs/authlib-skin/config"
)
type CaptchaService struct {
config config.Config
httpClient *http.Client
}
func NewCaptchaService(config config.Config, httpClient *http.Client) *CaptchaService {
return &CaptchaService{
config: config,
httpClient: httpClient,
}
}
func (c *CaptchaService) VerifyCaptcha(ctx context.Context, token, ip string) error {
if c.config.Captcha.Type != "turnstile" {
return nil
}
bw := &bytes.Buffer{}
err := json.NewEncoder(bw).Encode(turnstileResponse{
Secret: c.config.Captcha.Secret,
Response: token,
Remoteip: ip,
})
if err != nil {
return fmt.Errorf("verifyTurnstile: %w", err)
}
reqs, err := http.NewRequestWithContext(ctx, "POST", "https://challenges.cloudflare.com/turnstile/v0/siteverify", bw)
if err != nil {
return fmt.Errorf("verifyTurnstile: %w", err)
}
reqs.Header.Set("Accept", "*/*")
reqs.Header.Set("Content-Type", "application/json")
rep, err := c.httpClient.Do(reqs)
if err != nil {
return fmt.Errorf("verifyTurnstile: %w", err)
}
defer rep.Body.Close()
var t turnstileRet
err = json.NewDecoder(rep.Body).Decode(&t)
if err != nil {
return fmt.Errorf("verifyTurnstile: %w", err)
}
if !t.Success {
return fmt.Errorf("verifyTurnstile: %w", errors.Join(ErrTurnstile{
ErrorCodes: t.ErrorCodes,
}, ErrCaptcha))
}
return nil
}
type turnstileResponse struct {
Secret string `json:"secret"`
Response string `json:"response"`
Remoteip string `json:"remoteip"`
}
type turnstileRet struct {
Success bool `json:"success"`
ErrorCodes []string `json:"error-codes"`
}
var ErrCaptcha = errors.New("验证码错误")
type ErrTurnstile struct {
ErrorCodes []string
}
func (e ErrTurnstile) Error() string {
return strings.Join(e.ErrorCodes, " ")
}

1
service/service.go Normal file
View File

@ -0,0 +1 @@
package service

View File

@ -13,7 +13,7 @@ import (
"github.com/xmdhs/authlib-skin/db/ent/user"
"github.com/xmdhs/authlib-skin/db/ent/userprofile"
"github.com/xmdhs/authlib-skin/model"
utilsService "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/service/auth"
"github.com/xmdhs/authlib-skin/utils"
)
@ -86,7 +86,7 @@ func (w *WebService) Reg(ctx context.Context, u model.UserReg, ipPrefix, ip stri
return err
}
if du.ID == 1 {
err := tx.User.UpdateOne(du).SetState(utilsService.SetAdmin(0, true)).Exec(ctx)
err := tx.User.UpdateOne(du).SetState(auth.SetAdmin(0, true)).Exec(ctx)
if err != nil {
return err
}
@ -96,7 +96,7 @@ func (w *WebService) Reg(ctx context.Context, u model.UserReg, ipPrefix, ip stri
if err != nil {
return model.LoginRep{}, fmt.Errorf("Reg: %w", err)
}
jwt, err := utilsService.CreateToken(ctx, du, w.client, w.cache, w.prikey, "web", userUuid)
jwt, err := w.authService.CreateToken(ctx, du, "web", userUuid)
if err != nil {
return model.LoginRep{}, fmt.Errorf("Login: %w", err)
}
@ -125,7 +125,7 @@ func (w *WebService) Login(ctx context.Context, l model.Login, ip string) (model
if err != nil {
return model.LoginRep{}, fmt.Errorf("Login: %w", err)
}
jwt, err := utilsService.CreateToken(ctx, u, w.client, w.cache, w.prikey, "web", u.Edges.Profile.UUID)
jwt, err := w.authService.CreateToken(ctx, u, "web", u.Edges.Profile.UUID)
if err != nil {
return model.LoginRep{}, fmt.Errorf("Login: %w", err)
}
@ -141,7 +141,7 @@ func (w *WebService) Info(ctx context.Context, t *model.TokenClaims) (model.User
if err != nil {
return model.UserInfo{}, fmt.Errorf("Info: %w", err)
}
isAdmin := utilsService.IsAdmin(u.State)
isAdmin := auth.IsAdmin(u.State)
return model.UserInfo{
UID: t.UID,
UUID: t.Subject,

View File

@ -15,6 +15,7 @@ import (
"github.com/xmdhs/authlib-skin/db/ent"
"github.com/xmdhs/authlib-skin/db/ent/migrate"
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/service/auth"
)
var webService *WebService
@ -34,7 +35,8 @@ func initWebService(ctx context.Context) func() {
c := lo.Must(ent.Open("mysql", "root:root@tcp(127.0.0.1)/test"))
lo.Must0(c.Schema.Create(context.Background(), migrate.WithForeignKeys(false), migrate.WithDropIndex(true), migrate.WithDropColumn(true)))
rsa4 := lo.Must(rsa.GenerateKey(rand.Reader, 4096))
webService = NewWebService(config.Default(), c, &http.Client{}, cache.NewFastCache(100000), rsa4)
cache := cache.NewFastCache(100000)
webService = NewWebService(config.Default(), c, &http.Client{}, cache, rsa4, auth.NewAuthService(c, cache, &rsa4.PublicKey, rsa4))
return func() {
c.User.Delete().Exec(ctx)

View File

@ -9,24 +9,28 @@ import (
"github.com/xmdhs/authlib-skin/config"
"github.com/xmdhs/authlib-skin/db/cache"
"github.com/xmdhs/authlib-skin/db/ent"
"github.com/xmdhs/authlib-skin/service/auth"
"github.com/xmdhs/authlib-skin/utils"
)
type WebService struct {
config config.Config
client *ent.Client
httpClient *http.Client
cache cache.Cache
prikey *rsa.PrivateKey
config config.Config
client *ent.Client
httpClient *http.Client
cache cache.Cache
prikey *rsa.PrivateKey
authService *auth.AuthService
}
func NewWebService(c config.Config, e *ent.Client, hc *http.Client, cache cache.Cache, prikey *rsa.PrivateKey) *WebService {
func NewWebService(c config.Config, e *ent.Client, hc *http.Client,
cache cache.Cache, prikey *rsa.PrivateKey, authService *auth.AuthService) *WebService {
return &WebService{
config: c,
client: e,
httpClient: hc,
cache: cache,
prikey: prikey,
config: c,
client: e,
httpClient: hc,
cache: cache,
prikey: prikey,
authService: authService,
}
}

View File

@ -9,7 +9,7 @@ import (
"github.com/xmdhs/authlib-skin/db/ent/userprofile"
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/model/yggdrasil"
sutils "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/service/auth"
)
type sessionWithIP struct {
@ -19,7 +19,7 @@ type sessionWithIP struct {
func (y *Yggdrasil) SessionJoin(ctx context.Context, s yggdrasil.Session, t *model.TokenClaims, ip string) error {
if s.SelectedProfile != t.Subject {
return fmt.Errorf("SessionJoin: %w", sutils.ErrTokenInvalid)
return fmt.Errorf("SessionJoin: %w", auth.ErrTokenInvalid)
}
err := cache.CacheHelp[sessionWithIP]{Cache: y.cache}.Put([]byte("session"+s.ServerID), sessionWithIP{
User: *t,

View File

@ -25,7 +25,6 @@ import (
"github.com/xmdhs/authlib-skin/db/ent/usertoken"
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/model/yggdrasil"
sutils "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/utils"
"github.com/xmdhs/authlib-skin/utils/sign"
)
@ -67,7 +66,7 @@ func (y *Yggdrasil) Authenticate(cxt context.Context, auth yggdrasil.Authenticat
clientToken = strings.ReplaceAll(uuid.New().String(), "-", "")
}
jwts, err := sutils.CreateToken(cxt, u, y.client, y.cache, y.prikey, clientToken, u.Edges.Profile.UUID)
jwts, err := y.authService.CreateToken(cxt, u, clientToken, u.Edges.Profile.UUID)
if err != nil {
return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err)
}

View File

@ -17,19 +17,19 @@ import (
"github.com/xmdhs/authlib-skin/db/ent"
"github.com/xmdhs/authlib-skin/model"
"github.com/xmdhs/authlib-skin/model/yggdrasil"
sutils "github.com/xmdhs/authlib-skin/service/utils"
"github.com/xmdhs/authlib-skin/service/auth"
)
type Yggdrasil struct {
client *ent.Client
cache cache.Cache
config config.Config
prikey *rsa.PrivateKey
pubStr func() string
client *ent.Client
cache cache.Cache
config config.Config
prikey *rsa.PrivateKey
authService *auth.AuthService
pubStr func() string
}
func NewYggdrasil(client *ent.Client, cache cache.Cache, c config.Config, prikey *rsa.PrivateKey) *Yggdrasil {
func NewYggdrasil(client *ent.Client, cache cache.Cache, c config.Config, prikey *rsa.PrivateKey, authService *auth.AuthService) *Yggdrasil {
return &Yggdrasil{
client: client,
cache: cache,
@ -39,6 +39,7 @@ func NewYggdrasil(client *ent.Client, cache cache.Cache, c config.Config, prikey
derBytes := lo.Must(x509.MarshalPKIXPublicKey(&prikey.PublicKey))
return base64.StdEncoding.EncodeToString(derBytes)
}),
authService: authService,
}
}
@ -77,11 +78,11 @@ func putUint(n uint64, c cache.Cache, key []byte, d time.Duration) error {
}
func newJwtToken(jwtKey *rsa.PrivateKey, tokenID, clientToken, UUID string, userID int) (string, error) {
return sutils.NewJwtToken(jwtKey, tokenID, clientToken, UUID, userID)
return auth.NewJwtToken(jwtKey, tokenID, clientToken, UUID, userID)
}
func (y *Yggdrasil) Auth(ctx context.Context, t yggdrasil.ValidateToken) (*model.TokenClaims, error) {
u, err := sutils.Auth(ctx, t, y.client, y.cache, &y.prikey.PublicKey, true)
u, err := y.authService.Auth(ctx, t, true)
if err != nil {
return nil, fmt.Errorf("ValidateToken: %w", err)
}