package password import ( "errors" "unicode/utf8" "cmroubao/backend-api/internal/domain" "golang.org/x/crypto/bcrypt" ) var ( ErrInvalidPassword = errors.New("password is invalid") ErrPasswordMismatch = errors.New("password does not match") ) type Bcrypt struct { cost int dummyHash string } func NewBcrypt(cost int) (*Bcrypt, error) { if cost < bcrypt.MinCost || cost > bcrypt.MaxCost { return nil, errors.New("bcrypt cost is out of range") } dummyHash, err := bcrypt.GenerateFromPassword( []byte("cmroubao-dummy-password"), cost, ) if err != nil { return nil, errors.New("initialize bcrypt dummy hash") } return &Bcrypt{cost: cost, dummyHash: string(dummyHash)}, nil } func (manager *Bcrypt) Hash(plain string) (string, error) { if !utf8.ValidString(plain) || len([]byte(plain)) < domain.MinPasswordBytes || len([]byte(plain)) > domain.MaxPasswordBytes { return "", ErrInvalidPassword } value, err := bcrypt.GenerateFromPassword([]byte(plain), manager.cost) if err != nil { return "", errors.New("hash password") } return string(value), nil } func (manager *Bcrypt) VerifyDummy(plain string) { _ = bcrypt.CompareHashAndPassword( []byte(manager.dummyHash), []byte(plain), ) } func (manager *Bcrypt) Verify(encoded string, plain string) error { if encoded == "" || plain == "" || len([]byte(plain)) > domain.MaxPasswordBytes { return ErrPasswordMismatch } if err := bcrypt.CompareHashAndPassword( []byte(encoded), []byte(plain), ); err != nil { return ErrPasswordMismatch } return nil }