github.com/greenpau/go-identity@v1.1.6/password.go (about) 1 // Copyright 2020 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package identity 16 17 import ( 18 "github.com/greenpau/go-identity/pkg/errors" 19 "golang.org/x/crypto/bcrypt" 20 "strings" 21 "time" 22 ) 23 24 // Password is a memorized secret, typically a string of characters, 25 // used to confirm the identity of a user. 26 type Password struct { 27 Purpose string `json:"purpose,omitempty" xml:"purpose,omitempty" yaml:"purpose,omitempty"` 28 Algorithm string `json:"algorithm,omitempty" xml:"algorithm,omitempty" yaml:"algorithm,omitempty"` 29 Hash string `json:"hash,omitempty" xml:"hash,omitempty" yaml:"hash,omitempty"` 30 Cost int `json:"cost,omitempty" xml:"cost,omitempty" yaml:"cost,omitempty"` 31 Expired bool `json:"expired,omitempty" xml:"expired,omitempty" yaml:"expired,omitempty"` 32 ExpiredAt time.Time `json:"expired_at,omitempty" xml:"expired_at,omitempty" yaml:"expired_at,omitempty"` 33 CreatedAt time.Time `json:"created_at,omitempty" xml:"created_at,omitempty" yaml:"created_at,omitempty"` 34 Disabled bool `json:"disabled,omitempty" xml:"disabled,omitempty" yaml:"disabled,omitempty"` 35 DisabledAt time.Time `json:"disabled_at,omitempty" xml:"disabled_at,omitempty" yaml:"disabled_at,omitempty"` 36 } 37 38 // NewPassword returns an instance of Password. 39 func NewPassword(s string) (*Password, error) { 40 return NewPasswordWithOptions(s, "generic", "bcrypt", nil) 41 } 42 43 // NewPasswordWithOptions returns an instance of Password based on the 44 // provided parameters. 45 func NewPasswordWithOptions(s, purpose, algo string, params map[string]interface{}) (*Password, error) { 46 p := &Password{ 47 Purpose: purpose, 48 Algorithm: algo, 49 CreatedAt: time.Now().UTC(), 50 } 51 52 if params != nil { 53 if v, exists := params["cost"]; exists { 54 p.Cost = v.(int) 55 } 56 } 57 58 if err := p.hash(s); err != nil { 59 return nil, err 60 } 61 return p, nil 62 } 63 64 // Disable disables Password instance. 65 func (p *Password) Disable() { 66 p.Expired = true 67 p.ExpiredAt = time.Now().UTC() 68 p.Disabled = true 69 p.DisabledAt = time.Now().UTC() 70 } 71 72 func (p *Password) hash(s string) error { 73 s = strings.TrimSpace(s) 74 if s == "" { 75 return errors.ErrPasswordEmpty 76 } 77 switch p.Algorithm { 78 case "bcrypt": 79 if p.Cost < 8 { 80 p.Cost = 10 81 } 82 ph, err := bcrypt.GenerateFromPassword([]byte(s), p.Cost) 83 if err != nil { 84 return errors.ErrPasswordGenerate.WithArgs(err) 85 } 86 p.Hash = string(ph) 87 return nil 88 case "": 89 return errors.ErrPasswordEmptyAlgorithm 90 } 91 return errors.ErrPasswordUnsupportedAlgorithm.WithArgs(p.Algorithm) 92 } 93 94 // Match returns true when the provided password matches the user. 95 func (p *Password) Match(s string) bool { 96 if err := bcrypt.CompareHashAndPassword([]byte(p.Hash), []byte(s)); err == nil { 97 return true 98 } 99 return false 100 }