Welcome page on first login or after password change

This commit is contained in:
William Bouzourène 2024-12-22 13:00:05 +01:00
parent 16ca31f27a
commit eb02ba5ba5
Signed by: bouzoure
SSH key fingerprint: SHA256:19MbXpLua4rUtk8tunMesD8KUKb91LXLHg8E/qTooww
7 changed files with 230 additions and 6 deletions

119
controllers/welcome.go Normal file
View file

@ -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, "<br>"),
}, "layouts/main")
}

4
go.mod
View file

@ -26,6 +26,9 @@ require (
github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/glebarez/go-sqlite v1.21.2 // 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/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect github.com/gofiber/utils v1.1.0 // indirect
github.com/gofiber/utils/v2 v2.0.0-beta.3 // 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/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.17.2 // 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-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect

8
go.sum
View file

@ -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/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 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= 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 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= 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= 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/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.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=

View file

@ -82,12 +82,15 @@ func main() {
// Middlewares // Middlewares
app.Use(middlewares.AuthMiddleware) app.Use(middlewares.AuthMiddleware)
app.Use("/login", middlewares.DenyAuthMiddleware) app.Use("/login", middlewares.DenyAuthMiddleware)
app.Use(middlewares.WelcomeMiddleware)
// Controllers // Controllers
app.Get("/", controllers.Homepage) app.Get("/", controllers.Homepage)
app.Get("/login", controllers.LoginForm) app.Get("/login", controllers.LoginForm)
app.Post("/login", controllers.LoginProcess) app.Post("/login", controllers.LoginProcess)
app.Get("/logout", controllers.LogoutProcess) app.Get("/logout", controllers.LogoutProcess)
app.Get("/welcome", controllers.WelcomePage)
app.Post("/welcome", controllers.WelcomePage)
app.Get("/mfa/totp/enroll", controllers.TotpEnrollPage) app.Get("/mfa/totp/enroll", controllers.TotpEnrollPage)
listenAddr := fmt.Sprintf( listenAddr := fmt.Sprintf(

59
middlewares/welcome.go Normal file
View file

@ -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)
}

View file

@ -8,10 +8,11 @@ import (
type User struct { type User struct {
gorm.Model gorm.Model
Name string Name string
Email string Email string
Password string Password string
TotpSercet sql.NullString TotpSercet sql.NullString
DisabledAt sql.NullTime DisabledAt sql.NullTime
IsAdmin bool IsAdmin bool
SkipWelcome bool
} }

30
views/welcome.pug Normal file
View file

@ -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