go,tmpl: implement+activate validator
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
also ad initial password change: * switch the password field type to `password` * add a field for repeated password
This commit is contained in:
parent
ff87c35dd1
commit
96c0b53493
@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"git.dotya.ml/mirre-mt/pcmt/modules/validation"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo-contrib/session"
|
||||
"github.com/labstack/echo/v4"
|
||||
@ -73,6 +74,8 @@ func (a *App) SetServerSettings() {
|
||||
))
|
||||
}
|
||||
|
||||
e.Validator = validation.New()
|
||||
|
||||
// TODO: add check for prometheus config setting.
|
||||
// if true {
|
||||
// // import "github.com/labstack/echo-contrib/prometheus"
|
||||
|
5
go.mod
5
go.mod
@ -5,6 +5,7 @@ go 1.20
|
||||
require (
|
||||
entgo.io/ent v0.12.3
|
||||
github.com/CAFxX/httpcompression v0.0.8
|
||||
github.com/go-playground/validator/v10 v10.15.3
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/labstack/echo-contrib v0.15.0
|
||||
@ -29,7 +30,10 @@ require (
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/go-openapi/inflect v0.19.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
@ -38,6 +42,7 @@ require (
|
||||
github.com/hashicorp/hcl/v2 v2.17.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
|
16
go.sum
16
go.sum
@ -25,8 +25,17 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/fxamacker/cbor/v2 v2.2.1-0.20200511212021-28e39be4a84f/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
|
||||
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
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/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo=
|
||||
github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||
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=
|
||||
@ -71,6 +80,8 @@ github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8
|
||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406 h1:+OUpk+IVvmKU0jivOVFGtOzA6U5AWFs8HE4DRzWLOUE=
|
||||
github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/matthewhartstonge/argon2 v0.3.3 h1:38/hupgfzqO2UGxqXqmSqErE8KJvQnIxWWg7IXUqWgQ=
|
||||
@ -108,9 +119,14 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
|
@ -13,8 +13,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoSession = errors.New("No session found, please log in")
|
||||
ErrSessionExpired = errors.New("Session expired, log in again")
|
||||
ErrNoSession = errors.New("No session found, please log in")
|
||||
ErrSessionExpired = errors.New("Session expired, log in again")
|
||||
ErrValidationFailed = errors.New("Check your input data")
|
||||
)
|
||||
|
||||
func renderErrorPage(c echo.Context, status int, statusText, error string) error {
|
||||
|
@ -216,6 +216,7 @@ func CreateUser() echo.HandlerFunc { //nolint:gocognit
|
||||
data["flash"] = msg
|
||||
data["form"] = uc
|
||||
p.Data = data
|
||||
p.User = u
|
||||
|
||||
return c.Render(
|
||||
http.StatusBadRequest,
|
||||
@ -224,6 +225,15 @@ func CreateUser() echo.HandlerFunc { //nolint:gocognit
|
||||
)
|
||||
}
|
||||
|
||||
if err := c.Validate(uc); err != nil {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusBadRequest,
|
||||
http.StatusText(http.StatusBadRequest)+" - "+ErrValidationFailed.Error(),
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
var msg string
|
||||
|
||||
usr, err := moduser.CreateUser(ctx, dbclient, uc.Email, uc.Username, uc.Password, uc.IsAdmin)
|
||||
@ -309,6 +319,15 @@ func ViewUser() echo.HandlerFunc {
|
||||
|
||||
err := c.Bind(uid)
|
||||
if err == nil { //nolint:dupl
|
||||
if err := c.Validate(uid); err != nil {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusBadRequest,
|
||||
http.StatusText(http.StatusBadRequest)+" - make sure to pass in a proper UUID",
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
usr, err := getUserByID(ctx, dbclient, uid.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, moduser.ErrUserNotFound) { //nolint:gocritic
|
||||
@ -636,6 +655,15 @@ func UpdateUser() echo.HandlerFunc { //nolint:gocognit
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.Validate(uu); err != nil {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusBadRequest,
|
||||
http.StatusText(http.StatusBadRequest)+" - "+ErrValidationFailed.Error(),
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
if usr.Username != uu.Username {
|
||||
exists, err := moduser.UsernameExists(ctx, dbclient, uu.Username)
|
||||
if err != nil {
|
||||
|
@ -145,6 +145,15 @@ func SearchHIBP() echo.HandlerFunc {
|
||||
)
|
||||
}
|
||||
|
||||
if err := c.Validate(a); err != nil {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusBadRequest,
|
||||
http.StatusText(http.StatusBadRequest)+" - "+ErrValidationFailed.Error(),
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
breachNames, err := hibp.GetAllBreachesForAccount(a.Account)
|
||||
if err != nil {
|
||||
msg := "Error getting breaches for this account"
|
||||
|
@ -123,6 +123,15 @@ func SigninPost(client *ent.Client) echo.HandlerFunc {
|
||||
)
|
||||
}
|
||||
|
||||
if err := c.Validate(cu); err != nil {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusBadRequest,
|
||||
http.StatusText(http.StatusBadRequest)+" - "+ErrValidationFailed.Error(),
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
loginFailed := "Login Failed!"
|
||||
|
||||
ctx := context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||
|
@ -72,6 +72,15 @@ func SignupPost(client *ent.Client) echo.HandlerFunc {
|
||||
return c.Redirect(http.StatusFound, "/singup")
|
||||
}
|
||||
|
||||
if err := c.Validate(cu); err != nil {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusBadRequest,
|
||||
http.StatusText(http.StatusBadRequest)+" - "+ErrValidationFailed.Error(),
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
username := cu.Username
|
||||
email := cu.Email
|
||||
passwd := cu.Password
|
||||
|
@ -4,39 +4,40 @@
|
||||
package handlers
|
||||
|
||||
type userSignin struct {
|
||||
Username string `form:"username" json:"username" validate:"required,username,gte=2"`
|
||||
Password string `form:"password" json:"password" validate:"required,password,gte=12"`
|
||||
Username string `form:"username" json:"username" validate:"required,gte=2"`
|
||||
Password string `form:"password" json:"password" validate:"required,gte=20"`
|
||||
}
|
||||
|
||||
type userSignup struct {
|
||||
Username string `form:"username" json:"username" validate:"required,username,gte=2"`
|
||||
Email string `form:"email" json:"email" validate:"required,email"`
|
||||
Password string `form:"password" json:"password" validate:"required,password,gte=20"`
|
||||
Username string `form:"username" json:"username" validate:"required,gte=2"`
|
||||
Email string `form:"email" json:"email" validate:"required,email,gte=3"`
|
||||
Password string `form:"password" json:"password" validate:"required,gte=20"`
|
||||
}
|
||||
|
||||
// this struct is also used on update by admins, which is why the password fields are omitempty.
|
||||
// when users finish setting up, admins can no longer change their passwords.
|
||||
type userCreate struct {
|
||||
Username string `form:"username" json:"username" validate:"required,username,gte=2"`
|
||||
Email string `form:"email" json:"email" validate:"required,email"`
|
||||
Password string `form:"password" json:"password" validate:"omitempty,password,gte=20"`
|
||||
RepeatPassword string `form:"repeatPassword" json:"repeatPassword" validate:"omitempty,repeatPassword,gte=20"`
|
||||
IsAdmin bool `form:"isAdmin" json:"isAdmin" validate:"required,isAdmin"`
|
||||
IsActive *bool `form:"isActive" json:"isActive" validate:"omitempty,isActive"`
|
||||
Username string `form:"username" json:"username" validate:"required,gte=2"`
|
||||
Email string `form:"email" json:"email" validate:"required,email,gte=3"`
|
||||
Password string `form:"password" json:"password" validate:"omitempty,gte=20,eqfield=RepeatPassword"`
|
||||
RepeatPassword string `form:"repeatPassword" json:"repeatPassword" validate:"omitempty,gte=20,eqfield=Password"`
|
||||
IsAdmin bool `form:"isAdmin" json:"isAdmin" validate:"omitempty"`
|
||||
IsActive *bool `form:"isActive" json:"isActive" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type userID struct {
|
||||
ID string `param:"id" validate:"required,id"`
|
||||
ID string `param:"id" validate:"required,uuid"`
|
||||
}
|
||||
|
||||
type initPasswordChange struct {
|
||||
NewPassword string `form:"new-password" validate:"required,new-password,gte=20"`
|
||||
NewPassword string `form:"new-password" validate:"required,gte=20,eqfield=RepeatNewPassword"`
|
||||
RepeatNewPassword string `form:"repeat-new-password" validate:"required,gte=20,eqfield=NewPassword"`
|
||||
}
|
||||
|
||||
type hibpSearch struct {
|
||||
Account string `form:"search" validate:"required,search,gt=2"`
|
||||
Account string `form:"search" validate:"required,gt=2"`
|
||||
}
|
||||
|
||||
type hibpBreachDetail struct {
|
||||
BreachName string `param:"name" validate:"required,name,gt=0"`
|
||||
BreachName string `param:"name" validate:"required,gt=0"`
|
||||
}
|
||||
|
@ -65,6 +65,24 @@ func InitialPasswordChangePost() echo.HandlerFunc {
|
||||
)
|
||||
}
|
||||
|
||||
if pw.NewPassword != pw.RepeatNewPassword {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusBadRequest,
|
||||
http.StatusText(http.StatusBadRequest)+" - the passwords were not the same",
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
if err := c.Validate(pw); err != nil {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusBadRequest,
|
||||
http.StatusText(http.StatusBadRequest)+" - "+ErrValidationFailed.Error(),
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
err = moduser.ChangePassFirstLogin(ctx, dbclient, u.ID, pw.NewPassword)
|
||||
if err != nil {
|
||||
c.Logger().Errorf("error changing initial user password: %q", err)
|
||||
|
@ -38,6 +38,15 @@ func ViewHIBP() echo.HandlerFunc {
|
||||
)
|
||||
}
|
||||
|
||||
if err := c.Validate(h); err != nil {
|
||||
return renderErrorPage(
|
||||
c,
|
||||
http.StatusBadRequest,
|
||||
http.StatusText(http.StatusBadRequest)+" - "+ErrValidationFailed.Error(),
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
ctx, ok := c.Get("sloggerCtx").(context.Context)
|
||||
if !ok {
|
||||
ctx = context.WithValue(context.Background(), hibp.CtxKey{}, slogger)
|
||||
|
36
modules/validation/validator.go
Normal file
36
modules/validation/validator.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2023 wanderer <a_mirre at utb dot cz>
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// Validator defines a validator that can be used with the echo framework.
|
||||
type Validator struct {
|
||||
validator *validator.Validate
|
||||
}
|
||||
|
||||
// New provides a new instance of type Validator, initialised with the default
|
||||
// validator.
|
||||
func New() *Validator {
|
||||
return &Validator{
|
||||
validator: validator.New(
|
||||
validator.WithRequiredStructEnabled(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate implements echo framework's Validator interface.
|
||||
func (v *Validator) Validate(a any) error {
|
||||
if err := v.validator.Struct(a); err != nil {
|
||||
// Optionally, you could return the error to give each route more control over the status code
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -37,8 +37,18 @@
|
||||
<span class="absolute" role="img" aria-label="password lock icon">
|
||||
{{ template "svg-password.tmpl" }}
|
||||
</span>
|
||||
<input name="new-password" type="new-password"
|
||||
placeholder="New password" {{if and .Data.form .Data.form.Password}}value="{{.Data.form.Password}}"{{else}}{{end}}
|
||||
<input name="new-password" type="password"
|
||||
placeholder="New password" {{if and .Data.form .Data.form.NewPassword}}value="{{.Data.form.NewPassword}}"{{else}}{{end}}
|
||||
minlength=20
|
||||
required
|
||||
class="block w-full px-10 py-3 required:border-slate-500 dark:required:border-slate-300 required:border-3 valid:border text-gray-700 bg-white border rounded-lg dark:bg-gray-900 dark:text-gray-300 dark:valid:border-gray-600 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-blue-300 focus:outline-none focus:ring focus:ring-opacity-40">
|
||||
</div>
|
||||
<div class="relative flex items-center">
|
||||
<span class="absolute" role="img" aria-label="password lock icon">
|
||||
{{ template "svg-password.tmpl" }}
|
||||
</span>
|
||||
<input name="repeat-new-password" type="password"
|
||||
placeholder="New password repeated" {{if and .Data.form .Data.form.RepeatNewPassword}}value="{{.Data.form.RepeatNewPassword}}"{{else}}{{end}}
|
||||
minlength=20
|
||||
required
|
||||
class="block w-full px-10 py-3 required:border-slate-500 dark:required:border-slate-300 required:border-3 valid:border text-gray-700 bg-white border rounded-lg dark:bg-gray-900 dark:text-gray-300 dark:valid:border-gray-600 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-blue-300 focus:outline-none focus:ring focus:ring-opacity-40">
|
||||
|
Loading…
Reference in New Issue
Block a user