增加接口判断
This commit is contained in:
parent
5890e3321a
commit
bea9dcf1dd
File diff suppressed because it is too large
Load Diff
@ -62,6 +62,17 @@ message RegisterThirdPartyRequest {
|
|||||||
string timezone = 20;
|
string timezone = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckThirdPartyRegisteredRequest 只校验三方凭证并判断是否已有完整注册用户。
|
||||||
|
message CheckThirdPartyRegisteredRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string provider = 2;
|
||||||
|
string credential = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CheckThirdPartyRegisteredResponse {
|
||||||
|
bool registered = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// AuthResponse 返回标准认证令牌。
|
// AuthResponse 返回标准认证令牌。
|
||||||
message AuthResponse {
|
message AuthResponse {
|
||||||
AuthToken token = 1;
|
AuthToken token = 1;
|
||||||
@ -208,6 +219,7 @@ service AuthService {
|
|||||||
rpc LoginPassword(LoginPasswordRequest) returns (AuthResponse);
|
rpc LoginPassword(LoginPasswordRequest) returns (AuthResponse);
|
||||||
rpc LoginThirdParty(LoginThirdPartyRequest) returns (AuthResponse);
|
rpc LoginThirdParty(LoginThirdPartyRequest) returns (AuthResponse);
|
||||||
rpc RegisterThirdParty(RegisterThirdPartyRequest) returns (AuthResponse);
|
rpc RegisterThirdParty(RegisterThirdPartyRequest) returns (AuthResponse);
|
||||||
|
rpc CheckThirdPartyRegistered(CheckThirdPartyRegisteredRequest) returns (CheckThirdPartyRegisteredResponse);
|
||||||
rpc SetPassword(SetPasswordRequest) returns (SetPasswordResponse);
|
rpc SetPassword(SetPasswordRequest) returns (SetPasswordResponse);
|
||||||
rpc QuickCreateAccount(QuickCreateAccountRequest) returns (QuickCreateAccountResponse);
|
rpc QuickCreateAccount(QuickCreateAccountRequest) returns (QuickCreateAccountResponse);
|
||||||
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
|
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
|
||||||
|
|||||||
@ -22,6 +22,7 @@ const (
|
|||||||
AuthService_LoginPassword_FullMethodName = "/hyapp.user.v1.AuthService/LoginPassword"
|
AuthService_LoginPassword_FullMethodName = "/hyapp.user.v1.AuthService/LoginPassword"
|
||||||
AuthService_LoginThirdParty_FullMethodName = "/hyapp.user.v1.AuthService/LoginThirdParty"
|
AuthService_LoginThirdParty_FullMethodName = "/hyapp.user.v1.AuthService/LoginThirdParty"
|
||||||
AuthService_RegisterThirdParty_FullMethodName = "/hyapp.user.v1.AuthService/RegisterThirdParty"
|
AuthService_RegisterThirdParty_FullMethodName = "/hyapp.user.v1.AuthService/RegisterThirdParty"
|
||||||
|
AuthService_CheckThirdPartyRegistered_FullMethodName = "/hyapp.user.v1.AuthService/CheckThirdPartyRegistered"
|
||||||
AuthService_SetPassword_FullMethodName = "/hyapp.user.v1.AuthService/SetPassword"
|
AuthService_SetPassword_FullMethodName = "/hyapp.user.v1.AuthService/SetPassword"
|
||||||
AuthService_QuickCreateAccount_FullMethodName = "/hyapp.user.v1.AuthService/QuickCreateAccount"
|
AuthService_QuickCreateAccount_FullMethodName = "/hyapp.user.v1.AuthService/QuickCreateAccount"
|
||||||
AuthService_RefreshToken_FullMethodName = "/hyapp.user.v1.AuthService/RefreshToken"
|
AuthService_RefreshToken_FullMethodName = "/hyapp.user.v1.AuthService/RefreshToken"
|
||||||
@ -42,6 +43,7 @@ type AuthServiceClient interface {
|
|||||||
LoginPassword(ctx context.Context, in *LoginPasswordRequest, opts ...grpc.CallOption) (*AuthResponse, error)
|
LoginPassword(ctx context.Context, in *LoginPasswordRequest, opts ...grpc.CallOption) (*AuthResponse, error)
|
||||||
LoginThirdParty(ctx context.Context, in *LoginThirdPartyRequest, opts ...grpc.CallOption) (*AuthResponse, error)
|
LoginThirdParty(ctx context.Context, in *LoginThirdPartyRequest, opts ...grpc.CallOption) (*AuthResponse, error)
|
||||||
RegisterThirdParty(ctx context.Context, in *RegisterThirdPartyRequest, opts ...grpc.CallOption) (*AuthResponse, error)
|
RegisterThirdParty(ctx context.Context, in *RegisterThirdPartyRequest, opts ...grpc.CallOption) (*AuthResponse, error)
|
||||||
|
CheckThirdPartyRegistered(ctx context.Context, in *CheckThirdPartyRegisteredRequest, opts ...grpc.CallOption) (*CheckThirdPartyRegisteredResponse, error)
|
||||||
SetPassword(ctx context.Context, in *SetPasswordRequest, opts ...grpc.CallOption) (*SetPasswordResponse, error)
|
SetPassword(ctx context.Context, in *SetPasswordRequest, opts ...grpc.CallOption) (*SetPasswordResponse, error)
|
||||||
QuickCreateAccount(ctx context.Context, in *QuickCreateAccountRequest, opts ...grpc.CallOption) (*QuickCreateAccountResponse, error)
|
QuickCreateAccount(ctx context.Context, in *QuickCreateAccountRequest, opts ...grpc.CallOption) (*QuickCreateAccountResponse, error)
|
||||||
RefreshToken(ctx context.Context, in *RefreshTokenRequest, opts ...grpc.CallOption) (*RefreshTokenResponse, error)
|
RefreshToken(ctx context.Context, in *RefreshTokenRequest, opts ...grpc.CallOption) (*RefreshTokenResponse, error)
|
||||||
@ -91,6 +93,16 @@ func (c *authServiceClient) RegisterThirdParty(ctx context.Context, in *Register
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *authServiceClient) CheckThirdPartyRegistered(ctx context.Context, in *CheckThirdPartyRegisteredRequest, opts ...grpc.CallOption) (*CheckThirdPartyRegisteredResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(CheckThirdPartyRegisteredResponse)
|
||||||
|
err := c.cc.Invoke(ctx, AuthService_CheckThirdPartyRegistered_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *authServiceClient) SetPassword(ctx context.Context, in *SetPasswordRequest, opts ...grpc.CallOption) (*SetPasswordResponse, error) {
|
func (c *authServiceClient) SetPassword(ctx context.Context, in *SetPasswordRequest, opts ...grpc.CallOption) (*SetPasswordResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(SetPasswordResponse)
|
out := new(SetPasswordResponse)
|
||||||
@ -190,6 +202,7 @@ type AuthServiceServer interface {
|
|||||||
LoginPassword(context.Context, *LoginPasswordRequest) (*AuthResponse, error)
|
LoginPassword(context.Context, *LoginPasswordRequest) (*AuthResponse, error)
|
||||||
LoginThirdParty(context.Context, *LoginThirdPartyRequest) (*AuthResponse, error)
|
LoginThirdParty(context.Context, *LoginThirdPartyRequest) (*AuthResponse, error)
|
||||||
RegisterThirdParty(context.Context, *RegisterThirdPartyRequest) (*AuthResponse, error)
|
RegisterThirdParty(context.Context, *RegisterThirdPartyRequest) (*AuthResponse, error)
|
||||||
|
CheckThirdPartyRegistered(context.Context, *CheckThirdPartyRegisteredRequest) (*CheckThirdPartyRegisteredResponse, error)
|
||||||
SetPassword(context.Context, *SetPasswordRequest) (*SetPasswordResponse, error)
|
SetPassword(context.Context, *SetPasswordRequest) (*SetPasswordResponse, error)
|
||||||
QuickCreateAccount(context.Context, *QuickCreateAccountRequest) (*QuickCreateAccountResponse, error)
|
QuickCreateAccount(context.Context, *QuickCreateAccountRequest) (*QuickCreateAccountResponse, error)
|
||||||
RefreshToken(context.Context, *RefreshTokenRequest) (*RefreshTokenResponse, error)
|
RefreshToken(context.Context, *RefreshTokenRequest) (*RefreshTokenResponse, error)
|
||||||
@ -218,6 +231,9 @@ func (UnimplementedAuthServiceServer) LoginThirdParty(context.Context, *LoginThi
|
|||||||
func (UnimplementedAuthServiceServer) RegisterThirdParty(context.Context, *RegisterThirdPartyRequest) (*AuthResponse, error) {
|
func (UnimplementedAuthServiceServer) RegisterThirdParty(context.Context, *RegisterThirdPartyRequest) (*AuthResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RegisterThirdParty not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method RegisterThirdParty not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedAuthServiceServer) CheckThirdPartyRegistered(context.Context, *CheckThirdPartyRegisteredRequest) (*CheckThirdPartyRegisteredResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method CheckThirdPartyRegistered not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedAuthServiceServer) SetPassword(context.Context, *SetPasswordRequest) (*SetPasswordResponse, error) {
|
func (UnimplementedAuthServiceServer) SetPassword(context.Context, *SetPasswordRequest) (*SetPasswordResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method SetPassword not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method SetPassword not implemented")
|
||||||
}
|
}
|
||||||
@ -320,6 +336,24 @@ func _AuthService_RegisterThirdParty_Handler(srv interface{}, ctx context.Contex
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _AuthService_CheckThirdPartyRegistered_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(CheckThirdPartyRegisteredRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AuthServiceServer).CheckThirdPartyRegistered(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AuthService_CheckThirdPartyRegistered_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AuthServiceServer).CheckThirdPartyRegistered(ctx, req.(*CheckThirdPartyRegisteredRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _AuthService_SetPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _AuthService_SetPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(SetPasswordRequest)
|
in := new(SetPasswordRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -501,6 +535,10 @@ var AuthService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "RegisterThirdParty",
|
MethodName: "RegisterThirdParty",
|
||||||
Handler: _AuthService_RegisterThirdParty_Handler,
|
Handler: _AuthService_RegisterThirdParty_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "CheckThirdPartyRegistered",
|
||||||
|
Handler: _AuthService_CheckThirdPartyRegistered_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "SetPassword",
|
MethodName: "SetPassword",
|
||||||
Handler: _AuthService_SetPassword_Handler,
|
Handler: _AuthService_SetPassword_Handler,
|
||||||
|
|||||||
@ -6,7 +6,7 @@ Google 登录后,Flutter 只暂存 Firebase ID token 和 Google 基本信息
|
|||||||
|
|
||||||
资料页允许 pending third-party auth 进入,不要求 `_authService.isSignedIn`。用户点击完成后,Flutter 调注册接口;后端校验 Firebase ID token、创建或补齐用户资料、返回 access/refresh token。Flutter 收到 token 后再写入现有 `AppAuthSession`。
|
资料页允许 pending third-party auth 进入,不要求 `_authService.isSignedIn`。用户点击完成后,Flutter 调注册接口;后端校验 Firebase ID token、创建或补齐用户资料、返回 access/refresh token。Flutter 收到 token 后再写入现有 `AppAuthSession`。
|
||||||
|
|
||||||
旧接口 `POST /api/v1/auth/third-party/login` 仍保留兼容,不用于新的 Google 完整注册流程。
|
旧接口 `POST /api/v1/auth/third-party/login` 仍保留兼容。新 Google 流程只能在检查接口返回 `registered=true` 后调用它拿 session;不要在 `registered=false` 时调用它,避免旧接口创建 pending 用户。
|
||||||
|
|
||||||
## 调用流程
|
## 调用流程
|
||||||
|
|
||||||
@ -16,10 +16,64 @@ Google 登录后,Flutter 只暂存 Firebase ID token 和 Google 基本信息
|
|||||||
- `credential=<firebase_id_token>`
|
- `credential=<firebase_id_token>`
|
||||||
- Google 返回的昵称、头像等基础信息
|
- Google 返回的昵称、头像等基础信息
|
||||||
- `device_id`、`platform`、`language`、`timezone`
|
- `device_id`、`platform`、`language`、`timezone`
|
||||||
3. 进入资料页,用户补齐 `username`、`avatar`、`gender`、`country`、`invite_code`。
|
3. 调注册检查接口。
|
||||||
4. 如果用户选择本地头像,先调用注册头像上传接口拿 URL。
|
4. 如果 `registered=true`,调用旧三方登录接口拿 session,写入 `AppAuthSession` 后进首页。
|
||||||
5. 调 `POST /api/v1/auth/third-party/register`。
|
5. 如果 `registered=false`,进入资料页,用户补齐 `username`、`avatar`、`gender`、`country`、`invite_code`。
|
||||||
6. 成功后把返回的 token 写入 `AppAuthSession`。
|
6. 如果用户选择本地头像,先调用注册头像上传接口拿 URL。
|
||||||
|
7. 调 `POST /api/v1/auth/third-party/register`。
|
||||||
|
8. 成功后把返回的 token 写入 `AppAuthSession`。
|
||||||
|
|
||||||
|
## 注册检查
|
||||||
|
|
||||||
|
地址:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/v1/auth/third-party/registered/check
|
||||||
|
```
|
||||||
|
|
||||||
|
Headers:
|
||||||
|
|
||||||
|
| Header | 必填 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `Content-Type` | 是 | `application/json` |
|
||||||
|
| `X-App-Code` | 否 | App 编码,默认 `lalu` |
|
||||||
|
| `X-App-Package` | 否 | 包名;后端可按包名解析 app_code。 |
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `provider` | string | 是 | 固定传 `firebase`。 |
|
||||||
|
| `credential` | string | 是 | Firebase ID token。 |
|
||||||
|
|
||||||
|
请求示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"provider": "firebase",
|
||||||
|
"credential": "firebase_id_token"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
返回值:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "ok",
|
||||||
|
"request_id": "req_xxx",
|
||||||
|
"data": {
|
||||||
|
"registered": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Flutter 处理:
|
||||||
|
|
||||||
|
| `registered` | 处理 |
|
||||||
|
| --- | --- |
|
||||||
|
| `true` | 用户已完成注册;再调用 `POST /api/v1/auth/third-party/login` 获取 session。 |
|
||||||
|
| `false` | 用户未完成注册,或者只有旧流程留下的 pending 用户;进入资料页,最后调用注册接口。 |
|
||||||
|
|
||||||
## 注册头像上传
|
## 注册头像上传
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ type UserAuthClient interface {
|
|||||||
LoginPassword(ctx context.Context, req *userv1.LoginPasswordRequest) (*userv1.AuthResponse, error)
|
LoginPassword(ctx context.Context, req *userv1.LoginPasswordRequest) (*userv1.AuthResponse, error)
|
||||||
LoginThirdParty(ctx context.Context, req *userv1.LoginThirdPartyRequest) (*userv1.AuthResponse, error)
|
LoginThirdParty(ctx context.Context, req *userv1.LoginThirdPartyRequest) (*userv1.AuthResponse, error)
|
||||||
RegisterThirdParty(ctx context.Context, req *userv1.RegisterThirdPartyRequest) (*userv1.AuthResponse, error)
|
RegisterThirdParty(ctx context.Context, req *userv1.RegisterThirdPartyRequest) (*userv1.AuthResponse, error)
|
||||||
|
CheckThirdPartyRegistered(ctx context.Context, req *userv1.CheckThirdPartyRegisteredRequest) (*userv1.CheckThirdPartyRegisteredResponse, error)
|
||||||
SetPassword(ctx context.Context, req *userv1.SetPasswordRequest) (*userv1.SetPasswordResponse, error)
|
SetPassword(ctx context.Context, req *userv1.SetPasswordRequest) (*userv1.SetPasswordResponse, error)
|
||||||
QuickCreateAccount(ctx context.Context, req *userv1.QuickCreateAccountRequest) (*userv1.QuickCreateAccountResponse, error)
|
QuickCreateAccount(ctx context.Context, req *userv1.QuickCreateAccountRequest) (*userv1.QuickCreateAccountResponse, error)
|
||||||
RefreshToken(ctx context.Context, req *userv1.RefreshTokenRequest) (*userv1.RefreshTokenResponse, error)
|
RefreshToken(ctx context.Context, req *userv1.RefreshTokenRequest) (*userv1.RefreshTokenResponse, error)
|
||||||
@ -260,6 +261,10 @@ func (c *grpcUserAuthClient) RegisterThirdParty(ctx context.Context, req *userv1
|
|||||||
return c.client.RegisterThirdParty(ctx, req)
|
return c.client.RegisterThirdParty(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *grpcUserAuthClient) CheckThirdPartyRegistered(ctx context.Context, req *userv1.CheckThirdPartyRegisteredRequest) (*userv1.CheckThirdPartyRegisteredResponse, error) {
|
||||||
|
return c.client.CheckThirdPartyRegistered(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *grpcUserAuthClient) SetPassword(ctx context.Context, req *userv1.SetPasswordRequest) (*userv1.SetPasswordResponse, error) {
|
func (c *grpcUserAuthClient) SetPassword(ctx context.Context, req *userv1.SetPasswordRequest) (*userv1.SetPasswordResponse, error) {
|
||||||
return c.client.SetPassword(ctx, req)
|
return c.client.SetPassword(ctx, req)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -364,6 +364,39 @@ func (h *Handler) registerThirdParty(writer http.ResponseWriter, request *http.R
|
|||||||
httpkit.WriteOK(writer, request, authData(resp.GetToken(), &isNewUser))
|
httpkit.WriteOK(writer, request, authData(resp.GetToken(), &isNewUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkThirdPartyRegistered 只校验三方授权是否已绑定完整注册用户,不创建用户和 session。
|
||||||
|
func (h *Handler) checkThirdPartyRegistered(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var body struct {
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
Credential string `json:"credential"`
|
||||||
|
}
|
||||||
|
if !decode(writer, request, &body) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !h.allowPublicAuthRequest(writer, request, authRateInput{
|
||||||
|
operation: authOperationThirdParty,
|
||||||
|
provider: body.Provider,
|
||||||
|
}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if h.userClient == nil {
|
||||||
|
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.userClient.CheckThirdPartyRegistered(request.Context(), &userv1.CheckThirdPartyRegisteredRequest{
|
||||||
|
Meta: authRequestMeta(request, ""),
|
||||||
|
Provider: body.Provider,
|
||||||
|
Credential: body.Credential,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
httpkit.WriteRPCError(writer, request, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpkit.WriteOK(writer, request, map[string]bool{"registered": resp.GetRegistered()})
|
||||||
|
}
|
||||||
|
|
||||||
// setPassword 处理已登录用户首次设置密码。
|
// setPassword 处理已登录用户首次设置密码。
|
||||||
func (h *Handler) setPassword(writer http.ResponseWriter, request *http.Request) {
|
func (h *Handler) setPassword(writer http.ResponseWriter, request *http.Request) {
|
||||||
var body struct {
|
var body struct {
|
||||||
|
|||||||
@ -29,13 +29,14 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AuthHandlers struct {
|
type AuthHandlers struct {
|
||||||
LoginPassword http.HandlerFunc
|
LoginPassword http.HandlerFunc
|
||||||
SetPassword http.HandlerFunc
|
SetPassword http.HandlerFunc
|
||||||
QuickCreateAccount http.HandlerFunc
|
QuickCreateAccount http.HandlerFunc
|
||||||
LoginThirdParty http.HandlerFunc
|
LoginThirdParty http.HandlerFunc
|
||||||
RegisterThirdParty http.HandlerFunc
|
RegisterThirdParty http.HandlerFunc
|
||||||
RefreshToken http.HandlerFunc
|
CheckThirdPartyRegistered http.HandlerFunc
|
||||||
Logout http.HandlerFunc
|
RefreshToken http.HandlerFunc
|
||||||
|
Logout http.HandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppHandlers struct {
|
type AppHandlers struct {
|
||||||
@ -372,6 +373,7 @@ func (r routes) registerAuthRoutes() {
|
|||||||
r.public("/auth/account/quick-create", http.MethodPost, h.QuickCreateAccount)
|
r.public("/auth/account/quick-create", http.MethodPost, h.QuickCreateAccount)
|
||||||
r.public("/auth/third-party/login", "", h.LoginThirdParty)
|
r.public("/auth/third-party/login", "", h.LoginThirdParty)
|
||||||
r.public("/auth/third-party/register", http.MethodPost, h.RegisterThirdParty)
|
r.public("/auth/third-party/register", http.MethodPost, h.RegisterThirdParty)
|
||||||
|
r.public("/auth/third-party/registered/check", http.MethodPost, h.CheckThirdPartyRegistered)
|
||||||
r.public("/auth/token/refresh", "", h.RefreshToken)
|
r.public("/auth/token/refresh", "", h.RefreshToken)
|
||||||
r.public("/auth/logout", "", h.Logout)
|
r.public("/auth/logout", "", h.Logout)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -289,6 +289,7 @@ func (f *fakeRoomClient) UnfollowRoom(_ context.Context, req *roomv1.UnfollowRoo
|
|||||||
type fakeUserAuthClient struct {
|
type fakeUserAuthClient struct {
|
||||||
lastLoginThirdParty *userv1.LoginThirdPartyRequest
|
lastLoginThirdParty *userv1.LoginThirdPartyRequest
|
||||||
lastRegisterThird *userv1.RegisterThirdPartyRequest
|
lastRegisterThird *userv1.RegisterThirdPartyRequest
|
||||||
|
lastCheckThird *userv1.CheckThirdPartyRegisteredRequest
|
||||||
lastLoginPassword *userv1.LoginPasswordRequest
|
lastLoginPassword *userv1.LoginPasswordRequest
|
||||||
lastSetPassword *userv1.SetPasswordRequest
|
lastSetPassword *userv1.SetPasswordRequest
|
||||||
lastQuickCreate *userv1.QuickCreateAccountRequest
|
lastQuickCreate *userv1.QuickCreateAccountRequest
|
||||||
@ -305,6 +306,7 @@ type fakeUserAuthClient struct {
|
|||||||
whitelisted bool
|
whitelisted bool
|
||||||
loginErr error
|
loginErr error
|
||||||
loginThirdResp *userv1.AuthResponse
|
loginThirdResp *userv1.AuthResponse
|
||||||
|
checkRegistered bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeUserProfileClient struct {
|
type fakeUserProfileClient struct {
|
||||||
@ -712,6 +714,14 @@ func (f *fakeUserAuthClient) RegisterThirdParty(_ context.Context, req *userv1.R
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeUserAuthClient) CheckThirdPartyRegistered(_ context.Context, req *userv1.CheckThirdPartyRegisteredRequest) (*userv1.CheckThirdPartyRegisteredResponse, error) {
|
||||||
|
f.lastCheckThird = req
|
||||||
|
if f.loginErr != nil {
|
||||||
|
return nil, f.loginErr
|
||||||
|
}
|
||||||
|
return &userv1.CheckThirdPartyRegisteredResponse{Registered: f.checkRegistered}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *fakeUserAuthClient) SetPassword(_ context.Context, req *userv1.SetPasswordRequest) (*userv1.SetPasswordResponse, error) {
|
func (f *fakeUserAuthClient) SetPassword(_ context.Context, req *userv1.SetPasswordRequest) (*userv1.SetPasswordResponse, error) {
|
||||||
f.lastSetPassword = req
|
f.lastSetPassword = req
|
||||||
return &userv1.SetPasswordResponse{PasswordSet: true}, nil
|
return &userv1.SetPasswordResponse{PasswordSet: true}, nil
|
||||||
@ -5535,6 +5545,41 @@ func TestThirdPartyRegisterUsesPublicEnvelopeAndReturnsCompletedToken(t *testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckThirdPartyRegisteredOnlyReturnsRegisteredFlag(t *testing.T) {
|
||||||
|
userClient := &fakeUserAuthClient{checkRegistered: true}
|
||||||
|
router := NewHandler(&fakeRoomClient{}, userClient).Routes(auth.NewVerifier("secret"))
|
||||||
|
body := []byte(`{"provider":"firebase","credential":"firebase-id-token-1"}`)
|
||||||
|
request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/third-party/registered/check", bytes.NewReader(body))
|
||||||
|
request.Header.Set("X-Request-ID", "req-auth-check")
|
||||||
|
request.Header.Set("X-Forwarded-For", "203.0.113.10")
|
||||||
|
request.Header.Set("User-Agent", "hyapp-test/1.0")
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
router.ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
var response httpkit.ResponseEnvelope
|
||||||
|
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
|
||||||
|
t.Fatalf("decode response failed: %v", err)
|
||||||
|
}
|
||||||
|
data, ok := response.Data.(map[string]any)
|
||||||
|
if response.Code != httpkit.CodeOK || !ok || data["registered"] != true {
|
||||||
|
t.Fatalf("unexpected registered check response: %+v", response)
|
||||||
|
}
|
||||||
|
req := userClient.lastCheckThird
|
||||||
|
if req == nil {
|
||||||
|
t.Fatal("CheckThirdPartyRegistered request was not sent")
|
||||||
|
}
|
||||||
|
if req.GetProvider() != "firebase" || req.GetCredential() != "firebase-id-token-1" {
|
||||||
|
t.Fatalf("provider credential mismatch: %+v", req)
|
||||||
|
}
|
||||||
|
if userClient.lastLoginThirdParty != nil || userClient.lastRegisterThird != nil {
|
||||||
|
t.Fatalf("check endpoint must not login or register: login=%+v register=%+v", userClient.lastLoginThirdParty, userClient.lastRegisterThird)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAuthClientIPPrefersForwardedPublicIPOverProxyRealIP(t *testing.T) {
|
func TestAuthClientIPPrefersForwardedPublicIPOverProxyRealIP(t *testing.T) {
|
||||||
request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/third-party/login", nil)
|
request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/third-party/login", nil)
|
||||||
request.RemoteAddr = "10.0.0.9:54123"
|
request.RemoteAddr = "10.0.0.9:54123"
|
||||||
|
|||||||
@ -165,13 +165,14 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler {
|
|||||||
return h.profileAPIHandler(jwtVerifier, handler)
|
return h.profileAPIHandler(jwtVerifier, handler)
|
||||||
},
|
},
|
||||||
Auth: httproutes.AuthHandlers{
|
Auth: httproutes.AuthHandlers{
|
||||||
LoginPassword: h.loginPassword,
|
LoginPassword: h.loginPassword,
|
||||||
SetPassword: h.setPassword,
|
SetPassword: h.setPassword,
|
||||||
QuickCreateAccount: h.quickCreateAccount,
|
QuickCreateAccount: h.quickCreateAccount,
|
||||||
LoginThirdParty: h.loginThirdParty,
|
LoginThirdParty: h.loginThirdParty,
|
||||||
RegisterThirdParty: h.registerThirdParty,
|
RegisterThirdParty: h.registerThirdParty,
|
||||||
RefreshToken: h.refreshToken,
|
CheckThirdPartyRegistered: h.checkThirdPartyRegistered,
|
||||||
Logout: h.logout,
|
RefreshToken: h.refreshToken,
|
||||||
|
Logout: h.logout,
|
||||||
},
|
},
|
||||||
App: httproutes.AppHandlers{
|
App: httproutes.AppHandlers{
|
||||||
ListRegistrationCountries: userAPI.ListRegistrationCountries,
|
ListRegistrationCountries: userAPI.ListRegistrationCountries,
|
||||||
|
|||||||
@ -1085,6 +1085,51 @@ func TestRegisterThirdPartyCompletesExistingPendingUser(t *testing.T) {
|
|||||||
assertUserRegisteredOutboxFact(t, repository, pendingToken.UserID, country.CountryID, region.RegionID, now.UnixMilli())
|
assertUserRegisteredOutboxFact(t, repository, pendingToken.UserID, country.CountryID, region.RegionID, now.UnixMilli())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckThirdPartyRegisteredReturnsOnlyCompletedRegistration(t *testing.T) {
|
||||||
|
// 检查接口只服务前端路由判断,不创建用户、不创建 session;pending 用户仍然要进资料页。
|
||||||
|
ctx := context.Background()
|
||||||
|
repository := mysqltest.NewRepository(t)
|
||||||
|
seedCountry(t, repository, "SG")
|
||||||
|
now := time.UnixMilli(1000)
|
||||||
|
svc := newAuthService(repository, &now, []int64{900033}, []string{"100033"})
|
||||||
|
|
||||||
|
registered, err := svc.CheckThirdPartyRegistered(ctx, "wechat", "openid-check", authservice.Meta{RequestID: "req-check-empty"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CheckThirdPartyRegistered empty failed: %v", err)
|
||||||
|
}
|
||||||
|
if registered {
|
||||||
|
t.Fatalf("unbound third-party subject must not be registered")
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingToken, isNewUser, err := svc.LoginThirdParty(ctx, "wechat", "openid-check", authdomain.ThirdPartyRegistration{DeviceID: "dev-ios", Platform: "ios"}, authservice.Meta{RequestID: "req-check-pending"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("seed pending LoginThirdParty failed: %v", err)
|
||||||
|
}
|
||||||
|
if !isNewUser || pendingToken.ProfileCompleted {
|
||||||
|
t.Fatalf("seed login must create pending user: token=%+v isNew=%v", pendingToken, isNewUser)
|
||||||
|
}
|
||||||
|
registered, err = svc.CheckThirdPartyRegistered(ctx, "wechat", "openid-check", authservice.Meta{RequestID: "req-check-pending"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CheckThirdPartyRegistered pending failed: %v", err)
|
||||||
|
}
|
||||||
|
if registered {
|
||||||
|
t.Fatalf("pending third-party subject must not be treated as registered")
|
||||||
|
}
|
||||||
|
|
||||||
|
register := thirdPartyRegistration("ios")
|
||||||
|
register.DeviceID = "dev-ios-register"
|
||||||
|
if _, _, err := svc.RegisterThirdParty(ctx, "wechat", "openid-check", register, authservice.Meta{RequestID: "req-check-register"}); err != nil {
|
||||||
|
t.Fatalf("RegisterThirdParty completion failed: %v", err)
|
||||||
|
}
|
||||||
|
registered, err = svc.CheckThirdPartyRegistered(ctx, "wechat", "openid-check", authservice.Meta{RequestID: "req-check-completed"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CheckThirdPartyRegistered completed failed: %v", err)
|
||||||
|
}
|
||||||
|
if !registered {
|
||||||
|
t.Fatalf("completed third-party subject must be registered")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestThirdPartyRegisterRejectsReusedDeviceID(t *testing.T) {
|
func TestThirdPartyRegisterRejectsReusedDeviceID(t *testing.T) {
|
||||||
// 设备号只限制新账号注册;同一个 provider subject 的再次登录仍然可以创建新 session。
|
// 设备号只限制新账号注册;同一个 provider subject 的再次登录仍然可以创建新 session。
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|||||||
@ -158,6 +158,44 @@ func (s *Service) RegisterThirdParty(ctx context.Context, provider string, crede
|
|||||||
return s.createRegisteredThirdPartyUser(ctx, provider, profile, registration, meta, maxAccountsPerDevice)
|
return s.createRegisteredThirdPartyUser(ctx, provider, profile, registration, meta, maxAccountsPerDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckThirdPartyRegistered 只校验 provider credential,并判断当前 App 下是否已有完整注册用户。
|
||||||
|
// 它不创建用户、不创建 session,专门给 Flutter 决定“进首页登录链路”还是“进资料页注册链路”。
|
||||||
|
func (s *Service) CheckThirdPartyRegistered(ctx context.Context, provider string, credential string, meta Meta) (bool, error) {
|
||||||
|
meta.AppCode = appcode.Normalize(meta.AppCode)
|
||||||
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
||||||
|
credential = strings.TrimSpace(credential)
|
||||||
|
if provider == "" || credential == "" {
|
||||||
|
// 缺认证材料是客户端参数错误;不能返回 registered=false 掩盖真实调用错误。
|
||||||
|
return false, xerr.New(xerr.InvalidArgument, "provider and credential are required")
|
||||||
|
}
|
||||||
|
if s.authRepository == nil {
|
||||||
|
return false, xerr.New(xerr.Unavailable, "auth repository is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
profile, err := s.thirdPartyVerifier.Verify(ctx, provider, credential)
|
||||||
|
if err != nil {
|
||||||
|
return false, authFailed()
|
||||||
|
}
|
||||||
|
identity, err := s.authRepository.FindThirdPartyIdentity(ctx, provider, profile.ProviderSubject)
|
||||||
|
if xerr.IsCode(err, xerr.NotFound) {
|
||||||
|
// 未绑定表示从注册视角还不能进首页;客户端应继续资料页和注册接口。
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := s.freshUser(ctx, identity.UserID, s.now().UnixMilli(), meta.RequestID)
|
||||||
|
if xerr.IsCode(err, xerr.NotFound) {
|
||||||
|
// 极少数脏数据只有三方绑定没有用户;按未完成注册处理,避免前端误进首页。
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return user.ProfileCompleted, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) ensureDeviceCanRegisterThirdParty(ctx context.Context, registration authdomain.ThirdPartyRegistration, meta Meta, provider string) (int32, error) {
|
func (s *Service) ensureDeviceCanRegisterThirdParty(ctx context.Context, registration authdomain.ThirdPartyRegistration, meta Meta, provider string) (int32, error) {
|
||||||
// 设备号限制只约束“创建新账号”路径;已绑定三方身份登录不经过这里,避免把正常跨设备登录误拦。
|
// 设备号限制只约束“创建新账号”路径;已绑定三方身份登录不经过这里,避免把正常跨设备登录误拦。
|
||||||
config, err := s.GetRegisterRiskConfig(ctx)
|
config, err := s.GetRegisterRiskConfig(ctx)
|
||||||
|
|||||||
@ -171,6 +171,16 @@ func (s *Server) RegisterThirdParty(ctx context.Context, req *userv1.RegisterThi
|
|||||||
return &userv1.AuthResponse{Token: toProtoToken(token), IsNewUser: isNewUser, ProfileCompleted: token.ProfileCompleted, OnboardingStatus: token.OnboardingStatus}, nil
|
return &userv1.AuthResponse{Token: toProtoToken(token), IsNewUser: isNewUser, ProfileCompleted: token.ProfileCompleted, OnboardingStatus: token.OnboardingStatus}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckThirdPartyRegistered 只检查 Firebase/三方身份是否已完成注册,不创建用户或 session。
|
||||||
|
func (s *Server) CheckThirdPartyRegistered(ctx context.Context, req *userv1.CheckThirdPartyRegisteredRequest) (*userv1.CheckThirdPartyRegisteredResponse, error) {
|
||||||
|
ctx = contextWithApp(ctx, req.GetMeta())
|
||||||
|
registered, err := s.authSvc.CheckThirdPartyRegistered(ctx, req.GetProvider(), req.GetCredential(), authMeta(req.GetMeta()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerr.ToGRPCError(err)
|
||||||
|
}
|
||||||
|
return &userv1.CheckThirdPartyRegisteredResponse{Registered: registered}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetPassword 为已登录用户首次写入密码身份。
|
// SetPassword 为已登录用户首次写入密码身份。
|
||||||
func (s *Server) SetPassword(ctx context.Context, req *userv1.SetPasswordRequest) (*userv1.SetPasswordResponse, error) {
|
func (s *Server) SetPassword(ctx context.Context, req *userv1.SetPasswordRequest) (*userv1.SetPasswordResponse, error) {
|
||||||
ctx = contextWithApp(ctx, req.GetMeta())
|
ctx = contextWithApp(ctx, req.GetMeta())
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user