Gracefull error messages on password mishap

This commit is contained in:
Simon Martens
2025-05-22 23:01:59 +02:00
parent 2a08e5fec7
commit b81d783e8c
7 changed files with 109 additions and 63 deletions

View File

@@ -19,15 +19,17 @@ const (
TEMPLATE_LOGIN = "/login/"
)
var CSRF_CACHE *security.CSRFProtector
// TODO:
// - rate limiting
// - maybe csrf
func init() {
csrf_cache, err := security.NewCSRFProtector(time.Minute*5, time.Minute)
if err != nil {
panic(err)
}
CSRF_CACHE = csrf_cache
lp := &LoginPage{
StaticPage: pagemodels.StaticPage{
@@ -36,47 +38,60 @@ func init() {
Template: TEMPLATE_LOGIN,
URL: URL_LOGIN,
},
csrf_cache: csrf_cache,
}
app.Register(lp)
}
type LoginPage struct {
pagemodels.StaticPage
csrf_cache *security.CSRFProtector
}
func (p *LoginPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
router.GET(URL_LOGIN, p.GET(engine))
router.GET(URL_LOGIN, p.GET(engine, app))
router.POST(URL_LOGIN, p.POST(engine, app))
return nil
}
func (p *LoginPage) GET(engine *templating.Engine) HandleFunc {
func (p *LoginPage) GET(engine *templating.Engine, app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
data := make(map[string]any)
data["record"] = p
nonce, token, err := p.csrf_cache.GenerateTokenBundle()
nonce, token, err := CSRF_CACHE.GenerateTokenBundle()
if err != nil {
return engine.Response500(e, err, data)
}
data["csrf_nonce"] = nonce
data["csrf_token"] = token
// TODO: the function to delete tokens is not yet there
// as of right now, the tokens get only deleted from the clients
// We need to delete the tokens from the cache + table.
e.SetCookie(&http.Cookie{
Name: dbmodels.SESSION_COOKIE_NAME,
Path: "/",
MaxAge: -1,
})
e.Response.Header().Set("Clear-Site-Data", "\"cookies\"")
Logout(e, &app)
return engine.Response200(e, p.Template, data, p.Layout)
}
}
func Unauthorized(
engine *templating.Engine,
e *core.RequestEvent,
error error,
data map[string]any) error {
nonce, token, err := CSRF_CACHE.GenerateTokenBundle()
if err != nil {
return engine.Response500(e, err, data)
}
data["csrf_nonce"] = nonce
data["csrf_token"] = token
data["error"] = error.Error()
htm, err := engine.RenderToString(e, data, TEMPLATE_LOGIN, "blank")
if err != nil {
return engine.Response500(e, err, data)
}
return e.HTML(http.StatusUnauthorized, htm)
}
func (p *LoginPage) POST(engine *templating.Engine, app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
data := make(map[string]any)
@@ -94,17 +109,19 @@ func (p *LoginPage) POST(engine *templating.Engine, app core.App) HandleFunc {
return engine.Response500(e, err, data)
}
if _, err := p.csrf_cache.ValidateTokenBundle(formdata.CsrfNonce, formdata.CsrfToken); err != nil {
return engine.Response403(e, err, data)
data["formdata"] = formdata
if _, err := CSRF_CACHE.ValidateTokenBundle(formdata.CsrfNonce, formdata.CsrfToken); err != nil {
return Unauthorized(engine, e, fmt.Errorf("Ungültiges CSRF-Token oder Zeit abgelaufen. Bitte versuchen Sie es erneut."), data)
}
if formdata.Username == "" || formdata.Password == "" {
return engine.Response403(e, fmt.Errorf("Username and password are required"), data)
return Unauthorized(engine, e, fmt.Errorf("Benuztername oder Passwort falsch. Bitte versuchen Sie es erneut."), data)
}
record, err := app.FindFirstRecordByData(dbmodels.USERS_TABLE, dbmodels.USERS_EMAIL_FIELD, formdata.Username)
if err != nil || !record.ValidatePassword(formdata.Password) {
return engine.Response403(e, err, data)
return Unauthorized(engine, e, fmt.Errorf("Benuztername oder Passwort falsch. Bitte versuchen Sie es erneut."), data)
}
duration := time.Minute * 60
@@ -138,10 +155,6 @@ func (p *LoginPage) POST(engine *templating.Engine, app core.App) HandleFunc {
})
}
redirect := "/reihen"
if r := e.Request.URL.Query().Get("redirectTo"); r != "" {
redirect = r
}
return e.Redirect(303, redirect)
return RedirectTo(e)
}
}