features/bootstrapping #1

Merged
jtom38 merged 24 commits from features/bootstrapping into main 2024-06-02 19:55:25 -07:00
15 changed files with 112 additions and 37 deletions
Showing only changes of commit c873eaef3e - Show all commits

View File

@ -15,6 +15,8 @@ const (
type Users interface {
Login(username, password string) (domain.LoginResponse, error)
SignUp(username, password string) (domain.BaseResponse, error)
RefreshJwtToken(username, refreshToken string) (domain.LoginResponse, error)
RefreshSessionToken(jwtToken string) (domain.BaseResponse, error)
}
type userClient struct {
@ -79,11 +81,11 @@ func (a userClient) RefreshJwtToken(username, refreshToken string) (domain.Login
return bind, nil
}
func (a userClient) refreshSessionToken() (domain.BaseResponse, error) {
func (a userClient) RefreshSessionToken(jwtToken string) (domain.BaseResponse, error) {
endpoint := fmt.Sprintf("%s/%s/refresh/sessionToken", a.serverAddress, UserBaseRoute)
var bind = domain.BaseResponse{}
err := PostUrl(a.client, endpoint, &bind)
err := PostUrlAuthorized(a.client, endpoint, jwtToken, &bind)
if err != nil {
return domain.BaseResponse{}, err
}

View File

@ -3,10 +3,15 @@ package apiclient
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
const (
HeaderAuthorization = "Authorization"
)
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)
@ -30,8 +35,15 @@ func PostUrlForm(client http.Client, endpoint string, param url.Values, t any) e
return nil
}
func PostUrl(client http.Client, endpoint string, t any) error {
response, err := http.Post(endpoint, ApplicationJson, nil)
func PostUrlAuthorized(client http.Client, endpoint, jwtToken string, t any) error {
req, err := http.NewRequest(http.MethodPost, endpoint, nil)
if err != nil {
return err
}
req.Header.Add(HeaderAuthorization, fmt.Sprintf("%s %s", "Bearer", jwtToken))
response, err := client.Do(req)
//response, err := http.Post(endpoint, ApplicationJson, nil)
if err != nil {
return err
}

3
go.mod
View File

@ -5,8 +5,9 @@ go 1.22.1
require (
git.jamestombleson.com/jtom38/newsbot-api v0.0.0-20240510021003-4e9a17209f02
github.com/a-h/templ v0.2.680
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/joho/godotenv v1.5.1
github.com/labstack/echo-jwt/v4 v4.2.0
github.com/labstack/echo/v4 v4.12.0
)

6
go.sum
View File

@ -6,12 +6,14 @@ 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/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/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/labstack/echo-jwt/v4 v4.2.0 h1:odSISV9JgcSCuhgQSV/6Io3i7nUmfM/QkBeR5GVJj5c=
github.com/labstack/echo-jwt/v4 v4.2.0/go.mod h1:MA2RqdXdEn4/uEglx0HcUOgQSyBaTh5JcaHIan3biwU=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=

View File

@ -44,17 +44,11 @@ func NewServer(ctx context.Context, configs config.Configs, apiClient apiclient.
api: apiClient,
}
//jwtConfig := echojwt.Config{
// NewClaimsFunc: func(c echo.Context) jwt.Claims {
// return new(JwtToken)
// },
// SigningKey: []byte(configs.JwtSecret),
//}
router := echo.New()
router.Pre(middleware.RemoveTrailingSlash())
router.Pre(middleware.Logger())
router.Pre(middleware.Recover())
router.Use(middleware.Static("/internal/static"))
router.GET("/", s.HomeIndex)
router.GET("/about", s.HomeAbout)
@ -71,6 +65,9 @@ func NewServer(ctx context.Context, configs config.Configs, apiClient apiclient.
users.GET("/signup", s.UserSignUp)
users.POST("/signup", s.UserAfterSignUp)
//users.Use(echojwt.WithConfig(jwtConfig))
users.GET("/profile", s.UserProfile)
s.Router = router
return s
}

View File

@ -5,6 +5,7 @@ import (
"net/http"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/domain"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/views/layout"
"git.jamestombleson.com/jtom38/newsbot-portal/internal/views/users"
"github.com/labstack/echo/v4"
)
@ -46,4 +47,18 @@ func (h *Handler) UserAfterSignUp(c echo.Context) error {
return Render(c, http.StatusBadRequest, users.AfterLogin(msg, false))
}
return Render(c, http.StatusOK, users.AfterSignUp("Registration Successful!", true))
}
}
func (h *Handler) UserProfile(c echo.Context) error {
return Render(c, http.StatusOK, users.Profile())
}
func (h *Handler) ForceLogout(c echo.Context) error {
_, err := ValidateJwt(c, h.config.JwtSecret, h.config.ServerAddress)
if err != nil {
return Render(c, http.StatusOK, layout.Error(err))
}
h.api.Users.RefreshSessionToken(GetJwtToken(c))
return nil
}

View File

@ -8,7 +8,7 @@ import (
"git.jamestombleson.com/jtom38/newsbot-portal/internal/domain"
"github.com/a-h/templ"
"github.com/golang-jwt/jwt/v4"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4"
)
@ -23,11 +23,13 @@ func SetCookie(c echo.Context, key, value, path string) {
}
type jwtToken struct {
Exp time.Time `json:"exp"`
Iss string `json:"iss"`
Authorized bool `json:"authorized"`
UserName string `json:"username"`
Scopes []string `json:"scopes"`
Exp time.Time `json:"exp"`
Iss string `json:"iss"`
Authorized bool `json:"authorized"`
UserName string `json:"username"`
UserId int64 `json:"userId"`
Scopes []string `json:"scopes"`
SessionToken string `json:"sessionToken"`
jwt.RegisteredClaims
}
@ -63,6 +65,14 @@ func ValidateJwt(ctx echo.Context, sharedSecret, issuer string) (jwtToken, error
return *claims, nil
}
func GetJwtToken(c echo.Context) string {
cookie, err := c.Cookie(domain.CookieToken)
if err != nil {
return ""
}
return cookie.Value
}
func Render(ctx echo.Context, statusCode int, t templ.Component) error {
ctx.Response().Writer.WriteHeader(statusCode)
ctx.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTML)

View File

@ -0,0 +1,10 @@
package bulma
templ Hero(title, subtitle string) {
<section class="hero">
<div class="hero-body">
<p class="title">{ title }</p>
<p class="subtitle">{ subtitle }</p>
</div>
</section>
}

View File

@ -0,0 +1,11 @@
package bulma
templ Section(title, subtitle string) {
<section class="section">
<h1 class="title">Section</h1>
<h2 class="subtitle">
A simple container to divide your page into <strong>sections</strong>, like
the one you're currently reading.
</h2>
</section>
}

View File

@ -1,10 +1,11 @@
package home
import "git.jamestombleson.com/jtom38/newsbot-portal/internal/views/layout"
import "git.jamestombleson.com/jtom38/newsbot-portal/internal/views/bulma"
templ Index() {
@layout.WithTemplate() {
<h1 class="title">Welcome to Newsbot</h1>
@bulma.Hero("Welcome to Newsbot!", "Your new home for your news.")
<section class="section">
<p>

View File

@ -1,15 +1,7 @@
package layout
templ footer() {
<footer class="footer">
<div class="content has-text-centered">
<p>
<strong>Bulma</strong> by <a href="https://jgthms.com">Jeremy Thomas</a>.
The source code is licensed
<a href="http://opensource.org/licenses/mit-license.php">MIT</a>. The
website content is licensed
<a href="http://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY NC SA 4.0</a>.
</p>
</div>
</footer>
}
<div class="has-text-centered pin-botton">
<strong>Bulma</strong> by <a href="https://jgthms.com">Jeremy Thomas</a>.
</div>
}

View File

@ -2,6 +2,7 @@ package layout
templ header() {
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css"/>
<link rel="stylesheet" href="/css/main.css"/>
<script src="https://unpkg.com/htmx.org@1.9.11" integrity="sha384-0gxUXCCR8yv9FM2b+U3FDbsKthCI66oH5IA9fHppQq9DDMHuMauqq1ZHBpJxQ0J0" crossorigin="anonymous"></script>
<meta charset="utf-8"/>
<meta property="og:title" content=""/>
@ -9,4 +10,10 @@ templ header() {
<meta property="og:image" content=""/>
<meta property="og:description" content=""/>
<meta property="og:type" content=""/>
<style type="text/css">
.pin-bottom {
position: absolute;
bottom: 0;
}
</style>
}

View File

@ -32,7 +32,7 @@ templ navBar() {
<a class="navbar-link">{ getUsername(ctx) }</a>
<div class="navbar-dropdown">
<a class="navbar-item">Profile</a>
<a class="navbar-item" href="/users/profile" >Profile</a>
<a class="navbar-item">Logout</a>
</div>
</div>

View File

@ -4,15 +4,15 @@ templ WithTemplate() {
<!DOCTYPE html>
<html lang="en">
<head>
@header()
@header()
</head>
<body>
@navBar()
<br/>
<div class="container is-widescreen">
{ children... }
@footer()
</div>
</body>
@footer()
</html>
}

View File

@ -0,0 +1,15 @@
package users
import "git.jamestombleson.com/jtom38/newsbot-portal/internal/views/layout"
import "git.jamestombleson.com/jtom38/newsbot-portal/internal/views/bulma"
templ Profile() {
@layout.WithTemplate() {
@bulma.Hero("Profile", "Here you can update your profile 😀")
<button type="button" class="button">
<a href="/users/forcelogout">Logout Everywhere</a>
Logout Everywhere</button>
<p class="subtitle">This will force all active sessions to stop working and require a new login.</p>
}
}