diff --git a/docs/docs.go b/docs/docs.go index 463c253..58e97ea 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -903,6 +903,226 @@ const docTemplate = `{ } } } + }, + "/v1/users/login": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Logs into the API and returns a bearer token if successful", + "parameters": [ + { + "type": "string", + "name": "password", + "in": "formData" + }, + { + "type": "string", + "name": "username", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.LoginResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } + }, + "/v1/users/refreshToken": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": [ + "Users" + ], + "summary": "Generates a new token", + "parameters": [ + { + "description": "body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.RefreshTokenRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.LoginResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } + }, + "/v1/users/register": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Creates a new user", + "parameters": [ + { + "type": "string", + "name": "password", + "in": "formData" + }, + { + "type": "string", + "name": "username", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } + }, + "/v1/users/scopes/add": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Adds a new scope to a user account", + "parameters": [ + { + "description": "body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.UpdateScopesRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } + }, + "/v1/users/scopes/remove": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Adds a new scope to a user account", + "parameters": [ + { + "description": "body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.UpdateScopesRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } } }, "definitions": { @@ -1023,6 +1243,34 @@ const docTemplate = `{ } } }, + "domain.LoginResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "refreshToken": { + "type": "string" + }, + "token": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "domain.RefreshTokenRequest": { + "type": "object", + "properties": { + "refreshToken": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "domain.SourceDto": { "type": "object", "properties": { @@ -1059,6 +1307,23 @@ const docTemplate = `{ } } } + }, + "domain.UpdateScopesRequest": { + "type": "object", + "required": [ + "scopes" + ], + "properties": { + "scopes": { + "type": "array", + "items": { + "type": "string" + } + }, + "username": { + "type": "string" + } + } } }, "securityDefinitions": { diff --git a/docs/swagger.json b/docs/swagger.json index e9cb4ca..2e61798 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -894,6 +894,226 @@ } } } + }, + "/v1/users/login": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Logs into the API and returns a bearer token if successful", + "parameters": [ + { + "type": "string", + "name": "password", + "in": "formData" + }, + { + "type": "string", + "name": "username", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.LoginResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } + }, + "/v1/users/refreshToken": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": [ + "Users" + ], + "summary": "Generates a new token", + "parameters": [ + { + "description": "body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.RefreshTokenRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.LoginResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } + }, + "/v1/users/register": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Creates a new user", + "parameters": [ + { + "type": "string", + "name": "password", + "in": "formData" + }, + { + "type": "string", + "name": "username", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } + }, + "/v1/users/scopes/add": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Adds a new scope to a user account", + "parameters": [ + { + "description": "body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.UpdateScopesRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } + }, + "/v1/users/scopes/remove": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Adds a new scope to a user account", + "parameters": [ + { + "description": "body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.UpdateScopesRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.BaseResponse" + } + } + } + } } }, "definitions": { @@ -1014,6 +1234,34 @@ } } }, + "domain.LoginResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "refreshToken": { + "type": "string" + }, + "token": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "domain.RefreshTokenRequest": { + "type": "object", + "properties": { + "refreshToken": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "domain.SourceDto": { "type": "object", "properties": { @@ -1050,6 +1298,23 @@ } } } + }, + "domain.UpdateScopesRequest": { + "type": "object", + "required": [ + "scopes" + ], + "properties": { + "scopes": { + "type": "array", + "items": { + "type": "string" + } + }, + "username": { + "type": "string" + } + } } }, "securityDefinitions": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ad8429d..a60e835 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -78,6 +78,24 @@ definitions: $ref: '#/definitions/domain.DiscordWebHookDto' type: array type: object + domain.LoginResponse: + properties: + message: + type: string + refreshToken: + type: string + token: + type: string + type: + type: string + type: object + domain.RefreshTokenRequest: + properties: + refreshToken: + type: string + username: + type: string + type: object domain.SourceDto: properties: enabled: @@ -102,6 +120,17 @@ definitions: $ref: '#/definitions/domain.SourceDto' type: array type: object + domain.UpdateScopesRequest: + properties: + scopes: + items: + type: string + type: array + username: + type: string + required: + - scopes + type: object info: contact: {} title: NewsBot collector @@ -670,6 +699,145 @@ paths: summary: Creates a new youtube source to monitor. tags: - Source + /v1/users/login: + post: + parameters: + - in: formData + name: password + type: string + - in: formData + name: username + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.LoginResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.BaseResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/domain.BaseResponse' + summary: Logs into the API and returns a bearer token if successful + tags: + - Users + /v1/users/refreshToken: + post: + parameters: + - description: body + in: body + name: request + required: true + schema: + $ref: '#/definitions/domain.RefreshTokenRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.LoginResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.BaseResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/domain.BaseResponse' + security: + - Bearer: [] + summary: Generates a new token + tags: + - Users + /v1/users/register: + post: + parameters: + - in: formData + name: password + type: string + - in: formData + name: username + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.BaseResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.BaseResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/domain.BaseResponse' + summary: Creates a new user + tags: + - Users + /v1/users/scopes/add: + post: + consumes: + - application/json + parameters: + - description: body + in: body + name: request + required: true + schema: + $ref: '#/definitions/domain.UpdateScopesRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.BaseResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.BaseResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/domain.BaseResponse' + summary: Adds a new scope to a user account + tags: + - Users + /v1/users/scopes/remove: + post: + consumes: + - application/json + parameters: + - description: body + in: body + name: request + required: true + schema: + $ref: '#/definitions/domain.UpdateScopesRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.BaseResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.BaseResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/domain.BaseResponse' + summary: Adds a new scope to a user account + tags: + - Users securityDefinitions: Bearer: description: Type "Bearer" followed by a space and JWT token. diff --git a/internal/domain/scopes.go b/internal/domain/scopes.go index c3ef7df..23f4bbf 100644 --- a/internal/domain/scopes.go +++ b/internal/domain/scopes.go @@ -1,6 +1,8 @@ package domain const ( - ScopeAll = "newsbot:all" - ScopeRead = "newsbot:read" + ScopeAll = "newsbot:all" + ScopeArticleRead = "newsbot:article:read" + ScopeSourceCreate = "newsbot:source:create" + ScopeDiscordWebHookCreate = "newsbot:discordwebhook:create" ) diff --git a/internal/handler/v1/auth.go b/internal/handler/v1/auth.go index 7847f56..458c320 100644 --- a/internal/handler/v1/auth.go +++ b/internal/handler/v1/auth.go @@ -15,12 +15,15 @@ const ( ErrUsernameAlreadyExists = "the requested username already exists" ) -// Register // @Summary Creates a new user -// @Tags Users +// @Router /v1/users/register [post] +// @Param request formData domain.LoginFormRequest true "form" +// @Accepts x-www-form-urlencoded +// @Produce json +// @Tags Users // @Success 200 {object} domain.BaseResponse -// @Failure 400 {object} domain.BaseResponse -// @Failure 500 {object} domain.BaseResponse +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse func (h *Handler) AuthRegister(c echo.Context) error { username := c.FormValue("username") password := c.FormValue("password") @@ -44,7 +47,7 @@ func (h *Handler) AuthRegister(c echo.Context) error { return h.WriteError(c, err, http.StatusInternalServerError) } - _, err = h.repo.Users.Create(c.Request().Context(), username, password, domain.ScopeRead) + _, err = h.repo.Users.Create(c.Request().Context(), username, password, domain.ScopeArticleRead) if err != nil { return h.InternalServerErrorResponse(c, err.Error()) } @@ -54,17 +57,26 @@ func (h *Handler) AuthRegister(c echo.Context) error { }) } +// @Summary Logs into the API and returns a bearer token if successful +// @Router /v1/users/login [post] +// @Param request formData domain.LoginFormRequest true "form" +// @Accepts x-www-form-urlencoded +// @Produce json +// @Tags Users +// @Success 200 {object} domain.LoginResponse +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse func (h *Handler) AuthLogin(c echo.Context) error { username := c.FormValue("username") password := c.FormValue("password") // Check to see if they are trying to login with the admin token if username == "" { - return h.validateAdminToken(c, password) + return h.createAdminToken(c, password) } // check if the user exists - err := h.repo.Users.DoesUserExist(c.Request().Context(), username) + user, err := h.repo.Users.GetUser(c.Request().Context(), username) if err != nil { return h.InternalServerErrorResponse(c, err.Error()) } @@ -78,7 +90,7 @@ func (h *Handler) AuthLogin(c echo.Context) error { // TODO think about moving this down some? expiresAt := time.Now().Add(time.Hour * 48) - jwt, err := h.generateJwtWithExp(username, h.config.ServerAddress, expiresAt) + jwt, err := h.generateJwtWithExp(username, user.Scopes, h.config.ServerAddress, expiresAt) if err != nil { return h.InternalServerErrorResponse(c, err.Error()) } @@ -98,7 +110,7 @@ func (h *Handler) AuthLogin(c echo.Context) error { }) } -func (h *Handler) validateAdminToken(c echo.Context, password string) error { +func (h *Handler) createAdminToken(c echo.Context, password string) error { // if the admin token is blank, then the admin wanted this disabled. // this will fail right away and not progress. if h.config.AdminSecret == "" { @@ -109,15 +121,30 @@ func (h *Handler) validateAdminToken(c echo.Context, password string) error { return h.UnauthorizedResponse(c, ErrUserNotFound) } - token, err := h.generateJwt("admin", h.config.ServerAddress) + token, err := h.generateJwt("admin", domain.ScopeAll, h.config.ServerAddress) if err != nil { return h.InternalServerErrorResponse(c, err.Error()) } - return c.JSON(http.StatusOK, token) + return c.JSON(http.StatusOK, domain.LoginResponse{ + BaseResponse: domain.BaseResponse{ + Message: "OK", + }, + Token: token, + Type: "Bearer", + }) } // This will take collect some information about the requested refresh, validate and then return a new jwt token if approved. +// Register +// @Summary Generates a new token +// @Router /v1/users/refreshToken [post] +// @Param request body domain.RefreshTokenRequest true "body" +// @Tags Users +// @Success 200 {object} domain.LoginResponse +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse +// @Security Bearer func (h *Handler) RefreshJwtToken(c echo.Context) error { // Check the context for the refresh token var request domain.RefreshTokenRequest @@ -131,7 +158,12 @@ func (h *Handler) RefreshJwtToken(c echo.Context) error { return h.InternalServerErrorResponse(c, err.Error()) } - jwt, err := h.generateJwtWithExp(request.Username, h.config.ServerAddress, time.Now().Add(time.Hour*48)) + user, err := h.repo.Users.GetUser(c.Request().Context(), request.Username) + if err != nil { + return h.InternalServerErrorResponse(c, err.Error()) + } + + jwt, err := h.generateJwtWithExp(request.Username, user.Scopes, h.config.ServerAddress, time.Now().Add(time.Hour*48)) if err != nil { return h.InternalServerErrorResponse(c, err.Error()) } @@ -151,8 +183,17 @@ func (h *Handler) RefreshJwtToken(c echo.Context) error { }) } +// @Summary Adds a new scope to a user account +// @Router /v1/users/scopes/add [post] +// @Param request body domain.UpdateScopesRequest true "body" +// @Tags Users +// @Accept json +// @Produce json +// @Success 200 {object} domain.BaseResponse +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse func (h *Handler) AddScopes(c echo.Context) error { - token, err := h.getJwtToken(c) + token, err := h.getJwtTokenFromContext(c) if err != nil { return h.UnauthorizedResponse(c, err.Error()) } @@ -178,8 +219,17 @@ func (h *Handler) AddScopes(c echo.Context) error { }) } +// @Summary Adds a new scope to a user account +// @Router /v1/users/scopes/remove [post] +// @Param request body domain.UpdateScopesRequest true "body" +// @Tags Users +// @Accept json +// @Produce json +// @Success 200 {object} domain.BaseResponse +// @Failure 400 {object} domain.BaseResponse +// @Failure 500 {object} domain.BaseResponse func (h *Handler) RemoveScopes(c echo.Context) error { - token, err := h.getJwtToken(c) + token, err := h.getJwtTokenFromContext(c) if err != nil { return h.WriteError(c, err, http.StatusUnauthorized) } diff --git a/internal/handler/v1/handler.go b/internal/handler/v1/handler.go index 2bc9d9e..56e0da7 100644 --- a/internal/handler/v1/handler.go +++ b/internal/handler/v1/handler.go @@ -100,6 +100,14 @@ func NewServer(ctx context.Context, configs services.Configs, conn *sql.DB) *Han sources.POST("/:ID/disable", s.disableSource) sources.POST("/:ID/enable", s.enableSource) + users := v1.Group("/users") + users.POST("/login", s.AuthLogin) + users.POST("/register", s.AuthRegister) + users.Use(echojwt.WithConfig(jwtConfig)) + users.POST("/scopes/add", s.AddScopes) + users.POST("/scopes/remove", s.RemoveScopes) + users.POST("/refreshToken", s.RefreshJwtToken) + s.Router = router return s } @@ -136,3 +144,29 @@ func (s *Handler) UnauthorizedResponse(c echo.Context, msg string) error { Message: msg, }) } + +// 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 { + token, err := s.getJwtTokenFromContext(c) + if err != nil { + s.WriteMessage(c, ErrJwtMissing, http.StatusUnauthorized) + } + + err = token.hasScope(requiredScope) + if err != nil { + s.WriteMessage(c, ErrJwtScopeMissing, http.StatusUnauthorized) + } + + if token.Iss != s.config.ServerAddress { + s.WriteMessage(c, ErrJwtInvalidIssuer, http.StatusUnauthorized) + } + + err = token.hasExpired() + if err != nil { + s.WriteMessage(c, ErrJwtExpired, http.StatusUnauthorized) + } + + return token +} \ No newline at end of file diff --git a/internal/handler/v1/jwt.go b/internal/handler/v1/jwt.go index 92f77be..5ded140 100644 --- a/internal/handler/v1/jwt.go +++ b/internal/handler/v1/jwt.go @@ -15,6 +15,7 @@ const ( ErrJwtClaimsMissing = "claims missing on token" ErrJwtExpired = "auth token has expired" ErrJwtScopeMissing = "required scope is missing" + ErrJwtInvalidIssuer = "incorrect server issued the token" ) type JwtToken struct { @@ -32,6 +33,13 @@ func (j JwtToken) IsValid(scope string) error { return err } + // Check to see if they have the scope to do anything + // if they do, let them pass + err = j.hasScope(domain.ScopeAll) + if err == nil { + return nil + } + err = j.hasScope(scope) if err != nil { return err @@ -53,25 +61,27 @@ func (j JwtToken) hasExpired() error { return nil } +// This will check the users token to make sure they have the correct scope to access the handler. +// It will evaluate if you have the admin scope or the required scope for the handler. func (j JwtToken) hasScope(scope string) error { // they have the scope to access everything, so let them pass. - if strings.Contains(domain.ScopeAll, scope) { + userScopes := strings.Join(j.Scopes, "") + if strings.Contains(domain.ScopeAll, userScopes) { return nil } - for _, s := range j.Scopes { - if strings.Contains(s, scope) { - return nil - } + if strings.Contains(userScopes, scope) { + return nil } + return errors.New(ErrJwtScopeMissing) } -func (h *Handler) generateJwt(username, issuer string) (string, error) { - return h.generateJwtWithExp(username, issuer, time.Now().Add(10*time.Minute)) +func (h *Handler) generateJwt(username, scopes, issuer string) (string, error) { + return h.generateJwtWithExp(username, scopes, issuer, time.Now().Add(10*time.Minute)) } -func (h *Handler) generateJwtWithExp(username, issuer string, expiresAt time.Time) (string, error) { +func (h *Handler) generateJwtWithExp(username, userScopes, issuer string, expiresAt time.Time) (string, error) { secret := []byte(h.config.JwtSecret) // Anyone who wants to decrypt the key needs to use the same method @@ -83,13 +93,8 @@ func (h *Handler) generateJwtWithExp(username, issuer string, expiresAt time.Tim claims["iss"] = issuer var scopes []string - if username == "admin" { - scopes = append(scopes, domain.ScopeAll) - claims["scopes"] = scopes - } else { - scopes = append(scopes, domain.ScopeRead) - claims["scopes"] = scopes - } + scopes = append(scopes, domain.ScopeAll) + claims["scopes"] = scopes tokenString, err := token.SignedString(secret) if err != nil { @@ -99,7 +104,7 @@ func (h *Handler) generateJwtWithExp(username, issuer string, expiresAt time.Tim return tokenString, nil } -func (h *Handler) getJwtToken(c echo.Context) (JwtToken, error) { +func (h *Handler) getJwtTokenFromContext(c echo.Context) (JwtToken, error) { // Make sure that the request came with a jwtToken token, ok := c.Get("user").(*jwt.Token) if !ok { diff --git a/internal/services/config.go b/internal/services/config.go index 6b34302..f181aa1 100644 --- a/internal/services/config.go +++ b/internal/services/config.go @@ -67,6 +67,7 @@ func GetEnvConfig() Configs { return Configs{ ServerAddress: os.Getenv(ServerAddress), JwtSecret: os.Getenv("JwtSecret"), + AdminSecret: os.Getenv("AdminSecret"), RedditEnabled: processBoolConfig(os.Getenv(FEATURE_ENABLE_REDDIT_BACKEND)), RedditPullTop: processBoolConfig(os.Getenv(REDDIT_PULL_TOP)),