From 7493fd4885bc2ddfb804a7089554478fc9e59cdb Mon Sep 17 00:00:00 2001 From: Denis Istratov Date: Thu, 24 Oct 2024 12:00:00 +0300 Subject: [PATCH] update api.go, auth.go, middleware.go --- pkg/handler/api.go | 534 ++++++++++++++++++++++++++++++++++++++ pkg/handler/middleware.go | 36 +++ pkg/service/auth.go | 140 ++++++++++ 3 files changed, 710 insertions(+) create mode 100644 pkg/handler/api.go create mode 100644 pkg/handler/middleware.go create mode 100644 pkg/service/auth.go diff --git a/pkg/handler/api.go b/pkg/handler/api.go new file mode 100644 index 0000000..1dcdf72 --- /dev/null +++ b/pkg/handler/api.go @@ -0,0 +1,534 @@ +package handler + +import ( + "esaccount" + "esaccount/pkg/util" + "fmt" + "io" + "net/http" + "os" + "slices" + "strconv" + + "github.com/gin-gonic/gin" +) + +// confidential godoc +// @Summary Get all confidential settings +// @Tags confidential +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 +// @Router /api/confidential/ [get] +func (h *Handler) getConfidential(c *gin.Context) { + id, _ := c.Get(userCtx) + account, err := h.repo.GetAccount(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + c.JSON(http.StatusOK, account.Confidentials) +} + +// confidential godoc +// @Summary Set confidential settings +// @Tags confidential +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Param confidencial body string true "Confidencial settings" +// @Success 200 {object} esaccount.AuthorizedUser +// @Router /api/confidential/ [post] +func (h *Handler) setConfidential(c *gin.Context) { + id, _ := c.Get(userCtx) + account, err := h.repo.GetAccount(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + + var input esaccount.ConfidentialSettings + if err := c.BindJSON(&input); err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + for k, v := range input { + account.Confidentials[k] = v + } + account.Confidentials["picture_url"] = esaccount.All + + h.repo.SaveAccount(account) + + c.JSON(http.StatusOK, account) +} + +// public godoc +// @Summary Get all public data of users +// @Tags confidential +// @Produce json +// @Param Authorization header string false "JWT Bearer token (authorization)" +// @Param id query int true "user id" +// @Success 200 +// @Router /api/public/{id} [get] +func (h *Handler) getPublic(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + account, err := h.repo.GetAccount(id) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + header := c.GetHeader(authorizationHeader) + allowedFields := []string{} + isFriend := false + if header != "" { + authorized, err := h.services.Authorization.Authorize(header) + if err == nil { + isFriend = (slices.Contains(account.Followers, authorized.Id) && slices.Contains(account.Follows, authorized.Id)) + } + } + + for k, v := range account.Confidentials { + if (v == esaccount.All) || (isFriend && (v == esaccount.Friends)) { + allowedFields = append(allowedFields, k) + } + } + + res := map[string]any{} + user, _ := h.services.AuthorizeById(id) + + // auth user + if slices.Contains(allowedFields, "username") { + res["username"] = user.Username + } + if slices.Contains(allowedFields, "first_name") { + res["first_name"] = user.FirstName + } + if slices.Contains(allowedFields, "last_name") { + res["last_name"] = user.LastName + } + res["picture_url"] = user.LastName + if slices.Contains(allowedFields, "email") { + res["email"] = user.LastName + } + + // account + if slices.Contains(allowedFields, "description") { + res["description"] = account.Description + } + if slices.Contains(allowedFields, "date_of_birth") { + res["date_of_birth"] = account.DateOfBirth + } + if slices.Contains(allowedFields, "follows") { + res["follows"] = account.Follows + } + if slices.Contains(allowedFields, "followers") { + res["followers"] = account.Follows + } + + c.JSON(http.StatusOK, res) +} + +type accountResponce struct { + Description string `json:"description"` + DateOfBirth string `json:"date_of_birth"` + Follows []int64 `json:"follows"` + Followers []int64 `json:"followers"` +} + +// account godoc +// @Summary Get account information +// @Tags account +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 {object} handler.accountResponce +// @Router /api/account [get] +func (h *Handler) getAccount(c *gin.Context) { + id, _ := c.Get(userCtx) + account, err := h.repo.GetAccount(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + c.JSON(http.StatusOK, accountResponce{ + Description: account.Description, + DateOfBirth: account.DateOfBirth, + Follows: account.Follows, + Followers: account.Followers, + }) +} + +// picture godoc +// @Summary Get account information +// @Tags account +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Param file formData file true "Picture" +// @Success 200 +// @Router /api/account/picture [post] +func (h *Handler) picture(c *gin.Context) { + id, _ := c.Get(userCtx) + _, err := h.repo.GetAccount(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + + file, _, err := c.Request.FormFile("file") + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + defer file.Close() + + os.Remove(fmt.Sprintf("./public/usercontent/picture/%d.png", id)) + picFile, err := os.Create(fmt.Sprintf("./public/usercontent/picture/%d.png", id)) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + defer file.Close() + + if _, err := io.Copy(picFile, file); err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + header, _ := c.Get(userHeader) + picUrl := fmt.Sprintf("https://account.easystartup.su/static/picture/%d.png", id) + _, err = h.services.AuthorizeAndUpdatePicture(header.(string), picUrl) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + c.JSON(http.StatusOK, fmt.Sprintf("{ \"url\": %s \"\" }", picUrl)) +} + +type accountInput struct { + Description string `json:"description"` + DateOfBirth string `json:"date_of_birth"` +} + +// account godoc +// @Summary Set account information +// @Tags account +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Param account body handler.accountInput true "account info" +// @Success 200 {object} handler.accountInput +// @Router /api/account [post] +func (h *Handler) editAccount(c *gin.Context) { + id, _ := c.Get(userCtx) + account, err := h.repo.GetAccount(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + + var input accountInput + if err := c.BindJSON(&input); err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + account.Description = input.Description + account.DateOfBirth = input.DateOfBirth + h.repo.SaveAccount(account) + + c.JSON(http.StatusOK, input) +} + +// account godoc +// @Summary Get account information by id +// @Tags account +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Param id path int true "account id" +// @Success 200 {object} handler.accountResponce +// @Router /api/account/{id} [get] +func (h *Handler) getAccountById(c *gin.Context) { + current, _ := c.Get(userCtx) + currentUser, err := h.services.AuthorizeById(current.(int64)) + if (err != nil) || (currentUser.Access != "admin") { + util.NewErrorResponse(c, http.StatusForbidden, err.Error()) + return + } + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + + account, err := h.repo.GetAccount(id) + if err != nil { + authorized, err := h.services.AuthorizeById(id) + if (err == nil) && (authorized.Id == id) { + account, err = h.repo.CreateAccount(id) + } + + if err != nil { + util.NewErrorResponse(c, http.StatusInternalServerError, err.Error()) + return + } + } + c.JSON(http.StatusOK, accountResponce{ + Description: account.Description, + DateOfBirth: account.DateOfBirth, + Follows: account.Follows, + Followers: account.Followers, + }) +} + +// account godoc +// @Summary Set account information by id +// @Tags account +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Param account body handler.accountResponce true "account info" +// @Param id path int true "account id" +// @Success 200 {object} handler.accountResponce +// @Router /api/account/{id} [post] +func (h *Handler) editAccountById(c *gin.Context) { + current, _ := c.Get(userCtx) + currentUser, err := h.services.AuthorizeById(current.(int64)) + if (err != nil) || (currentUser.Access != "admin") { + util.NewErrorResponse(c, http.StatusForbidden, err.Error()) + return + } + + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + account, err := h.repo.GetAccount(id) + if err != nil { + authorized, err := h.services.AuthorizeById(id) + if (err == nil) && (authorized.Id == id) { + account, err = h.repo.CreateAccount(id) + } + if err != nil { + util.NewErrorResponse(c, http.StatusInternalServerError, err.Error()) + return + } + } + + var input accountResponce + if err := c.BindJSON(&input); err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + account.Description = input.Description + account.DateOfBirth = input.DateOfBirth + account.Followers = input.Followers + account.Follows = input.Follows + h.repo.SaveAccount(account) + + c.JSON(http.StatusOK, input) +} + +// account godoc +// @Summary Delete account by id +// @Tags account +// @Produce json +// @Param id path int true "id of user" +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 +// @Router /api/account/{id} [delete] +func (h *Handler) delete(c *gin.Context) { + current, _ := c.Get(userCtx) + currentUser, err := h.services.AuthorizeById(current.(int64)) + if (err != nil) || (currentUser.Access != "admin") { + util.NewErrorResponse(c, http.StatusForbidden, err.Error()) + return + } + + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + _, err = h.repo.GetAccount(id) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + err = h.repo.DeleteAccount(id) + if err != nil { + util.NewErrorResponse(c, http.StatusInternalServerError, err.Error()) + return + } + + c.JSON(http.StatusOK, "{ \"status\": \"ok\" }") +} + +// confidential godoc +// @Summary Get all confidential settings by id +// @Tags confidential +// @Produce json +// @Param id path int true "id of user" +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 +// @Router /api/confidential/{id} [get] +func (h *Handler) getConfidentialById(c *gin.Context) { + current, _ := c.Get(userCtx) + currentUser, err := h.services.AuthorizeById(current.(int64)) + if (err != nil) || (currentUser.Access != "admin") { + util.NewErrorResponse(c, http.StatusForbidden, err.Error()) + return + } + + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + account, err := h.repo.GetAccount(id) + if err != nil { + authorized, err := h.services.AuthorizeById(id) + if (err == nil) && (authorized.Id == id) { + account, err = h.repo.CreateAccount(id) + } + if err != nil { + util.NewErrorResponse(c, http.StatusInternalServerError, err.Error()) + return + } + } + c.JSON(http.StatusOK, account.Confidentials) +} + +// confidential godoc +// @Summary Set confidential settings by id +// @Tags confidential +// @Produce json +// @Param id path int true "id of user" +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Param confidencial body string true "Confidencial settings" +// @Success 200 {object} esaccount.AuthorizedUser +// @Router /api/confidential/{id} [post] +func (h *Handler) setConfidentialById(c *gin.Context) { + current, _ := c.Get(userCtx) + currentUser, err := h.services.AuthorizeById(current.(int64)) + if (err != nil) || (currentUser.Access != "admin") { + util.NewErrorResponse(c, http.StatusForbidden, err.Error()) + return + } + + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + account, err := h.repo.GetAccount(id) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + + var input esaccount.ConfidentialSettings + if err := c.BindJSON(&input); err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + for k, v := range input { + account.Confidentials[k] = v + } + account.Confidentials["picture_url"] = esaccount.All + + h.repo.SaveAccount(account) + + c.JSON(http.StatusOK, account) +} + +// following godoc +// @Summary Follow to user +// @Tags following +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 +// @Router /api/following/follow/{id} [put] +func (h *Handler) follow(c *gin.Context) { + id, _ := c.Get(userCtx) + account, err := h.repo.GetAccount(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + + id, err = strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + followed, err := h.repo.GetAccount(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + + if !slices.Contains(followed.Followers, account.Id) { + followed.Followers = append(followed.Followers, account.Id) + h.repo.SaveAccount(followed) + } + + if !slices.Contains(account.Follows, followed.Id) { + account.Follows = append(followed.Follows, followed.Id) + h.repo.SaveAccount(account) + } + + c.JSON(http.StatusOK, "{ \"status\": \"ok\" }") +} + +// following godoc +// @Summary Unfollow user +// @Tags following +// @Produce json +// @Param Authorization header string true "JWT Bearer token (authorization)" +// @Success 200 +// @Router /api/following/unfollow/{id} [put] +func (h *Handler) unfollow(c *gin.Context) { + id, _ := c.Get(userCtx) + account, err := h.repo.GetAccount(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + + id, err = strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + util.NewErrorResponse(c, http.StatusBadRequest, err.Error()) + return + } + + followed, err := h.repo.GetAccount(id.(int64)) + if err != nil { + util.NewErrorResponse(c, http.StatusNotFound, err.Error()) + return + } + + followed.Followers = removeElement(followed.Followers, account.Id) + account.Follows = removeElement(followed.Follows, followed.Id) + + h.repo.SaveAccount(followed) + h.repo.SaveAccount(account) + + c.JSON(http.StatusOK, "{ \"status\": \"ok\" }") +} + +func removeElement(slice []int64, value int64) []int64 { + // Find the index of the value + for i, v := range slice { + if v == value { + // Remove the element by slicing around it + return append(slice[:i], slice[i+1:]...) + } + } + // If the value is not found, return the original slice + return slice +} diff --git a/pkg/handler/middleware.go b/pkg/handler/middleware.go new file mode 100644 index 0000000..9cf5412 --- /dev/null +++ b/pkg/handler/middleware.go @@ -0,0 +1,36 @@ +package handler + +import ( + "esaccount/pkg/util" + "net/http" + + "github.com/gin-gonic/gin" +) + +const ( + authorizationHeader = "Authorization" + userCtx = "UserInfo" + userHeader = "UserHeader" +) + +func (h *Handler) userIdentity(c *gin.Context) { + header := c.GetHeader(authorizationHeader) + if header == "" { + util.NewErrorResponse(c, http.StatusUnauthorized, "empty auth header") + return + } + + user, err := h.services.Authorization.Authorize(header) + if err != nil { + util.NewErrorResponse(c, http.StatusUnauthorized, err.Error()) + } + + _, err = h.repo.GetAccount(user.Id) + if err != nil { + h.repo.CreateAccount(user.Id) + } + + c.Set(userCtx, user.Id) + c.Set(userHeader, header) +} + diff --git a/pkg/service/auth.go b/pkg/service/auth.go new file mode 100644 index 0000000..c3c5f9d --- /dev/null +++ b/pkg/service/auth.go @@ -0,0 +1,140 @@ +package service + +import ( + "bytes" + "encoding/json" + "esaccount" + "fmt" + "io" + "net/http" + "os" +) + +type AuthService struct { +} + +func NewAuthService() *AuthService { + return &AuthService{} +} + +func (s *AuthService) Authorize(header string) (*esaccount.AuthorizedUser, error) { + client := &http.Client{} + req, err := http.NewRequest("GET", "https://auth.easystartup.su/api/user/self", nil) + if err != nil { + return nil, err + } + + req.Header.Add("Authorization", header) + req.Header.Add("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // Check if the response status is 200 OK + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to authozire: %d %s\nheader: %s", resp.StatusCode, resp.Status, header) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var result esaccount.AuthorizedUser + err = json.Unmarshal(body, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func (s *AuthService) AuthorizeById(id int64) (*esaccount.AuthorizedUser, error) { + client := &http.Client{} + req, err := http.NewRequest("GET", fmt.Sprintf("https://auth.easystartup.su/api/user/%d", id), nil) + if err != nil { + return nil, err + } + + req.Header.Add("Authorization", "Bearer "+os.Getenv("ADMIN_TOKEN")) + req.Header.Add("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // Check if the response status is 200 OK + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to authozire: %d %s", resp.StatusCode, resp.Status) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var result esaccount.AuthorizedUser + err = json.Unmarshal(body, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + + +func (s *AuthService) AuthorizeAndUpdatePicture(header string, picUrl string) (*esaccount.AuthorizedUser, error) { + client := &http.Client{} + req, err := http.NewRequest("GET", "https://auth.easystartup.su/api/user/self", nil) + if err != nil { + return nil, err + } + + req.Header.Add("Authorization", header) + req.Header.Add("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // Check if the response status is 200 OK + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to authozire: %d %s\nheader: %s", resp.StatusCode, resp.Status, header) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var result esaccount.AuthorizedUser + err = json.Unmarshal(body, &result) + if err != nil { + return nil, err + } + + result.PictureURL = picUrl + requestBody, _ := json.Marshal(result) + + req, err = http.NewRequest("POST", "https://auth.easystartup.su/api/user/self", bytes.NewBuffer(requestBody)) + if err != nil { + return nil, err + } + + req.Header.Add("Authorization", header) + req.Header.Add("Accept", "application/json") + + resp, err = client.Do(req) + if err != nil { + return nil, err + } + + return &result, nil +} \ No newline at end of file