features/cookies-maybe #2

Merged
jtom38 merged 4 commits from features/cookies-maybe into main 2024-04-13 11:53:29 -07:00
16 changed files with 265 additions and 91 deletions
Showing only changes of commit ea3fd917d6 - Show all commits

3
.gitignore vendored
View File

@ -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~

View File

@ -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

View File

@ -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),
} }
} }

View File

@ -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"`
Type string `json:"type"`
RefreshToken string `json:"refreshToken"`
}
func (a AuthClient) Login(username, password string) (LoginResponse, error) {
endpoint := fmt.Sprintf("%s/api/v1/auth/login", a.serverAddress)
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
} }
req.Header.Set(HeaderContentType, MIMEApplicationForm) return bind, nil
req.Form.Add("username", username) }
req.Form.Add("password", password)
resp, err := a.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
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
View 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
View 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
View 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"))
}

View File

@ -3,22 +3,46 @@ package handlers
import ( import (
"net/http" "net/http"
"templ-test/views" "templ-test/views"
"templ-test/views/auth"
"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, views.Home())
}
func (h *Handlers) AuthShowCookies(c echo.Context) error {
cookies := GetCookieValues(c)
return Render(c, http.StatusOK, auth.ShowCookie(cookies))
} }

View File

@ -2,35 +2,42 @@ package handlers
import ( import (
"templ-test/client" "templ-test/client"
"templ-test/models"
"templ-test/services" "templ-test/services"
"github.com/a-h/templ" "github.com/a-h/templ"
"github.com/gorilla/sessions"
"github.com/labstack/echo-contrib/session"
"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) func (h *Handlers) Register(group echo.Group) {
group.GET("/", h.HomeHandler)
auth := e.Group("/auth") 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 +45,24 @@ 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)
} }
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
}

19
main.go
View File

@ -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
View File

@ -0,0 +1,11 @@
package models
type AllCookies struct {
Username string
Token string
RefreshToken string
}
type ShowCookie struct {
AllCookies
}

View File

@ -13,9 +13,12 @@ type EnvConfig struct {
} }
func NewEnvConfig() EnvConfig { func NewEnvConfig() EnvConfig {
err := godotenv.Load() _, err := os.Stat(".env")
if err != nil { if err == nil {
log.Println(err) err = godotenv.Load()
if err != nil {
log.Println(err)
}
} }
return EnvConfig{ return EnvConfig{

View File

@ -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
View File

@ -0,0 +1,13 @@
package auth
import "templ-test/views"
import "templ-test/models"
templ ShowCookie(m models.AllCookies) {
@views.WithLayout("Cookie Explorer", true) {
<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
View File

@ -0,0 +1,23 @@
package auth
import "templ-test/views"
templ AuthLogin() {
@views.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>
}
}