From bbb6b4b74666566645402bd73a5363bf4cc5164b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Mon, 6 Oct 2025 21:14:58 +0800 Subject: [PATCH] add login and register api --- generate/api/member.api | 1 + internal/domain/const.go | 1 + internal/logic/auth/generate_token.go | 40 +++++++++++ internal/logic/auth/login_logic.go | 68 ++++++++++++++++++- internal/logic/auth/refresh_token_logic.go | 26 ++++++- internal/logic/auth/register_logic.go | 37 +--------- .../auth/request_password_reset_logic.go | 2 +- .../auth/verify_password_reset_code_logic.go | 2 +- internal/types/types.go | 1 + pkg/permission/usecase/token.go | 11 +-- pkg/permission/usecase/token_claims.go | 18 +++++ 11 files changed, 158 insertions(+), 49 deletions(-) create mode 100644 internal/logic/auth/generate_token.go diff --git a/generate/api/member.api b/generate/api/member.api index 1801e3a..34b5742 100644 --- a/generate/api/member.api +++ b/generate/api/member.api @@ -59,6 +59,7 @@ type ( // --- 4. 權杖刷新 --- // RefreshTokenReq 更新 AccessToken RefreshTokenReq { + AccessToken string `json:"access_token" validate:"required"` RefreshToken string `json:"refresh_token" validate:"required"` } diff --git a/internal/domain/const.go b/internal/domain/const.go index 3cee543..1cebed4 100644 --- a/internal/domain/const.go +++ b/internal/domain/const.go @@ -2,3 +2,4 @@ package domain const SuccessCode = 10200 const SuccessMessage = "success" +const DefaultScope = "gateway" diff --git a/internal/logic/auth/generate_token.go b/internal/logic/auth/generate_token.go new file mode 100644 index 0000000..a3cfea0 --- /dev/null +++ b/internal/logic/auth/generate_token.go @@ -0,0 +1,40 @@ +package auth + +import ( + "backend/internal/svc" + "backend/internal/types" + "backend/pkg/permission/domain/entity" + "backend/pkg/permission/domain/token" + "context" + "time" +) + +// 生成 Token +func generateToken(svc *svc.ServiceContext, ctx context.Context, req *types.LoginReq, uid string) (entity.TokenResp, error) { + // scope role 要修改,refresh tl + role := "user" + + tk, err := svc.TokenUC.NewToken(ctx, entity.AuthorizationReq{ + GrantType: token.ClientCredentials.ToString(), + DeviceID: uid, // TODO 沒傳暫時先用UID 替代 + Scope: "gateway", + IsRefreshToken: true, + Expires: time.Now().UTC().Add(svc.Config.Token.AccessTokenExpiry).Unix(), + Data: map[string]string{ + "uid": uid, + }, + Role: role, + Account: req.LoginID, + }) + if err != nil { + return entity.TokenResp{}, err + } + + return entity.TokenResp{ + AccessToken: tk.AccessToken, + TokenType: tk.TokenType, + ExpiresIn: tk.ExpiresIn, + RefreshToken: tk.RefreshToken, + }, nil + +} diff --git a/internal/logic/auth/login_logic.go b/internal/logic/auth/login_logic.go index a36fa26..e0c421b 100644 --- a/internal/logic/auth/login_logic.go +++ b/internal/logic/auth/login_logic.go @@ -1,6 +1,10 @@ package auth import ( + "backend/pkg/library/errs" + "backend/pkg/library/errs/code" + memberD "backend/pkg/member/domain/member" + member "backend/pkg/member/domain/usecase" "context" "backend/internal/svc" @@ -15,7 +19,7 @@ type LoginLogic struct { svcCtx *svc.ServiceContext } -// 使用者登入 +// NewLoginLogic 使用者登入 func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic { return &LoginLogic{ Logger: logx.WithContext(ctx), @@ -25,7 +29,65 @@ func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic } func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginResp, err error) { - // todo: add your logic here and delete this line + //var result member.VerifyAuthResultResponse + switch req.AuthMethod { + case "credentials": + cr, err := l.svcCtx.AccountUC.VerifyPlatformAuthResult(l.ctx, member.VerifyAuthResultRequest{ + Account: req.LoginID, + Token: req.Credentials.Password, + }) + if err != nil { + return nil, err + } - return + if !cr.Status { + return nil, errs.Unauthorized("failed to verify password") + } + case "platform": + switch req.Platform.Provider { + case memberD.Google.ToString(): + _, err := l.svcCtx.AccountUC.VerifyGoogleAuthResult(l.ctx, member.VerifyAuthResultRequest{ + Account: req.LoginID, + Token: req.Platform.Token, + }) + if err != nil { + return nil, err + } + case memberD.Line.ToString(): + // 原始平台驗證 + accessToken, err := l.svcCtx.AccountUC.LineCodeToAccessToken(l.ctx, req.LoginID) + if err != nil { + return nil, err + } + // 換資料 + userInfo, err := l.svcCtx.AccountUC.LineGetProfileByAccessToken(l.ctx, accessToken.AccessToken) + if err != nil { + return nil, err + } + req.LoginID = userInfo.UserID + default: + return nil, errs.InvalidFormatWithScope(code.CloudEPMember, "unsupported 3 party platform") + } + default: + return nil, errs.InvalidFormatWithScope(code.CloudEPMember, "failed to get correct auth method") + } + + account, err := l.svcCtx.AccountUC.GetUIDByAccount(l.ctx, member.GetUIDByAccountRequest{ + Account: req.LoginID, + }) + if err != nil { + return nil, err + } + + tk, err := generateToken(l.svcCtx, l.ctx, req, account.UID) + if err != nil { + return nil, err + } + + return &types.LoginResp{ + UID: account.UID, + AccessToken: tk.AccessToken, + RefreshToken: tk.RefreshToken, + TokenType: tk.TokenType, + }, nil } diff --git a/internal/logic/auth/refresh_token_logic.go b/internal/logic/auth/refresh_token_logic.go index 4c2d19d..84715fd 100644 --- a/internal/logic/auth/refresh_token_logic.go +++ b/internal/logic/auth/refresh_token_logic.go @@ -1,7 +1,10 @@ package auth import ( + "backend/internal/domain" + "backend/pkg/permission/domain/entity" "context" + "time" "backend/internal/svc" "backend/internal/types" @@ -15,7 +18,7 @@ type RefreshTokenLogic struct { svcCtx *svc.ServiceContext } -// 刷新 Access Token +// NewRefreshTokenLogic 刷新 Access Token func NewRefreshTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RefreshTokenLogic { return &RefreshTokenLogic{ Logger: logx.WithContext(ctx), @@ -25,7 +28,24 @@ func NewRefreshTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Refr } func (l *RefreshTokenLogic) RefreshToken(req *types.RefreshTokenReq) (resp *types.RefreshTokenResp, err error) { - // todo: add your logic here and delete this line + data, err := l.svcCtx.TokenUC.ReadTokenBasicData(l.ctx, req.AccessToken) + if err != nil { + return nil, err + } - return + tk, err := l.svcCtx.TokenUC.RefreshToken(l.ctx, entity.RefreshTokenReq{ + Token: req.RefreshToken, + Scope: domain.DefaultScope, + Expires: time.Now().UTC().Add(l.svcCtx.Config.Token.RefreshTokenExpiry).Unix(), + DeviceID: data["uid"], + }) + if err != nil { + return nil, err + } + + return &types.RefreshTokenResp{ + AccessToken: tk.Token, + RefreshToken: tk.OneTimeToken, + TokenType: tk.TokenType, + }, nil } diff --git a/internal/logic/auth/register_logic.go b/internal/logic/auth/register_logic.go index b3a1873..16f0d21 100644 --- a/internal/logic/auth/register_logic.go +++ b/internal/logic/auth/register_logic.go @@ -7,11 +7,7 @@ import ( "backend/pkg/library/errs/code" mb "backend/pkg/member/domain/member" member "backend/pkg/member/domain/usecase" - "backend/pkg/permission/domain/entity" - "backend/pkg/permission/domain/token" "context" - "time" - "google.golang.org/protobuf/proto" "github.com/zeromicro/go-zero/core/logx" @@ -82,7 +78,7 @@ func (l *RegisterLogic) Register(req *types.LoginReq) (resp *types.LoginResp, er // Step 5: 生成 Token req.LoginID = bd.CreateAccountReq.LoginID - tk, err := l.generateToken(req, account.UID) + tk, err := generateToken(l.svcCtx, l.ctx, req, account.UID) if err != nil { return nil, err } @@ -184,34 +180,3 @@ func buildLineData(ctx context.Context, req *types.LoginReq, svc *svc.ServiceCon }, }, nil } - -// 生成 Token -func (l *RegisterLogic) generateToken(req *types.LoginReq, uid string) (entity.TokenResp, error) { - // scope role 要修改,refresh tl - role := "user" - - tk, err := l.svcCtx.TokenUC.NewToken(l.ctx, entity.AuthorizationReq{ - GrantType: token.ClientCredentials.ToString(), - DeviceID: uid, // TODO 沒傳暫時先用UID 替代 - Scope: "gateway", - IsRefreshToken: true, - Expires: time.Now().UTC().Add(l.svcCtx.Config.Token.AccessTokenExpiry).Unix(), - Data: map[string]string{ - "uid": uid, - "role": role, - }, - Role: role, - Account: req.LoginID, - }) - if err != nil { - return entity.TokenResp{}, err - } - - return entity.TokenResp{ - AccessToken: tk.AccessToken, - TokenType: tk.TokenType, - ExpiresIn: tk.ExpiresIn, - RefreshToken: tk.RefreshToken, - }, nil - -} diff --git a/internal/logic/auth/request_password_reset_logic.go b/internal/logic/auth/request_password_reset_logic.go index eaadd90..9de0048 100644 --- a/internal/logic/auth/request_password_reset_logic.go +++ b/internal/logic/auth/request_password_reset_logic.go @@ -15,7 +15,6 @@ type RequestPasswordResetLogic struct { svcCtx *svc.ServiceContext } -// 請求發送密碼重設驗證碼 func NewRequestPasswordResetLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RequestPasswordResetLogic { return &RequestPasswordResetLogic{ Logger: logx.WithContext(ctx), @@ -24,6 +23,7 @@ func NewRequestPasswordResetLogic(ctx context.Context, svcCtx *svc.ServiceContex } } +// RequestPasswordReset 請求發送密碼重設驗證碼 aka 忘記密碼 func (l *RequestPasswordResetLogic) RequestPasswordReset(req *types.RequestPasswordResetReq) (resp *types.RespOK, err error) { // todo: add your logic here and delete this line diff --git a/internal/logic/auth/verify_password_reset_code_logic.go b/internal/logic/auth/verify_password_reset_code_logic.go index 1759270..fe5f6a9 100644 --- a/internal/logic/auth/verify_password_reset_code_logic.go +++ b/internal/logic/auth/verify_password_reset_code_logic.go @@ -15,7 +15,6 @@ type VerifyPasswordResetCodeLogic struct { svcCtx *svc.ServiceContext } -// 校驗密碼重設驗證碼 func NewVerifyPasswordResetCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *VerifyPasswordResetCodeLogic { return &VerifyPasswordResetCodeLogic{ Logger: logx.WithContext(ctx), @@ -24,6 +23,7 @@ func NewVerifyPasswordResetCodeLogic(ctx context.Context, svcCtx *svc.ServiceCon } } +// VerifyPasswordResetCode 校驗密碼重設驗證碼(頁面需求,預先檢查看看, 顯示表演用) func (l *VerifyPasswordResetCodeLogic) VerifyPasswordResetCode(req *types.VerifyCodeReq) (resp *types.RespOK, err error) { // todo: add your logic here and delete this line diff --git a/internal/types/types.go b/internal/types/types.go index 0d9ad4c..b2e9bc8 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -49,6 +49,7 @@ type PlatformPayload struct { } type RefreshTokenReq struct { + AccessToken string `json:"access_token" validate:"required"` RefreshToken string `json:"refresh_token" validate:"required"` } diff --git a/pkg/permission/usecase/token.go b/pkg/permission/usecase/token.go index f49c756..6728802 100755 --- a/pkg/permission/usecase/token.go +++ b/pkg/permission/usecase/token.go @@ -174,12 +174,13 @@ func (use *TokenUseCase) RefreshToken(ctx context.Context, req entity.RefreshTok credentials := token.ClientCredentials newToken, err := use.newToken(ctx, &entity.AuthorizationReq{ GrantType: credentials.ToString(), - Scope: req.Scope, + Scope: claimsData.Scope(), DeviceID: req.DeviceID, Data: claimsData, Expires: req.Expires, IsRefreshToken: true, - Account: req.DeviceID, + Account: claimsData.Account(), + Role: claimsData.Role(), }) if err != nil { return entity.RefreshTokenResp{}, @@ -474,9 +475,9 @@ func (use *TokenUseCase) wrapTokenError(ctx context.Context, param wrapTokenErro {Key: "func", Value: param.funcName}, {Key: "err", Value: param.err.Error()}, } - + logx.WithContext(ctx).Errorw(param.message, logFields...) - + wrappedErr := errs.NewError( code.CatToken, code.CatToken, @@ -595,7 +596,7 @@ func (use *TokenUseCase) BlacklistToken(ctx context.Context, token string, reaso }) } - logx.WithContext(ctx).Infow("token blacklisted", + logx.WithContext(ctx).Infow("token blacklisted", logx.Field("jti", jtiStr), logx.Field("uid", uid), logx.Field("reason", reason)) diff --git a/pkg/permission/usecase/token_claims.go b/pkg/permission/usecase/token_claims.go index e87c769..f83eda8 100755 --- a/pkg/permission/usecase/token_claims.go +++ b/pkg/permission/usecase/token_claims.go @@ -57,3 +57,21 @@ func (tc tokenClaims) UID() string { return uid } + +func (tc tokenClaims) Scope() string { + scope, ok := tc["scope"] + if !ok { + return "" + } + + return scope +} + +func (tc tokenClaims) Account() string { + scope, ok := tc["account"] + if !ok { + return "" + } + + return scope +}