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.
234 lines
6.3 KiB
234 lines
6.3 KiB
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.aiterp.net/lucifer/lucifer/internal/respond"
|
|
"git.aiterp.net/lucifer/lucifer/models"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// The UserController is a controller for all user inports.
|
|
type UserController struct {
|
|
users models.UserRepository
|
|
sessions models.SessionRepository
|
|
}
|
|
|
|
// getUsers (`GET /`): List users
|
|
func (c *UserController) getUsers(w http.ResponseWriter, r *http.Request) {
|
|
if session := models.SessionFromContext(r.Context()); session == nil {
|
|
respond.Error(w, http.StatusForbidden, "permission_denied", "You must log in")
|
|
return
|
|
}
|
|
|
|
// TODO: Only admin should be allowed to do this?
|
|
|
|
users, err := c.users.List(r.Context())
|
|
if err != nil {
|
|
respond.Error(w, 500, "db_error", err.Error())
|
|
return
|
|
}
|
|
|
|
respond.Data(w, users)
|
|
}
|
|
|
|
// getUser (`GET /:id`): Get user by id
|
|
func (c *UserController) getUser(w http.ResponseWriter, r *http.Request) {
|
|
session := models.SessionFromContext(r.Context())
|
|
if session == nil {
|
|
respond.Error(w, http.StatusForbidden, "permission_denied", "You must log in")
|
|
return
|
|
}
|
|
|
|
// TODO: Only admin should be allowed to do this?
|
|
|
|
var user models.User
|
|
|
|
id, err := strconv.Atoi(mux.Vars(r)["id"])
|
|
if err == nil {
|
|
user, err = c.users.FindByID(r.Context(), id)
|
|
if err != nil {
|
|
respond.Error(w, 404, "not_found", err.Error())
|
|
return
|
|
}
|
|
} else {
|
|
user, err = c.users.FindByName(r.Context(), mux.Vars(r)["id"])
|
|
if err != nil {
|
|
respond.Error(w, 404, "not_found", err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
respond.Data(w, user)
|
|
}
|
|
|
|
func (c *UserController) session(w http.ResponseWriter, r *http.Request) {
|
|
type response struct {
|
|
LoggedIn bool `json:"loggedIn"`
|
|
User *models.User `json:"user"`
|
|
}
|
|
|
|
session := models.SessionFromContext(r.Context())
|
|
if session == nil {
|
|
respond.JSON(w, 200, response{})
|
|
return
|
|
}
|
|
|
|
user, err := c.users.FindByID(r.Context(), session.UserID)
|
|
if err != nil {
|
|
respond.JSON(w, 200, response{})
|
|
return
|
|
}
|
|
|
|
respond.JSON(w, 200, response{
|
|
LoggedIn: true,
|
|
User: &user,
|
|
})
|
|
}
|
|
|
|
// login (`POST /login`): Log in as user
|
|
func (c *UserController) login(w http.ResponseWriter, r *http.Request) {
|
|
loginData := struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}{}
|
|
|
|
err := json.NewDecoder(r.Body).Decode(&loginData)
|
|
if err != nil {
|
|
respond.Error(w, 400, "invalid_json", "Input is not valid JSON.")
|
|
return
|
|
}
|
|
|
|
user, err := c.users.FindByName(r.Context(), loginData.Username)
|
|
if err != nil {
|
|
respond.Error(w, http.StatusUnauthorized, "login_failed", "Login failed.")
|
|
return
|
|
}
|
|
|
|
if err := user.CheckPassword(loginData.Password); err != nil {
|
|
respond.Error(w, http.StatusUnauthorized, "login_failed", "Login failed.")
|
|
return
|
|
}
|
|
|
|
session := models.Session{
|
|
Expires: time.Now().Add(7 * 24 * time.Hour),
|
|
UserID: user.ID,
|
|
}
|
|
session.GenerateID()
|
|
|
|
if err := c.sessions.Insert(r.Context(), session); err != nil {
|
|
log.Printf("Session create for user %s (%d) failed: %s", user.Name, user.ID, err)
|
|
respond.Error(w, http.StatusInternalServerError, "internal_error", "Failed to open session.")
|
|
return
|
|
}
|
|
|
|
http.SetCookie(w, session.Cookie())
|
|
|
|
log.Printf("User %s logged in", user.Name)
|
|
|
|
respond.Data(w, user)
|
|
}
|
|
|
|
func (c *UserController) register(w http.ResponseWriter, r *http.Request) {
|
|
registerData := struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}{}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(®isterData); err != nil {
|
|
respond.Error(w, http.StatusBadRequest, "invalid_json", "Input is not valid JSON.")
|
|
return
|
|
}
|
|
|
|
if len(registerData.Username) < 1 {
|
|
respond.Error(w, http.StatusBadRequest, "invalid_username", "The username cannot be empty.")
|
|
return
|
|
}
|
|
if _, err := strconv.Atoi(registerData.Username); err == nil {
|
|
respond.Error(w, http.StatusBadRequest, "invalid_username", "The username cannot start with a number.")
|
|
return
|
|
}
|
|
|
|
user := models.User{Name: registerData.Username}
|
|
|
|
if err := user.SetPassword(registerData.Password); err != nil {
|
|
respond.Error(w, http.StatusBadRequest, "invalid_password", "The password is not valid: "+err.Error())
|
|
return
|
|
}
|
|
|
|
user, err := c.users.Insert(r.Context(), user)
|
|
if err != nil {
|
|
respond.Error(w, http.StatusBadRequest, "invalid_password", "The password is not valid: "+err.Error())
|
|
return
|
|
}
|
|
|
|
log.Printf("User %s registered", user.Name)
|
|
|
|
respond.Data(w, user)
|
|
}
|
|
|
|
// login (`POST /logout`): Log in as user
|
|
func (c *UserController) logout(w http.ResponseWriter, r *http.Request) {
|
|
logoutData := struct {
|
|
ClearAll bool `json:"clearAll"`
|
|
}{}
|
|
|
|
session := models.SessionFromContext(r.Context())
|
|
if session == nil {
|
|
respond.Error(w, http.StatusUnauthorized, "permission_denied", "You are not logged in (that's what you wanted anyway, wasn't it?)")
|
|
return
|
|
}
|
|
|
|
json.NewDecoder(r.Body).Decode(&logoutData)
|
|
|
|
user, err := c.users.FindByID(r.Context(), session.UserID)
|
|
if err != nil {
|
|
respond.Error(w, http.StatusNotFound, "not_found", "You user was not found")
|
|
return
|
|
}
|
|
|
|
if logoutData.ClearAll {
|
|
err := c.sessions.Clear(r.Context(), user)
|
|
if err != nil {
|
|
log.Printf("Session clear for user %s (%d) failed: %s", user.Name, user.ID, err)
|
|
respond.Error(w, http.StatusInternalServerError, "clear_failed", "Sesison clear failed")
|
|
return
|
|
}
|
|
} else {
|
|
err := c.sessions.Remove(r.Context(), *session)
|
|
if err != nil {
|
|
log.Printf("Session remove for user %s (%d) with id %s failed: %s", user.Name, user.ID, session.ID, err)
|
|
respond.Error(w, http.StatusInternalServerError, "remove_failed", "Session remove failed")
|
|
return
|
|
}
|
|
}
|
|
|
|
cookie := session.Cookie()
|
|
cookie.Expires = time.Unix(0, 0)
|
|
http.SetCookie(w, cookie)
|
|
|
|
log.Printf("User %s logged out (clearAll: %t)", user.Name, logoutData.ClearAll)
|
|
|
|
respond.Data(w, logoutData)
|
|
}
|
|
|
|
// Mount mounts the controller
|
|
func (c *UserController) Mount(router *mux.Router, prefix string) {
|
|
sub := router.PathPrefix(prefix).Subrouter()
|
|
|
|
sub.HandleFunc("/", c.getUsers).Methods("GET")
|
|
sub.HandleFunc("/session", c.session).Methods("GET")
|
|
sub.HandleFunc("/{id}", c.getUser).Methods("GET")
|
|
sub.HandleFunc("/login", c.login).Methods("POST")
|
|
sub.HandleFunc("/logout", c.logout).Methods("POST")
|
|
sub.HandleFunc("/register", c.register).Methods("POST")
|
|
}
|
|
|
|
// NewUserController creates a new UserController.
|
|
func NewUserController(users models.UserRepository, sessions models.SessionRepository) *UserController {
|
|
return &UserController{users: users, sessions: sessions}
|
|
}
|