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 }