You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
quartermaster/pkg/api/users/views.go

120 lines
3.6 KiB

package users
import (
"encoding/json"
"net/http"
"time"
"github.com/dustinpianalto/errors"
"github.com/dustinpianalto/quartermaster"
ijwt "github.com/dustinpianalto/quartermaster/internal/jwt"
"github.com/dustinpianalto/quartermaster/pkg/services"
"github.com/golang-jwt/jwt"
"golang.org/x/crypto/bcrypt"
)
func loginHandler(w *http.ResponseWriter, r *http.Request) error {
const method errors.Method = "users/login"
var userReq quartermaster.User
err := json.NewDecoder(r.Body).Decode(&userReq)
if err != nil {
return errors.E(method, errors.Malformed, "failed to decode user request", err)
}
user, err := services.UserService.User(userReq.Username)
if err != nil {
return errors.E(method, errors.Username(userReq.Username), errors.Incorrect, "user not found", err)
}
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(userReq.Password)); err != nil {
return errors.E(method, errors.Username(userReq.Username), errors.Incorrect, "incorrect password", err)
}
expires := time.Now().Add(10 * time.Minute)
claims := quartermaster.Claims{
ID: user.ID,
Username: user.Username,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expires.Unix(),
},
}
tokenString, err := ijwt.CreateJWTToken(claims)
if err != nil {
return errors.E(method, errors.Username(user.Username), errors.Internal, "error creating token", err)
}
t := quartermaster.TokenResponse{
Token: tokenString,
}
tJson, err := json.Marshal(t)
if err != nil {
return errors.E(method, errors.Internal, "error marshalling token", err)
}
(*w).Header().Set("Content-Type", "application/json")
(*w).WriteHeader(http.StatusOK)
(*w).Write(tJson)
return nil
}
func registerHandler(w *http.ResponseWriter, r *http.Request) error {
const method errors.Method = "users/register"
var user *quartermaster.User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
return errors.E(method, errors.Malformed, "failed to decode user request", err)
}
p, err := bcrypt.GenerateFromPassword([]byte(user.Password), 5)
if err != nil {
return errors.E(method, errors.Username(user.Username), errors.Internal, "error hashing password", err)
}
user.Password = string(p)
user, err = services.UserService.AddUser(user)
if err != nil {
return errors.E(method, errors.Username(user.Username), errors.Conflict, "user already exists", err)
}
return nil
}
func refreshHandler(w *http.ResponseWriter, r *http.Request) error {
const method errors.Method = "users/refresh"
claims, err := ijwt.AuthenticateJWTToken(r)
if err != nil {
return errors.E(method, errors.Malformed, "error with authentication header", err)
}
// We ensure that a new token is not issued until enough time has elapsed
// In this case, a new token will only be issued if the old token is within
// 2 minutes of expiry. Otherwise, return a bad request status
if time.Until(time.Unix(claims.StandardClaims.ExpiresAt, 0)) >= 2*time.Minute {
return errors.E(method, errors.Malformed, "not enough time passed")
}
// Now, create a new token for the current use, with a renewed expiration time
expirationTime := time.Now().Add(10 * time.Minute)
claims.StandardClaims.ExpiresAt = expirationTime.Unix()
tokenString, err := ijwt.CreateJWTToken(*claims)
if err != nil {
return errors.E(method, errors.Internal, "error creating token", err)
}
t := quartermaster.TokenResponse{
Token: tokenString,
}
tJson, err := json.Marshal(t)
if err != nil {
return errors.E(method, errors.Internal, "error marshalling token", err)
}
(*w).Header().Set("Content-Type", "application/json")
(*w).WriteHeader(http.StatusOK)
(*w).Write(tJson)
return nil
}