mirror of https://gitee.com/answerdev/answer.git
fix(plugin): add external login binding send email interface
This commit is contained in:
parent
01c02ae2b7
commit
1db4f5586b
|
@ -4,5 +4,5 @@ import "time"
|
|||
|
||||
const (
|
||||
ConnectorUserExternalInfoCacheKey = "answer:connector:"
|
||||
ConnectorUserExternalInfoCacheTime = 1 * time.Hour
|
||||
ConnectorUserExternalInfoCacheTime = 24 * time.Hour
|
||||
)
|
||||
|
|
|
@ -15,8 +15,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
connectorRedirectRouterPrefix = "/answer/api/v1/connector/redirect/"
|
||||
connectorLoginRouterPrefix = "/answer/api/v1/connector/login/"
|
||||
ConnectorRedirectRouterPrefix = "/answer/api/v1/connector/redirect/"
|
||||
ConnectorLoginRouterPrefix = "/answer/api/v1/connector/login/"
|
||||
)
|
||||
|
||||
// ConnectorController comment controller
|
||||
|
@ -36,15 +36,6 @@ func NewConnectorController(
|
|||
}
|
||||
}
|
||||
|
||||
func (cc *ConnectorController) ConnectorRedirectRegisterRouters(r *gin.Engine) {
|
||||
_ = plugin.CallConnector(func(connector plugin.Connector) error {
|
||||
r.GET(connectorLoginRouterPrefix+connector.ConnectorSlugName(), cc.ConnectorRedirect(connector))
|
||||
r.GET(connectorRedirectRouterPrefix+connector.ConnectorSlugName(), cc.ConnectorLogin(connector))
|
||||
return nil
|
||||
})
|
||||
r.GET("/answer/api/v1/connector/info", cc.ConnectorsInfo)
|
||||
}
|
||||
|
||||
func (cc *ConnectorController) ConnectorRedirect(connector plugin.Connector) (fn func(ctx *gin.Context)) {
|
||||
return func(ctx *gin.Context) {
|
||||
general, err := cc.siteInfoService.GetSiteGeneral(ctx)
|
||||
|
@ -53,7 +44,7 @@ func (cc *ConnectorController) ConnectorRedirect(connector plugin.Connector) (fn
|
|||
return
|
||||
}
|
||||
|
||||
receiverURL := fmt.Sprintf("%s%s%s", general.SiteUrl, connectorLoginRouterPrefix, connector.ConnectorSlugName())
|
||||
receiverURL := fmt.Sprintf("%s%s%s", general.SiteUrl, ConnectorLoginRouterPrefix, connector.ConnectorSlugName())
|
||||
redirectURL := connector.ConnectorSender(ctx, receiverURL)
|
||||
if len(redirectURL) > 0 {
|
||||
ctx.Redirect(http.StatusFound, redirectURL)
|
||||
|
@ -69,7 +60,8 @@ func (cc *ConnectorController) ConnectorLogin(connector plugin.Connector) (fn fu
|
|||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
return
|
||||
}
|
||||
resp, err := cc.userExternalService.ExternalLogin(ctx, connector.ConnectorSlugName(), userInfo)
|
||||
userInfo.Provider = connector.ConnectorSlugName()
|
||||
resp, err := cc.userExternalService.ExternalLogin(ctx, userInfo)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
|
@ -78,7 +70,7 @@ func (cc *ConnectorController) ConnectorLogin(connector plugin.Connector) (fn fu
|
|||
if len(resp.AccessToken) > 0 {
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("/index?token=%s", resp.AccessToken))
|
||||
} else {
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("/binding?external_id=%s", resp.ExternalID))
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("/binding?binding_key=%s", resp.BindingKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +87,7 @@ func (cc *ConnectorController) ConnectorsInfo(ctx *gin.Context) {
|
|||
resp = append(resp, &schema.ConnectorInfoResp{
|
||||
Name: fn.ConnectorSlugName(),
|
||||
Icon: fn.ConnectorLogo(),
|
||||
Link: fmt.Sprintf("%s%s%s", general.SiteUrl, connectorLoginRouterPrefix, fn.ConnectorSlugName()),
|
||||
Link: fmt.Sprintf("%s%s%s", general.SiteUrl, ConnectorLoginRouterPrefix, fn.ConnectorSlugName()),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
@ -105,3 +97,21 @@ func (cc *ConnectorController) ConnectorsInfo(ctx *gin.Context) {
|
|||
}
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
}
|
||||
|
||||
func (cc *ConnectorController) ExternalLoginBindingUserSendEmail(ctx *gin.Context) {
|
||||
req := &schema.ExternalLoginBindingUserSendEmailReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
resp, err := cc.userExternalService.ExternalLoginBindingUserSendEmail(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
func (cc *ConnectorController) ExternalLoginBindingUser(ctx *gin.Context) {
|
||||
req := &schema.ExternalLoginBindingUserReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
resp, err := cc.userExternalService.ExternalLoginBindingUser(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
|
|
@ -29,11 +29,19 @@ type Connector interface {
|
|||
ConnectorReceiver(ctx *GinContext) (userInfo ExternalLoginUserInfo, err error)
|
||||
}
|
||||
|
||||
// ExternalLoginUserInfo external login user info
|
||||
type ExternalLoginUserInfo struct {
|
||||
// Third party identification
|
||||
// e.g. facebook, twitter, instagram
|
||||
Provider string
|
||||
// required. The unique user ID provided by the third-party login
|
||||
ExternalID string
|
||||
Name string
|
||||
Email string
|
||||
MetaInfo string
|
||||
// optional. This name is used preferentially during registration
|
||||
Name string
|
||||
// optional. If email exist will bind the existing user
|
||||
Email string
|
||||
// optional. The original user information provided by the third-party login platform
|
||||
MetaInfo string
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
@ -55,7 +55,7 @@ func (ur *userExternalLoginRepo) GetByExternalID(ctx context.Context, externalID
|
|||
|
||||
// SetCacheUserExternalLoginInfo cache user info for external login
|
||||
func (ur *userExternalLoginRepo) SetCacheUserExternalLoginInfo(
|
||||
ctx context.Context, info plugin.ExternalLoginUserInfo) (err error) {
|
||||
ctx context.Context, key string, info plugin.ExternalLoginUserInfo) (err error) {
|
||||
cacheData, _ := json.Marshal(info)
|
||||
return ur.data.Cache.SetString(ctx, constant.ConnectorUserExternalInfoCacheKey+info.ExternalID,
|
||||
string(cacheData), constant.ConnectorUserExternalInfoCacheTime)
|
||||
|
@ -63,8 +63,8 @@ func (ur *userExternalLoginRepo) SetCacheUserExternalLoginInfo(
|
|||
|
||||
// GetCacheUserExternalLoginInfo cache user info for external login
|
||||
func (ur *userExternalLoginRepo) GetCacheUserExternalLoginInfo(
|
||||
ctx context.Context, externalID string) (info plugin.ExternalLoginUserInfo, err error) {
|
||||
res, err := ur.data.Cache.GetString(ctx, constant.ConnectorUserExternalInfoCacheKey+externalID)
|
||||
ctx context.Context, key string) (info plugin.ExternalLoginUserInfo, err error) {
|
||||
res, err := ur.data.Cache.GetString(ctx, constant.ConnectorUserExternalInfoCacheKey+key)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package router
|
|||
|
||||
import (
|
||||
"github.com/answerdev/answer/internal/controller"
|
||||
"github.com/answerdev/answer/internal/plugin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
|
@ -18,5 +19,14 @@ func NewPluginAPIRouter(
|
|||
}
|
||||
|
||||
func (pr *PluginAPIRouter) RegisterConnector(r *gin.Engine) {
|
||||
pr.connectorController.ConnectorRedirectRegisterRouters(r)
|
||||
connectorController := pr.connectorController
|
||||
_ = plugin.CallConnector(func(connector plugin.Connector) error {
|
||||
connectorSlugName := connector.ConnectorSlugName()
|
||||
r.GET(controller.ConnectorLoginRouterPrefix+connectorSlugName, connectorController.ConnectorRedirect(connector))
|
||||
r.GET(controller.ConnectorRedirectRouterPrefix+connectorSlugName, connectorController.ConnectorLogin(connector))
|
||||
return nil
|
||||
})
|
||||
r.GET("/answer/api/v1/connector/info", connectorController.ConnectorsInfo)
|
||||
r.POST("/answer/api/v1/connector/binding/email", connectorController.ExternalLoginBindingUserSendEmail)
|
||||
r.POST("/answer/api/v1/connector/binding", connectorController.ExternalLoginBindingUser)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,30 @@ package schema
|
|||
|
||||
// UserExternalLoginResp user external login resp
|
||||
type UserExternalLoginResp struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
BindingKey string `json:"binding_key"`
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
// ExternalLoginBindingUserSendEmailReq external login binding user request
|
||||
type ExternalLoginBindingUserSendEmailReq struct {
|
||||
BindingKey string `validate:"required,gt=1,lte=100" json:"binding_key"`
|
||||
Email string `validate:"required,gt=1,lte=512,email" json:"email"`
|
||||
// If must is true, whatever email if exists, try to bind user.
|
||||
// If must is false, when email exist, will only be prompted with a warning.
|
||||
Must bool `json:"must"`
|
||||
}
|
||||
|
||||
// ExternalLoginBindingUserSendEmailResp external login binding user response
|
||||
type ExternalLoginBindingUserSendEmailResp struct {
|
||||
EmailExistAndMustBeConfirmed bool `json:"email_exist_and_must_be_confirmed"`
|
||||
}
|
||||
|
||||
// ExternalLoginBindingUserReq external login binding user request
|
||||
type ExternalLoginBindingUserReq struct {
|
||||
Code string `validate:"required,gt=0,lte=500" json:"code"`
|
||||
}
|
||||
|
||||
// ExternalLoginBindingUserResp external login binding user response
|
||||
type ExternalLoginBindingUserResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
|
|
@ -4,10 +4,13 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/plugin"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||
"github.com/answerdev/answer/pkg/token"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
||||
type UserExternalLoginRepo interface {
|
||||
|
@ -15,9 +18,9 @@ type UserExternalLoginRepo interface {
|
|||
UpdateInfo(ctx context.Context, userInfo *entity.UserExternalLogin) (err error)
|
||||
GetByExternalID(ctx context.Context, externalID string) (userInfo *entity.UserExternalLogin, exist bool, err error)
|
||||
SetCacheUserExternalLoginInfo(
|
||||
ctx context.Context, info plugin.ExternalLoginUserInfo) (err error)
|
||||
ctx context.Context, key string, info plugin.ExternalLoginUserInfo) (err error)
|
||||
GetCacheUserExternalLoginInfo(
|
||||
ctx context.Context, externalID string) (info plugin.ExternalLoginUserInfo, err error)
|
||||
ctx context.Context, key string) (info plugin.ExternalLoginUserInfo, err error)
|
||||
}
|
||||
|
||||
// UserExternalLoginService user external login service
|
||||
|
@ -42,15 +45,16 @@ func NewUserExternalLoginService(
|
|||
|
||||
// ExternalLogin if user is already a member logged in
|
||||
func (us *UserExternalLoginService) ExternalLogin(
|
||||
ctx context.Context, provider string, externalUserInfo plugin.ExternalLoginUserInfo) (
|
||||
ctx context.Context, externalUserInfo plugin.ExternalLoginUserInfo) (
|
||||
resp *schema.UserExternalLoginResp, err error) {
|
||||
// cache external user info, waiting for user enter email address.
|
||||
if len(externalUserInfo.Email) == 0 {
|
||||
err = us.userExternalLoginRepo.SetCacheUserExternalLoginInfo(ctx, externalUserInfo)
|
||||
bindingKey := token.GenerateToken()
|
||||
err = us.userExternalLoginRepo.SetCacheUserExternalLoginInfo(ctx, bindingKey, externalUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.UserExternalLoginResp{ExternalID: externalUserInfo.ExternalID}, nil
|
||||
return &schema.UserExternalLoginResp{BindingKey: bindingKey}, nil
|
||||
}
|
||||
|
||||
oldUserInfo, exist, err := us.userRepo.GetByEmail(ctx, externalUserInfo.Email)
|
||||
|
@ -58,12 +62,12 @@ func (us *UserExternalLoginService) ExternalLogin(
|
|||
return nil, err
|
||||
}
|
||||
if !exist {
|
||||
oldUserInfo, err = us.RegisterNewUser(ctx, provider, externalUserInfo)
|
||||
oldUserInfo, err = us.RegisterNewUser(ctx, externalUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = us.BindOldUser(ctx, provider, externalUserInfo, oldUserInfo)
|
||||
err = us.BindOldUser(ctx, externalUserInfo, oldUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -73,7 +77,7 @@ func (us *UserExternalLoginService) ExternalLogin(
|
|||
return &schema.UserExternalLoginResp{AccessToken: accessToken}, err
|
||||
}
|
||||
|
||||
func (us *UserExternalLoginService) RegisterNewUser(ctx context.Context, provider string,
|
||||
func (us *UserExternalLoginService) RegisterNewUser(ctx context.Context,
|
||||
externalUserInfo plugin.ExternalLoginUserInfo) (userInfo *entity.User, err error) {
|
||||
userInfo = &entity.User{}
|
||||
userInfo.EMail = externalUserInfo.Email
|
||||
|
@ -92,7 +96,7 @@ func (us *UserExternalLoginService) RegisterNewUser(ctx context.Context, provide
|
|||
return userInfo, nil
|
||||
}
|
||||
|
||||
func (us *UserExternalLoginService) BindOldUser(ctx context.Context, provider string,
|
||||
func (us *UserExternalLoginService) BindOldUser(ctx context.Context,
|
||||
externalUserInfo plugin.ExternalLoginUserInfo, oldUserInfo *entity.User) (err error) {
|
||||
oldExternalUserInfo, exist, err := us.userExternalLoginRepo.GetByExternalID(ctx, externalUserInfo.ExternalID)
|
||||
if err != nil {
|
||||
|
@ -105,7 +109,7 @@ func (us *UserExternalLoginService) BindOldUser(ctx context.Context, provider st
|
|||
} else {
|
||||
newExternalUserInfo := &entity.UserExternalLogin{
|
||||
UserID: oldUserInfo.ID,
|
||||
Provider: provider,
|
||||
Provider: externalUserInfo.Provider,
|
||||
ExternalID: externalUserInfo.ExternalID,
|
||||
MetaInfo: externalUserInfo.MetaInfo,
|
||||
}
|
||||
|
@ -113,3 +117,39 @@ func (us *UserExternalLoginService) BindOldUser(ctx context.Context, provider st
|
|||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (us *UserExternalLoginService) ExternalLoginBindingUserSendEmail(
|
||||
ctx context.Context, req *schema.ExternalLoginBindingUserSendEmailReq) (
|
||||
resp *schema.ExternalLoginBindingUserSendEmailResp, err error) {
|
||||
resp = &schema.ExternalLoginBindingUserSendEmailResp{}
|
||||
externalLoginInfo, err := us.userExternalLoginRepo.GetCacheUserExternalLoginInfo(ctx, req.BindingKey)
|
||||
if err != nil || len(externalLoginInfo.ExternalID) == 0 {
|
||||
return nil, errors.BadRequest(reason.UserNotFound)
|
||||
}
|
||||
|
||||
_, exist, err := us.userRepo.GetByEmail(ctx, req.Email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist && !req.Must {
|
||||
resp.EmailExistAndMustBeConfirmed = true
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if !exist {
|
||||
externalLoginInfo.Email = req.Email
|
||||
_, err = us.RegisterNewUser(ctx, externalLoginInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO send bind confirmation email
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (us *UserExternalLoginService) ExternalLoginBindingUser(
|
||||
ctx context.Context, req *schema.ExternalLoginBindingUserReq) (
|
||||
resp *schema.ExternalLoginBindingUserResp, err error) {
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue