github.com/bosssauce/ponzu@v0.11.1-0.20200102001432-9bc41b703131/system/admin/user/auth.go (about) 1 // Package user contains the basic admin user creation and authentication code, 2 // specific to Ponzu systems. 3 package user 4 5 import ( 6 "bytes" 7 crand "crypto/rand" 8 "encoding/base64" 9 "log" 10 mrand "math/rand" 11 "net/http" 12 "time" 13 14 "github.com/nilslice/jwt" 15 "golang.org/x/crypto/bcrypt" 16 ) 17 18 // User defines a admin user in the system 19 type User struct { 20 ID int `json:"id"` 21 Email string `json:"email"` 22 Hash string `json:"hash"` 23 Salt string `json:"salt"` 24 } 25 26 var ( 27 r = mrand.New(mrand.NewSource(time.Now().Unix())) 28 ) 29 30 // New creates a user 31 func New(email, password string) (*User, error) { 32 salt, err := randSalt() 33 if err != nil { 34 return nil, err 35 } 36 37 hash, err := hashPassword([]byte(password), salt) 38 if err != nil { 39 return nil, err 40 } 41 42 user := &User{ 43 Email: email, 44 Hash: string(hash), 45 Salt: base64.StdEncoding.EncodeToString(salt), 46 } 47 48 return user, nil 49 } 50 51 // Auth is HTTP middleware to ensure the request has proper token credentials 52 func Auth(next http.HandlerFunc) http.HandlerFunc { 53 return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 54 redir := req.URL.Scheme + req.URL.Host + "/admin/login" 55 56 if IsValid(req) { 57 next.ServeHTTP(res, req) 58 } else { 59 http.Redirect(res, req, redir, http.StatusFound) 60 } 61 }) 62 } 63 64 // IsValid checks if the user request is authenticated 65 func IsValid(req *http.Request) bool { 66 // check if token exists in cookie 67 cookie, err := req.Cookie("_token") 68 if err != nil { 69 return false 70 } 71 // validate it and allow or redirect request 72 token := cookie.Value 73 return jwt.Passes(token) 74 } 75 76 // IsUser checks for consistency in email/pass combination 77 func IsUser(usr *User, password string) bool { 78 salt, err := base64.StdEncoding.DecodeString(usr.Salt) 79 if err != nil { 80 return false 81 } 82 83 err = checkPassword([]byte(usr.Hash), []byte(password), salt) 84 if err != nil { 85 log.Println("Error checking password:", err) 86 return false 87 } 88 89 return true 90 } 91 92 // randSalt generates 16 * 8 bits of data for a random salt 93 func randSalt() ([]byte, error) { 94 buf := make([]byte, 16) 95 count := len(buf) 96 n, err := crand.Read(buf) 97 if err != nil { 98 return nil, err 99 } 100 101 if n != count || err != nil { 102 for count > 0 { 103 count-- 104 buf[count] = byte(r.Int31n(256)) 105 } 106 } 107 108 return buf, nil 109 } 110 111 // saltPassword combines the salt and password provided 112 func saltPassword(password, salt []byte) ([]byte, error) { 113 salted := &bytes.Buffer{} 114 _, err := salted.Write(append(salt, password...)) 115 if err != nil { 116 return nil, err 117 } 118 119 return salted.Bytes(), nil 120 } 121 122 // hashPassword encrypts the salted password using bcrypt 123 func hashPassword(password, salt []byte) ([]byte, error) { 124 salted, err := saltPassword(password, salt) 125 if err != nil { 126 return nil, err 127 } 128 129 hash, err := bcrypt.GenerateFromPassword(salted, 10) 130 if err != nil { 131 return nil, err 132 } 133 134 return hash, nil 135 } 136 137 // checkPassword compares the hash with the salted password. A nil return means 138 // the password is correct, but an error could mean either the password is not 139 // correct, or the salt process failed - indicated in logs 140 func checkPassword(hash, password, salt []byte) error { 141 salted, err := saltPassword(password, salt) 142 if err != nil { 143 return err 144 } 145 146 return bcrypt.CompareHashAndPassword(hash, salted) 147 }