features/bootstrapping #1
@ -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
|
||||
}
|
||||
|
@ -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
3
go.mod
@ -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
6
go.sum
@ -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=
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
10
internal/views/bulma/hero.templ
Normal file
10
internal/views/bulma/hero.templ
Normal 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>
|
||||
}
|
11
internal/views/bulma/section.templ
Normal file
11
internal/views/bulma/section.templ
Normal 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>
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
}
|
||||
|
@ -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>
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
}
|
||||
|
15
internal/views/users/profile.templ
Normal file
15
internal/views/users/profile.templ
Normal 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>
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user