187 lines
4.2 KiB
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
|
|
}
|