From 5ff6a8ddae398da8230785131479f0e41dcfb793 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Tue, 7 May 2024 22:01:32 -0700 Subject: [PATCH] updated error handling and refined how the jwt gets used and validated --- internal/handler/v1/articles.go | 35 +++-- internal/handler/v1/auth.go | 29 ++-- internal/handler/v1/discordwebhooks.go | 175 ++++++++++++++----------- internal/handler/v1/handler.go | 36 ++--- internal/handler/v1/jwt.go | 13 +- internal/handler/v1/sources.go | 132 ++++++++++++------- 6 files changed, 257 insertions(+), 163 deletions(-) diff --git a/internal/handler/v1/articles.go b/internal/handler/v1/articles.go index 870180a..b77f4d9 100644 --- a/internal/handler/v1/articles.go +++ b/internal/handler/v1/articles.go @@ -20,7 +20,10 @@ import ( // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) listArticles(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeArticleRead) + _, err := s.ValidateJwtToken(c, domain.ScopeArticleRead) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } resp := domain.ArticleResponse{ BaseResponse: domain.BaseResponse{ @@ -35,7 +38,7 @@ func (s *Handler) listArticles(c echo.Context) error { res, err := s.repo.Articles.ListByPage(c.Request().Context(), page, 25) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } resp.Payload = services.ArticlesToDto(res) @@ -53,7 +56,11 @@ func (s *Handler) listArticles(c echo.Context) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) getArticle(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeArticleRead) + _, err := s.ValidateJwtToken(c, domain.ScopeArticleRead) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + p := domain.ArticleResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -63,7 +70,7 @@ func (s *Handler) getArticle(c echo.Context) error { id := c.Param("ID") idNumber, err := strconv.Atoi(id) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } item, err := s.repo.Articles.GetById(c.Request().Context(), int64(idNumber)) @@ -89,7 +96,11 @@ func (s *Handler) getArticle(c echo.Context) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) getArticleDetails(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeArticleRead) + _, err := s.ValidateJwtToken(c, domain.ScopeArticleRead) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + p := domain.ArticleDetailedResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -99,17 +110,17 @@ func (s *Handler) getArticleDetails(c echo.Context) error { id, err := strconv.Atoi(c.Param("ID")) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } article, err := s.repo.Articles.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } source, err := s.repo.Sources.GetById(c.Request().Context(), article.SourceID) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } p.Payload.Article = services.ArticleToDto(article) @@ -130,7 +141,11 @@ func (s *Handler) getArticleDetails(c echo.Context) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) ListArticlesBySourceId(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeArticleRead) + _, err := s.ValidateJwtToken(c, domain.ScopeArticleRead) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + p := domain.ArticleResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -139,7 +154,7 @@ func (s *Handler) ListArticlesBySourceId(c echo.Context) error { id, err := strconv.Atoi(c.QueryParam("id")) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } // if the page number is missing, default to 0 diff --git a/internal/handler/v1/auth.go b/internal/handler/v1/auth.go index bfd8496..9320cb6 100644 --- a/internal/handler/v1/auth.go +++ b/internal/handler/v1/auth.go @@ -2,6 +2,7 @@ package v1 import ( "net/http" + "strings" "time" "git.jamestombleson.com/jtom38/newsbot-api/internal/domain" @@ -89,8 +90,9 @@ func (h *Handler) AuthLogin(c echo.Context) error { // TODO think about moving this down some? expiresAt := time.Now().Add(time.Hour * 48) + userScopes := strings.Split(user.Scopes, ",") - jwt, err := h.generateJwtWithExp(username, user.Scopes, h.config.ServerAddress, user.ID, expiresAt) + jwt, err := h.generateJwtWithExp(username, h.config.ServerAddress, userScopes, user.ID, expiresAt) if err != nil { return h.InternalServerErrorResponse(c, err.Error()) } @@ -120,8 +122,10 @@ func (h *Handler) createAdminToken(c echo.Context, password string) error { if h.config.AdminSecret != password { return h.UnauthorizedResponse(c, ErrUserNotFound) } + var userScopes []string + userScopes = append(userScopes, domain.ScopeAll) - token, err := h.generateJwt("admin", domain.ScopeAll, h.config.ServerAddress, -1) + token, err := h.generateJwt("admin", h.config.ServerAddress, userScopes, -1) if err != nil { return h.InternalServerErrorResponse(c, err.Error()) } @@ -146,9 +150,14 @@ func (h *Handler) createAdminToken(c echo.Context, password string) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (h *Handler) RefreshJwtToken(c echo.Context) error { + _, err := h.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate) + if err != nil { + return h.WriteError(c, err, http.StatusBadRequest) + } + // Check the context for the refresh token var request domain.RefreshTokenRequest - err := (&echo.DefaultBinder{}).BindBody(c, &request) + err = (&echo.DefaultBinder{}).BindBody(c, &request) if err != nil { return h.InternalServerErrorResponse(c, err.Error()) } @@ -162,8 +171,9 @@ func (h *Handler) RefreshJwtToken(c echo.Context) error { if err != nil { return h.InternalServerErrorResponse(c, err.Error()) } + userScopes := strings.Split(user.Scopes, ",") - jwt, err := h.generateJwtWithExp(request.Username, user.Scopes, h.config.ServerAddress, user.ID, time.Now().Add(time.Hour*48)) + jwt, err := h.generateJwtWithExp(request.Username, h.config.ServerAddress, userScopes, user.ID, time.Now().Add(time.Hour*48)) if err != nil { return h.InternalServerErrorResponse(c, err.Error()) } @@ -193,20 +203,15 @@ func (h *Handler) RefreshJwtToken(c echo.Context) error { // @Failure 400 {object} domain.BaseResponse // @Failure 500 {object} domain.BaseResponse func (h *Handler) AddScopes(c echo.Context) error { - token, err := h.getJwtTokenFromContext(c) + _, err := h.ValidateJwtToken(c, domain.ScopeAll) if err != nil { - return h.UnauthorizedResponse(c, err.Error()) - } - - err = token.IsValid(domain.ScopeAll) - if err != nil { - return h.UnauthorizedResponse(c, err.Error()) + return h.WriteError(c, err, http.StatusBadRequest) } request := domain.UpdateScopesRequest{} err = (&echo.DefaultBinder{}).BindBody(c, &request) if err != nil { - h.WriteError(c, err, http.StatusBadRequest) + return h.WriteError(c, err, http.StatusBadRequest) } err = h.repo.Users.AddScopes(c.Request().Context(), request.Username, request.Scopes) diff --git a/internal/handler/v1/discordwebhooks.go b/internal/handler/v1/discordwebhooks.go index 7e2e6f6..4328904 100644 --- a/internal/handler/v1/discordwebhooks.go +++ b/internal/handler/v1/discordwebhooks.go @@ -11,16 +11,20 @@ import ( ) // ListDiscordWebhooks -// @Summary Returns the top 100 -// @Produce application/json -// @Tags DiscordWebhook -// @Router /v1/discord/webhooks [get] -// @Success 200 {object} domain.DiscordWebhookResponse -// @Failure 400 {object} domain.BaseResponse -// @Failure 500 {object} domain.BaseResponse +// @Summary Returns the top 100 +// @Produce application/json +// @Tags DiscordWebhook +// @Router /v1/discord/webhooks [get] +// @Success 200 {object} domain.DiscordWebhookResponse +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) ListDiscordWebHooks(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead) + _, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + p := domain.DiscordWebhookResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -36,17 +40,21 @@ func (s *Handler) ListDiscordWebHooks(c echo.Context) error { } // GetDiscordWebHook -// @Summary Returns the top 100 entries from the queue to be processed. -// @Produce application/json -// @Param id path int true "id" -// @Tags DiscordWebhook -// @Router /v1/discord/webhooks/{id} [get] -// @Success 200 {object} domain.DiscordWebhookResponse "OK" -// @Failure 400 {object} domain.BaseResponse -// @Failure 500 {object} domain.BaseResponse +// @Summary Returns the top 100 entries from the queue to be processed. +// @Produce application/json +// @Param id path int true "id" +// @Tags DiscordWebhook +// @Router /v1/discord/webhooks/{id} [get] +// @Success 200 {object} domain.DiscordWebhookResponse "OK" +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) GetDiscordWebHooksById(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead) + _, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + p := domain.DiscordWebhookResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -55,12 +63,12 @@ func (s *Handler) GetDiscordWebHooksById(c echo.Context) error { id, err := strconv.Atoi(c.Param("ID")) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } res, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } var dtos []domain.DiscordWebHookDto dtos = append(dtos, services.DiscordWebhookToDto(res)) @@ -69,18 +77,22 @@ func (s *Handler) GetDiscordWebHooksById(c echo.Context) error { } // GetDiscordWebHookByServerAndChannel -// @Summary Returns all the known web hooks based on the Server and Channel given. -// @Produce application/json -// @Param server query string true "Fancy Server" -// @Param channel query string true "memes" -// @Tags DiscordWebhook -// @Router /v1/discord/webhooks/by/serverAndChannel [get] -// @Success 200 {object} domain.DiscordWebhookResponse "OK" -// @Failure 400 {object} domain.BaseResponse -// @Failure 500 {object} domain.BaseResponse +// @Summary Returns all the known web hooks based on the Server and Channel given. +// @Produce application/json +// @Param server query string true "Fancy Server" +// @Param channel query string true "memes" +// @Tags DiscordWebhook +// @Router /v1/discord/webhooks/by/serverAndChannel [get] +// @Success 200 {object} domain.DiscordWebhookResponse "OK" +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) GetDiscordWebHooksByServerAndChannel(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead) + _, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebhookRead) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + p := domain.DiscordWebhookResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -89,17 +101,17 @@ func (s *Handler) GetDiscordWebHooksByServerAndChannel(c echo.Context) error { _server := c.QueryParam("server") if _server == "" { - s.WriteMessage(c, "server was not defined", http.StatusBadRequest) + return s.WriteMessage(c, "server was not defined", http.StatusBadRequest) } _channel := c.QueryParam("channel") if _channel == "" { - s.WriteMessage(c, "channel was not defined", http.StatusBadRequest) + return s.WriteMessage(c, "channel was not defined", http.StatusBadRequest) } res, err := s.repo.DiscordWebHooks.ListByServerAndChannel(c.Request().Context(), _server, _channel) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } p.Payload = services.DiscordWebhooksToDto(res) @@ -107,18 +119,21 @@ func (s *Handler) GetDiscordWebHooksByServerAndChannel(c echo.Context) error { } // NewDiscordWebHook -// @Summary Creates a new record for a discord web hook to post data to. -// @Param url query string true "url" -// @Param server query string true "Server name" -// @Param channel query string true "Channel name" -// @Tags DiscordWebhook -// @Router /v1/discord/webhooks/new [post] -// @Success 200 {object} domain.DiscordWebhookResponse "OK" -// @Failure 400 {object} domain.BaseResponse -// @Failure 500 {object} domain.BaseResponse +// @Summary Creates a new record for a discord web hook to post data to. +// @Param url query string true "url" +// @Param server query string true "Server name" +// @Param channel query string true "Channel name" +// @Tags DiscordWebhook +// @Router /v1/discord/webhooks/new [post] +// @Success 200 {object} domain.DiscordWebhookResponse "OK" +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) NewDiscordWebHook(c echo.Context) error { - token := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate) + token, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } _url := c.QueryParam("url") _server := c.QueryParam("server") @@ -147,21 +162,21 @@ func (s *Handler) NewDiscordWebHook(c echo.Context) error { user, err := s.repo.Users.GetUser(c.Request().Context(), token.UserName) if err != nil { - s.WriteMessage(c, ErrUserUnknown, http.StatusBadRequest) + return s.WriteMessage(c, ErrUserUnknown, http.StatusBadRequest) } rows, err := s.repo.DiscordWebHooks.Create(c.Request().Context(), user.ID, _url, _server, _channel, true) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } if rows != 1 { - s.WriteMessage(c, "data was not written to database", http.StatusInternalServerError) + return s.WriteMessage(c, "data was not written to database", http.StatusInternalServerError) } item, err := s.repo.DiscordWebHooks.GetByUrl(c.Request().Context(), _url) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } var dtos []domain.DiscordWebHookDto @@ -176,16 +191,20 @@ func (s *Handler) NewDiscordWebHook(c echo.Context) error { } // DisableDiscordWebHooks -// @Summary Disables a Webhook from being used. -// @Param id path int true "id" -// @Tags DiscordWebhook -// @Router /v1/discord/webhooks/{ID}/disable [post] -// @Success 200 {object} domain.DiscordWebhookResponse "OK" -// @Failure 400 {object} domain.BaseResponse -// @Failure 500 {object} domain.BaseResponse +// @Summary Disables a Webhook from being used. +// @Param id path int true "id" +// @Tags DiscordWebhook +// @Router /v1/discord/webhooks/{ID}/disable [post] +// @Success 200 {object} domain.DiscordWebhookResponse "OK" +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) disableDiscordWebHook(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate) + _, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + id, err := strconv.Atoi(c.Param("ID")) if err != nil { return c.JSON(http.StatusBadRequest, domain.BaseResponse{ @@ -196,27 +215,27 @@ func (s *Handler) disableDiscordWebHook(c echo.Context) error { // Check to make sure we can find the record record, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } if record.UserID != s.GetUserIdFromJwtToken(c) { - s.WriteMessage(c, ErrYouDontOwnTheRecord, http.StatusBadRequest) + return s.WriteMessage(c, ErrYouDontOwnTheRecord, http.StatusBadRequest) } // flip the it updated, err := s.repo.DiscordWebHooks.Disable(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } // make sure we got a row updated if updated != 1 { - s.WriteMessage(c, "unexpected number of updates found", http.StatusInternalServerError) + return s.WriteMessage(c, "unexpected number of updates found", http.StatusInternalServerError) } item, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } var dtos []domain.DiscordWebHookDto @@ -230,40 +249,44 @@ func (s *Handler) disableDiscordWebHook(c echo.Context) error { } // EnableDiscordWebHook -// @Summary Enables a source to continue processing. -// @Param id path int true "id" -// @Tags DiscordWebhook -// @Router /v1/discord/webhooks/{ID}/enable [post] +// @Summary Enables a source to continue processing. +// @Param id path int true "id" +// @Tags DiscordWebhook +// @Router /v1/discord/webhooks/{ID}/enable [post] // @Security Bearer func (s *Handler) enableDiscordWebHook(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate) + _, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + id, err := strconv.Atoi(c.Param("ID")) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } // Check to make sure we can find the record record, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } if record.UserID != s.GetUserIdFromJwtToken(c) { - s.WriteMessage(c, ErrYouDontOwnTheRecord, http.StatusBadRequest) + return s.WriteMessage(c, ErrYouDontOwnTheRecord, http.StatusBadRequest) } updated, err := s.repo.DiscordWebHooks.Enable(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } if updated != 1 { - s.WriteMessage(c, "unexpected number of updates found", http.StatusInternalServerError) + return s.WriteMessage(c, ErrFailedToUpdateRecord, http.StatusInternalServerError) } item, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } var dtos []domain.DiscordWebHookDto @@ -285,7 +308,11 @@ func (s *Handler) enableDiscordWebHook(c echo.Context) error { // @Failure 400 {object} domain.BaseResponse // @Failure 500 {object} domain.BaseResponse func (s *Handler) deleteDiscordWebHook(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate) + _, err := s.ValidateJwtToken(c, domain.ScopeDiscordWebHookCreate) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + id, err := strconv.Atoi(c.Param("ID")) if err != nil { return c.JSON(http.StatusBadRequest, err.Error()) @@ -298,7 +325,7 @@ func (s *Handler) deleteDiscordWebHook(c echo.Context) error { } if record.UserID != s.GetUserIdFromJwtToken(c) { - s.WriteMessage(c, ErrYouDontOwnTheRecord, http.StatusBadRequest) + return s.WriteMessage(c, ErrYouDontOwnTheRecord, http.StatusBadRequest) } // Soft delete the record @@ -308,12 +335,12 @@ func (s *Handler) deleteDiscordWebHook(c echo.Context) error { } if updated != 1 { - s.WriteMessage(c, "unexpected number of updates found", http.StatusInternalServerError) + return s.WriteMessage(c, ErrFailedToUpdateRecord, http.StatusInternalServerError) } item, err := s.repo.DiscordWebHooks.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } var dtos []domain.DiscordWebHookDto diff --git a/internal/handler/v1/handler.go b/internal/handler/v1/handler.go index d8369f0..d3e76ad 100644 --- a/internal/handler/v1/handler.go +++ b/internal/handler/v1/handler.go @@ -3,6 +3,7 @@ package v1 import ( "context" "database/sql" + "errors" "net/http" "github.com/golang-jwt/jwt/v5" @@ -115,14 +116,14 @@ func NewServer(ctx context.Context, configs services.Configs, conn *sql.DB) *Han return s } -type ApiStatusModel struct { - StatusCode int `json:"status"` - Message string `json:"message"` -} +//type ApiStatusModel struct { +// StatusCode int `json:"status"` +// Message string `json:"message"` +//} -type ApiError struct { - *ApiStatusModel -} +//type ApiError struct { +// *ApiStatusModel +//} func (s *Handler) WriteError(c echo.Context, errMessage error, HttpStatusCode int) error { return c.JSON(HttpStatusCode, domain.BaseResponse{ @@ -151,27 +152,30 @@ func (s *Handler) UnauthorizedResponse(c echo.Context, msg string) error { // If the token is not valid then an json error will be returned. // If the token has the wrong scope, a json error will be returned. // If the token passes all the checks, it is valid and is returned back to the caller. -func (s *Handler) ValidateJwtToken(c echo.Context, requiredScope string) JwtToken { +func (s *Handler) ValidateJwtToken(c echo.Context, requiredScope string) (JwtToken, error) { token, err := s.getJwtTokenFromContext(c) if err != nil { s.WriteMessage(c, ErrJwtMissing, http.StatusUnauthorized) } + err = token.hasExpired() + if err != nil { + return JwtToken{}, errors.New(ErrJwtExpired) + //s.WriteMessage(c, ErrJwtExpired, http.StatusUnauthorized) + } + err = token.hasScope(requiredScope) if err != nil { - s.WriteMessage(c, ErrJwtScopeMissing, http.StatusUnauthorized) + return JwtToken{}, errors.New(ErrJwtScopeMissing) + //s.WriteMessage(c, ErrJwtScopeMissing, http.StatusUnauthorized) } if token.Iss != s.config.ServerAddress { - s.WriteMessage(c, ErrJwtInvalidIssuer, http.StatusUnauthorized) + return JwtToken{}, errors.New(ErrJwtInvalidIssuer) + //s.WriteMessage(c, ErrJwtInvalidIssuer, http.StatusUnauthorized) } - err = token.hasExpired() - if err != nil { - s.WriteMessage(c, ErrJwtExpired, http.StatusUnauthorized) - } - - return token + return token, nil } func (s *Handler) GetUserIdFromJwtToken(c echo.Context) int64 { diff --git a/internal/handler/v1/jwt.go b/internal/handler/v1/jwt.go index 22aaa74..1a8d565 100644 --- a/internal/handler/v1/jwt.go +++ b/internal/handler/v1/jwt.go @@ -59,8 +59,9 @@ func (j JwtToken) GetUserId() int64 { func (j JwtToken) hasExpired() error { // Check to see if the token has expired - hasExpired := j.Exp.Compare(time.Now()) - if hasExpired == -1 { + //hasExpired := j.Exp.Compare(time.Now()) + hasExpired := time.Now().Compare(j.Exp) + if hasExpired == 1 { return errors.New(ErrJwtExpired) } return nil @@ -82,11 +83,11 @@ func (j JwtToken) hasScope(scope string) error { return errors.New(ErrJwtScopeMissing) } -func (h *Handler) generateJwt(username, scopes, issuer string, userId int64) (string, error) { - return h.generateJwtWithExp(username, scopes, issuer, userId, time.Now().Add(10*time.Minute)) +func (h *Handler) generateJwt(username, issuer string, userScopes []string, userId int64) (string, error) { + return h.generateJwtWithExp(username, issuer, userScopes, userId, time.Now().Add(10*time.Minute)) } -func (h *Handler) generateJwtWithExp(username, userScopes, issuer string, userId int64, expiresAt time.Time) (string, error) { +func (h *Handler) generateJwtWithExp(username, issuer string, userScopes []string, userId int64, expiresAt time.Time) (string, error) { secret := []byte(h.config.JwtSecret) // Anyone who wants to decrypt the key needs to use the same method @@ -99,7 +100,7 @@ func (h *Handler) generateJwtWithExp(username, userScopes, issuer string, userId claims["userId"] = userId var scopes []string - scopes = append(scopes, domain.ScopeAll) + scopes = append(scopes, userScopes...) claims["scopes"] = scopes tokenString, err := token.SignedString(secret) diff --git a/internal/handler/v1/sources.go b/internal/handler/v1/sources.go index f5f1543..000cfde 100644 --- a/internal/handler/v1/sources.go +++ b/internal/handler/v1/sources.go @@ -21,7 +21,11 @@ import ( // @Failure 400 {object} domain.BaseResponse "Unable to reach SQL or Data problems" // @Security Bearer func (s *Handler) listSources(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeSourceRead) + _, err := s.ValidateJwtToken(c, domain.ScopeSourceRead) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + resp := domain.SourcesResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -36,7 +40,7 @@ func (s *Handler) listSources(c echo.Context) error { // Default way of showing all sources items, err := s.repo.Sources.List(c.Request().Context(), page, 25) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } resp.Payload = services.SourcesToDto(items) @@ -55,7 +59,11 @@ func (s *Handler) listSources(c echo.Context) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) listSourcesBySource(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeSourceRead) + _, err := s.ValidateJwtToken(c, domain.ScopeSourceRead) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + resp := domain.SourcesResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -64,7 +72,7 @@ func (s *Handler) listSourcesBySource(c echo.Context) error { source := c.QueryParam("source") if source == "" { - s.WriteMessage(c, fmt.Sprintf("%s source", ErrParameterMissing), http.StatusBadRequest) + return s.WriteMessage(c, fmt.Sprintf("%s source", ErrParameterMissing), http.StatusBadRequest) } page, err := strconv.Atoi(c.QueryParam("page")) @@ -95,7 +103,11 @@ func (s *Handler) listSourcesBySource(c echo.Context) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) getSource(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeSourceRead) + _, err := s.ValidateJwtToken(c, domain.ScopeSourceRead) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + resp := domain.SourcesResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -111,7 +123,7 @@ func (s *Handler) getSource(c echo.Context) error { item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } var dto []domain.SourceDto @@ -132,7 +144,11 @@ func (s *Handler) getSource(c echo.Context) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) GetSourceBySourceAndName(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeSourceRead) + _, err := s.ValidateJwtToken(c, domain.ScopeSourceRead) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + resp := domain.SourcesResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -140,7 +156,7 @@ func (s *Handler) GetSourceBySourceAndName(c echo.Context) error { } var param domain.GetSourceBySourceAndNameParamRequest - err := c.Bind(¶m) + err = c.Bind(¶m) if err != nil { return c.JSON(http.StatusBadRequest, domain.BaseResponse{ Message: err.Error(), @@ -169,7 +185,10 @@ func (s *Handler) GetSourceBySourceAndName(c echo.Context) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) newRedditSource(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeSourceCreate) + _, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } resp := domain.SourcesResponse{ BaseResponse: domain.BaseResponse{ @@ -178,30 +197,30 @@ func (s *Handler) newRedditSource(c echo.Context) error { } var param domain.NewSourceParamRequest - err := c.Bind(¶m) + err = c.Bind(¶m) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } if param.Url == "" { - s.WriteMessage(c, "url is missing", http.StatusBadRequest) + return s.WriteMessage(c, "url is missing", http.StatusBadRequest) } if !strings.Contains(param.Url, "reddit.com") { - s.WriteMessage(c, "invalid url", http.StatusBadRequest) + return s.WriteMessage(c, "invalid url", http.StatusBadRequest) } tags := fmt.Sprintf("twitch, %v, %s", param.Name, param.Tags) rows, err := s.repo.Sources.Create(c.Request().Context(), domain.SourceCollectorReddit, param.Name, param.Url, tags, true) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } if rows != 1 { - s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError) + return s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError) } item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorReddit, param.Name) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } var dto []domain.SourceDto @@ -219,18 +238,21 @@ func (s *Handler) newRedditSource(c echo.Context) error { // @Security Bearer func (s *Handler) newYoutubeSource(c echo.Context) error { // Validate the jwt - s.ValidateJwtToken(c, domain.ScopeSourceCreate) + _, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } var param domain.NewSourceParamRequest - err := c.Bind(¶m) + err = c.Bind(¶m) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } if param.Url == "" { - s.WriteMessage(c, "url is missing a value", http.StatusBadRequest) + return s.WriteMessage(c, "url is missing a value", http.StatusBadRequest) } if !strings.Contains(param.Url, "youtube.com") { - s.WriteMessage(c, "invalid url", http.StatusBadRequest) + return s.WriteMessage(c, "invalid url", http.StatusBadRequest) } resp := domain.SourcesResponse{ @@ -254,7 +276,7 @@ func (s *Handler) newYoutubeSource(c echo.Context) error { } if rows != 1 { - s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError) + return s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError) } item, err = s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorYoutube, param.Name) @@ -275,10 +297,13 @@ func (s *Handler) newYoutubeSource(c echo.Context) error { // @Router /v1/sources/new/twitch [post] // @Security Bearer func (s *Handler) newTwitchSource(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeSourceCreate) + _, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } var param domain.NewSourceParamRequest - err := c.Bind(¶m) + err = c.Bind(¶m) if err != nil { return c.JSON(http.StatusBadRequest, domain.BaseResponse{ Message: err.Error(), @@ -311,10 +336,10 @@ func (s *Handler) newTwitchSource(c echo.Context) error { } if rows != 1 { - s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError) + return s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError) } - item, err = s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorTwitch, param.Name) + item, _ = s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorTwitch, param.Name) var dto []domain.SourceDto dto = append(dto, services.SourceToDto(item)) resp.Payload = dto @@ -333,7 +358,10 @@ func (s *Handler) newTwitchSource(c echo.Context) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) newRssSource(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeSourceCreate) + _, err := s.ValidateJwtToken(c, domain.ScopeSourceCreate) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } resp := domain.SourcesResponse{ BaseResponse: domain.BaseResponse{ @@ -342,7 +370,7 @@ func (s *Handler) newRssSource(c echo.Context) error { } var param domain.NewSourceParamRequest - err := c.Bind(¶m) + err = c.Bind(¶m) if err != nil { return c.JSON(http.StatusBadRequest, domain.BaseResponse{ Message: err.Error(), @@ -358,16 +386,16 @@ func (s *Handler) newRssSource(c echo.Context) error { tags := fmt.Sprintf("rss, %v, %s", param.Name, param.Tags) rows, err := s.repo.Sources.Create(c.Request().Context(), domain.SourceCollectorRss, param.Name, param.Url, tags, true) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } if rows != 1 { - s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError) + return s.WriteMessage(c, ErrFailedToCreateRecord, http.StatusInternalServerError) } item, err := s.repo.Sources.GetBySourceAndName(c.Request().Context(), domain.SourceCollectorRss, param.Name) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } var dto []domain.SourceDto @@ -386,31 +414,35 @@ func (s *Handler) newRssSource(c echo.Context) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) deleteSources(c echo.Context) error { - s.ValidateJwtToken(c, domain.ScopeAll) + _, err := s.ValidateJwtToken(c, domain.ScopeAll) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + id, err := strconv.Atoi(c.Param("ID")) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } // Check to make sure we can find the record _, err = s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } // Delete the record rows, err := s.repo.Sources.SoftDelete(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } if rows != 1 { - s.WriteMessage(c, ErrFailedToUpdateRecord, http.StatusInternalServerError) + return s.WriteMessage(c, ErrFailedToUpdateRecord, http.StatusInternalServerError) } // pull the record with its updated value item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } var items []domain.SourceDto @@ -434,6 +466,11 @@ func (s *Handler) deleteSources(c echo.Context) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) disableSource(c echo.Context) error { + _, err := s.ValidateJwtToken(c, domain.ScopeAll) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + resp := domain.SourcesResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -442,23 +479,23 @@ func (s *Handler) disableSource(c echo.Context) error { id, err := strconv.Atoi(c.Param("ID")) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } // Check to make sure we can find the record _, err = s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } _, err = s.repo.Sources.Disable(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } var dto []domain.SourceDto @@ -477,6 +514,11 @@ func (s *Handler) disableSource(c echo.Context) error { // @Failure 500 {object} domain.BaseResponse // @Security Bearer func (s *Handler) enableSource(c echo.Context) error { + _, err := s.ValidateJwtToken(c, domain.ScopeAll) + if err != nil { + return s.WriteError(c, err, http.StatusBadRequest) + } + resp := domain.SourcesResponse{ BaseResponse: domain.BaseResponse{ Message: ResponseMessageSuccess, @@ -485,23 +527,23 @@ func (s *Handler) enableSource(c echo.Context) error { id, err := strconv.Atoi(c.Param("ID")) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } // Check to make sure we can find the record _, err = s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusBadRequest) + return s.WriteError(c, err, http.StatusBadRequest) } _, err = s.repo.Sources.Enable(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } item, err := s.repo.Sources.GetById(c.Request().Context(), int64(id)) if err != nil { - s.WriteError(c, err, http.StatusInternalServerError) + return s.WriteError(c, err, http.StatusInternalServerError) } var dto []domain.SourceDto