user_handler.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. package api
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "strconv"
  8. "strings"
  9. "github.com/porter-dev/porter/internal/kubernetes"
  10. "golang.org/x/crypto/bcrypt"
  11. "gorm.io/gorm"
  12. "github.com/go-chi/chi"
  13. "github.com/porter-dev/porter/internal/forms"
  14. "github.com/porter-dev/porter/internal/models"
  15. "github.com/porter-dev/porter/internal/repository"
  16. )
  17. // Enumeration of user API error codes, represented as int64
  18. const (
  19. ErrUserDecode ErrorCode = iota + 600
  20. ErrUserValidateFields
  21. ErrUserDataRead
  22. )
  23. // HandleCreateUser validates a user form entry, converts the user to a gorm
  24. // model, and saves the user to the database
  25. func (app *App) HandleCreateUser(w http.ResponseWriter, r *http.Request) {
  26. session, err := app.store.Get(r, app.cookieName)
  27. if err != nil {
  28. app.handleErrorDataRead(err, w)
  29. }
  30. form := &forms.CreateUserForm{}
  31. user, err := app.writeUser(
  32. form,
  33. app.repo.User.CreateUser,
  34. w,
  35. r,
  36. doesUserExist,
  37. )
  38. if err == nil {
  39. app.logger.Info().Msgf("New user created: %d", user.ID)
  40. session.Values["authenticated"] = true
  41. session.Values["user_id"] = user.ID
  42. session.Save(r, w)
  43. w.WriteHeader(http.StatusCreated)
  44. }
  45. }
  46. // HandleAuthCheck checks whether current session is authenticated.
  47. func (app *App) HandleAuthCheck(w http.ResponseWriter, r *http.Request) {
  48. session, err := app.store.Get(r, app.cookieName)
  49. cook, _ := r.Cookie("porter")
  50. fmt.Println("cooki", cook)
  51. if err != nil {
  52. http.Error(w, err.Error(), http.StatusInternalServerError)
  53. }
  54. if auth, ok := session.Values["authenticated"].(bool); !auth || !ok {
  55. app.logger.Info().Msgf(strconv.FormatBool(auth))
  56. w.WriteHeader(http.StatusOK)
  57. w.Write([]byte("false"))
  58. return
  59. }
  60. w.WriteHeader(http.StatusOK)
  61. w.Write([]byte("true"))
  62. }
  63. // HandleLoginUser checks the request header for cookie and validates the user.
  64. func (app *App) HandleLoginUser(w http.ResponseWriter, r *http.Request) {
  65. session, err := app.store.Get(r, app.cookieName)
  66. if err != nil {
  67. app.handleErrorDataRead(err, w)
  68. }
  69. form := &forms.LoginUserForm{}
  70. // decode from JSON to form value
  71. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  72. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  73. return
  74. }
  75. storedUser, readErr := app.repo.User.ReadUserByEmail(form.Email)
  76. if readErr != nil {
  77. app.sendExternalError(readErr, http.StatusUnauthorized, HTTPError{
  78. Errors: []string{"email not registered"},
  79. Code: http.StatusUnauthorized,
  80. }, w)
  81. return
  82. }
  83. if err := bcrypt.CompareHashAndPassword([]byte(storedUser.Password), []byte(form.Password)); err != nil {
  84. app.sendExternalError(readErr, http.StatusUnauthorized, HTTPError{
  85. Errors: []string{"incorrect password"},
  86. Code: http.StatusUnauthorized,
  87. }, w)
  88. return
  89. }
  90. // Set user as authenticated
  91. session.Values["authenticated"] = true
  92. session.Values["user_id"] = storedUser.ID
  93. if err := session.Save(r, w); err != nil {
  94. app.logger.Warn().Err(err)
  95. }
  96. w.WriteHeader(http.StatusOK)
  97. }
  98. // HandleLogoutUser detaches the user from the session
  99. func (app *App) HandleLogoutUser(w http.ResponseWriter, r *http.Request) {
  100. session, err := app.store.Get(r, app.cookieName)
  101. if err != nil {
  102. app.handleErrorDataRead(err, w)
  103. }
  104. session.Values["authenticated"] = false
  105. session.Values["user_id"] = nil
  106. session.Save(r, w)
  107. w.WriteHeader(http.StatusOK)
  108. }
  109. // HandleReadUser returns an externalized User (models.UserExternal)
  110. // based on an ID
  111. func (app *App) HandleReadUser(w http.ResponseWriter, r *http.Request) {
  112. user, err := app.readUser(w, r)
  113. // error already handled by helper
  114. if err != nil {
  115. return
  116. }
  117. extUser := user.Externalize()
  118. if err := json.NewEncoder(w).Encode(extUser); err != nil {
  119. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  120. return
  121. }
  122. w.WriteHeader(http.StatusOK)
  123. }
  124. // HandleReadUserContexts returns the externalized User.Contexts ([]models.Context)
  125. // based on a user ID
  126. func (app *App) HandleReadUserContexts(w http.ResponseWriter, r *http.Request) {
  127. user, err := app.readUser(w, r)
  128. // error already handled by helper
  129. if err != nil {
  130. return
  131. }
  132. contexts, err := kubernetes.GetContextsFromBytes(user.RawKubeConfig, user.ContextToSlice())
  133. if err != nil {
  134. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  135. return
  136. }
  137. if err := json.NewEncoder(w).Encode(contexts); err != nil {
  138. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  139. return
  140. }
  141. w.WriteHeader(http.StatusOK)
  142. }
  143. // HandleUpdateUser validates an update user form entry, updates the user
  144. // in the database, and writes status accepted
  145. func (app *App) HandleUpdateUser(w http.ResponseWriter, r *http.Request) {
  146. id, err := strconv.ParseUint(chi.URLParam(r, "id"), 0, 64)
  147. if err != nil || id == 0 {
  148. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  149. return
  150. }
  151. form := &forms.UpdateUserForm{
  152. ID: uint(id),
  153. }
  154. user, err := app.writeUser(form, app.repo.User.UpdateUser, w, r)
  155. if err == nil {
  156. app.logger.Info().Msgf("User updated: %d", user.ID)
  157. w.WriteHeader(http.StatusNoContent)
  158. }
  159. }
  160. // HandleDeleteUser removes a user after checking that the sent password is correct
  161. func (app *App) HandleDeleteUser(w http.ResponseWriter, r *http.Request) {
  162. id, err := strconv.ParseUint(chi.URLParam(r, "id"), 0, 64)
  163. if err != nil || id == 0 {
  164. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  165. return
  166. }
  167. // TODO -- HASH AND VERIFY PASSWORD BEFORE USER DELETION
  168. form := &forms.DeleteUserForm{
  169. ID: uint(id),
  170. }
  171. user, err := app.writeUser(form, app.repo.User.DeleteUser, w, r)
  172. if err == nil {
  173. app.logger.Info().Msgf("User deleted: %d", user.ID)
  174. w.WriteHeader(http.StatusNoContent)
  175. }
  176. }
  177. // ------------------------ User handler helper functions ------------------------ //
  178. // writeUser will take a POST or PUT request to the /api/users endpoint and decode
  179. // the request into a forms.WriteUserForm model, convert it to a models.User, and
  180. // write to the database.
  181. func (app *App) writeUser(
  182. form forms.WriteUserForm,
  183. dbWrite repository.WriteUser,
  184. w http.ResponseWriter,
  185. r *http.Request,
  186. validators ...func(repo *repository.Repository, user *models.User) *HTTPError,
  187. ) (*models.User, error) {
  188. // decode from JSON to form value
  189. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  190. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  191. return nil, err
  192. }
  193. // validate the form
  194. if err := app.validator.Struct(form); err != nil {
  195. app.handleErrorFormValidation(err, ErrUserValidateFields, w)
  196. return nil, err
  197. }
  198. // convert the form to a user model -- WriteUserForm must implement ToUser
  199. userModel, err := form.ToUser(app.repo.User)
  200. if err != nil {
  201. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  202. return nil, err
  203. }
  204. // Check any additional validators for any semantic errors
  205. // We have completed all syntax checks, so these will be sent
  206. // with http.StatusUnprocessableEntity (422), unless this is
  207. // an internal server error
  208. for _, validator := range validators {
  209. err := validator(app.repo, userModel)
  210. if err != nil {
  211. goErr := errors.New(strings.Join(err.Errors, ", "))
  212. if err.Code == 500 {
  213. app.sendExternalError(
  214. goErr,
  215. http.StatusInternalServerError,
  216. *err,
  217. w,
  218. )
  219. } else {
  220. app.sendExternalError(
  221. goErr,
  222. http.StatusUnprocessableEntity,
  223. *err,
  224. w,
  225. )
  226. }
  227. return nil, goErr
  228. }
  229. }
  230. // handle write to the database
  231. user, err := dbWrite(userModel)
  232. if err != nil {
  233. app.handleErrorDataWrite(err, w)
  234. return nil, err
  235. }
  236. return user, nil
  237. }
  238. func (app *App) readUser(w http.ResponseWriter, r *http.Request) (*models.User, error) {
  239. id, err := strconv.ParseUint(chi.URLParam(r, "id"), 0, 64)
  240. if err != nil || id == 0 {
  241. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  242. return nil, err
  243. }
  244. user, err := app.repo.User.ReadUser(uint(id))
  245. if err != nil {
  246. app.handleErrorRead(err, ErrUserDataRead, w)
  247. return nil, err
  248. }
  249. return user, nil
  250. }
  251. func doesUserExist(repo *repository.Repository, user *models.User) *HTTPError {
  252. user, err := repo.User.ReadUserByEmail(user.Email)
  253. if user != nil && err == nil {
  254. return &HTTPError{
  255. Code: ErrUserValidateFields,
  256. Errors: []string{
  257. "email already taken",
  258. },
  259. }
  260. }
  261. if err != gorm.ErrRecordNotFound {
  262. return &ErrorDataRead
  263. }
  264. return nil
  265. }