features/token-things #2

Merged
jtom38 merged 7 commits from features/token-things into main 2024-07-07 08:03:00 -07:00
3 changed files with 118 additions and 44 deletions
Showing only changes of commit 5361e3c655 - Show all commits

View File

@ -49,6 +49,7 @@ func NewServer(ctx context.Context, configs config.Configs, apiClient apiclient.
router.Pre(middleware.Logger()) router.Pre(middleware.Logger())
router.Pre(middleware.Recover()) router.Pre(middleware.Recover())
router.Use(middleware.Static("/internal/static")) router.Use(middleware.Static("/internal/static"))
//router.Use(RefreshJwtMiddleware(apiClient))
router.GET("/", s.HomeIndex) router.GET("/", s.HomeIndex)
router.GET("/about", s.HomeAbout) router.GET("/about", s.HomeAbout)
@ -57,57 +58,23 @@ func NewServer(ctx context.Context, configs config.Configs, apiClient apiclient.
debug.GET("/cookies", s.DebugCookies) debug.GET("/cookies", s.DebugCookies)
articles := router.Group("/articles") articles := router.Group("/articles")
//articles.Use(ValidateJwtMiddleware(configs.JwtSecret))
articles.GET("", s.ArticlesList) articles.GET("", s.ArticlesList)
sources := router.Group("/sources") sources := router.Group("/sources")
//sources.Use(ValidateJwtMiddleware(configs.JwtSecret))
sources.GET("", s.ListAllSources) sources.GET("", s.ListAllSources)
sources.GET("/add", s.AddSource)
users := router.Group("/users") users := router.Group("/users")
users.GET("/login", s.UserLogin) users.GET("/login", s.UserLogin)
users.POST("/login", s.UserAfterLogin) users.POST("/login", s.UserAfterLogin)
users.GET("/signup", s.UserSignUp) users.GET("/signup", s.UserSignUp)
users.POST("/signup", s.UserAfterSignUp) users.POST("/signup", s.UserAfterSignUp)
users.Use(ValidateJwtMiddleware(configs.JwtSecret))
users.GET("/logout", s.UsersLogout) users.GET("/logout", s.UsersLogout)
users.GET("/profile", s.UserProfile) users.GET("/profile", s.UserProfile)
s.Router = router s.Router = router
return s return s
} }
// 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, 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 {
// return JwtToken{}, errors.New(ErrJwtScopeMissing)
// //s.WriteMessage(c, ErrJwtScopeMissing, http.StatusUnauthorized)
// }
//
// if token.Iss != s.config.ServerAddress {
// return JwtToken{}, errors.New(ErrJwtInvalidIssuer)
// //s.WriteMessage(c, ErrJwtInvalidIssuer, http.StatusUnauthorized)
// }
//
// return token, nil
//}
//func (s *Handler) GetUserIdFromJwtToken(c echo.Context) int64 {
// token, err := s.getJwtTokenFromContext(c)
// if err != nil {
// s.WriteMessage(c, ErrJwtMissing, http.StatusUnauthorized)
// }
//
// return token.GetUserId()
//}

View File

@ -0,0 +1,64 @@
package handlers
import (
"time"
"git.jamestombleson.com/jtom38/newsbot-portal/apiclient"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/domain"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4"
)
func ValidateJwtMiddleware(jwtSecret string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cookie, err := c.Cookie(domain.CookieToken)
if err != nil {
return err
}
if cookie.Value == "" {
return echo.NewHTTPError(401, "Authorization token is missing.")
}
token, err := jwt.ParseWithClaims(cookie.Value, &jwtToken{}, func(token *jwt.Token) (interface{}, error) {
return []byte(jwtSecret), nil
})
if err != nil {
return err
}
if !token.Valid {
return echo.NewHTTPError(401, "Invalid authorization token.")
//return errors.New("invalid jwt token")
}
claims := token.Claims.(*jwtToken)
if !claims.Exp.After(time.Now()) {
return echo.NewHTTPError(401, "Your Authorization token has expired.")
//return errors.New("the jwt token has expired")
}
//if claims.Iss != issuer {
// return jwtToken{}, errors.New("the issuer was invalid")
//}
return next(c)
}
}
}
func RefreshJwtMiddleware(api apiclient.ApiClient) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
resp, err := api.Users.RefreshJwtTokenFromContext(c)
if err != nil {
return next(c)
}
SetCookie(c, domain.CookieToken, resp.Token, "/")
SetCookie(c, domain.CookieRefreshToken, resp.RefreshToken, "/")
return next(c)
}
}
}

View File

@ -4,9 +4,12 @@ import (
"context" "context"
"errors" "errors"
"net/http" "net/http"
"strings"
"time" "time"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/config"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/domain" "git.jamestombleson.com/jtom38/newsbot-portal/internal/domain"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/views/layout"
"github.com/a-h/templ" "github.com/a-h/templ"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -33,7 +36,31 @@ type jwtToken struct {
jwt.RegisteredClaims jwt.RegisteredClaims
} }
func ValidateJwt(ctx echo.Context, sharedSecret, issuer string) (jwtToken, error) { func IsLoggedIn(ctx echo.Context) error {
_, err := GetUserInfo(ctx)
if err != nil {
return err
}
return nil
}
func HasValidScope(ctx echo.Context, scope string) error {
token, err := GetUserInfo(ctx)
if err != nil {
return err
}
userScopes := strings.Join(token.Scopes, ",")
if strings.Contains(userScopes, scope) {
return nil
}
return errors.New("required permission is missing")
}
func GetUserInfo(ctx echo.Context) (jwtToken, error) {
cfg := config.New()
cookie, err := ctx.Cookie(domain.CookieToken) cookie, err := ctx.Cookie(domain.CookieToken)
if err != nil { if err != nil {
return jwtToken{}, err return jwtToken{}, err
@ -44,7 +71,7 @@ func ValidateJwt(ctx echo.Context, sharedSecret, issuer string) (jwtToken, error
} }
token, err := jwt.ParseWithClaims(cookie.Value, &jwtToken{}, func(token *jwt.Token) (interface{}, error) { token, err := jwt.ParseWithClaims(cookie.Value, &jwtToken{}, func(token *jwt.Token) (interface{}, error) {
return []byte(sharedSecret), nil return []byte(cfg.JwtSecret), nil
}) })
if err != nil { if err != nil {
return jwtToken{}, err return jwtToken{}, err
@ -58,9 +85,6 @@ func ValidateJwt(ctx echo.Context, sharedSecret, issuer string) (jwtToken, error
if !claims.Exp.After(time.Now()) { if !claims.Exp.After(time.Now()) {
return jwtToken{}, errors.New("the jwt token has expired") return jwtToken{}, errors.New("the jwt token has expired")
} }
//if claims.Iss != issuer {
// return jwtToken{}, errors.New("the issuer was invalid")
//}
return *claims, nil return *claims, nil
} }
@ -90,3 +114,22 @@ func Render(ctx echo.Context, statusCode int, t templ.Component) error {
return t.Render(request, ctx.Response().Writer) return t.Render(request, ctx.Response().Writer)
} }
func RenderError(ctx echo.Context, err error) error {
var t templ.Component = layout.Error(err)
ctx.Response().Writer.WriteHeader(200)
ctx.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTML)
// take the request context and make it a var
request := ctx.Request().Context()
//Check to see if we the echo context has the cookie we are looking for, if so, create a new context based on what we had and add the value
username, err := ctx.Cookie(domain.CookieUser)
if err == nil {
request = context.WithValue(request, domain.UserNameContext, username.Value)
} else {
request = context.WithValue(request, domain.UserNameContext, "")
}
return t.Render(request, ctx.Response().Writer)
}