features/cookies-maybe #2
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,6 +2,9 @@
|
|||||||
# If you prefer the allow list template instead of the deny list, see community template:
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
#
|
#
|
||||||
|
|
||||||
|
*.env
|
||||||
|
|
||||||
# Binaries for programs and plugins
|
# Binaries for programs and plugins
|
||||||
*.exe
|
*.exe
|
||||||
*.exe~
|
*.exe~
|
||||||
|
6
Justfile
6
Justfile
@ -1,4 +1,8 @@
|
|||||||
|
|
||||||
# Runs the 'templ generate` every time a file is updated and reloads the debugger
|
# Runs the 'templ generate` every time a file is updated and reloads the debugger
|
||||||
debug:
|
debug:
|
||||||
templ generate --watch --proxy="http://localhost:3000" --cmd="go run ."
|
templ generate --watch --proxy="http://localhost:3000" --cmd="go run cmd/main.go"
|
||||||
|
|
||||||
|
# Generates templ files
|
||||||
|
gen:
|
||||||
|
templ generate
|
||||||
|
@ -7,6 +7,7 @@ const (
|
|||||||
|
|
||||||
type ApiClient struct {
|
type ApiClient struct {
|
||||||
Auth Auth
|
Auth Auth
|
||||||
|
Demo DemoApiClient
|
||||||
|
|
||||||
ServerAddress string
|
ServerAddress string
|
||||||
}
|
}
|
||||||
@ -14,5 +15,6 @@ type ApiClient struct {
|
|||||||
func New(serverAddress string) ApiClient {
|
func New(serverAddress string) ApiClient {
|
||||||
return ApiClient{
|
return ApiClient{
|
||||||
Auth: newAuthClient(serverAddress),
|
Auth: newAuthClient(serverAddress),
|
||||||
|
Demo: newDemoClient(serverAddress),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"git.jamestombleson.com/jtom38/go-cook/api/domain"
|
"git.jamestombleson.com/jtom38/go-cook/api/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Auth interface {
|
type Auth interface {
|
||||||
Register(username, password string) error
|
Register(username, password string) error
|
||||||
Login(username, password string) error
|
Login(username, password string) (LoginResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthClient struct {
|
type AuthClient struct {
|
||||||
@ -42,7 +43,7 @@ func (a AuthClient) Register(username, password string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
//defer resp.Body.Close()
|
||||||
|
|
||||||
content, err := io.ReadAll(resp.Body)
|
content, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -58,31 +59,25 @@ func (a AuthClient) Register(username, password string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AuthClient) Login(username, password string) error {
|
type LoginResponse struct {
|
||||||
endpoint := fmt.Sprintf("%s/api/v1/auth/register", a.serverAddress)
|
Success bool `json:"success"`
|
||||||
req, err := http.NewRequest(http.MethodPost, endpoint, nil)
|
Token string `json:"token"`
|
||||||
if err != nil {
|
Type string `json:"type"`
|
||||||
return err
|
RefreshToken string `json:"refreshToken"`
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set(HeaderContentType, MIMEApplicationForm)
|
func (a AuthClient) Login(username, password string) (LoginResponse, error) {
|
||||||
req.Form.Add("username", username)
|
endpoint := fmt.Sprintf("%s/api/v1/auth/login", a.serverAddress)
|
||||||
req.Form.Add("password", password)
|
|
||||||
resp, err := a.client.Do(req)
|
param := url.Values{}
|
||||||
|
param.Set("username", username)
|
||||||
|
param.Set("password", password)
|
||||||
|
|
||||||
|
var bind = LoginResponse{}
|
||||||
|
err := PostUrlForm(a.client, endpoint, param, &bind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return LoginResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
return bind, nil
|
||||||
content, err := io.ReadAll(req.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var bind = domain.ErrorResponse{}
|
|
||||||
err = json.Unmarshal(content, &bind)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
47
client/demo.go
Normal file
47
client/demo.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DemoApiClient struct {
|
||||||
|
serverAddress string
|
||||||
|
client http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDemoClient(serverAddress string) DemoApiClient {
|
||||||
|
return DemoApiClient{
|
||||||
|
serverAddress: serverAddress,
|
||||||
|
client: http.Client{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelloBodyParam struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an example route to demo passing a body in
|
||||||
|
func (d DemoApiClient) Hello() error {
|
||||||
|
endpoint := fmt.Sprintf("%s/api/v1/demo/hello", d.serverAddress)
|
||||||
|
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := d.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
//defer resp.Body.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println(string(content))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
31
client/util.go
Normal file
31
client/util.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostUrlForm(client http.Client, endpoint string, param url.Values, t any) error {
|
||||||
|
payload := bytes.NewBufferString(param.Encode())
|
||||||
|
req, err := http.NewRequest(http.MethodPost, endpoint, payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set(HeaderContentType, MIMEApplicationForm)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(resp.Body)
|
||||||
|
err = decoder.Decode(&t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
31
cmd/main.go
Normal file
31
cmd/main.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"templ-test/client"
|
||||||
|
"templ-test/handlers"
|
||||||
|
"templ-test/services"
|
||||||
|
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
"github.com/labstack/echo-contrib/session"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg := services.NewEnvConfig()
|
||||||
|
|
||||||
|
// connect to api server
|
||||||
|
apiClient := client.New(cfg.ApiServerUri)
|
||||||
|
|
||||||
|
e := echo.New()
|
||||||
|
e.Use(session.Middleware(sessions.NewCookieStore([]byte(cfg.CookieSecret))))
|
||||||
|
e.Pre(middleware.Logger())
|
||||||
|
|
||||||
|
handler := e.Group("")
|
||||||
|
portalClient := handlers.NewHandlerClient(apiClient, cfg)
|
||||||
|
portalClient.Register(*handler)
|
||||||
|
|
||||||
|
fmt.Println("Listening on :1324")
|
||||||
|
e.Logger.Fatal(e.Start(":1324"))
|
||||||
|
}
|
1
go.mod
1
go.mod
@ -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
2
go.sum
@ -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=
|
||||||
|
@ -1,24 +1,56 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"templ-test/views"
|
"templ-test/views/auth"
|
||||||
|
"templ-test/views/home"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *Handlers) AuthLogin(c echo.Context) error {
|
func (h *Handlers) AuthLogin(c echo.Context) error {
|
||||||
return Render(c, http.StatusOK, views.AuthLogin())
|
return Render(c, http.StatusOK, auth.AuthLogin())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) AuthLoginPost(c echo.Context) error {
|
func (h *Handlers) AuthLoginPost(c echo.Context) error {
|
||||||
// check the form data
|
// check the form data
|
||||||
//user := c.FormValue("email")
|
user := c.FormValue("username")
|
||||||
//password := c.FormValue("password")
|
password := c.FormValue("password")
|
||||||
|
|
||||||
// send request to the API
|
// send request to the API
|
||||||
//h.api.
|
resp, err := h.api.Auth.Login(user, password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie := new(http.Cookie)
|
||||||
|
cookie.Name = CookieToken
|
||||||
|
cookie.Value = resp.Token
|
||||||
|
c.SetCookie(cookie)
|
||||||
|
|
||||||
|
cookie = new(http.Cookie)
|
||||||
|
cookie.Name = CookieRefreshToken
|
||||||
|
cookie.Value = resp.RefreshToken
|
||||||
|
c.SetCookie(cookie)
|
||||||
|
|
||||||
|
cookie = new(http.Cookie)
|
||||||
|
cookie.Name = CookieUser
|
||||||
|
cookie.Value = user
|
||||||
|
c.SetCookie(cookie)
|
||||||
|
|
||||||
// render
|
// render
|
||||||
return nil
|
return Render(c, http.StatusOK, home.Home())
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return Render(c, http.StatusOK, auth.ShowCookie(cookies))
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,48 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"templ-test/client"
|
"templ-test/client"
|
||||||
|
"templ-test/models"
|
||||||
"templ-test/services"
|
"templ-test/services"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/a-h/templ"
|
"github.com/a-h/templ"
|
||||||
"github.com/gorilla/sessions"
|
|
||||||
"github.com/labstack/echo-contrib/session"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CookieToken = "token"
|
||||||
|
CookieRefreshToken = "refresh"
|
||||||
|
CookieUser = "user"
|
||||||
|
)
|
||||||
|
|
||||||
type Handlers struct {
|
type Handlers struct {
|
||||||
Server *echo.Echo
|
|
||||||
api client.ApiClient
|
api client.ApiClient
|
||||||
|
cfg services.EnvConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandlerClient(api client.ApiClient, cfg services.EnvConfig) *Handlers {
|
func NewHandlerClient(api client.ApiClient, cfg services.EnvConfig) *Handlers {
|
||||||
h := Handlers{
|
h := Handlers{
|
||||||
api: api,
|
api: api,
|
||||||
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
e := echo.New()
|
return &h
|
||||||
e.Use(session.Middleware(sessions.NewCookieStore([]byte(cfg.CookieSecret))))
|
}
|
||||||
e.GET("/", h.HomeHandler)
|
|
||||||
e.GET("/list", h.ListHandler)
|
|
||||||
|
|
||||||
auth := e.Group("/auth")
|
func (h *Handlers) Register(group echo.Group) {
|
||||||
|
group.GET("/", h.HomeHandler)
|
||||||
|
group.GET("/settings", h.Settings)
|
||||||
|
group.POST("/settings", h.SettingsPost)
|
||||||
|
//group.GET("/list", h.ListHandler)
|
||||||
|
|
||||||
|
auth := group.Group("/auth")
|
||||||
auth.GET("/login", h.AuthLogin)
|
auth.GET("/login", h.AuthLogin)
|
||||||
auth.POST("/login", h.AuthLoginPost)
|
auth.POST("/login", h.AuthLoginPost)
|
||||||
|
auth.GET("/cookie", h.AuthShowCookies)
|
||||||
h.Server = e
|
|
||||||
return &h
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Render(ctx echo.Context, statusCode int, t templ.Component) error {
|
func Render(ctx echo.Context, statusCode int, t templ.Component) error {
|
||||||
@ -38,3 +50,61 @@ func Render(ctx echo.Context, statusCode int, t templ.Component) error {
|
|||||||
ctx.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTML)
|
ctx.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTML)
|
||||||
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 {
|
||||||
|
m := models.AllCookies{}
|
||||||
|
|
||||||
|
token, err := ctx.Cookie(CookieToken)
|
||||||
|
if err == nil {
|
||||||
|
m.Token = token.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := ctx.Cookie(CookieUser)
|
||||||
|
if err == nil {
|
||||||
|
m.Username = user.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh, err := ctx.Cookie(CookieRefreshToken)
|
||||||
|
if err == nil {
|
||||||
|
m.RefreshToken = refresh.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
@ -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())
|
||||||
|
//}
|
||||||
|
19
main.go
19
main.go
@ -1,19 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"templ-test/client"
|
|
||||||
"templ-test/handlers"
|
|
||||||
"templ-test/services"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cfg := services.NewEnvConfig()
|
|
||||||
|
|
||||||
// connect to api server
|
|
||||||
apiClient := client.New(cfg.ApiServerUri)
|
|
||||||
handler := handlers.NewHandlerClient(apiClient, cfg)
|
|
||||||
|
|
||||||
fmt.Println("Listening on :3000")
|
|
||||||
handler.Server.Start(":3000")
|
|
||||||
}
|
|
11
models/auth.go
Normal file
11
models/auth.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type AllCookies struct {
|
||||||
|
Username string
|
||||||
|
Token string
|
||||||
|
RefreshToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShowCookie struct {
|
||||||
|
AllCookies
|
||||||
|
}
|
@ -9,17 +9,22 @@ import (
|
|||||||
|
|
||||||
type EnvConfig struct {
|
type EnvConfig struct {
|
||||||
ApiServerUri string
|
ApiServerUri string
|
||||||
|
SharedApiSecret string
|
||||||
CookieSecret string
|
CookieSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEnvConfig() EnvConfig {
|
func NewEnvConfig() EnvConfig {
|
||||||
err := godotenv.Load()
|
_, err := os.Stat(".env")
|
||||||
|
if err == nil {
|
||||||
|
err = godotenv.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return EnvConfig{
|
return EnvConfig{
|
||||||
ApiServerUri: os.Getenv("ApiServerUri"),
|
ApiServerUri: os.Getenv("ApiServerUri"),
|
||||||
|
SharedApiSecret: os.Getenv("SharedApiSecret"),
|
||||||
CookieSecret: os.Getenv("CookieSecret"),
|
CookieSecret: os.Getenv("CookieSecret"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package views
|
|
||||||
|
|
||||||
templ AuthLogin() {
|
|
||||||
@WithLayout("Login", true) {
|
|
||||||
<form>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="exampleInputEmail1" class="form-label">Email address</label>
|
|
||||||
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"/>
|
|
||||||
<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="exampleInputPassword1" class="form-label">Password</label>
|
|
||||||
<input type="password" class="form-control" id="exampleInputPassword1"/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3 form-check">
|
|
||||||
<input type="checkbox" class="form-check-input" id="exampleCheck1"/>
|
|
||||||
<label class="form-check-label" for="exampleCheck1">Check me out</label>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary" hx-post="/auth/" >Submit</button>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
}
|
|
13
views/auth/cookie.templ
Normal file
13
views/auth/cookie.templ
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import "templ-test/models"
|
||||||
|
import "templ-test/views/layout"
|
||||||
|
|
||||||
|
templ ShowCookie(m models.AllCookies) {
|
||||||
|
@layout.Testing("Cookie Explorer") {
|
||||||
|
<h2>These are stored as cookies</h2>
|
||||||
|
<p>Username: { m.Username }</p>
|
||||||
|
<p>JWT Token: { m.Token }</p>
|
||||||
|
<p>RefreshToken: { m.RefreshToken }</p>
|
||||||
|
}
|
||||||
|
}
|
23
views/auth/login.templ
Normal file
23
views/auth/login.templ
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import "templ-test/views/layout"
|
||||||
|
|
||||||
|
templ AuthLogin() {
|
||||||
|
@layout.WithLayout("Login", true) {
|
||||||
|
<form hx-post="/auth/login">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">Username</label>
|
||||||
|
<input type="text" name="username" class="form-control" id="username"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="exampleInputPassword1" class="form-label">Password</label>
|
||||||
|
<input type="password" name="password" class="form-control" id="exampleInputPassword1"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="exampleCheck1"/>
|
||||||
|
<label class="form-check-label" for="exampleCheck1">Check me out</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
}
|
10
views/home/error.templ
Normal file
10
views/home/error.templ
Normal 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>
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
9
views/home/settings.templ
Normal file
9
views/home/settings.templ
Normal 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
27
views/layout/body.templ
Normal 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
19
views/layout/header.templ
Normal 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>
|
||||||
|
}
|
@ -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
9
views/layout/util.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package layout
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func useLightOrDarkTheme(ctx context.Context) string {
|
||||||
|
return "dark"
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user