user_handler.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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. // HandleReadUserClusters returns the externalized User.Clusters (models.ClusterConfigs)
  125. // based on a user ID
  126. func (app *App) HandleReadUserClusters(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. extClusters := make([]models.ClusterConfigExternal, 0)
  133. for _, cluster := range user.Clusters {
  134. extClusters = append(extClusters, *cluster.Externalize())
  135. }
  136. if err := json.NewEncoder(w).Encode(extClusters); err != nil {
  137. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  138. return
  139. }
  140. w.WriteHeader(http.StatusOK)
  141. }
  142. // HandleReadUserClustersAll returns all models.ClusterConfigs parsed from a KubeConfig
  143. // that is attached to a specific user, identified through the user ID
  144. func (app *App) HandleReadUserClustersAll(w http.ResponseWriter, r *http.Request) {
  145. user, err := app.readUser(w, r)
  146. // if there is an error, it's already handled by helper
  147. if err == nil {
  148. clusters, err := kubernetes.GetAllClusterConfigsFromBytes(user.RawKubeConfig)
  149. if err != nil {
  150. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  151. return
  152. }
  153. extClusters := make([]models.ClusterConfigExternal, 0)
  154. for _, cluster := range clusters {
  155. extClusters = append(extClusters, *cluster.Externalize())
  156. }
  157. if err := json.NewEncoder(w).Encode(extClusters); err != nil {
  158. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  159. return
  160. }
  161. w.WriteHeader(http.StatusOK)
  162. }
  163. }
  164. // HandleUpdateUser validates an update user form entry, updates the user
  165. // in the database, and writes status accepted
  166. func (app *App) HandleUpdateUser(w http.ResponseWriter, r *http.Request) {
  167. id, err := strconv.ParseUint(chi.URLParam(r, "id"), 0, 64)
  168. if err != nil || id == 0 {
  169. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  170. return
  171. }
  172. form := &forms.UpdateUserForm{
  173. ID: uint(id),
  174. }
  175. user, err := app.writeUser(form, app.repo.User.UpdateUser, w, r)
  176. if err == nil {
  177. app.logger.Info().Msgf("User updated: %d", user.ID)
  178. w.WriteHeader(http.StatusNoContent)
  179. }
  180. }
  181. // HandleDeleteUser removes a user after checking that the sent password is correct
  182. func (app *App) HandleDeleteUser(w http.ResponseWriter, r *http.Request) {
  183. id, err := strconv.ParseUint(chi.URLParam(r, "id"), 0, 64)
  184. if err != nil || id == 0 {
  185. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  186. return
  187. }
  188. // TODO -- HASH AND VERIFY PASSWORD BEFORE USER DELETION
  189. form := &forms.DeleteUserForm{
  190. ID: uint(id),
  191. }
  192. user, err := app.writeUser(form, app.repo.User.DeleteUser, w, r)
  193. if err == nil {
  194. app.logger.Info().Msgf("User deleted: %d", user.ID)
  195. w.WriteHeader(http.StatusNoContent)
  196. }
  197. }
  198. // ------------------------ User handler helper functions ------------------------ //
  199. // writeUser will take a POST or PUT request to the /api/users endpoint and decode
  200. // the request into a forms.WriteUserForm model, convert it to a models.User, and
  201. // write to the database.
  202. func (app *App) writeUser(
  203. form forms.WriteUserForm,
  204. dbWrite repository.WriteUser,
  205. w http.ResponseWriter,
  206. r *http.Request,
  207. validators ...func(repo *repository.Repository, user *models.User) *HTTPError,
  208. ) (*models.User, error) {
  209. // decode from JSON to form value
  210. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  211. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  212. return nil, err
  213. }
  214. // validate the form
  215. if err := app.validator.Struct(form); err != nil {
  216. app.handleErrorFormValidation(err, ErrUserValidateFields, w)
  217. return nil, err
  218. }
  219. // convert the form to a user model -- WriteUserForm must implement ToUser
  220. userModel, err := form.ToUser(app.repo.User)
  221. if err != nil {
  222. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  223. return nil, err
  224. }
  225. // Check any additional validators for any semantic errors
  226. // We have completed all syntax checks, so these will be sent
  227. // with http.StatusUnprocessableEntity (422), unless this is
  228. // an internal server error
  229. for _, validator := range validators {
  230. err := validator(app.repo, userModel)
  231. if err != nil {
  232. goErr := errors.New(strings.Join(err.Errors, ", "))
  233. if err.Code == 500 {
  234. app.sendExternalError(
  235. goErr,
  236. http.StatusInternalServerError,
  237. *err,
  238. w,
  239. )
  240. } else {
  241. app.sendExternalError(
  242. goErr,
  243. http.StatusUnprocessableEntity,
  244. *err,
  245. w,
  246. )
  247. }
  248. return nil, goErr
  249. }
  250. }
  251. // handle write to the database
  252. user, err := dbWrite(userModel)
  253. if err != nil {
  254. app.handleErrorDataWrite(err, w)
  255. return nil, err
  256. }
  257. return user, nil
  258. }
  259. func (app *App) readUser(w http.ResponseWriter, r *http.Request) (*models.User, error) {
  260. id, err := strconv.ParseUint(chi.URLParam(r, "id"), 0, 64)
  261. if err != nil || id == 0 {
  262. app.handleErrorFormDecoding(err, ErrUserDecode, w)
  263. return nil, err
  264. }
  265. user, err := app.repo.User.ReadUser(uint(id))
  266. if err != nil {
  267. app.handleErrorRead(err, ErrUserDataRead, w)
  268. return nil, err
  269. }
  270. return user, nil
  271. }
  272. func doesUserExist(repo *repository.Repository, user *models.User) *HTTPError {
  273. user, err := repo.User.ReadUserByEmail(user.Email)
  274. if user != nil && err == nil {
  275. return &HTTPError{
  276. Code: ErrUserValidateFields,
  277. Errors: []string{
  278. "email already taken",
  279. },
  280. }
  281. }
  282. if err != gorm.ErrRecordNotFound {
  283. return &ErrorDataRead
  284. }
  285. return nil
  286. }