From 3e4b5a811a5da831fdd008f5f2bd7ec7b3ab0f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Bouzour=C3=A8ne?= Date: Sun, 22 Dec 2024 13:00:05 +0100 Subject: [PATCH] Welcome page on first login or after password change --- controllers/welcome.go | 119 +++++++++++++++++++++++++++++++++++++++++ go.mod | 4 ++ go.sum | 8 +++ main.go | 3 ++ middlewares/welcome.go | 59 ++++++++++++++++++++ models/users.go | 13 ++--- views/welcome.pug | 30 +++++++++++ 7 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 controllers/welcome.go create mode 100644 middlewares/welcome.go create mode 100644 views/welcome.pug diff --git a/controllers/welcome.go b/controllers/welcome.go new file mode 100644 index 0000000..bc4326b --- /dev/null +++ b/controllers/welcome.go @@ -0,0 +1,119 @@ +package controllers + +import ( + "fmt" + "strings" + + "git.readonly.ch/bouzoure/popvaud-people/helpers" + "git.readonly.ch/bouzoure/popvaud-people/models" + "github.com/go-playground/validator" + "github.com/gofiber/fiber/v2" +) + +type WelcomeValidation struct { + Email string `validate:"required,min=6,max=100,email"` + Name string `validate:"required,min=2,max=100"` + Password string `validate:"required,min=12,max=100"` + PasswordVerify string `validate:"required,eqfield=Password"` +} + +func WelcomePage(c *fiber.Ctx) error { + db, err := helpers.GetDatabase() + if err != nil { + return err + } + + userid, err := helpers.GetSessionUserId(c) + if err != nil { + return err + } + + var user models.User + result := db.First(&user, "id = ?", userid) + + if result.Error != nil { + return err + } + + if user.SkipWelcome { + return fiber.NewError(fiber.StatusForbidden, "Forbidden") + } + + var formErrors []string + emailUpdate := user.Email == "admin@invalid.tld" + + if c.Method() == "POST" { + data := WelcomeValidation{ + Email: c.FormValue("email", user.Email), + Name: c.FormValue("name"), + Password: c.FormValue("password"), + PasswordVerify: c.FormValue("password-verify"), + } + + validate := validator.New() + validErrs := validate.Struct(data) + + user.Email = data.Email + user.Name = data.Name + + if validErrs == nil { + passwordHash, err := helpers.HashPassword(data.Password) + if err != nil { + return err + } + + user.Password = passwordHash + user.SkipWelcome = true + + result = db.Save(&user) + if result.Error != nil { + return err + } + + redirectId := c.Query("redirect") + redirectUrl := "/" + + if len(redirectId) > 0 { + sess, err := helpers.GetSessionStore(c) + if err != nil { + return err + } + + redirectKey := fmt.Sprintf("redirect-%s", redirectId) + redirectVal := sess.Get(redirectKey) + + if redirectVal != nil { + redirectUrl = redirectVal.(string) + } + } + + return c.Redirect(redirectUrl) + } else { + for _, validErr := range validErrs.(validator.ValidationErrors) { + if validErr.Field() == "Email" { + formErrors = append(formErrors, "L'adresse email doit être valide.") + } + + if validErr.Field() == "Name" { + formErrors = append(formErrors, "Le nom doit contenir entre 2 et 100 caractères.") + } + + if validErr.Field() == "Password" { + formErrors = append(formErrors, "Le mot de passe doit contenir entre 12 et 100 caractères.") + } + + if validErr.Field() == "PasswordVerify" { + formErrors = append(formErrors, "Les mots de passe doivent correspondre.") + } + } + } + } + + return c.Render("welcome", fiber.Map{ + "PageTitle": "Paramètres du compte", + "Email": user.Email, + "Name": user.Name, + "EmailUpdate": emailUpdate, + "FormErrors": strings.Join(formErrors, "
"), + }, "layouts/main") +} diff --git a/go.mod b/go.mod index fac9644..381124a 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,9 @@ require ( github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator v9.31.0+incompatible // indirect github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/utils v1.1.0 // indirect github.com/gofiber/utils/v2 v2.0.0-beta.3 // indirect @@ -39,6 +42,7 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/klauspost/compress v1.17.2 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect diff --git a/go.sum b/go.sum index 016de18..056499b 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,12 @@ github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9g github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= +github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/storage/badger/v2 v2.0.1 h1:iIB5Dh2dypJjdEruYgBf7H4l5a98R5pVKVLk5wbY5bo= @@ -122,6 +128,8 @@ github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3x github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= diff --git a/main.go b/main.go index 5321357..e9424e0 100644 --- a/main.go +++ b/main.go @@ -82,12 +82,15 @@ func main() { // Middlewares app.Use(middlewares.AuthMiddleware) app.Use("/login", middlewares.DenyAuthMiddleware) + app.Use(middlewares.WelcomeMiddleware) // Controllers app.Get("/", controllers.Homepage) app.Get("/login", controllers.LoginForm) app.Post("/login", controllers.LoginProcess) app.Get("/logout", controllers.LogoutProcess) + app.Get("/welcome", controllers.WelcomePage) + app.Post("/welcome", controllers.WelcomePage) app.Get("/mfa/totp/enroll", controllers.TotpEnrollPage) listenAddr := fmt.Sprintf( diff --git a/middlewares/welcome.go b/middlewares/welcome.go new file mode 100644 index 0000000..085d3c1 --- /dev/null +++ b/middlewares/welcome.go @@ -0,0 +1,59 @@ +package middlewares + +import ( + "fmt" + + "git.readonly.ch/bouzoure/popvaud-people/helpers" + "git.readonly.ch/bouzoure/popvaud-people/models" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" +) + +func WelcomeMiddleware(c *fiber.Ctx) error { + if c.Path() == "/login" || c.Path() == "/welcome" { + return c.Next() + } + + db, err := helpers.GetDatabase() + if err != nil { + return err + } + + userid, err := helpers.GetSessionUserId(c) + if err != nil { + return err + } + + var user models.User + result := db.First(&user, "id = ?", userid) + + if result.Error != nil { + return err + } + + if user.SkipWelcome { + return c.Next() + } + + if c.Path() == "/" { + return c.Redirect("/welcome") + } + + id := uuid.NewString() + key := fmt.Sprintf("redirect-%s", id) + + sess, err := helpers.GetSessionStore(c) + if err != nil { + return err + } + + sess.Set(key, c.Path()) + sess.Save() + + redirectUrl := fmt.Sprintf( + "/welcome?redirect=%s", + id, + ) + + return c.Redirect(redirectUrl) +} diff --git a/models/users.go b/models/users.go index d195be6..d233e0b 100644 --- a/models/users.go +++ b/models/users.go @@ -8,10 +8,11 @@ import ( type User struct { gorm.Model - Name string - Email string - Password string - TotpSercet sql.NullString - DisabledAt sql.NullTime - IsAdmin bool + Name string + Email string + Password string + TotpSercet sql.NullString + DisabledAt sql.NullTime + IsAdmin bool + SkipWelcome bool } diff --git a/views/welcome.pug b/views/welcome.pug new file mode 100644 index 0000000..c81b1d5 --- /dev/null +++ b/views/welcome.pug @@ -0,0 +1,30 @@ +include partials/header.pug + +.container + #login-card.my-5 + .card + .card-header + | Paramètres du compte + .card-body + .alert.alert-danger + | !{.FormErrors} + form#login(method="post") + .mb-3 + label.form-label(for="email") Adresse email + if .EmailUpdate + input#email.form-control(type="email", required, name="email", value=.Email) + else + input#email.form-control(type="text", disabled, name="email", value=.Email) + .mb-3 + label.form-label(for="name") Nom complet + input#name.form-control(type="text", required, name="name", value=.Name) + .mb-3 + label.form-label(for="password") Nouveau mot de passe + input#password.form-control(type="password", required, name="password") + .mb-3 + label.form-label(for="password-verify") Vérifier le mot de passe + input#password-verify.form-control(type="password", required, name="password-verify") + .mt-3.text-end + button.btn.btn-primary(type="submit") + i.me-2(data-feather="save") + | Enregistrer