Added logout functionality and client error message for 500 status codes

This commit is contained in:
2025-02-14 17:36:07 +11:00
parent eff06c0da6
commit 5c8bec0ad2
9 changed files with 168 additions and 8 deletions

View File

@@ -48,7 +48,6 @@ func HandleLoginRequest(
config *config.Config, config *config.Config,
logger *zerolog.Logger, logger *zerolog.Logger,
conn *sql.DB, conn *sql.DB,
secretKey string,
) http.Handler { ) http.Handler {
return http.HandlerFunc( return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {

62
handlers/logout.go Normal file
View File

@@ -0,0 +1,62 @@
package handlers
import (
"database/sql"
"net/http"
"projectreshoot/config"
"projectreshoot/cookies"
"projectreshoot/jwt"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
// Retrieve and revoke the user's tokens
func revokeTokens(
config *config.Config,
conn *sql.DB,
r *http.Request,
) error {
// get the tokens from the cookies
atStr, rtStr := cookies.GetTokenStrings(r)
aT, err := jwt.ParseAccessToken(config, conn, atStr)
if err != nil {
return errors.Wrap(err, "jwt.ParseAccessToken")
}
rT, err := jwt.ParseRefreshToken(config, conn, rtStr)
if err != nil {
return errors.Wrap(err, "jwt.ParseRefreshToken")
}
// revoke the refresh token first as the access token expires quicker
// only matters if there is an error revoking the tokens
err = jwt.RevokeToken(conn, rT)
if err != nil {
return errors.Wrap(err, "jwt.RevokeToken")
}
err = jwt.RevokeToken(conn, aT)
if err != nil {
return errors.Wrap(err, "jwt.RevokeToken")
}
return nil
}
// Handle a logout request
func HandleLogout(
config *config.Config,
logger *zerolog.Logger,
conn *sql.DB,
) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
err := revokeTokens(config, conn, r)
if err != nil {
logger.Error().Err(err).Msg("Error occured on user logout")
w.WriteHeader(http.StatusInternalServerError)
return
}
cookies.DeleteCookie(w, "access", "/")
cookies.DeleteCookie(w, "refresh", "/")
w.Header().Set("HX-Redirect", "/login")
},
)
}

View File

@@ -36,8 +36,9 @@ func addRoutes(
config, config,
logger, logger,
conn, conn,
config.SecretKey,
)) ))
// Logout
mux.Handle("POST /logout", handlers.HandleLogout(config, logger, conn))
// Profile page // Profile page
} }

View File

@@ -1,12 +1,16 @@
@import url("https://fonts.googleapis.com/css2?family=Ubuntu+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Ubuntu+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap");
@import "tailwindcss"; @import "tailwindcss";
[x-cloak] {
display: none !important;
}
@theme inline { @theme inline {
--color-rosewater: var(--rosewater); --color-rosewater: var(--rosewater);
--color-flamingo: var(--flamingo); --color-flamingo: var(--flamingo);
--color-pink: var(--pink); --color-pink: var(--pink);
--color-mauve: var(--mauve); --color-mauve: var(--mauve);
--color-red: var(--red); --color-red: var(--red);
--color-dark-red: var(--dark-red);
--color-maroon: var(--maroon); --color-maroon: var(--maroon);
--color-peach: var(--peach); --color-peach: var(--peach);
--color-yellow: var(--yellow); --color-yellow: var(--yellow);
@@ -35,6 +39,7 @@
--pink: hsl(316, 73%, 69%); --pink: hsl(316, 73%, 69%);
--mauve: hsl(266, 85%, 58%); --mauve: hsl(266, 85%, 58%);
--red: hsl(347, 87%, 44%); --red: hsl(347, 87%, 44%);
--dark-red: hsl(343, 50%, 82%);
--maroon: hsl(355, 76%, 59%); --maroon: hsl(355, 76%, 59%);
--peach: hsl(22, 99%, 52%); --peach: hsl(22, 99%, 52%);
--yellow: hsl(35, 77%, 49%); --yellow: hsl(35, 77%, 49%);
@@ -64,6 +69,7 @@
--pink: hsl(316, 72%, 86%); --pink: hsl(316, 72%, 86%);
--mauve: hsl(267, 84%, 81%); --mauve: hsl(267, 84%, 81%);
--red: hsl(343, 81%, 75%); --red: hsl(343, 81%, 75%);
--dark-red: hsl(316, 19%, 27%);
--maroon: hsl(350, 65%, 77%); --maroon: hsl(350, 65%, 77%);
--peach: hsl(23, 92%, 75%); --peach: hsl(23, 92%, 75%);
--yellow: hsl(41, 86%, 83%); --yellow: hsl(41, 86%, 83%);

View File

@@ -0,0 +1,63 @@
package component
templ ErrorPopup() {
<div
x-cloak
x-show="showError"
class="absolute w-82 left-0 right-0 mt-20 mr-5 ml-auto"
x-transition:enter="transform translate-x-[100%] opacity-0 duration-200"
x-transition:enter-start="opacity-0 translate-x-[100%]"
x-transition:enter-end="opacity-100 translate-x-0"
x-transition:leave="opacity-0 duration-200"
x-transition:leave-start="opacity-100 translate-x-0"
x-transition:leave-end="opacity-0 translate-x-[100%]"
>
<div
role="alert"
class="rounded-sm bg-dark-red p-4"
>
<div class="flex justify-between">
<div class="flex items-center gap-2 text-red w-fit">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-5"
>
<path
fill-rule="evenodd"
d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355
12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309
0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75
0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0
01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z"
clip-rule="evenodd"
></path>
</svg>
<strong class="block font-medium">Something went wrong </strong>
</div>
<div class="flex">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 text-subtext0 hover:cursor-pointer"
@click="showError=false"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6 18L18 6M6 6l12 12"
></path>
</svg>
</div>
</div>
<p class="mt-2 text-sm text-red">
An error occured on the server. Please try again later,
or contact an administrator
</p>
</div>
</div>
}

View File

@@ -40,7 +40,9 @@ templ Footer() {
> >
<path <path
fill-rule="evenodd" fill-rule="evenodd"
d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293
3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4
4a1 1 0 010 1.414z"
clip-rule="evenodd" clip-rule="evenodd"
></path> ></path>
</svg> </svg>

View File

@@ -14,7 +14,11 @@ templ LoginForm(loginError string) {
} else { } else {
errCreds = "false" errCreds = "false"
} }
xdata := fmt.Sprintf("{credentialError: %s, errorMessage: '%s'}", errCreds, loginError) xdata := fmt.Sprintf(
"{credentialError: %s, errorMessage: '%s'}",
errCreds,
loginError,
)
}} }}
<form <form
hx-post="/login" hx-post="/login"
@@ -57,7 +61,12 @@ templ LoginForm(loginError string) {
viewBox="0 0 16 16" viewBox="0 0 16 16"
aria-hidden="true" aria-hidden="true"
> >
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"></path> <path
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8
4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0
0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1
1 0 1 0 0 2 1 1 0 0 0 0-2z"
></path>
</svg> </svg>
</div> </div>
</div> </div>
@@ -101,7 +110,12 @@ templ LoginForm(loginError string) {
viewBox="0 0 16 16" viewBox="0 0 16 16"
aria-hidden="true" aria-hidden="true"
> >
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"></path> <path
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8
4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0
0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1
1 0 1 0 0 2 1 1 0 0 0 0-2z"
></path>
</svg> </svg>
</div> </div>
</div> </div>

View File

@@ -72,6 +72,7 @@ templ navRight() {
rounded-lg px-4 py-2 text-md text-red rounded-lg px-4 py-2 text-md text-red
hover:bg-red/25 hover:cursor-pointer" hover:bg-red/25 hover:cursor-pointer"
role="menuitem" role="menuitem"
@click="isActive=false"
> >
Logout Logout
</button> </button>

View File

@@ -2,6 +2,7 @@ package layout
import "projectreshoot/view/component/nav" import "projectreshoot/view/component/nav"
import "projectreshoot/view/component/footer" import "projectreshoot/view/component/footer"
import "projectreshoot/view/component"
// Global page layout. Includes HTML document settings, header tags // Global page layout. Includes HTML document settings, header tags
// navbar and footer // navbar and footer
@@ -37,11 +38,22 @@ templ Global() {
<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script> <script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js"></script>
<script src="https://unpkg.com/alpinejs" defer></script> <script src="https://unpkg.com/alpinejs" defer></script>
<script>
// uncomment this line to enable logging of htmx events
//htmx.logAll();
</script>
</head> </head>
<body <body
class="bg-base text-text ubuntu-mono-regular" class="bg-base text-text ubuntu-mono-regular overflow-x-hidden"
x-data="{ showError: false }"
x-on:htmx:error="if ($event.detail.errorInfo.error.includes('Code 500'))
showError = true; setTimeout(() => showError = false, 6000)"
>
@component.ErrorPopup()
<div
id="main-content"
class="flex flex-col h-screen justify-between"
> >
<div id="main-content" class="flex flex-col h-screen justify-between">
@nav.Navbar() @nav.Navbar()
<div id="page-content" class="mb-auto"> <div id="page-content" class="mb-auto">
{ children... } { children... }