Compare commits

...

2 Commits

9 changed files with 50 additions and 50 deletions

View File

@ -60,7 +60,7 @@ const docTemplate = `{
} }
} }
}, },
"/v1/articles/by/sourceid": { "/v1/articles/by/sourceId": {
"get": { "get": {
"security": [ "security": [
{ {
@ -1035,7 +1035,7 @@ const docTemplate = `{
} }
} }
}, },
"/v1/users/refreshToken": { "/v1/users/refresh/token": {
"post": { "post": {
"security": [ "security": [
{ {
@ -1350,9 +1350,6 @@ const docTemplate = `{
"refreshToken": { "refreshToken": {
"type": "string" "type": "string"
}, },
"sessionToken": {
"type": "string"
},
"token": { "token": {
"type": "string" "type": "string"
}, },

View File

@ -51,7 +51,7 @@
} }
} }
}, },
"/v1/articles/by/sourceid": { "/v1/articles/by/sourceId": {
"get": { "get": {
"security": [ "security": [
{ {
@ -1026,7 +1026,7 @@
} }
} }
}, },
"/v1/users/refreshToken": { "/v1/users/refresh/token": {
"post": { "post": {
"security": [ "security": [
{ {
@ -1341,9 +1341,6 @@
"refreshToken": { "refreshToken": {
"type": "string" "type": "string"
}, },
"sessionToken": {
"type": "string"
},
"token": { "token": {
"type": "string" "type": "string"
}, },

View File

@ -84,8 +84,6 @@ definitions:
type: string type: string
refreshToken: refreshToken:
type: string type: string
sessionToken:
type: string
token: token:
type: string type: string
type: type:
@ -221,7 +219,7 @@ paths:
summary: Returns an article and source based on defined ID. summary: Returns an article and source based on defined ID.
tags: tags:
- Articles - Articles
/v1/articles/by/sourceid: /v1/articles/by/sourceId:
get: get:
parameters: parameters:
- description: source id - description: source id
@ -776,7 +774,7 @@ paths:
summary: Revokes the current session token and replaces it with a new one. summary: Revokes the current session token and replaces it with a new one.
tags: tags:
- Users - Users
/v1/users/refreshToken: /v1/users/refresh/token:
post: post:
parameters: parameters:
- description: body - description: body

View File

@ -1,6 +1,5 @@
package domain package domain
type BaseResponse struct { type BaseResponse struct {
Message string `json:"message"` Message string `json:"message"`
} }
@ -10,7 +9,6 @@ type LoginResponse struct {
Token string `json:"token"` Token string `json:"token"`
Type string `json:"type"` Type string `json:"type"`
RefreshToken string `json:"refreshToken"` RefreshToken string `json:"refreshToken"`
SessionToken string `json:"sessionToken"`
} }
type ArticleResponse struct { type ArticleResponse struct {
@ -36,4 +34,4 @@ type DiscordWebhookResponse struct {
type SourcesResponse struct { type SourcesResponse struct {
BaseResponse BaseResponse
Payload []SourceDto `json:"payload"` Payload []SourceDto `json:"payload"`
} }

View File

@ -135,7 +135,7 @@ func (s *Handler) getArticleDetails(c echo.Context) error {
// @Param page query int false "Page to query" // @Param page query int false "Page to query"
// @Produce application/json // @Produce application/json
// @Tags Articles // @Tags Articles
// @Router /v1/articles/by/sourceid [get] // @Router /v1/articles/by/sourceId [get]
// @Success 200 {object} domain.ArticleResponse "OK" // @Success 200 {object} domain.ArticleResponse "OK"
// @Failure 400 {object} domain.BaseResponse // @Failure 400 {object} domain.BaseResponse
// @Failure 500 {object} domain.BaseResponse // @Failure 500 {object} domain.BaseResponse

View File

@ -110,7 +110,8 @@ func NewServer(ctx context.Context, configs services.Configs, conn *sql.DB) *Han
users.Use(echojwt.WithConfig(jwtConfig)) users.Use(echojwt.WithConfig(jwtConfig))
users.POST("/scopes/add", s.AddScopes) users.POST("/scopes/add", s.AddScopes)
users.POST("/scopes/remove", s.RemoveScopes) users.POST("/scopes/remove", s.RemoveScopes)
users.POST("/refreshToken", s.RefreshJwtToken) users.POST("/refresh/token", s.RefreshJwtToken)
users.POST("/refresh/sessionToken", s.NewSessionToken)
s.Router = router s.Router = router
return s return s
@ -161,18 +162,24 @@ func (s *Handler) ValidateJwtToken(c echo.Context, requiredScope string) (JwtTok
err = token.hasExpired() err = token.hasExpired()
if err != nil { if err != nil {
return JwtToken{}, errors.New(ErrJwtExpired) return JwtToken{}, errors.New(ErrJwtExpired)
//s.WriteMessage(c, ErrJwtExpired, http.StatusUnauthorized)
} }
err = token.hasScope(requiredScope) err = token.hasScope(requiredScope)
if err != nil { if err != nil {
return JwtToken{}, errors.New(ErrJwtScopeMissing) return JwtToken{}, errors.New(ErrJwtScopeMissing)
//s.WriteMessage(c, ErrJwtScopeMissing, http.StatusUnauthorized)
} }
if token.Iss != s.config.ServerAddress { if token.Iss != s.config.ServerAddress {
return JwtToken{}, errors.New(ErrJwtInvalidIssuer) return JwtToken{}, errors.New(ErrJwtInvalidIssuer)
//s.WriteMessage(c, ErrJwtInvalidIssuer, http.StatusUnauthorized) }
user, err := s.repo.Users.GetUser(c.Request().Context(), token.UserName)
if err != nil {
return JwtToken{}, errors.New("user record not found")
}
if user.SessionToken != token.SessionToken {
return JwtToken{}, errors.New("invalid session token")
} }
return token, nil return token, nil

View File

@ -19,12 +19,13 @@ const (
) )
type JwtToken struct { type JwtToken struct {
Exp time.Time `json:"exp"` Exp time.Time `json:"exp"`
Iss string `json:"iss"` Iss string `json:"iss"`
Authorized bool `json:"authorized"` Authorized bool `json:"authorized"`
UserName string `json:"username"` UserName string `json:"username"`
UserId int64 `json:"userId"` UserId int64 `json:"userId"`
Scopes []string `json:"scopes"` Scopes []string `json:"scopes"`
SessionToken string `json:"sessionToken"`
jwt.RegisteredClaims jwt.RegisteredClaims
} }

View File

@ -49,7 +49,7 @@ func (h *Handler) AuthRegister(c echo.Context) error {
return h.WriteError(c, err, http.StatusInternalServerError) return h.WriteError(c, err, http.StatusInternalServerError)
} }
_, err = h.repo.Users.Create(c.Request().Context(), username, password, "", domain.ScopeArticleRead) _, err = h.repo.Users.Create(c.Request().Context(), username, password, domain.ScopeArticleRead)
if err != nil { if err != nil {
return h.InternalServerErrorResponse(c, err.Error()) return h.InternalServerErrorResponse(c, err.Error())
} }
@ -92,12 +92,8 @@ func (h *Handler) AuthLogin(c echo.Context) error {
// TODO think about moving this down some? // TODO think about moving this down some?
expiresAt := time.Now().Add(time.Hour * 48) expiresAt := time.Now().Add(time.Hour * 48)
userScopes := strings.Split(user.Scopes, ",") userScopes := strings.Split(user.Scopes, ",")
sessionToken, err := uuid.NewV7()
if err != nil {
return h.InternalServerErrorResponse(c, err.Error())
}
jwt, err := h.generateJwtWithExp(username, h.config.ServerAddress, sessionToken.String(), userScopes, user.ID, expiresAt) jwt, err := h.generateJwtWithExp(username, h.config.ServerAddress, user.SessionToken, userScopes, user.ID, expiresAt)
if err != nil { if err != nil {
return h.InternalServerErrorResponse(c, err.Error()) return h.InternalServerErrorResponse(c, err.Error())
} }
@ -114,7 +110,6 @@ func (h *Handler) AuthLogin(c echo.Context) error {
Token: jwt, Token: jwt,
Type: "Bearer", Type: "Bearer",
RefreshToken: refresh, RefreshToken: refresh,
SessionToken: sessionToken.String(),
}) })
} }
@ -152,7 +147,7 @@ func (h *Handler) createAdminToken(c echo.Context, password string) error {
// This will take collect some information about the requested refresh, validate and then return a new jwt token if approved. // This will take collect some information about the requested refresh, validate and then return a new jwt token if approved.
// Register // Register
// @Summary Generates a new token // @Summary Generates a new token
// @Router /v1/users/refreshToken [post] // @Router /v1/users/refresh/token [post]
// @Param request body domain.RefreshTokenRequest true "body" // @Param request body domain.RefreshTokenRequest true "body"
// @Tags Users // @Tags Users
// @Success 200 {object} domain.LoginResponse // @Success 200 {object} domain.LoginResponse
@ -183,7 +178,7 @@ func (h *Handler) RefreshJwtToken(c echo.Context) error {
} }
userScopes := strings.Split(user.Scopes, ",") userScopes := strings.Split(user.Scopes, ",")
jwt, err := h.generateJwtWithExp(request.Username, h.config.ServerAddress, "", userScopes, user.ID, time.Now().Add(time.Hour*48)) jwt, err := h.generateJwtWithExp(request.Username, h.config.ServerAddress, user.SessionToken, userScopes, user.ID, time.Now().Add(time.Hour*48))
if err != nil { if err != nil {
return h.InternalServerErrorResponse(c, err.Error()) return h.InternalServerErrorResponse(c, err.Error())
} }
@ -281,16 +276,18 @@ func (h *Handler) RemoveScopes(c echo.Context) error {
// @Failure 400 {object} domain.BaseResponse // @Failure 400 {object} domain.BaseResponse
// @Failure 500 {object} domain.BaseResponse // @Failure 500 {object} domain.BaseResponse
// @Security Bearer // @Security Bearer
func (h *Handler) LogoutEverywhere(c echo.Context) error { func (h *Handler) NewSessionToken(c echo.Context) error {
token, err := h.getJwtTokenFromContext(c) token, err := h.getJwtTokenFromContext(c)
if err != nil { if err != nil {
return h.WriteError(c, err, http.StatusUnauthorized) return h.WriteError(c, err, http.StatusUnauthorized)
} }
err = token.IsValid(domain.ScopeAll) _, err = h.repo.Users.NewSessionToken(c.Request().Context(), token.UserName)
if err != nil { if err != nil {
return h.WriteError(c, err, http.StatusUnauthorized) return h.WriteError(c, err, http.StatusInternalServerError)
} }
return c.String(http.StatusInternalServerError, "Not Implemented") return c.JSON(http.StatusInternalServerError, domain.BaseResponse{
Message: "OK",
})
} }

View File

@ -27,8 +27,8 @@ type UserServices interface {
GetUser(ctx context.Context, username string) (entity.UserEntity, error) GetUser(ctx context.Context, username string) (entity.UserEntity, error)
AddScopes(ctx context.Context, username string, scopes []string) error AddScopes(ctx context.Context, username string, scopes []string) error
RemoveScopes(ctx context.Context, username string, scopes []string) error RemoveScopes(ctx context.Context, username string, scopes []string) error
Create(ctx context.Context, name, password, sessionToken, scope string) (entity.UserEntity, error) Create(ctx context.Context, name, password, scope string) (entity.UserEntity, error)
NewSessionToken(ctx context.Context, name string) error NewSessionToken(ctx context.Context, name string) (string, error)
CheckPasswordForRequirements(password string) error CheckPasswordForRequirements(password string) error
} }
@ -128,32 +128,37 @@ func (us UserService) doesScopeExist(scopes []string, target string) bool {
return false return false
} }
func (us UserService) Create(ctx context.Context, name, password, sessionToken, scope string) (entity.UserEntity, error) { func (us UserService) Create(ctx context.Context, name, password, scope string) (entity.UserEntity, error) {
err := us.CheckPasswordForRequirements(password) err := us.CheckPasswordForRequirements(password)
if err != nil { if err != nil {
return entity.UserEntity{}, err return entity.UserEntity{}, err
} }
us.repo.Create(ctx, name, password, sessionToken, domain.ScopeArticleRead) token, err := uuid.NewV7()
if err != nil {
return entity.UserEntity{}, err
}
us.repo.Create(ctx, name, password, token.String(), domain.ScopeArticleRead)
return entity.UserEntity{}, nil return entity.UserEntity{}, nil
} }
func (us UserService) NewSessionToken(ctx context.Context, name string) error { func (us UserService) NewSessionToken(ctx context.Context, name string) (string, error) {
token, err := uuid.NewV7() token, err := uuid.NewV7()
if err != nil { if err != nil {
return err return "", err
} }
rows, err := us.repo.UpdateSessionToken(ctx, name, token.String()) rows, err := us.repo.UpdateSessionToken(ctx, name, token.String())
if err != nil { if err != nil {
return err return "", err
} }
if rows != 1 { if rows != 1 {
return fmt.Errorf("UserService.NewSessionToken %w", err) return "", fmt.Errorf("UserService.NewSessionToken %w", err)
} }
return nil return token.String(), nil
} }
func (us UserService) CheckPasswordForRequirements(password string) error { func (us UserService) CheckPasswordForRequirements(password string) error {