// Copyright 2023 wanderer // SPDX-License-Identifier: AGPL-3.0-only package handlers import ( "context" "errors" "net/http" "strconv" "git.dotya.ml/mirre-mt/pcmt/ent" passwd "git.dotya.ml/mirre-mt/pcmt/modules/password" moduser "git.dotya.ml/mirre-mt/pcmt/modules/user" "github.com/gorilla/sessions" "github.com/labstack/echo-contrib/session" "github.com/labstack/echo/v4" ) func Signin() echo.HandlerFunc { return func(c echo.Context) error { defer addHeaders(c) reauth := false r := c.QueryParam("reauth") if r == "true" { reauth = true } sess, _ := session.Get(setting.SessionCookieName(), c) csrf := c.Get("csrf").(string) p := newPage() p.Title = "Sign in" p.Current = "signin" p.CSRF = csrf var uf *userSignin if sess != nil { if uname, ok := sess.Values["username"].(string); ok && uname != "" { if !reauth { return c.Redirect(http.StatusPermanentRedirect, "/home") } uf = &userSignin{Username: uname} } } if reauth { fl := sess.Values["reauthFlash"] if reFl, ok := fl.(string); !ok { p.Data["info"] = "re-login, please." } else { p.Data["reauthFlash"] = reFl if uf != nil { p.Data["form"] = uf } } } else { if i, ok := sess.Values["info"].(string); ok { p.Data["infoFlash"] = i delete(sess.Values, "info") if err := sess.Save(c.Request(), c.Response()); err != nil { log.Error("Failed to save session", "module", "middleware") return renderErrorPage( c, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)+" could not save the session cookie", err.Error(), ) } } } return c.Render( http.StatusOK, "signin.tmpl", p, ) } } func SigninPost(client *ent.Client) echo.HandlerFunc { return func(c echo.Context) error { defer addHeaders(c) cu := new(userSignin) if err := c.Bind(cu); err != nil { return renderErrorPage( c, http.StatusBadRequest, http.StatusText(http.StatusBadRequest), err.Error(), ) } csrf := c.Get("csrf").(string) username := cu.Username password := cu.Password p := newPage() data := make(map[string]any) p.Title = "Sign in" p.Current = "signin" p.CSRF = csrf if username == "" || password == "" { c.Logger().Warn("username or password not set, returning to /signin") data["flash"] = "you need to set both the username and the password" data["form"] = cu p.Data = data return c.Render( http.StatusBadRequest, "signin.tmpl", p, ) } 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) usr, err := moduser.QueryUser(ctx, client, username) if err == nil { log.Info("attempting login", "user", &usr.ID) if !passwd.Compare(usr.Password, password) { log.Warn("wrong credentials", "user", &usr.ID) data["flash"] = loginFailed data["form"] = cu p.Data = data return c.Render( http.StatusBadRequest, "signin.tmpl", p, ) } } else { if ent.IsNotFound(err) { c.Logger().Error("user not found") } else { // just log the error instead of returning it to the user and // redirect back to /signin. c.Logger().Error( http.StatusText(http.StatusUnauthorized)+" "+err.Error(), strconv.Itoa(http.StatusUnauthorized)+" "+http.StatusText(http.StatusUnauthorized)+" "+err.Error(), ) } data["form"] = cu data["flash"] = loginFailed p.Data = data return c.Render( http.StatusBadRequest, "signin.tmpl", p, ) } sess, _ := session.Get(setting.SessionCookieName(), c) if sess != nil { sess.Options = &sessions.Options{ Path: "/", MaxAge: setting.SessionMaxAge(), HttpOnly: true, Secure: setting.HTTPSecure(), SameSite: http.SameSiteStrictMode, } c.Logger().Debug("saving username to the session cookie") sess.Values["username"] = username err := sess.Save(c.Request(), c.Response()) if err != nil { c.Logger().Error("failed to save session") return renderErrorPage( c, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)+" (make sure you've got cookies enabled)", err.Error(), ) } } if err = moduser.UpdateUserLastLogin(ctx, client, usr.ID, usr.IsAdmin); err != nil { if !errors.Is(err, moduser.ErrUnfinishedSetupLastLoginUpdate) { return renderErrorPage( c, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), err.Error(), ) } log.Error("could not update LastLogin", "endpoint", "/home", "method", "post") } return c.Redirect(http.StatusMovedPermanently, "/home") } }