Add logout all sessions functionality

This commit is contained in:
Luis Eduardo Jeréz Girón
2024-07-23 19:03:04 -06:00
parent 836c671da0
commit eaece77e62
9 changed files with 264 additions and 0 deletions

View File

@@ -17,3 +17,16 @@ func (h *handlers) logoutHandler(c echo.Context) error {
h.servs.AuthService.ClearSessionCookie(c)
return htmx.RespondRedirect(c, "/auth/login")
}
func (h *handlers) logoutAllSessionsHandler(c echo.Context) error {
ctx := c.Request().Context()
reqCtx := reqctx.GetCtx(c)
err := h.servs.AuthService.DeleteAllUserSessions(ctx, reqCtx.User.ID)
if err != nil {
return htmx.RespondToastError(c, err.Error())
}
h.servs.AuthService.ClearSessionCookie(c)
return htmx.RespondRedirect(c, "/auth/login")
}

View File

@@ -30,4 +30,5 @@ func MountRouter(
}))
requireAuth.POST("/logout", h.logoutHandler)
requireAuth.POST("/logout-all", h.logoutAllSessionsHandler)
}

View File

@@ -0,0 +1,26 @@
package databases
import (
"net/http"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
)
func (h *handlers) indexPageHandler(c echo.Context) error {
return echoutil.RenderGomponent(c, http.StatusOK, indexPage())
}
func indexPage() gomponents.Node {
content := []gomponents.Node{
component.H1Text("Databases"),
}
return layout.Dashboard(layout.DashboardParams{
Title: "Databases",
Body: content,
})
}

View File

@@ -0,0 +1,23 @@
package databases
import (
"github.com/eduardolat/pgbackweb/internal/service"
"github.com/eduardolat/pgbackweb/internal/view/middleware"
"github.com/labstack/echo/v4"
)
type handlers struct {
servs *service.Service
}
func newHandlers(servs *service.Service) *handlers {
return &handlers{servs: servs}
}
func MountRouter(
parent *echo.Group, mids *middleware.Middleware, servs *service.Service,
) {
h := newHandlers(servs)
parent.GET("", h.indexPageHandler)
}

View File

@@ -0,0 +1,26 @@
package profile
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
)
func closeAllSessionsForm() gomponents.Node {
return component.CardBox(component.CardBoxParams{
Children: []gomponents.Node{
component.H2Text("Close all sessions"),
component.PText("This will log you out from all devices including this one."),
html.Button(
htmx.HxPost("/auth/logout-all"),
htmx.HxDisabledELT("this"),
htmx.HxConfirm("Are you sure you want to close all your sessions?"),
html.Class("mt-2 btn btn-error"),
component.SpanText("Close all sessions"),
lucide.LogOut(),
),
},
})
}

View File

@@ -0,0 +1,36 @@
package profile
import (
"net/http"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
"github.com/eduardolat/pgbackweb/internal/view/reqctx"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
)
func (h *handlers) indexPageHandler(c echo.Context) error {
reqCtx := reqctx.GetCtx(c)
return echoutil.RenderGomponent(c, http.StatusOK, indexPage(reqCtx.User))
}
func indexPage(user dbgen.User) gomponents.Node {
content := []gomponents.Node{
component.H1Text("Profile"),
html.Div(
html.Class("mt-4 grid grid-cols-2 gap-4"),
html.Div(updateUserForm(user)),
html.Div(closeAllSessionsForm()),
),
}
return layout.Dashboard(layout.DashboardParams{
Title: "Profile",
Body: content,
})
}

View File

@@ -0,0 +1,24 @@
package profile
import (
"github.com/eduardolat/pgbackweb/internal/service"
"github.com/eduardolat/pgbackweb/internal/view/middleware"
"github.com/labstack/echo/v4"
)
type handlers struct {
servs *service.Service
}
func newHandlers(servs *service.Service) *handlers {
return &handlers{servs: servs}
}
func MountRouter(
parent *echo.Group, mids *middleware.Middleware, servs *service.Service,
) {
h := newHandlers(servs)
parent.GET("", h.indexPageHandler)
parent.POST("", h.updateUserHandler)
}

View File

@@ -0,0 +1,111 @@
package profile
import (
"database/sql"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/validate"
"github.com/eduardolat/pgbackweb/internal/view/reqctx"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
)
func (h *handlers) updateUserHandler(c echo.Context) error {
reqCtx := reqctx.GetCtx(c)
ctx := c.Request().Context()
var formData struct {
Name string `form:"name" validate:"required"`
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"omitempty,min=6,max=50"`
PasswordConfirmation string `form:"password_confirmation" validate:"omitempty,eqfield=Password"`
}
if err := c.Bind(&formData); err != nil {
return htmx.RespondToastError(c, err.Error())
}
if err := validate.Struct(&formData); err != nil {
return htmx.RespondToastError(c, err.Error())
}
_, err := h.servs.UsersService.UpdateUser(ctx, dbgen.UsersServiceUpdateUserParams{
ID: reqCtx.User.ID,
Name: sql.NullString{String: formData.Name, Valid: true},
Email: sql.NullString{String: formData.Email, Valid: true},
Password: sql.NullString{String: formData.Password, Valid: formData.Password != ""},
})
if err != nil {
return htmx.RespondToastError(c, err.Error())
}
return htmx.RespondToastSuccess(c, "Profile updated")
}
func updateUserForm(user dbgen.User) gomponents.Node {
return component.CardBox(component.CardBoxParams{
Children: []gomponents.Node{
html.Form(
htmx.HxPost("/dashboard/profile"),
htmx.HxDisabledELT("find button"),
html.Class("space-y-2"),
component.H2Text("Update profile"),
component.InputControl(component.InputControlParams{
Name: "name",
Label: "Full name",
Placeholder: "Your full name",
Required: true,
Type: component.InputTypeText,
AutoComplete: "name",
Children: []gomponents.Node{
html.Value(user.Name),
},
}),
component.InputControl(component.InputControlParams{
Name: "email",
Label: "Email",
Placeholder: "Your email",
Required: true,
AutoComplete: "email",
Type: component.InputTypeEmail,
Children: []gomponents.Node{
html.Value(user.Email),
},
}),
component.InputControl(component.InputControlParams{
Name: "password",
Label: "Change password",
Placeholder: "New password",
AutoComplete: "new-password",
Type: component.InputTypePassword,
HelpText: "Leave empty to keep your current password",
}),
component.InputControl(component.InputControlParams{
Name: "password_confirmation",
Label: "Confirm password",
Placeholder: "Confirm new password",
AutoComplete: "new-password",
Type: component.InputTypePassword,
}),
html.Div(
html.Class("flex justify-end items-center space-x-2 pt-2"),
component.HxLoadingMd(),
html.Button(
html.Class("btn btn-primary"),
html.Type("submit"),
component.SpanText("Save changes"),
lucide.Save(),
),
),
),
},
})
}

View File

@@ -3,6 +3,8 @@ package dashboard
import (
"github.com/eduardolat/pgbackweb/internal/service"
"github.com/eduardolat/pgbackweb/internal/view/middleware"
"github.com/eduardolat/pgbackweb/internal/view/web/dashboard/databases"
"github.com/eduardolat/pgbackweb/internal/view/web/dashboard/profile"
"github.com/eduardolat/pgbackweb/internal/view/web/dashboard/summary"
"github.com/labstack/echo/v4"
)
@@ -11,4 +13,6 @@ func MountRouter(
parent *echo.Group, mids *middleware.Middleware, servs *service.Service,
) {
summary.MountRouter(parent.Group(""), mids, servs)
databases.MountRouter(parent.Group("/databases"), mids, servs)
profile.MountRouter(parent.Group("/profile"), mids, servs)
}