The main server, and probably only repository in this org.
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

  1. package controllers
  2. import (
  3. "encoding/json"
  4. "log"
  5. "net/http"
  6. "strconv"
  7. "time"
  8. "git.aiterp.net/lucifer/lucifer/internal/respond"
  9. "git.aiterp.net/lucifer/lucifer/models"
  10. "github.com/gorilla/mux"
  11. )
  12. // The UserController is a controller for all users.
  13. type UserController struct {
  14. users models.UserRepository
  15. sessions models.SessionRepository
  16. }
  17. // getUsers (`GET /`): List users
  18. func (c *UserController) getUsers(w http.ResponseWriter, r *http.Request) {
  19. if session := models.SessionFromContext(r.Context()); session == nil {
  20. respond.Error(w, http.StatusForbidden, "permission_denied", "You must log in")
  21. return
  22. }
  23. // TODO: Only admin should be allowed to do this?
  24. users, err := c.users.List(r.Context())
  25. if err != nil {
  26. respond.Error(w, 500, "db_error", err.Error())
  27. return
  28. }
  29. respond.Data(w, users)
  30. }
  31. // getUser (`GET /:id`): Get user by id
  32. func (c *UserController) getUser(w http.ResponseWriter, r *http.Request) {
  33. session := models.SessionFromContext(r.Context())
  34. if session == nil {
  35. respond.Error(w, http.StatusForbidden, "permission_denied", "You must log in")
  36. return
  37. }
  38. // TODO: Only admin should be allowed to do this?
  39. var user models.User
  40. id, err := strconv.Atoi(mux.Vars(r)["id"])
  41. if err == nil {
  42. user, err = c.users.FindByID(r.Context(), id)
  43. if err != nil {
  44. respond.Error(w, 404, "not_found", err.Error())
  45. return
  46. }
  47. } else {
  48. user, err = c.users.FindByName(r.Context(), mux.Vars(r)["id"])
  49. if err != nil {
  50. respond.Error(w, 404, "not_found", err.Error())
  51. return
  52. }
  53. }
  54. respond.Data(w, user)
  55. }
  56. func (c *UserController) session(w http.ResponseWriter, r *http.Request) {
  57. type response struct {
  58. LoggedIn bool `json:"loggedIn"`
  59. User *models.User `json:"user"`
  60. }
  61. session := models.SessionFromContext(r.Context())
  62. if session == nil {
  63. respond.Data(w, response{})
  64. return
  65. }
  66. user, err := c.users.FindByID(r.Context(), session.UserID)
  67. if err != nil {
  68. respond.Data(w, response{})
  69. return
  70. }
  71. respond.Data(w, response{
  72. LoggedIn: true,
  73. User: &user,
  74. })
  75. }
  76. // login (`POST /login`): Log in as user
  77. func (c *UserController) login(w http.ResponseWriter, r *http.Request) {
  78. loginData := struct {
  79. Username string `json:"username"`
  80. Password string `json:"password"`
  81. }{}
  82. err := json.NewDecoder(r.Body).Decode(&loginData)
  83. if err != nil {
  84. respond.Error(w, 400, "invalid_json", "Input is not valid JSON.")
  85. return
  86. }
  87. user, err := c.users.FindByName(r.Context(), loginData.Username)
  88. if err != nil {
  89. respond.Error(w, http.StatusUnauthorized, "login_failed", "Login failed.")
  90. return
  91. }
  92. if err := user.CheckPassword(loginData.Password); err != nil {
  93. respond.Error(w, http.StatusUnauthorized, "login_failed", "Login failed.")
  94. return
  95. }
  96. session := models.Session{
  97. Expires: time.Now().Add(7 * 24 * time.Hour),
  98. UserID: user.ID,
  99. }
  100. session.GenerateID()
  101. if err := c.sessions.Insert(r.Context(), session); err != nil {
  102. log.Printf("Session create for user %s (%d) failed: %s", user.Name, user.ID, err)
  103. respond.Error(w, http.StatusInternalServerError, "internal_error", "Failed to open session.")
  104. return
  105. }
  106. http.SetCookie(w, session.Cookie())
  107. log.Printf("User %s logged in", user.Name)
  108. respond.Data(w, user)
  109. }
  110. func (c *UserController) register(w http.ResponseWriter, r *http.Request) {
  111. registerData := struct {
  112. Username string `json:"username"`
  113. Password string `json:"password"`
  114. }{}
  115. if err := json.NewDecoder(r.Body).Decode(&registerData); err != nil {
  116. respond.Error(w, http.StatusBadRequest, "invalid_json", "Input is not valid JSON.")
  117. return
  118. }
  119. if len(registerData.Username) < 1 {
  120. respond.Error(w, http.StatusBadRequest, "invalid_username", "The username cannot be empty.")
  121. return
  122. }
  123. if _, err := strconv.Atoi(registerData.Username); err == nil {
  124. respond.Error(w, http.StatusBadRequest, "invalid_username", "The username cannot start with a number.")
  125. return
  126. }
  127. user := models.User{Name: registerData.Username}
  128. if err := user.SetPassword(registerData.Password); err != nil {
  129. respond.Error(w, http.StatusBadRequest, "invalid_password", "The password is not valid: "+err.Error())
  130. return
  131. }
  132. user, err := c.users.Insert(r.Context(), user)
  133. if err != nil {
  134. respond.Error(w, http.StatusBadRequest, "invalid_password", "The password is not valid: "+err.Error())
  135. return
  136. }
  137. log.Printf("User %s registered", user.Name)
  138. respond.Data(w, user)
  139. }
  140. // login (`POST /logout`): Log in as user
  141. func (c *UserController) logout(w http.ResponseWriter, r *http.Request) {
  142. logoutData := struct {
  143. ClearAll bool `json:"clearAll"`
  144. }{}
  145. session := models.SessionFromContext(r.Context())
  146. if session == nil {
  147. respond.Error(w, http.StatusUnauthorized, "permission_denied", "You are not logged in (that's what you wanted anyway, wasn't it?)")
  148. return
  149. }
  150. json.NewDecoder(r.Body).Decode(&logoutData)
  151. user, err := c.users.FindByID(r.Context(), session.UserID)
  152. if err != nil {
  153. respond.Error(w, http.StatusNotFound, "not_found", "You user was not found")
  154. return
  155. }
  156. if logoutData.ClearAll {
  157. err := c.sessions.Clear(r.Context(), user)
  158. if err != nil {
  159. log.Printf("Session clear for user %s (%d) failed: %s", user.Name, user.ID, err)
  160. respond.Error(w, http.StatusInternalServerError, "clear_failed", "Sesison clear failed")
  161. return
  162. }
  163. } else {
  164. err := c.sessions.Remove(r.Context(), *session)
  165. if err != nil {
  166. log.Printf("Session remove for user %s (%d) with id %s failed: %s", user.Name, user.ID, session.ID, err)
  167. respond.Error(w, http.StatusInternalServerError, "remove_failed", "Session remove failed")
  168. return
  169. }
  170. }
  171. cookie := session.Cookie()
  172. cookie.Expires = time.Unix(0, 0)
  173. http.SetCookie(w, cookie)
  174. log.Printf("User %s logged out (clearAll: %t)", user.Name, logoutData.ClearAll)
  175. respond.Data(w, logoutData)
  176. }
  177. // Mount mounts the controller
  178. func (c *UserController) Mount(router *mux.Router, prefix string) {
  179. sub := router.PathPrefix(prefix).Subrouter()
  180. sub.HandleFunc("/", c.getUsers).Methods("GET")
  181. sub.HandleFunc("/session", c.session).Methods("GET")
  182. sub.HandleFunc("/{id}", c.getUser).Methods("GET")
  183. sub.HandleFunc("/login", c.login).Methods("POST")
  184. sub.HandleFunc("/logout", c.logout).Methods("POST")
  185. sub.HandleFunc("/register", c.register).Methods("POST")
  186. }
  187. // NewUserController creates a new UserController.
  188. func NewUserController(users models.UserRepository, sessions models.SessionRepository) *UserController {
  189. return &UserController{users: users, sessions: sessions}
  190. }