package auth import ( "fmt" "net/http" "github.com/gorilla/sessions" "golang.org/x/crypto/bcrypt" ) var ( // ErrInvalidPassword returned when password and hash don't match ErrInvalidPassword = fmt.Errorf("Invalid password") // ErrInvalidSecret returned when cookie's secret is don't match ErrInvalidSecret = fmt.Errorf("Invalid secret") // ErrCorrupted returned when session have been corrupted ErrCorrupted = fmt.Errorf("Corrupted session") ) // UserBackend interface for user backend type UserBackend interface { Get(username string) (User, error) } // User interface for user type User interface { GetHash() string HasRole(string) bool } // Authorizer handle sesssion type Authorizer struct { backend UserBackend cookiejar *sessions.CookieStore cookieName string peeper string cost int } // New Authorizer peeper is like a salt but not stored in database, // cost is the bcrypt cost for hashing the password func New(backend UserBackend, peeper, cookieName, cookieKey string, cost int) *Authorizer { return &Authorizer{ backend: backend, cookiejar: sessions.NewCookieStore([]byte(cookieKey)), cookieName: cookieName, peeper: peeper, cost: cost, } } // GenHash generates a new hash from a password func (a *Authorizer) GenHash(password string) (string, error) { b, err := bcrypt.GenerateFromPassword([]byte(password+a.peeper), a.cost) if err != nil { return "", err } return string(b), nil } // Login cheks password and updates cookie info func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, password string) error { cookie, err := a.cookiejar.Get(req, a.cookieName) if err != nil { return err } u, err := a.backend.Get(username) if err != nil { return err } err = bcrypt.CompareHashAndPassword([]byte(u.GetHash()), []byte(password+a.peeper)) if err != nil { return ErrInvalidPassword } cookie.Values["username"] = username // genereate secret b, err := bcrypt.GenerateFromPassword([]byte(u.GetHash()), a.cost) if err != nil { return err } cookie.Values["secret"] = string(b) err = cookie.Save(req, rw) if err != nil { return err } return nil } func (a *Authorizer) RegenSecret(user User, w http.ResponseWriter, r *http.Request) error { cookie, err := a.cookiejar.Get(r, a.cookieName) if err != nil { return err } // genereate secret b, err := bcrypt.GenerateFromPassword([]byte(user.GetHash()), a.cost) if err != nil { return err } cookie.Values["secret"] = string(b) err = cookie.Save(r, w) if err != nil { return err } return nil } // Logout remove cookie info func (a *Authorizer) Logout(rw http.ResponseWriter, req *http.Request) error { cookie, err := a.cookiejar.Get(req, a.cookieName) if err != nil { return err } cookie.Values["username"] = nil cookie.Values["secret"] = nil cookie.Options.MaxAge = -1 // kill the cookie err = cookie.Save(req, rw) if err != nil { return err } return nil } // CurrentUser returns the logged in username from session func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (User, error) { cookie, err := a.cookiejar.Get(req, a.cookieName) if err != nil { return nil, err } if cookie.IsNew { return nil, nil } usernameTmp := cookie.Values["username"] if usernameTmp == nil { return nil, nil } username, ok := usernameTmp.(string) if !ok { return nil, ErrCorrupted } u, err := a.backend.Get(username) if err != nil { return nil, err } // Check secret hash := u.GetHash() secretTmp := cookie.Values["secret"] if secretTmp == nil { return nil, nil } secret, ok := secretTmp.(string) if !ok { return nil, ErrCorrupted } err = bcrypt.CompareHashAndPassword([]byte(secret), []byte(hash)) if err != nil { return nil, ErrInvalidSecret } return u, nil }