Compare commits

..

3 Commits

15 changed files with 161 additions and 71 deletions

1
go.mod
View File

@ -9,6 +9,7 @@ require (
require ( require (
github.com/a-h/templ v0.2.648 github.com/a-h/templ v0.2.648
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/gorilla/sessions v1.2.2 github.com/gorilla/sessions v1.2.2
github.com/labstack/echo-contrib v0.16.0 github.com/labstack/echo-contrib v0.16.0
github.com/labstack/echo/v4 v4.11.4 github.com/labstack/echo/v4 v4.11.4

2
go.sum
View File

@ -6,6 +6,8 @@ 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=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=

View File

@ -1,9 +1,10 @@
package handlers package handlers
import ( import (
"log"
"net/http" "net/http"
"templ-test/views"
"templ-test/views/auth" "templ-test/views/auth"
"templ-test/views/home"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -39,10 +40,17 @@ func (h *Handlers) AuthLoginPost(c echo.Context) error {
c.SetCookie(cookie) c.SetCookie(cookie)
// render // render
return Render(c, http.StatusOK, views.Home()) return Render(c, http.StatusOK, home.Home())
} }
func (h *Handlers) AuthShowCookies(c echo.Context) error { func (h *Handlers) AuthShowCookies(c echo.Context) error {
claims, err := ValidateJwt(c, h.cfg.SharedApiSecret, h.cfg.ApiServerUri)
if err != nil {
return Render(c, http.StatusInternalServerError, home.Error(err))
}
log.Println(claims)
cookies := GetCookieValues(c) cookies := GetCookieValues(c)
return Render(c, http.StatusOK, auth.ShowCookie(cookies)) return Render(c, http.StatusOK, auth.ShowCookie(cookies))
} }

View File

@ -1,12 +1,15 @@
package handlers package handlers
import ( import (
"errors"
"templ-test/client" "templ-test/client"
"templ-test/models" "templ-test/models"
"templ-test/services" "templ-test/services"
"time"
"github.com/a-h/templ" "github.com/a-h/templ"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -32,7 +35,9 @@ func NewHandlerClient(api client.ApiClient, cfg services.EnvConfig) *Handlers {
func (h *Handlers) Register(group echo.Group) { func (h *Handlers) Register(group echo.Group) {
group.GET("/", h.HomeHandler) group.GET("/", h.HomeHandler)
group.GET("/list", h.ListHandler) group.GET("/settings", h.Settings)
group.POST("/settings", h.SettingsPost)
//group.GET("/list", h.ListHandler)
auth := group.Group("/auth") auth := group.Group("/auth")
auth.GET("/login", h.AuthLogin) auth.GET("/login", h.AuthLogin)
@ -46,6 +51,43 @@ func Render(ctx echo.Context, statusCode int, t templ.Component) error {
return t.Render(ctx.Request().Context(), ctx.Response().Writer) return t.Render(ctx.Request().Context(), ctx.Response().Writer)
} }
type jwtToken struct {
Exp time.Time `json:"exp"`
Iss string `json:"iss"`
Authorized bool `json:"authorized"`
UserName string `json:"username"`
Scopes []string `json:"scopes"`
jwt.RegisteredClaims
}
func ValidateJwt(ctx echo.Context, sharedSecret, issuer string) (jwtToken, error) {
cookies := GetCookieValues(ctx)
if cookies.Token == "" {
return jwtToken{}, errors.New("JWT Bearer Token is missing")
}
token, err := jwt.ParseWithClaims(cookies.Token, &jwtToken{}, func(token *jwt.Token) (interface{}, error) {
return []byte(sharedSecret), nil
})
if err != nil {
return jwtToken{}, err
}
if !token.Valid {
return jwtToken{}, errors.New("invalid jwt token")
}
claims := token.Claims.(*jwtToken)
if !claims.Exp.After(time.Now()) {
return jwtToken{}, errors.New("the jwt token has expired")
}
if claims.Iss != issuer {
return jwtToken{}, errors.New("the issuer was invalid")
}
return *claims, nil
}
func GetCookieValues(ctx echo.Context) models.AllCookies { func GetCookieValues(ctx echo.Context) models.AllCookies {
m := models.AllCookies{} m := models.AllCookies{}

View File

@ -2,15 +2,25 @@ package handlers
import ( import (
"net/http" "net/http"
"templ-test/views" "templ-test/views/home"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
func (h *Handlers) HomeHandler(c echo.Context) error { func (h *Handlers) HomeHandler(c echo.Context) error {
return Render(c, http.StatusOK, views.Home()) return Render(c, http.StatusOK, home.Home())
} }
func (h *Handlers) ListHandler(c echo.Context) error { func (h *Handlers) Settings(c echo.Context) error {
return Render(c, http.StatusOK, views.List()) return Render(c, http.StatusOK, home.UserSettings())
} }
func (h *Handlers) SettingsPost(c echo.Context) error {
// take in the updated values from he user and write the cookies... tbd
return Render(c, http.StatusOK, home.UserSettings())
}
//func (h *Handlers) ListHandler(c echo.Context) error {
// return Render(c, http.StatusOK, views.List())
//}

View File

@ -8,8 +8,9 @@ import (
) )
type EnvConfig struct { type EnvConfig struct {
ApiServerUri string ApiServerUri string
CookieSecret string SharedApiSecret string
CookieSecret string
} }
func NewEnvConfig() EnvConfig { func NewEnvConfig() EnvConfig {
@ -22,7 +23,8 @@ func NewEnvConfig() EnvConfig {
} }
return EnvConfig{ return EnvConfig{
ApiServerUri: os.Getenv("ApiServerUri"), ApiServerUri: os.Getenv("ApiServerUri"),
CookieSecret: os.Getenv("CookieSecret"), SharedApiSecret: os.Getenv("SharedApiSecret"),
CookieSecret: os.Getenv("CookieSecret"),
} }
} }

View File

@ -1,10 +1,10 @@
package auth package auth
import "templ-test/views"
import "templ-test/models" import "templ-test/models"
import "templ-test/views/layout"
templ ShowCookie(m models.AllCookies) { templ ShowCookie(m models.AllCookies) {
@views.WithLayout("Cookie Explorer", true) { @layout.Testing("Cookie Explorer") {
<h2>These are stored as cookies</h2> <h2>These are stored as cookies</h2>
<p>Username: { m.Username }</p> <p>Username: { m.Username }</p>
<p>JWT Token: { m.Token }</p> <p>JWT Token: { m.Token }</p>

View File

@ -1,9 +1,9 @@
package auth package auth
import "templ-test/views" import "templ-test/views/layout"
templ AuthLogin() { templ AuthLogin() {
@views.WithLayout("Login", true) { @layout.WithLayout("Login", true) {
<form hx-post="/auth/login"> <form hx-post="/auth/login">
<div class="mb-3"> <div class="mb-3">
<label for="username" class="form-label">Username</label> <label for="username" class="form-label">Username</label>

10
views/home/error.templ Normal file
View File

@ -0,0 +1,10 @@
package home
import "templ-test/views/layout"
templ Error(message error) {
@layout.Testing("Error") {
<h1>Oops... :(</h1>
<h3>{ message.Error() } </h3>
}
}

View File

@ -1,9 +1,10 @@
package views package home
import "templ-test/views/components/bootstrap" import "templ-test/views/components/bootstrap"
import "templ-test/views/layout"
templ Home() { templ Home() {
@Testing("Home", true) { @layout.WithLayout("Home", true) {
<p> <p>
this should be above the alert this should be above the alert
</p> </p>
@ -14,9 +15,3 @@ templ Home() {
} }
} }
templ List() {
@Testing("Lists", true) {
}
}

View File

@ -0,0 +1,9 @@
package home
import "templ-test/views/layout"
templ UserSettings() {
@layout.Testing("Settings") {
<h2>This is not ready yet</h2>
}
}

27
views/layout/body.templ Normal file
View File

@ -0,0 +1,27 @@
package layout
templ WithLayout(pageName string, useDarkMode bool) {
<html>
@getHtmlHead()
<body>
@bootstrapNavBar()
@getBodyHeader(pageName)
<div class="container-fluid">
{ children... }
</div>
</body>
</html>
}
templ Testing(pageName string) {
<html>
@getHtmlHead()
<body>
@bootstrapNavBar()
@getBodyHeader(pageName)
<div class="container-fluid">
{ children... }
</div>
</body>
</html>
}

19
views/layout/header.templ Normal file
View File

@ -0,0 +1,19 @@
package layout
templ getHtmlHead() {
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://unpkg.com/htmx.org@1.9.11" integrity="sha384-0gxUXCCR8yv9FM2b+U3FDbsKthCI66oH5IA9fHppQq9DDMHuMauqq1ZHBpJxQ0J0" crossorigin="anonymous"></script>
<meta charset="utf-8"/>
<meta name="twitter:card" content="fill in later"/>
<meta name="twitter:image" content=""/>
<meta name="og:image" content=""/>
</head>
}
templ getBodyHeader(pageName string) {
<header>
<h1>{ pageName }</h1>
</header>
}

View File

@ -1,51 +1,7 @@
package views package layout
templ WithLayout(pageName string, useDarkMode bool) {
<html>
@getHtmlHead()
<body>
@bootstrapNavBar()
@getBodyHeader(pageName)
<div class="container-fluid">
{ children... }
</div>
</body>
</html>
}
templ Testing(pageName string, useDarkMode bool) {
<html>
@getHtmlHead()
<body>
@bootstrapNavBar()
@getBodyHeader(pageName)
<div class="container-fluid">
{ children... }
</div>
</body>
</html>
}
templ getHtmlHead() {
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://unpkg.com/htmx.org@1.9.11" integrity="sha384-0gxUXCCR8yv9FM2b+U3FDbsKthCI66oH5IA9fHppQq9DDMHuMauqq1ZHBpJxQ0J0" crossorigin="anonymous"></script>
<meta charset="utf-8"/>
<meta name="twitter:card" content="fill in later"/>
<meta name="twitter:image" content=""/>
<meta name="og:image" content=""/>
</head>
}
templ getBodyHeader(pageName string) {
<header>
<h1>{ pageName }</h1>
</header>
}
templ bootstrapNavBar() { templ bootstrapNavBar() {
<nav class="navbar navbar-expand-lg bg-body-tertiary" data-bs-theme="dark"> <nav class="navbar navbar-expand-lg bg-body-tertiary" data-bs-theme={ useLightOrDarkTheme(ctx)}>
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a> <a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">

9
views/layout/util.go Normal file
View File

@ -0,0 +1,9 @@
package layout
import (
"context"
)
func useLightOrDarkTheme(ctx context.Context) string {
return "dark"
}