canape/backend/auth/auth.go

187 lines
4.2 KiB
Go

package auth
import (
"fmt"
"net/http"
"strings"
"time"
jwt "github.com/dgrijalva/jwt-go"
"github.com/jmoiron/sqlx"
"git.quimbo.fr/odwrtw/canape/backend/tokens"
"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")
// ErrUnauthenticatedUser returned when a user is not authenticated
ErrUnauthenticatedUser = fmt.Errorf("Unauthenticated user")
)
// 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 {
db *sqlx.DB
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(db *sqlx.DB, params Params) *Authorizer {
return &Authorizer{
db: db,
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
}
// GenerateJWTToken generates a JWT token for a user
func (a *Authorizer) GenerateJWTToken(r *http.Request, u User) (*tokens.Token, error) {
// Create a jwt token
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
// Not before
"nbf": time.Now().Unix(),
// Issued at
"iat": time.Now().Unix(),
// Private claims
"username": u.GetName(),
"isAdmin": u.IsAdmin(),
"isActivated": u.IsActivated(),
})
// Sign the token
ss, err := jwtToken.SignedString([]byte(a.Secret))
if err != nil {
return nil, err
}
return &tokens.Token{
Token: ss,
Username: u.GetName(),
IP: getIPFromRequest(r),
}, nil
}
// Login cheks password and creates a jwt token
func (a *Authorizer) Login(r *http.Request, username, password string) (*tokens.Token, 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
}
t, err := a.GenerateJWTToken(r, u)
if err != nil {
return nil, err
}
if err := t.Add(a.db); err != nil {
return nil, err
}
return t, 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, ErrUnauthenticatedUser
}
tokenStr = tokenCookie.Value
}
// No user logged
if tokenStr == "" {
return nil, ErrUnauthenticatedUser
}
// 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
}
jwtToken, err := jwt.ParseWithClaims(tokenStr, &tokenClaims, keyfunc)
if err != nil {
return nil, err
}
// Check the token validity
if !jwtToken.Valid {
return nil, ErrInvalidToken
}
// Get the user
u, err := a.Backend.GetUser(tokenClaims.Username)
if err != nil {
return nil, err
}
// Check the token in database
token, err := tokens.GetUserToken(a.db, u.GetName(), tokenStr)
if err != nil {
return nil, ErrInvalidToken
}
token.UserAgent = req.UserAgent()
token.IP = getIPFromRequest(req)
if err := token.Update(a.db); err != nil {
return nil, err
}
return u, nil
}