package security import ( "fmt" "github.com/dgrijalva/jwt-go" "golang.org/x/crypto/bcrypt" ) // Security is the security service object. Security presents an easy to use // interface for common actions needed in CRUD applications such as: // 1. Creating and comparing hashes (for passwords). // 2. Creating and reading from tokens (for persistents when sessions are not // available). // // Usage: // // Do this once in your application. // sec := security.Default("MY_SECRET_HERE") // // // Create password hash for database // hash, err := sec.HashCreate(someSalt, somePass) // if err != nil {} // // // Check hash for attempted login. // if err := HashCompare(userInput, hash); err != nil { // panic("incorrect password") // } // // // Create token for user struct. // token, err := sec.TokenCreate(TokenMap{"UserID": user.ID()}) // if err != nil {} // // // Decode user struct token. // claims, err := sec.TokenFrom(token) // if err != nil {} // id, ok := claims["UserID"].(string) // if !ok {} type Security struct{ secret string } // Default builds the security service object. // // Usage: // // Do this once in your application. // sec := security.Default("MY_SECRET_HERE") // // // Create password hash for database // hash, err := sec.HashCreate(someSalt, somePass) // if err != nil {} // // // Check hash for attempted login. // if err := HashCompare(userInput, hash); err != nil { // panic("incorrect password") // } // // // Create token for user struct. // token, err := sec.TokenCreate(TokenMap{"UserID": user.ID()}) // if err != nil {} // // // Decode user struct token. // claims, err := sec.TokenFrom(token) // if err != nil {} // id, ok := claims["UserID"].(string) // if !ok {} func Default(secret string) Security { return Security{secret} } // HashCreate will create a hash for a password. func (sec Security) HashCreate(salt, pass string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(salt+pass), 4) return string(bytes), err } // HashCompare will compare a user input with a previously created hash value. // Commonly used for password verification while logging users in. Returns an // error when password differs from the hash input. func (sec Security) HashCompare(salt, pass, hash string) error { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(salt+pass)) } // TokenMap is the type supplied to and returned from TokenCreate and TokenFrom // respectively. Use a you would a `map[string]interface{}`. It's an abstraction // over `jwt.MapClaims` (github.com/dgrijalva/jwt-go) so you don't have to // import both libraries. type TokenMap = jwt.MapClaims // TokenCreate creates a jwt string value given a `TokenMap`. Will return an // error for unable to create token. func (sec Security) TokenCreate(val TokenMap) (string, error) { // nolint // Not linted because linter complains about `TokenMap` not being // `jwt.MapClaims`. token := jwt.NewWithClaims(jwt.SigningMethodHS256, val) return token.SignedString([]byte(sec.secret)) } // TokenFrom returns a `TokenMap` item given a jwt string value. Be // careful, you will need to type check these values at run time like so: // claims, err := sec.TokenFrom(token) // if err != nil {} // id, ok := claims["UserID"].(string) // if !ok {} // // no you can use the `id` item stored within the token. func (sec Security) TokenFrom(tokenString string) (_ret TokenMap, recoverErr error) { defer func() { if recover() != nil { // That's right, jwt will pnic on bad input. Don't do this. recoverErr = fmt.Errorf("hash has no value") } }() token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(sec.secret), nil }) if val, ok := token.Claims.(TokenMap); ok && token.Valid { return val, nil } return TokenMap{}, err }