canape/backend/auth/auth.go

132 lines
2.9 KiB
Go

package auth
import (
"fmt"
"net/http"
"strings"
jwt "github.com/dgrijalva/jwt-go"
"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")
// ErrInvalidToken returned when the jwt token is invalid
ErrInvalidToken = fmt.Errorf("Invalid token")
)
// UserBackend interface for user backend
type UserBackend interface {
GetUser(username string) (User, error)
}
// User interface for user
type User interface {
GetName() string
GetHash() string
HasRole(string) bool
IsAdmin() bool
IsActivated() bool
}
// Authorizer handle sesssion
type Authorizer struct {
Params
}
// Params for Authorizer creation
type Params struct {
Backend UserBackend
Pepper string
Cost int
Secret string
}
// 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 creates a jwt token
func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, password string) (User, error) {
u, err := a.Backend.GetUser(username)
if err != nil {
return nil, err
}
// Compare the password
err = bcrypt.CompareHashAndPassword([]byte(u.GetHash()), []byte(password+a.Pepper))
if err != nil {
return nil, ErrInvalidPassword
}
return u, nil
}
// CurrentUser returns the logged in username from session and verifies the token
func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (User, error) {
var tokenStr string
h := req.Header.Get("Authorization")
if h != "" {
// Get the token from the header
tokenStr = strings.Replace(h, "Bearer ", "", -1)
}
// If the token string is still empty, check in the cookies
if tokenStr == "" {
tokenCookie, err := req.Cookie("token")
if err != nil || tokenCookie == nil {
return nil, nil
}
tokenStr = tokenCookie.Value
}
// No user logged
if tokenStr == "" {
return nil, nil
}
// Keyfunc to decode the token
var keyfunc jwt.Keyfunc = func(token *jwt.Token) (interface{}, error) {
return []byte(a.Secret), nil
}
var tokenClaims struct {
Username string `json:"username"`
jwt.StandardClaims
}
token, err := jwt.ParseWithClaims(tokenStr, &tokenClaims, keyfunc)
if err != nil {
return nil, err
}
// Check the token validity
if !token.Valid {
return nil, ErrInvalidToken
}
// Get the user
u, err := a.Backend.GetUser(tokenClaims.Username)
if err != nil {
return nil, err
}
return u, nil
}