Skip to content

Commit

Permalink
add bcrypt as authentication method
Browse files Browse the repository at this point in the history
  • Loading branch information
alimy committed Feb 2, 2024
1 parent 859da55 commit 2631db9
Show file tree
Hide file tree
Showing 22 changed files with 204 additions and 79 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ All notable changes to paopao-ce are documented in this file.
- frontend: add tweets filter support use tag for home page and make it as default behavior.
- add pin topic support.
- support upload webp format image as picture when send tweet.
- support use bcrypt or md5 as authentication method. Use md5 as authentication default if not custom add `BcryptAuthMethod` or `Md5AuthMethod` to `conf.yaml` 's `Features` section.
add `BcryptAuthMethod` or `Md5AuthMethod` to `conf.yaml` 's `Features` section to enable this feature like below:
```yaml
# file config.yaml
...
Features:
Default: ["Postgres", "Meili", "LocalOSS", "LoggerOpenObserve", "BcryptAuthMethod", "web"]
...
```



## 0.5.2
### Change
Expand Down
4 changes: 4 additions & 0 deletions internal/conf/alipay.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package conf

import (
Expand Down
27 changes: 27 additions & 0 deletions internal/conf/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2024 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package conf

import (
"github.com/alimy/tryst/cfg"
"github.com/rocboss/paopao-ce/pkg/auth"
"golang.org/x/crypto/bcrypt"
)

func NewPasswordProvider() (provider auth.PasswordProvider) {
cfg.On(cfg.Actions{
"Md5AuthMethod": func() {
provider = auth.NewMd5PasswordProvider()
},
"BcryptAuthMethod": func() {
provider = auth.NewBcryptPasswordProvider(bcrypt.DefaultCost)
},
},
func() {
provider = auth.NewMd5PasswordProvider()
},
)
return
}
1 change: 0 additions & 1 deletion internal/dao/jinzhu/dbr/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ type User struct {
Username string `json:"username"`
Phone string `json:"phone"`
Password string `json:"password"`
Salt string `json:"salt"`
Status int `json:"status"`
Avatar string `json:"avatar"`
Balance int64 `json:"balance"`
Expand Down
4 changes: 2 additions & 2 deletions internal/servants/chain/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func JWT() gin.HandlerFunc {
// 加载用户信息
if user, err := ums.GetUserByID(claims.UID); err == nil {
// 强制下线机制
if app.IssuerFrom(user.Salt) == claims.Issuer {
if app.IssuerFrom(user.CreatedOn) == claims.Issuer {
c.Set("USER", user)
c.Set("UID", claims.UID)
c.Set("USERNAME", claims.Username)
Expand Down Expand Up @@ -132,7 +132,7 @@ func JwtLoose() gin.HandlerFunc {
if claims, err := app.ParseToken(token); err == nil {
// 加载用户信息
user, err := ums.GetUserByID(claims.UID)
if err == nil && app.IssuerFrom(user.Salt) == claims.Issuer {
if err == nil && app.IssuerFrom(user.CreatedOn) == claims.Issuer {
c.Set("UID", claims.UID)
c.Set("USERNAME", claims.Username)
c.Set("USER", user)
Expand Down
34 changes: 21 additions & 13 deletions internal/servants/web/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/rocboss/paopao-ce/internal/model/web"
"github.com/rocboss/paopao-ce/internal/servants/base"
"github.com/rocboss/paopao-ce/internal/servants/chain"
"github.com/rocboss/paopao-ce/pkg/auth"
"github.com/rocboss/paopao-ce/pkg/xerror"
"github.com/sirupsen/logrus"
)
Expand All @@ -38,10 +39,11 @@ var (
type coreSrv struct {
api.UnimplementedCoreServant
*base.DaoServant
oss core.ObjectStorageService
wc core.WebCache
messagesExpire int64
prefixMessages string
oss core.ObjectStorageService
wc core.WebCache
passwordProvider auth.PasswordProvider
messagesExpire int64
prefixMessages string
}

func (s *coreSrv) Chain() gin.HandlersChain {
Expand Down Expand Up @@ -305,12 +307,17 @@ func (s *coreSrv) ChangePassword(req *web.ChangePasswordReq) mir.Error {
}
// 旧密码校验
user := req.User
if !validPassword(user.Password, req.OldPassword, req.User.Salt) {
err := s.passwordProvider.Compare(user.Password, req.OldPassword)
if err != nil {
return web.ErrErrorOldPassword
}
// 更新入库
user.Password, user.Salt = encryptPasswordAndSalt(req.Password)
if err := s.Ds.UpdateUser(user); err != nil {
user.Password, err = s.passwordProvider.Generate(req.Password)
if err != nil {
logrus.Errorf("generate hashed password err: %s", err)
return xerror.ServerError
}
if err = s.Ds.UpdateUser(user); err != nil {
logrus.Errorf("Ds.UpdateUser err: %s", err)
return xerror.ServerError
}
Expand Down Expand Up @@ -418,13 +425,14 @@ func (s *coreSrv) messagesFromCache(req *web.GetMessagesReq, limit int, offset i
return
}

func newCoreSrv(s *base.DaoServant, oss core.ObjectStorageService, wc core.WebCache) api.Core {
func newCoreSrv(s *base.DaoServant, oss core.ObjectStorageService, wc core.WebCache, provider auth.PasswordProvider) api.Core {
cs := conf.CacheSetting
return &coreSrv{
DaoServant: s,
oss: oss,
wc: wc,
messagesExpire: cs.MessagesExpire,
prefixMessages: conf.PrefixMessages,
DaoServant: s,
oss: oss,
wc: wc,
messagesExpire: cs.MessagesExpire,
prefixMessages: conf.PrefixMessages,
passwordProvider: provider,
}
}
19 changes: 13 additions & 6 deletions internal/servants/web/pub.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/rocboss/paopao-ce/internal/servants/base"
"github.com/rocboss/paopao-ce/internal/servants/web/assets"
"github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/auth"
"github.com/rocboss/paopao-ce/pkg/utils"
"github.com/rocboss/paopao-ce/pkg/version"
"github.com/rocboss/paopao-ce/pkg/xerror"
Expand All @@ -40,6 +41,8 @@ const (
type pubSrv struct {
api.UnimplementedPubServant
*base.DaoServant

passwordProvider auth.PasswordProvider
}

func (s *pubSrv) SendCaptcha(req *web.SendCaptchaReq) mir.Error {
Expand Down Expand Up @@ -104,16 +107,19 @@ func (s *pubSrv) Register(req *web.RegisterReq) (*web.RegisterResp, mir.Error) {
logrus.Errorf("scheckPassword err: %v", err)
return nil, web.ErrUserRegisterFailed
}
password, salt := encryptPasswordAndSalt(req.Password)
password, err := s.passwordProvider.Generate(req.Password)
if err != nil {
logrus.Errorf("generate hashed password err: %v", err)
return nil, web.ErrUserRegisterFailed
}
user := &ms.User{
Nickname: req.Username,
Username: req.Username,
Password: password,
Avatar: getRandomAvatar(),
Salt: salt,
Status: ms.UserStatusNormal,
}
user, err := s.Ds.CreateUser(user)
user, err = s.Ds.CreateUser(user)
if err != nil {
logrus.Errorf("Ds.CreateUser err: %s", err)
return nil, web.ErrUserRegisterFailed
Expand All @@ -137,7 +143,7 @@ func (s *pubSrv) Login(req *web.LoginReq) (*web.LoginResp, mir.Error) {
return nil, web.ErrTooManyLoginError
}
// 对比密码是否正确
if validPassword(user.Password, req.Password, user.Salt) {
if err := s.passwordProvider.Compare(user.Password, req.Password); err == nil {
if user.Status == ms.UserStatusClosed {
return nil, web.ErrUserHasBeenBanned
}
Expand Down Expand Up @@ -187,8 +193,9 @@ func (s *pubSrv) validUsername(username string) mir.Error {
return nil
}

func newPubSrv(s *base.DaoServant) api.Pub {
func newPubSrv(s *base.DaoServant, provider auth.PasswordProvider) api.Pub {
return &pubSrv{
DaoServant: s,
DaoServant: s,
passwordProvider: provider,
}
}
14 changes: 0 additions & 14 deletions internal/servants/web/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ import (
"unicode/utf8"

"github.com/alimy/mir/v4"
"github.com/gofrs/uuid/v5"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/core/ms"
"github.com/rocboss/paopao-ce/internal/model/web"
"github.com/rocboss/paopao-ce/pkg/utils"
"github.com/rocboss/paopao-ce/pkg/xerror"
"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -88,18 +86,6 @@ func checkPassword(password string) mir.Error {
return nil
}

// ValidPassword 检查密码是否一致
func validPassword(dbPassword, password, salt string) bool {
return strings.Compare(dbPassword, utils.EncodeMD5(utils.EncodeMD5(password)+salt)) == 0
}

// encryptPasswordAndSalt 密码加密&生成salt
func encryptPasswordAndSalt(password string) (string, string) {
salt := uuid.Must(uuid.NewV4()).String()[:8]
password = utils.EncodeMD5(utils.EncodeMD5(password) + salt)
return password, salt
}

// deleteOssObjects 删除推文的媒体内容, 宽松处理错误(就是不处理), 后续完善
func deleteOssObjects(oss core.ObjectStorageService, mediaContents []string) {
mediaContentsSize := len(mediaContents)
Expand Down
5 changes: 3 additions & 2 deletions internal/servants/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ var (
func RouteWeb(e *gin.Engine) {
lazyInitial()
ds := base.NewDaoServant()
provider := conf.NewPasswordProvider()
// aways register servants
api.RegisterAdminServant(e, newAdminSrv(ds, _wc))
api.RegisterCoreServant(e, newCoreSrv(ds, _oss, _wc))
api.RegisterCoreServant(e, newCoreSrv(ds, _oss, _wc, provider))
api.RegisterRelaxServant(e, newRelaxSrv(ds, _wc), newRelaxChain())
api.RegisterLooseServant(e, newLooseSrv(ds, _ac))
api.RegisterPrivServant(e, newPrivSrv(ds, _oss), newPrivChain())
api.RegisterPubServant(e, newPubSrv(ds))
api.RegisterPubServant(e, newPubSrv(ds, provider))
api.RegisterTrendsServant(e, newTrendsSrv(ds))
api.RegisterFollowshipServant(e, newFollowshipSrv(ds))
api.RegisterFriendshipServant(e, newFriendshipSrv(ds))
Expand Down
6 changes: 4 additions & 2 deletions pkg/app/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package app
import (
"crypto/md5"
"encoding/hex"
"strconv"
"time"

"github.com/golang-jwt/jwt/v5"
Expand All @@ -31,7 +32,7 @@ func GenerateToken(user *ms.User) (string, error) {
Username: user.Username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expireTime),
Issuer: IssuerFrom(user.Salt),
Issuer: IssuerFrom(user.CreatedOn),
},
}

Expand All @@ -53,7 +54,8 @@ func ParseToken(token string) (res *Claims, err error) {
return
}

func IssuerFrom(data string) string {
func IssuerFrom(num int64) string {
data := strconv.FormatInt(num, 10)
contents := make([]byte, 0, len(conf.JWTSetting.Issuer)+len(data))
copy(contents, []byte(conf.JWTSetting.Issuer))
contents = append(contents, []byte(data)...)
Expand Down
60 changes: 60 additions & 0 deletions pkg/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2024 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package auth

import (
"errors"
"strings"

"github.com/gofrs/uuid/v5"
"github.com/rocboss/paopao-ce/pkg/utils"
"golang.org/x/crypto/bcrypt"
)

type PasswordProvider interface {
Generate(password string) (string, error)
Compare(hashedPassword, password string) error
}

type bcryptPasswordProvider struct {
cost int
}

type md5PasswordProvider struct{}

func (p *bcryptPasswordProvider) Generate(password string) (string, error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), p.cost)
return utils.String(hashedPassword), err
}

func (p *bcryptPasswordProvider) Compare(hashedPassword, password string) error {
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
}

func (p md5PasswordProvider) Generate(password string) (string, error) {
salt := uuid.Must(uuid.NewV4()).String()[:8]
return utils.EncodeMD5(utils.EncodeMD5(password)+salt) + ":" + salt, nil
}

func (p md5PasswordProvider) Compare(hashedPassword, password string) error {
passwordSalt := strings.Split(string(hashedPassword), ":")
if len(passwordSalt) != 2 {
return errors.New("invalid hashed password")
}
if strings.Compare(passwordSalt[0], utils.EncodeMD5(utils.EncodeMD5(password)+passwordSalt[1])) != 0 {
return errors.New("invalid password")
}
return nil
}

func NewBcryptPasswordProvider(cost int) PasswordProvider {
return &bcryptPasswordProvider{
cost: cost,
}
}

func NewMd5PasswordProvider() PasswordProvider {
return md5PasswordProvider{}
}
32 changes: 0 additions & 32 deletions pkg/types/password.go

This file was deleted.

1 change: 0 additions & 1 deletion pkg/utils/md5.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,5 @@ import (
func EncodeMD5(value string) string {
m := md5.New()
m.Write([]byte(value))

return hex.EncodeToString(m.Sum(nil))
}
9 changes: 9 additions & 0 deletions scripts/migration/mysql/0016_password_use_bcrypt.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ALTER TABLE `p_user` ADD COLUMN `salt` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '盐值';

UPDATE
`p_user`
SET
`salt` = SUBSTRING_INDEX(`password`, ':', -1),
`password` = SUBSTRING_INDEX(`password`, ':', 1);

ALTER TABLE `p_user` MODIFY COLUMN `password` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '密码';
Loading

0 comments on commit 2631db9

Please sign in to comment.