172 lines
3.7 KiB
Go

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 {
Params
}
// Params for Authorizer creation
type Params struct {
Backend UserBackend
Cookiejar *sessions.CookieStore
CookieName string
Pepper string
Cost int
}
// New Authorizer pepper is like a salt but not stored in database,
// cost is the bcrypt cost for hashing the password
func New(params Params) *Authorizer {
return &Authorizer{
Params: params,
}
}
// GenHash generates a new hash from a password
func (a *Authorizer) GenHash(password string) (string, error) {
b, err := bcrypt.GenerateFromPassword([]byte(password+a.Pepper), 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.Pepper))
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
}
// RegenSecret update secret in cookie with user info, usefull when updating password
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
}