package auth import ( "fmt" "net/http" "strings" "time" "git.quimbo.fr/odwrtw/canape/backend/models" jwt "github.com/dgrijalva/jwt-go" "github.com/jmoiron/sqlx" "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") ) // Authorizer handle sesssion type Authorizer struct { db *sqlx.DB Params } // Params for Authorizer creation type Params struct { 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 *models.User) (*models.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.Name, "isAdmin": u.IsAdmin(), "isActivated": u.IsActivated(), }) // Sign the token ss, err := jwtToken.SignedString([]byte(a.Secret)) if err != nil { return nil, err } return &models.Token{ Token: ss, Username: u.Name, IP: getIPFromRequest(r), }, nil } // Login cheks password and creates a jwt token func (a *Authorizer) Login(r *http.Request, username, password string) (*models.Token, error) { u, err := models.GetUser(a.db, username) if err != nil { return nil, err } // Compare the password err = bcrypt.CompareHashAndPassword([]byte(u.Hash), []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) (*models.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 := models.GetUser(a.db, tokenClaims.Username) if err != nil { return nil, err } // Check the token in database token, err := models.GetUserToken(a.db, u.Name, 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 }