171 lines
3.8 KiB
Go
171 lines
3.8 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 {
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|