From 48521bb9c95a877e73c5428baeef0ab42fb2d7dc Mon Sep 17 00:00:00 2001 From: lwnmengjing Date: Tue, 13 Aug 2024 19:15:00 +0800 Subject: [PATCH 1/2] :sparkles: feat: support login by email --- apis/notice.go | 6 +- apis/user.go | 87 ++++++++++++++++++++++++++- center/default.go | 18 ++++++ center/type.go | 8 +++ config/cache.go | 5 +- dto/user.go | 4 +- models/user.go | 84 +++++++++++++++++++------- models/user_oauth2.go | 50 +++++++-------- notice/email/login_verify_code.html | 49 +++++++++++++++ notice/email/password_reset_code.html | 49 +++++++++++++++ notice/email/send.go | 79 ++++++++++++++++++++++++ pkg/enum.go | 14 +++-- storage/cache/verify_code.go | 51 ++++++++++++++++ 13 files changed, 445 insertions(+), 59 deletions(-) create mode 100644 notice/email/login_verify_code.html create mode 100644 notice/email/password_reset_code.html create mode 100644 notice/email/send.go create mode 100644 storage/cache/verify_code.go diff --git a/apis/notice.go b/apis/notice.go index 423de6a..8e7c055 100644 --- a/apis/notice.go +++ b/apis/notice.go @@ -42,7 +42,7 @@ type Notice struct { //} func (e *Notice) Other(r *gin.RouterGroup) { - r.GET("/notice/unread", response.AuthHandler, e.Unread) + r.GET("/notice/unread", e.Unread) r.PUT("/notice/read/:id", response.AuthHandler, e.MarkRead) r.GET("/notice/read/:id", response.AuthHandler, e.Read) } @@ -122,6 +122,10 @@ func (e *Notice) MarkRead(ctx *gin.Context) { func (e *Notice) Unread(ctx *gin.Context) { api := response.Make(ctx) verify := response.VerifyHandler(ctx) + if verify == nil { + api.OK(nil) + return + } list := make([]*models.Notice, 0) err := center.Default.GetDB(ctx, &models.Notice{}).Model(&models.Notice{}). Where(&models.Notice{Read: false, UserID: verify.GetUserID()}). diff --git a/apis/user.go b/apis/user.go index 68bdbf4..139f46e 100644 --- a/apis/user.go +++ b/apis/user.go @@ -2,8 +2,11 @@ package apis import ( "errors" + "fmt" + "github.com/mss-boot-io/mss-boot-admin/notice/email" "gorm.io/gorm" "net/http" + "time" "github.com/gin-gonic/gin" "github.com/mss-boot-io/mss-boot-admin/dto" @@ -46,6 +49,7 @@ type User struct { // Other handler func (e *User) Other(r *gin.RouterGroup) { r.POST("/user/login", middleware.Auth.LoginHandler) + r.POST("/user/fakeCaptcha", e.FakeCaptcha) r.POST("/user/login/github", middleware.Auth.LoginHandler) r.GET("/user/refresh-token", middleware.Auth.RefreshHandler) r.GET("/user/userInfo", middleware.Auth.MiddlewareFunc(), e.UserInfo) @@ -153,7 +157,88 @@ func (e *User) RefreshToken(_ *gin.Context) { // @Param data body dto.FakeCaptchaRequest true "data" // @Success 200 {object} dto.FakeCaptchaResponse // @Router /admin/api/user/fakeCaptcha [post] -func (e *User) FakeCaptcha(*gin.Context) {} +func (e *User) FakeCaptcha(ctx *gin.Context) { + api := response.Make(ctx) + req := &dto.FakeCaptchaRequest{} + if api.Bind(req).Error != nil { + api.Err(http.StatusUnprocessableEntity) + return + } + resp := &dto.FakeCaptchaResponse{} + if req.Email != "" { + // setup 01 get user by email + user := &models.User{} + err := center.Default. + GetDB(ctx, &models.User{}). + Where("email = ?", req.Email). + First(user).Error + if err != nil { + api.AddError(err) + if errors.Is(err, gorm.ErrRecordNotFound) { + api.Err(http.StatusNotFound) + return + } + api.Log.Error("GetUser error") + api.Err(http.StatusInternalServerError) + return + } + // setup 02 generate verify code + code, err := center.Default.GenerateCode(ctx, req.Email, 5*time.Minute) + if err != nil { + api.AddError(err).Log.Error("GenerateCode error") + api.Err(http.StatusInternalServerError) + return + } + // setup 03 send email + smtpHost, ok := center.GetAppConfig().GetAppConfig(ctx, "email.smtpHost") + if !ok { + api.AddError(fmt.Errorf("not support send email")). + Err(http.StatusNotImplemented) + return + } + smtpPort, ok := center.GetAppConfig().GetAppConfig(ctx, "email.smtpPort") + if !ok { + api.AddError(fmt.Errorf("not support send email")). + Err(http.StatusNotImplemented) + return + } + username, ok := center.GetAppConfig().GetAppConfig(ctx, "email.username") + if !ok { + api.AddError(fmt.Errorf("not support send email")). + Err(http.StatusNotImplemented) + return + } + password, ok := center.GetAppConfig().GetAppConfig(ctx, "email.password") + if !ok { + api.AddError(fmt.Errorf("not support send email")). + Err(http.StatusNotImplemented) + return + } + organization, ok := center.GetAppConfig().GetAppConfig(ctx, "base.websiteName") + if !ok || organization == "" { + organization = "mss-boot-io" + } + err = email.SendVerifyCode( + smtpHost, smtpPort, + username, password, + user.Username, + user.Email, + code, + organization) + if err != nil { + api.AddError(err).Log.Error("send email error") + api.Err(http.StatusInternalServerError) + return + } + + resp.Status = "ok" + api.OK(resp) + return + } + err := fmt.Errorf("not support phone") + api.AddError(err).Err(http.StatusNotImplemented) + return +} // UserInfo 获取登录用户信息 // @Summary 获取登录用户信息 diff --git a/center/default.go b/center/default.go index 05f7b0a..cb5e924 100644 --- a/center/default.go +++ b/center/default.go @@ -42,6 +42,7 @@ type DefaultCenter struct { storage.AdapterCache storage.AdapterQueue storage.AdapterLocker + VerifyCodeStoreImp } func (d *DefaultCenter) SetNotice(n NoticeImp) { @@ -112,6 +113,10 @@ func (d *DefaultCenter) SetLocker(l storage.AdapterLocker) { d.AdapterLocker = l } +func (d *DefaultCenter) SetVerifyCodeStore(v VerifyCodeStoreImp) { + d.VerifyCodeStoreImp = v +} + func (d *DefaultCenter) GetNotice() NoticeImp { return d.NoticeImp } @@ -180,6 +185,10 @@ func (d *DefaultCenter) GetLocker() storage.AdapterLocker { return d.AdapterLocker } +func (d *DefaultCenter) GetVerifyCodeStore() VerifyCodeStoreImp { + return d.VerifyCodeStoreImp +} + func (d *DefaultCenter) Stage() string { stage := os.Getenv("STAGE") if stage == "" { @@ -280,6 +289,11 @@ func SetLocker(l storage.AdapterLocker) *DefaultCenter { return Default } +func SetVerifyCodeStore(v VerifyCodeStoreImp) *DefaultCenter { + Default.SetVerifyCodeStore(v) + return Default +} + func GetNotice() NoticeImp { return Default.GetNotice() } @@ -351,3 +365,7 @@ func GetQueue() storage.AdapterQueue { func GetLocker() storage.AdapterLocker { return Default.GetLocker() } + +func GetVerifyCodeStore() VerifyCodeStoreImp { + return Default.GetVerifyCodeStore() +} diff --git a/center/type.go b/center/type.go index 72c00ed..76bf571 100644 --- a/center/type.go +++ b/center/type.go @@ -1,6 +1,7 @@ package center import ( + "context" "github.com/gin-gonic/gin" "github.com/mss-boot-io/mss-boot-admin/storage" "github.com/mss-boot-io/mss-boot/core/server" @@ -10,6 +11,7 @@ import ( "google.golang.org/grpc" "gorm.io/gorm" "gorm.io/gorm/schema" + "time" ) /* @@ -36,6 +38,7 @@ type Center interface { storage.AdapterCache storage.AdapterQueue storage.AdapterLocker + VerifyCodeStoreImp } type GRPCClientImp interface { @@ -110,3 +113,8 @@ type StatisticsImp interface { NowIncrease(ctx *gin.Context, object StatisticsObject) error NowReduce(ctx *gin.Context, object StatisticsObject) error } + +type VerifyCodeStoreImp interface { + GenerateCode(ctx context.Context, key string, expire time.Duration) (string, error) + VerifyCode(ctx context.Context, key, code string) (bool, error) +} diff --git a/config/cache.go b/config/cache.go index 79e7e97..68ee3c7 100644 --- a/config/cache.go +++ b/config/cache.go @@ -51,9 +51,12 @@ func (e Cache) Init() { _redis = r.GetClient() } center.SetCache(r) + center.SetVerifyCodeStore(cache.NewVerifyCode(r)) } if e.Memory != nil { - center.SetCache(cache.NewMemory(opts...)) + m := cache.NewMemory(opts...) + center.SetCache(m) + center.SetVerifyCodeStore(cache.NewVerifyCode(m)) } if e.QueryCache && e.QueryCacheDuration > 0 && gormdb.DB != nil { cache.NewExpiration(context.Background(), e.QueryCacheDuration) diff --git a/dto/user.go b/dto/user.go index ebb7e10..1e36bb0 100644 --- a/dto/user.go +++ b/dto/user.go @@ -28,11 +28,11 @@ type LoginResponse struct { } type FakeCaptchaRequest struct { - Phone string `json:"phone" binding:"required"` + Phone string `json:"phone"` + Email string `json:"email"` } type FakeCaptchaResponse struct { - Code int8 `json:"code"` Status string `json:"status"` } diff --git a/models/user.go b/models/user.go index 7516701..a4f3eaf 100644 --- a/models/user.go +++ b/models/user.go @@ -116,24 +116,35 @@ func GetUserByUsername(ctx *gin.Context, username string) (*User, error) { return &user, nil } +// GetUserByEmail get user by email +func GetUserByEmail(ctx *gin.Context, email string) (*User, error) { + var user User + err := center.GetDB(ctx, &user).Preload("Role").First(&user, "email = ?", email).Error + if err != nil { + return nil, err + } + return &user, nil +} + type UserLogin struct { - RoleID string `json:"roleID" gorm:"index;type:varchar(64)" swaggerignore:"true"` - Role *Role `json:"role" gorm:"foreignKey:RoleID;references:ID"` - PostID string `json:"postID" gorm:"index;type:varchar(64)" swaggerignore:"true"` - Post *Post `json:"post" gorm:"foreignKey:PostID;references:ID"` - DepartmentID string `json:"departmentID" gorm:"index;type:varchar(64)" swaggerignore:"true"` - Department *Department `json:"department" gorm:"foreignKey:DepartmentID;references:ID"` - Username string `json:"username" gorm:"type:varchar(20);index"` - Email string `json:"email" gorm:"type:varchar(100);index"` - Password string `json:"password,omitempty" gorm:"-"` - PasswordHash string `json:"-" gorm:"size:255;comment:密码hash" swaggerignore:"true"` - PasswordStrength string `json:"passwordStrength" gorm:"size:20;comment:密码强度"` - Salt string `json:"-" gorm:"size:255;comment:加盐" swaggerignore:"true"` - Status enum.Status `json:"status" gorm:"size:10"` - OAuth2 []*UserOAuth2 `json:"oauth2" gorm:"foreignKey:UserID;references:ID"` - Provider pkg.OAuth2Provider `json:"type" gorm:"-"` - RefreshTokenDisable bool `json:"-" gorm:"-"` - PersonAccessToken string `json:"-" gorm:"-"` + RoleID string `json:"roleID" gorm:"index;type:varchar(64)" swaggerignore:"true"` + Role *Role `json:"role" gorm:"foreignKey:RoleID;references:ID"` + PostID string `json:"postID" gorm:"index;type:varchar(64)" swaggerignore:"true"` + Post *Post `json:"post" gorm:"foreignKey:PostID;references:ID"` + DepartmentID string `json:"departmentID" gorm:"index;type:varchar(64)" swaggerignore:"true"` + Department *Department `json:"department" gorm:"foreignKey:DepartmentID;references:ID"` + Username string `json:"username" gorm:"type:varchar(20);index"` + Email string `json:"email" gorm:"type:varchar(100);index"` + Password string `json:"password,omitempty" gorm:"-"` + PasswordHash string `json:"-" gorm:"size:255;comment:密码hash" swaggerignore:"true"` + PasswordStrength string `json:"passwordStrength" gorm:"size:20;comment:密码强度"` + Salt string `json:"-" gorm:"size:255;comment:加盐" swaggerignore:"true"` + Status enum.Status `json:"status" gorm:"size:10"` + OAuth2 []*UserOAuth2 `json:"oauth2" gorm:"foreignKey:UserID;references:ID"` + Provider pkg.LoginProvider `json:"type" gorm:"-"` + RefreshTokenDisable bool `json:"-" gorm:"-"` + PersonAccessToken string `json:"-" gorm:"-"` + Captcha string `json:"captcha" gorm:"-"` } func (e *UserLogin) TableName() string { @@ -229,7 +240,7 @@ func (e *UserLogin) Verify(ctx context.Context) (bool, security.Verifier, error) defaultRole := &Role{Default: true} _ = center.GetDB(ctx.(*gin.Context), &Role{}).Where(*defaultRole).First(defaultRole).Error switch e.Provider { - case pkg.OAuth2GithubProvider: + case pkg.GithubLoginProvider: // get user from github, then add user to db // github user clientID, _ := center.GetAppConfig().GetAppConfig(c, "security.githubClientId") @@ -290,14 +301,14 @@ func (e *UserLogin) Verify(ctx context.Context) (bool, security.Verifier, error) Website: githubUser.HTMLURL, EmailVerified: true, Locale: githubUser.Location, - Provider: pkg.OAuth2GithubProvider, + Provider: pkg.GithubLoginProvider, User: &User{ UserLogin: UserLogin{ RoleID: defaultRole.ID, Username: githubUser.Email, Email: githubUser.Email, Password: e.Password, - Provider: pkg.OAuth2GithubProvider, + Provider: pkg.GithubLoginProvider, Status: enum.Enabled, }, Name: githubUser.Login, @@ -319,7 +330,7 @@ func (e *UserLogin) Verify(ctx context.Context) (bool, security.Verifier, error) userOAuth2.User.Role = defaultRole } return true, userOAuth2.User, nil - case pkg.OAuth2LarkProvider: + case pkg.LarkLoginProvider: client := http.Client{} req, err := http.NewRequest(http.MethodGet, "https://open.larksuite.com/open-apis/authen/v1/user_info", nil) if err != nil { @@ -370,14 +381,14 @@ func (e *UserLogin) Verify(ctx context.Context) (bool, security.Verifier, error) Picture: *result.Data.AvatarUrl, NickName: *result.Data.Name, EmailVerified: email != "", - Provider: pkg.OAuth2LarkProvider, + Provider: pkg.LarkLoginProvider, User: &User{ UserLogin: UserLogin{ RoleID: defaultRole.ID, Username: *result.Data.UserId, Email: email, Password: e.Password, - Provider: pkg.OAuth2LarkProvider, + Provider: pkg.LarkLoginProvider, Status: enum.Enabled, }, Name: *result.Data.Name, @@ -399,12 +410,39 @@ func (e *UserLogin) Verify(ctx context.Context) (bool, security.Verifier, error) userOAuth2.User.Role = defaultRole } return true, userOAuth2.User, nil + case pkg.EmailLoginProvider: + fmt.Println("email login", e) + // verify captcha + if e.Captcha == "" { + return false, nil, nil + } + ok, err := center.Default.VerifyCode(c, e.Email, e.Captcha) + if err != nil { + return false, nil, err + } + if !ok { + return false, nil, nil + } + // get user from db + user, err := GetUserByEmail(c, e.Email) + if err != nil { + return false, nil, err + } + return true, user, nil } // username and password user, err := GetUserByUsername(ctx.(*gin.Context), e.Username) if err != nil { return false, nil, err } + if e.Captcha != "" { + var ok bool + ok, err = center.Default.VerifyCode(c, e.Username, e.Captcha) + if err != nil { + return false, nil, err + } + return ok, user, nil + } verify, err := security.SetPassword(e.Password, user.Salt) if err != nil { return false, nil, err diff --git a/models/user_oauth2.go b/models/user_oauth2.go index f8bf92c..e40703e 100644 --- a/models/user_oauth2.go +++ b/models/user_oauth2.go @@ -11,31 +11,31 @@ import "github.com/mss-boot-io/mss-boot-admin/pkg" type UserOAuth2 struct { ModelGormTenant - User *User `json:"user" gorm:"foreignKey:UserID;references:ID" swaggerignore:"true"` - UserID string `json:"user_id" gorm:"size:64"` - OpenID string `json:"openID" gorm:"size:64"` - UnionID string `json:"unionID" gorm:"column:union_id;size:64"` - Sub string `json:"sub" gorm:"size:255;comment:主题"` - Name string `json:"name" gorm:"size:255;comment:名称"` - GivenName string `json:"given_name" gorm:"size:255;comment:名"` - FamilyName string `json:"family_name" gorm:"size:255;comment:姓"` - MiddleName string `json:"middle_name" gorm:"size:255;comment:中间名"` - NickName string `json:"nickname" gorm:"size:255;comment:昵称"` - PreferredUsername string `json:"preferred_username" gorm:"size:255;comment:首选用户名"` - Profile string `json:"profile" gorm:"size:255;comment:个人资料"` - Picture string `json:"picture" gorm:"size:255;comment:图片"` - Website string `json:"website" gorm:"size:255;comment:网站"` - Email string `json:"email" gorm:"size:255;comment:邮箱"` - EmailVerified bool `json:"email_verified" gorm:"default:false;comment:邮箱是否验证"` - Gender string `json:"gender" gorm:"size:255;comment:性别"` - Birthdata string `json:"birthdata" gorm:"size:255;comment:出生日期"` - Zoneinfo string `json:"zoneinfo" gorm:"size:255;comment:时区"` - Locale string `json:"locale" gorm:"size:255;comment:语言"` - PhoneNumber string `json:"phone_number" gorm:"size:255;comment:手机号"` - PhoneNumberVerified bool `json:"phone_number_verified" gorm:"default:false;comment:手机号是否验证"` - Address string `json:"address" gorm:"size:255;comment:地址"` - EmployeeNO string `json:"employee_no" gorm:"column:employee_no;size:255;comment:员工编号"` - Provider pkg.OAuth2Provider `json:"type" gorm:"size:20;comment:登录类型"` + User *User `json:"user" gorm:"foreignKey:UserID;references:ID" swaggerignore:"true"` + UserID string `json:"user_id" gorm:"size:64"` + OpenID string `json:"openID" gorm:"size:64"` + UnionID string `json:"unionID" gorm:"column:union_id;size:64"` + Sub string `json:"sub" gorm:"size:255;comment:主题"` + Name string `json:"name" gorm:"size:255;comment:名称"` + GivenName string `json:"given_name" gorm:"size:255;comment:名"` + FamilyName string `json:"family_name" gorm:"size:255;comment:姓"` + MiddleName string `json:"middle_name" gorm:"size:255;comment:中间名"` + NickName string `json:"nickname" gorm:"size:255;comment:昵称"` + PreferredUsername string `json:"preferred_username" gorm:"size:255;comment:首选用户名"` + Profile string `json:"profile" gorm:"size:255;comment:个人资料"` + Picture string `json:"picture" gorm:"size:255;comment:图片"` + Website string `json:"website" gorm:"size:255;comment:网站"` + Email string `json:"email" gorm:"size:255;comment:邮箱"` + EmailVerified bool `json:"email_verified" gorm:"default:false;comment:邮箱是否验证"` + Gender string `json:"gender" gorm:"size:255;comment:性别"` + Birthdata string `json:"birthdata" gorm:"size:255;comment:出生日期"` + Zoneinfo string `json:"zoneinfo" gorm:"size:255;comment:时区"` + Locale string `json:"locale" gorm:"size:255;comment:语言"` + PhoneNumber string `json:"phone_number" gorm:"size:255;comment:手机号"` + PhoneNumberVerified bool `json:"phone_number_verified" gorm:"default:false;comment:手机号是否验证"` + Address string `json:"address" gorm:"size:255;comment:地址"` + EmployeeNO string `json:"employee_no" gorm:"column:employee_no;size:255;comment:员工编号"` + Provider pkg.LoginProvider `json:"type" gorm:"size:20;comment:登录类型"` } func (*UserOAuth2) TableName() string { diff --git a/notice/email/login_verify_code.html b/notice/email/login_verify_code.html new file mode 100644 index 0000000..d8c1ebe --- /dev/null +++ b/notice/email/login_verify_code.html @@ -0,0 +1,49 @@ + + + + + Verification Code + + + +
+

Login Verification Code

+

Dear User,

+

Your verification code for login is:

+
{{ .Code }}
+

Please use this code to complete your login. The code is valid for 10 minutes.

+

If you did not request this code, please ignore this email.

+ +
+ + \ No newline at end of file diff --git a/notice/email/password_reset_code.html b/notice/email/password_reset_code.html new file mode 100644 index 0000000..46bea32 --- /dev/null +++ b/notice/email/password_reset_code.html @@ -0,0 +1,49 @@ + + + + + Password Reset Code + + + +
+

Password Reset Code

+

Dear User,

+

You have requested to reset your password. Your password reset code is:

+
{{ .Code }}
+

Please use this code to reset your password. The code is valid for 15 minutes.

+

If you did not request this, please ignore this email.

+ +
+ + diff --git a/notice/email/send.go b/notice/email/send.go new file mode 100644 index 0000000..6ba017e --- /dev/null +++ b/notice/email/send.go @@ -0,0 +1,79 @@ +package email + +import ( + "bytes" + "embed" + "fmt" + "html/template" + "log/slog" + "net/mail" + "net/smtp" + "time" +) + +/* + * @Author: lwnmengjing + * @Date: 2024/8/13 17:04:03 + * @Last Modified by: lwnmengjing + * @Last Modified time: 2024/8/13 17:04:03 + */ + +//go:embed *.html +var FS embed.FS + +func SendVerifyCode(smtpHost, smtpPort, from, password, username, to, code, organization string) error { + rb, err := FS.ReadFile("login_verify_code.html") + if err != nil { + return err + } + // html template parse + tmpl, err := template.New("email").Parse(string(rb)) + if err != nil { + return err + } + data := map[string]any{ + "Code": code, + "Year": time.Now().Year(), + "Organization": organization, + } + var body bytes.Buffer + if err = tmpl.Execute(&body, data); err != nil { + return err + } + // 发件人信息 + fromAddress := mail.Address{Name: organization, Address: from} + // 收件人信息 + toAddress := mail.Address{Name: username, Address: to} + + // 邮件头信息 + headers := make(map[string]string) + headers["From"] = fromAddress.String() + headers["To"] = toAddress.String() + headers["Subject"] = "Your verification code is " + code + " (valid for 5 minutes)" + headers["Date"] = time.Now().Format(time.RFC1123Z) + headers["MIME-Version"] = "1.0" + headers["Content-Type"] = `text/html; charset="UTF-8"` + // 构建邮件内容 + var msg bytes.Buffer + for k, v := range headers { + _, err = fmt.Fprintf(&msg, "%s: %s\r\n", k, v) + if err != nil { + return err + } + } + _, err = fmt.Fprintf(&msg, "\r\n%s", body.String()) + if err != nil { + return err + } + // SMTP 配置 + auth := smtp.PlainAuth("", fromAddress.Address, password, smtpHost) + + // 发送邮件 + err = smtp.SendMail(smtpHost+":"+smtpPort, auth, fromAddress.Address, []string{toAddress.Address}, msg.Bytes()) + if err != nil { + slog.Error("Failed to send email", slog.Any("error", err)) + return err + } + slog.Info("Email sent successfully!") + return nil +} diff --git a/pkg/enum.go b/pkg/enum.go index 4bf1db3..e07b3e7 100644 --- a/pkg/enum.go +++ b/pkg/enum.go @@ -24,15 +24,17 @@ func (a AccessType) String() string { return string(a) } -type OAuth2Provider string +type LoginProvider string const ( - // OAuth2GithubProvider github oauth provider - OAuth2GithubProvider OAuth2Provider = "github" - // OAuth2LarkProvider lark oauth provider - OAuth2LarkProvider OAuth2Provider = "lark" + // GithubLoginProvider github oauth provider + GithubLoginProvider LoginProvider = "github" + // LarkLoginProvider lark oauth provider + LarkLoginProvider LoginProvider = "lark" + // EmailLoginProvider email login provider + EmailLoginProvider LoginProvider = "email" ) -func (o OAuth2Provider) String() string { +func (o LoginProvider) String() string { return string(o) } diff --git a/storage/cache/verify_code.go b/storage/cache/verify_code.go new file mode 100644 index 0000000..e362429 --- /dev/null +++ b/storage/cache/verify_code.go @@ -0,0 +1,51 @@ +package cache + +import ( + "context" + "fmt" + "github.com/mss-boot-io/mss-boot-admin/storage" + "github.com/spf13/cast" + "math/rand" + "time" +) + +/* + * @Author: lwnmengjing + * @Date: 2024/8/13 15:33:16 + * @Last Modified by: lwnmengjing + * @Last Modified time: 2024/8/13 15:33:16 + */ + +func generateCode6() int { + rand.New(rand.NewSource(time.Now().UnixNano())) + return rand.Intn(900000) + 100000 +} + +// NewVerifyCode create a new verify code +func NewVerifyCode(cache storage.AdapterCache) *VerifyCode { + return &VerifyCode{Cache: cache} +} + +type VerifyCode struct { + Cache storage.AdapterCache +} + +func (v *VerifyCode) GenerateCode(ctx context.Context, key string, expire time.Duration) (string, error) { + code := generateCode6() + err := v.Cache.Set(ctx, fmt.Sprintf("verify-code-%s", key), code, expire) + if err != nil { + return "", err + } + return cast.ToString(code), nil +} + +func (v *VerifyCode) VerifyCode(ctx context.Context, key, code string) (bool, error) { + s, err := v.Cache.Get(ctx, fmt.Sprintf("verify-code-%s", key)) + if err != nil { + return false, err + } + if s == "" { + return false, nil + } + return s == code, nil +} From 034d0addb687fe7c29752cdb71859d0ebb5d176f Mon Sep 17 00:00:00 2001 From: lwnmengjing Date: Tue, 13 Aug 2024 19:20:03 +0800 Subject: [PATCH 2/2] :sparkles: feat: support login by email --- apis/user.go | 10 +++++----- center/type.go | 6 ++++-- models/user.go | 8 -------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/apis/user.go b/apis/user.go index 139f46e..275118c 100644 --- a/apis/user.go +++ b/apis/user.go @@ -3,22 +3,22 @@ package apis import ( "errors" "fmt" - "github.com/mss-boot-io/mss-boot-admin/notice/email" "gorm.io/gorm" "net/http" "time" "github.com/gin-gonic/gin" - "github.com/mss-boot-io/mss-boot-admin/dto" - "github.com/mss-boot-io/mss-boot-admin/middleware" - "github.com/mss-boot-io/mss-boot-admin/models" - "github.com/mss-boot-io/mss-boot-admin/pkg" "github.com/mss-boot-io/mss-boot/pkg/config/gormdb" "github.com/mss-boot-io/mss-boot/pkg/response" "github.com/mss-boot-io/mss-boot/pkg/response/actions" "github.com/mss-boot-io/mss-boot/pkg/response/controller" "github.com/mss-boot-io/mss-boot-admin/center" + "github.com/mss-boot-io/mss-boot-admin/dto" + "github.com/mss-boot-io/mss-boot-admin/middleware" + "github.com/mss-boot-io/mss-boot-admin/models" + "github.com/mss-boot-io/mss-boot-admin/notice/email" + "github.com/mss-boot-io/mss-boot-admin/pkg" "github.com/mss-boot-io/mss-boot-admin/service" ) diff --git a/center/type.go b/center/type.go index 76bf571..30bf4e0 100644 --- a/center/type.go +++ b/center/type.go @@ -2,8 +2,9 @@ package center import ( "context" + "time" + "github.com/gin-gonic/gin" - "github.com/mss-boot-io/mss-boot-admin/storage" "github.com/mss-boot-io/mss-boot/core/server" "github.com/mss-boot-io/mss-boot/pkg/config/source" "github.com/mss-boot-io/mss-boot/pkg/security" @@ -11,7 +12,8 @@ import ( "google.golang.org/grpc" "gorm.io/gorm" "gorm.io/gorm/schema" - "time" + + "github.com/mss-boot-io/mss-boot-admin/storage" ) /* diff --git a/models/user.go b/models/user.go index a4f3eaf..68f5b7a 100644 --- a/models/user.go +++ b/models/user.go @@ -435,14 +435,6 @@ func (e *UserLogin) Verify(ctx context.Context) (bool, security.Verifier, error) if err != nil { return false, nil, err } - if e.Captcha != "" { - var ok bool - ok, err = center.Default.VerifyCode(c, e.Username, e.Captcha) - if err != nil { - return false, nil, err - } - return ok, user, nil - } verify, err := security.SetPassword(e.Password, user.Salt) if err != nil { return false, nil, err