features/token-things #2

Merged
jtom38 merged 7 commits from features/token-things into main 2024-07-07 08:03:00 -07:00
16 changed files with 225 additions and 97 deletions

2
.gitignore vendored
View File

@ -10,7 +10,7 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
tmp/
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test

View File

@ -6,6 +6,7 @@ import (
"net/url" "net/url"
"git.jamestombleson.com/jtom38/newsbot-api/domain" "git.jamestombleson.com/jtom38/newsbot-api/domain"
"github.com/labstack/echo/v4"
) )
const ( const (
@ -16,6 +17,7 @@ type Users interface {
Login(username, password string) (domain.LoginResponse, error) Login(username, password string) (domain.LoginResponse, error)
SignUp(username, password string) (domain.BaseResponse, error) SignUp(username, password string) (domain.BaseResponse, error)
RefreshJwtToken(username, refreshToken string) (domain.LoginResponse, error) RefreshJwtToken(username, refreshToken string) (domain.LoginResponse, error)
RefreshJwtTokenFromContext(ctx echo.Context) (domain.LoginResponse, error)
RefreshSessionToken(jwtToken string) (domain.BaseResponse, error) RefreshSessionToken(jwtToken string) (domain.BaseResponse, error)
} }
@ -81,6 +83,23 @@ func (a userClient) RefreshJwtToken(username, refreshToken string) (domain.Login
return bind, nil return bind, nil
} }
func (a userClient) RefreshJwtTokenFromContext(ctx echo.Context) (domain.LoginResponse, error) {
resp := domain.LoginResponse{}
username, err := ctx.Cookie("newsbot.user")
if err != nil {
return resp, err
}
refreshToken, err := ctx.Cookie("newsbot.refreshToken")
if err != nil {
return resp, err
}
return a.RefreshJwtToken(username.Value, refreshToken.Value)
}
func (a userClient) RefreshSessionToken(jwtToken string) (domain.BaseResponse, error) { func (a userClient) RefreshSessionToken(jwtToken string) (domain.BaseResponse, error) {
endpoint := fmt.Sprintf("%s/%s/refresh/sessionToken", a.serverAddress, UserBaseRoute) endpoint := fmt.Sprintf("%s/%s/refresh/sessionToken", a.serverAddress, UserBaseRoute)

View File

@ -13,7 +13,7 @@ func main() {
ctx := context.Background() ctx := context.Background()
cfg := config.New() cfg := config.New()
apiClient := apiclient.New(cfg.ServerAddress) apiClient := apiclient.New(cfg.ApiServerAddress)
server := handlers.NewServer(ctx, cfg, apiClient) server := handlers.NewServer(ctx, cfg, apiClient)
fmt.Println("The server is online and waiting for requests.") fmt.Println("The server is online and waiting for requests.")

4
go.mod
View File

@ -4,7 +4,7 @@ go 1.22.1
require ( require (
git.jamestombleson.com/jtom38/newsbot-api v0.0.0-20240603002809-9237369e5a76 git.jamestombleson.com/jtom38/newsbot-api v0.0.0-20240603002809-9237369e5a76
github.com/a-h/templ v0.2.680 github.com/a-h/templ v0.2.747
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/labstack/echo/v4 v4.12.0 github.com/labstack/echo/v4 v4.12.0
@ -19,7 +19,7 @@ require (
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.22.0 // indirect golang.org/x/crypto v0.22.0 // indirect
golang.org/x/net v0.24.0 // indirect golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
) )

8
go.sum
View File

@ -1,7 +1,7 @@
git.jamestombleson.com/jtom38/newsbot-api v0.0.0-20240603002809-9237369e5a76 h1:B9t5fcfVerMjqnXXPUmYwdmUk76EoEL8x9IRehqg2c4= git.jamestombleson.com/jtom38/newsbot-api v0.0.0-20240603002809-9237369e5a76 h1:B9t5fcfVerMjqnXXPUmYwdmUk76EoEL8x9IRehqg2c4=
git.jamestombleson.com/jtom38/newsbot-api v0.0.0-20240603002809-9237369e5a76/go.mod h1:A3UdJyQ/IEy3utEwJiC4nbi0ohfgrUNRLTei2iZhLLA= git.jamestombleson.com/jtom38/newsbot-api v0.0.0-20240603002809-9237369e5a76/go.mod h1:A3UdJyQ/IEy3utEwJiC4nbi0ohfgrUNRLTei2iZhLLA=
github.com/a-h/templ v0.2.680 h1:TflYFucxp5rmOxAXB9Xy3+QHTk8s8xG9+nCT/cLzjeE= github.com/a-h/templ v0.2.747 h1:D0dQ2lxC3W7Dxl6fxQ/1zZHBQslSkTSvl5FxP/CfdKg=
github.com/a-h/templ v0.2.680/go.mod h1:NQGQOycaPKBxRB14DmAaeIpcGC1AOBPJEMO4ozS7m90= github.com/a-h/templ v0.2.747/go.mod h1:69ObQIbrcuwPCU32ohNaWce3Cb7qM5GMiqN1K+2yop4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
@ -35,8 +35,8 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=

View File

@ -10,6 +10,7 @@ import (
type Configs struct { type Configs struct {
ServerAddress string ServerAddress string
JwtSecret string JwtSecret string
ApiServerAddress string
} }
func New() Configs { func New() Configs {
@ -22,6 +23,7 @@ func getEnvConfig() Configs {
return Configs{ return Configs{
ServerAddress: os.Getenv("ServerAddress"), ServerAddress: os.Getenv("ServerAddress"),
JwtSecret: os.Getenv("JwtSecret"), JwtSecret: os.Getenv("JwtSecret"),
ApiServerAddress: os.Getenv("ApiServerAddress"),
} }
} }

View File

@ -3,38 +3,32 @@ package handlers
import ( import (
"net/http" "net/http"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/domain" apidomain "git.jamestombleson.com/jtom38/newsbot-api/domain"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/models" "git.jamestombleson.com/jtom38/newsbot-portal/internal/models"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/views/articles" "git.jamestombleson.com/jtom38/newsbot-portal/internal/views/articles"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/views/layout"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
func (h *Handler) ArticlesList(c echo.Context) error { func (h *Handler) ArticlesList(c echo.Context) error {
_, err := ValidateJwt(c, h.config.JwtSecret, h.config.ServerAddress) err := HasValidScope(c, apidomain.ScopeArticleRead)
if err != nil { if err != nil {
return Render(c, http.StatusOK, layout.Error(err)) return RenderError(c, err)
} }
userToken, err := c.Cookie(domain.CookieToken) resp, err := h.api.Articles.List(GetJwtToken(c), 0)
if err != nil { if err != nil {
return Render(c, http.StatusBadRequest, layout.Error(err)) return RenderError(c, err)
}
resp, err := h.api.Articles.List(userToken.Value, 0)
if err != nil {
return Render(c, http.StatusBadRequest, layout.Error(err))
} }
vm := models.ListArticlesViewModel{} vm := models.ListArticlesViewModel{}
for _, article := range resp.Payload { for _, article := range resp.Payload {
source, err := h.api.Sources.GetById(userToken.Value, article.SourceID) source, err := h.api.Sources.GetById(GetJwtToken(c), article.SourceID)
if err != nil { if err != nil {
return Render(c, http.StatusBadRequest, layout.Error(err)) return RenderError(c, err)
} }
item := models.ListArticleSourceModel { item := models.ListArticleSourceModel{
Article: article, Article: article,
Source: source.Payload[0], Source: source.Payload[0],
} }

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

@ -3,27 +3,22 @@ package handlers
import ( import (
"net/http" "net/http"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/domain"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/models" "git.jamestombleson.com/jtom38/newsbot-portal/internal/models"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/views/layout"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/views/sources" "git.jamestombleson.com/jtom38/newsbot-portal/internal/views/sources"
apidomain "git.jamestombleson.com/jtom38/newsbot-api/domain"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
func (h *Handler) ListAllSources(c echo.Context) error { func (h *Handler) ListAllSources(c echo.Context) error {
_, err := ValidateJwt(c, h.config.JwtSecret, h.config.ServerAddress) err := HasValidScope(c, apidomain.ScopeSourceRead)
if err != nil { if err != nil {
return Render(c, http.StatusOK, layout.Error(err)) return RenderError(c, err)
} }
userToken, err := c.Cookie(domain.CookieToken) resp, err := h.api.Sources.ListAll(GetJwtToken(c), 0)
if err != nil { if err != nil {
return Render(c, http.StatusBadRequest, layout.Error(err)) return RenderError(c, err)
}
resp, err := h.api.Sources.ListAll(userToken.Value, 0)
if err != nil {
return Render(c, http.StatusOK, layout.Error(err))
} }
return Render(c, http.StatusOK, sources.ListAll(models.ListAllSourcesViewModel{ return Render(c, http.StatusOK, sources.ListAll(models.ListAllSourcesViewModel{
@ -32,3 +27,12 @@ func (h *Handler) ListAllSources(c echo.Context) error {
Message: resp.Message, Message: resp.Message,
})) }))
} }
func (h *Handler) AddSource(c echo.Context) error {
err := HasValidScope(c, apidomain.ScopeSourceCreate)
if err != nil {
return RenderError(c, err)
}
return Render(c, http.StatusOK, sources.Add(models.AddSourcePayloadModel{}))
}

View File

@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"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" "git.jamestombleson.com/jtom38/newsbot-portal/internal/models"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/views/users" "git.jamestombleson.com/jtom38/newsbot-portal/internal/views/users"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -20,7 +20,10 @@ func (h *Handler) UserAfterLogin(c echo.Context) error {
resp, err := h.api.Users.Login(user, password) resp, err := h.api.Users.Login(user, password)
if err != nil { if err != nil {
return Render(c, http.StatusBadRequest, users.AfterLogin(err.Error(), false)) return Render(c, http.StatusBadRequest, users.AfterLogin(models.AfterLoginViewModel{
Success: false,
Message: err.Error(),
}))
} }
if user == "" { if user == "" {
@ -31,7 +34,11 @@ func (h *Handler) UserAfterLogin(c echo.Context) error {
SetCookie(c, domain.CookieRefreshToken, resp.RefreshToken, "/") SetCookie(c, domain.CookieRefreshToken, resp.RefreshToken, "/")
SetCookie(c, domain.CookieUser, user, "/") SetCookie(c, domain.CookieUser, user, "/")
return Render(c, http.StatusOK, users.AfterLogin("Login Successful!", true)) vm := models.AfterLoginViewModel{
Success: true,
Message: "Login Successful!",
}
return Render(c, http.StatusOK, users.AfterLogin(vm))
} }
func (h *Handler) UserSignUp(c echo.Context) error { func (h *Handler) UserSignUp(c echo.Context) error {
@ -44,11 +51,17 @@ func (h *Handler) UserAfterSignUp(c echo.Context) error {
resp, err := h.api.Users.SignUp(user, password) resp, err := h.api.Users.SignUp(user, password)
if err != nil { if err != nil {
return Render(c, http.StatusBadRequest, users.AfterLogin(err.Error(), false)) return Render(c, http.StatusBadRequest, users.AfterLogin(models.AfterLoginViewModel{
Success: false,
Message: err.Error(),
}))
} }
if resp.Message != "OK" { if resp.Message != "OK" {
msg := fmt.Sprintf("Failed to create account. Message: %s", resp.Message) msg := fmt.Sprintf("Failed to create account. Message: %s", resp.Message)
return Render(c, http.StatusBadRequest, users.AfterLogin(msg, false)) return Render(c, http.StatusBadRequest, users.AfterLogin(models.AfterLoginViewModel{
Message: msg,
Success: false,
}))
} }
return Render(c, http.StatusOK, users.AfterSignUp("Registration Successful!", true)) return Render(c, http.StatusOK, users.AfterSignUp("Registration Successful!", true))
} }
@ -58,9 +71,9 @@ func (h *Handler) UserProfile(c echo.Context) error {
} }
func (h *Handler) ForceLogout(c echo.Context) error { func (h *Handler) ForceLogout(c echo.Context) error {
_, err := ValidateJwt(c, h.config.JwtSecret, h.config.ServerAddress) err := IsLoggedIn(c)
if err != nil { if err != nil {
return Render(c, http.StatusOK, layout.Error(err)) return RenderError(c, err)
} }
h.api.Users.RefreshSessionToken(GetJwtToken(c)) h.api.Users.RefreshSessionToken(GetJwtToken(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)
}

View File

@ -7,3 +7,7 @@ type ListAllSourcesViewModel struct {
Message string Message string
Items []domain.SourceDto Items []domain.SourceDto
} }
type AddSourcePayloadModel struct {
}

6
internal/models/users.go Normal file
View File

@ -0,0 +1,6 @@
package models
type AfterLoginViewModel struct {
Message string
Success bool
}

View File

@ -0,0 +1,10 @@
package sources
import "git.jamestombleson.com/jtom38/newsbot-portal/internal/views/layout"
import "git.jamestombleson.com/jtom38/newsbot-portal/internal/models"
templ Add(model models.AddSourcePayloadModel) {
@layout.WithTemplate() {
<form hx-post="/sources/add"></form>
}
}

View File

@ -1,15 +1,17 @@
package users package users
import "git.jamestombleson.com/jtom38/newsbot-portal/internal/models"
// This is returned after the user logs into the application. // This is returned after the user logs into the application.
// It just returns a partial view because it will overlap with the existing template. // It just returns a partial view because it will overlap with the existing template.
templ AfterLogin(message string, success bool) { templ AfterLogin(vm models.AfterLoginViewModel) {
if success { if vm.Success {
<div class="notification is-success"> <div class="notification is-success">
{ message } { vm.Message }
</div> </div>
} else { } else {
<div class="notification is-error"> <div class="notification is-error">
{ message } { vm.Message }
</div> </div>
} }
} }