fix(go,tmpl): solve the Chromium/Safari logout...
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
...issue by deleting the session cookie after successful password change and forcing the user to re-authenticate. additionally, split the InitialPasswordChange func into separate "GET" and "POST" variants.
This commit is contained in:
parent
e8515d9a89
commit
1b2d860beb
@ -75,7 +75,7 @@ func (a *App) SetupRoutes() error {
|
|||||||
user := e.Group("/user", handlers.MiddlewareSession, xsrf)
|
user := e.Group("/user", handlers.MiddlewareSession, xsrf)
|
||||||
|
|
||||||
user.GET("/initial-password-change", handlers.InitialPasswordChange())
|
user.GET("/initial-password-change", handlers.InitialPasswordChange())
|
||||||
user.POST("/initial-password-change", handlers.InitialPasswordChange())
|
user.POST("/initial-password-change", handlers.InitialPasswordChangePost())
|
||||||
user.GET("/hibp-search", handlers.GetSearchHIBP())
|
user.GET("/hibp-search", handlers.GetSearchHIBP())
|
||||||
user.POST("/hibp-search", handlers.SearchHIBP())
|
user.POST("/hibp-search", handlers.SearchHIBP())
|
||||||
user.GET("/hibp-breach-details/:name", handlers.ViewHIBP())
|
user.GET("/hibp-breach-details/:name", handlers.ViewHIBP())
|
||||||
|
@ -36,7 +36,7 @@ func Home(client *ent.Client) echo.HandlerFunc {
|
|||||||
return c.Redirect(http.StatusSeeOther, "/signin")
|
return c.Redirect(http.StatusSeeOther, "/signin")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("gorilla session", "username", sess.Values["username"].(string))
|
log.Debug("session", "username", sess.Values["username"].(string), "endpoint", "/home")
|
||||||
username = sess.Values["username"].(string)
|
username = sess.Values["username"].(string)
|
||||||
|
|
||||||
// example denial.
|
// example denial.
|
||||||
@ -94,17 +94,22 @@ func Home(client *ent.Client) echo.HandlerFunc {
|
|||||||
p.Name = username
|
p.Name = username
|
||||||
p.User = u
|
p.User = u
|
||||||
|
|
||||||
data := make(map[string]any)
|
if flsh, ok := sess.Values["flash"].(string); ok {
|
||||||
flash := sess.Values["flash"]
|
p.Data["flash"] = flsh
|
||||||
|
|
||||||
if flash != nil {
|
|
||||||
data["flash"] = flash.(string)
|
|
||||||
|
|
||||||
delete(sess.Values, "flash")
|
delete(sess.Values, "flash")
|
||||||
|
|
||||||
_ = sess.Save(c.Request(), c.Response())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := sess.Values["reauthFlash"].(string); ok {
|
||||||
|
p.Data["info"] = "First time after changing the password, yay!"
|
||||||
|
|
||||||
|
// if this is the first login after the initial password change, delete
|
||||||
|
// the cookie value.
|
||||||
|
delete(sess.Values, "reauthFlash")
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = sess.Save(c.Request(), c.Response())
|
||||||
|
|
||||||
err := c.Render(http.StatusOK, "home.tmpl", p)
|
err := c.Render(http.StatusOK, "home.tmpl", p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger().Errorf("error: %q", err)
|
c.Logger().Errorf("error: %q", err)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -30,6 +31,25 @@ func GetSearchHIBP() echo.HandlerFunc {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !u.IsAdmin {
|
||||||
|
ctx := context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||||
|
|
||||||
|
f, err := moduser.UsrFinishedSetup(ctx, dbclient, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f {
|
||||||
|
log.Warn("resource access attempt without performing the initial password change", "user", u.Username, "endpoint", "/user/hibp-search")
|
||||||
|
return c.Redirect(http.StatusSeeOther, "/user/initial-password-change")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
csrf := c.Get("csrf").(string)
|
csrf := c.Get("csrf").(string)
|
||||||
p := newPage()
|
p := newPage()
|
||||||
|
|
||||||
@ -90,6 +110,29 @@ func SearchHIBP() echo.HandlerFunc {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !u.IsAdmin {
|
||||||
|
ctx := context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||||
|
|
||||||
|
f, err := moduser.UsrFinishedSetup(ctx, dbclient, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f {
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
http.StatusText(http.StatusUnauthorized)+" - you need to perform your initial password change before accessing this resource",
|
||||||
|
"user never changed password",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
csrf := c.Get("csrf").(string)
|
csrf := c.Get("csrf").(string)
|
||||||
|
|
||||||
a := new(hibpSearch)
|
a := new(hibpSearch)
|
||||||
|
@ -20,10 +20,27 @@ func Signin() echo.HandlerFunc {
|
|||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
addHeaders(c)
|
addHeaders(c)
|
||||||
|
|
||||||
if sess, _ := session.Get(setting.SessionCookieName(), c); sess != nil {
|
reauth := false
|
||||||
|
r := c.QueryParam("reauth")
|
||||||
|
|
||||||
|
if r == "true" {
|
||||||
|
reauth = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// slog.Info("XXX session cookie name:", setting.SessionCookieName())
|
||||||
|
|
||||||
|
sess, _ := session.Get(setting.SessionCookieName(), c)
|
||||||
|
|
||||||
|
var uf *userSignin
|
||||||
|
|
||||||
|
if sess != nil {
|
||||||
username := sess.Values["username"]
|
username := sess.Values["username"]
|
||||||
if username != nil {
|
if username != nil {
|
||||||
return c.Redirect(http.StatusFound, "/home")
|
if !reauth {
|
||||||
|
return c.Redirect(http.StatusPermanentRedirect, "/home")
|
||||||
|
}
|
||||||
|
|
||||||
|
uf = &userSignin{Username: username.(string)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,6 +51,18 @@ func Signin() echo.HandlerFunc {
|
|||||||
p.Current = "signin"
|
p.Current = "signin"
|
||||||
p.CSRF = csrf
|
p.CSRF = csrf
|
||||||
|
|
||||||
|
if reauth {
|
||||||
|
fl := sess.Values["reauthFlash"]
|
||||||
|
if _, ok := fl.(string); !ok {
|
||||||
|
p.Data["flash"] = "re-login please"
|
||||||
|
} else {
|
||||||
|
p.Data["reauthFlash"] = fl.(string)
|
||||||
|
if uf != nil {
|
||||||
|
p.Data["form"] = uf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return c.Render(
|
return c.Render(
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
"signin.tmpl",
|
"signin.tmpl",
|
||||||
|
@ -13,6 +13,107 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func InitialPasswordChangePost() echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
addHeaders(c)
|
||||||
|
|
||||||
|
u, ok := c.Get("sessUsr").(moduser.User)
|
||||||
|
if !ok {
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
http.StatusText(http.StatusUnauthorized)+", perhaps you need to log in first?",
|
||||||
|
"Username was nil",
|
||||||
|
)
|
||||||
|
} else if u.IsAdmin {
|
||||||
|
status := http.StatusUnauthorized
|
||||||
|
msg := http.StatusText(status)
|
||||||
|
|
||||||
|
return renderErrorPage(
|
||||||
|
c, status, msg+": You should not be here", "This endpoint is for users only",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, ok := c.Get("sloggerCtx").(context.Context)
|
||||||
|
if !ok {
|
||||||
|
ctx = context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := moduser.UsrFinishedSetup(ctx, dbclient, u.ID)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
case f:
|
||||||
|
return c.Redirect(http.StatusSeeOther, "/user/init-password-change")
|
||||||
|
}
|
||||||
|
|
||||||
|
pw := new(initPasswordChange)
|
||||||
|
|
||||||
|
if err := c.Bind(pw); err != nil {
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
http.StatusText(http.StatusBadRequest),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = moduser.ChangePassFirstLogin(ctx, dbclient, u.ID, pw.NewPassword)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Errorf("error changing initial user password: %q", err)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, moduser.ErrPasswordEmpty):
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
http.StatusText(http.StatusBadRequest),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
case errors.Is(err, moduser.ErrNewPasswordCannotEqual):
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
http.StatusText(http.StatusBadRequest)+" - the new password needs to be different from the original",
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("successfully performed initial password change", "user", u.Username)
|
||||||
|
|
||||||
|
if sess, ok := c.Get("sess").(*sessions.Session); ok {
|
||||||
|
sess.Values["reauthFlash"] = "Successfully updated your password, log in again, please"
|
||||||
|
|
||||||
|
if err = sess.Save(c.Request(), c.Response()); err != nil {
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError)+" - could not change the session cookie",
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Redirect(http.StatusSeeOther, "/signin?reauth=true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func InitialPasswordChange() echo.HandlerFunc {
|
func InitialPasswordChange() echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
addHeaders(c)
|
addHeaders(c)
|
||||||
@ -42,105 +143,41 @@ func InitialPasswordChange() echo.HandlerFunc {
|
|||||||
f, err := moduser.UsrFinishedSetup(ctx, dbclient, u.ID)
|
f, err := moduser.UsrFinishedSetup(ctx, dbclient, u.ID)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case c.Request().Method == "POST":
|
case err != nil:
|
||||||
switch {
|
return renderErrorPage(
|
||||||
case err != nil:
|
c,
|
||||||
return renderErrorPage(
|
http.StatusInternalServerError,
|
||||||
c,
|
http.StatusText(http.StatusInternalServerError),
|
||||||
http.StatusInternalServerError,
|
err.Error(),
|
||||||
http.StatusText(http.StatusInternalServerError),
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
case f:
|
|
||||||
return c.Redirect(http.StatusSeeOther, "/user/init-password-change")
|
|
||||||
}
|
|
||||||
|
|
||||||
pw := new(initPasswordChange)
|
|
||||||
|
|
||||||
if err := c.Bind(pw); err != nil {
|
|
||||||
return renderErrorPage(
|
|
||||||
c,
|
|
||||||
http.StatusBadRequest,
|
|
||||||
http.StatusText(http.StatusBadRequest),
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = moduser.ChangePassFirstLogin(ctx, dbclient, u.ID, pw.NewPassword)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Errorf("error changing initial user password: %q", err)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, moduser.ErrPasswordEmpty):
|
|
||||||
return renderErrorPage(
|
|
||||||
c,
|
|
||||||
http.StatusBadRequest,
|
|
||||||
http.StatusText(http.StatusBadRequest),
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
case errors.Is(err, moduser.ErrNewPasswordCannotEqual):
|
|
||||||
return renderErrorPage(
|
|
||||||
c,
|
|
||||||
http.StatusBadRequest,
|
|
||||||
http.StatusText(http.StatusBadRequest)+" - the new password needs to be different from the original",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return renderErrorPage(
|
|
||||||
c,
|
|
||||||
http.StatusInternalServerError,
|
|
||||||
http.StatusText(http.StatusInternalServerError),
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sess, ok := c.Get("sess").(*sessions.Session); ok {
|
|
||||||
sess.Values["flash"] = "Successfully updated your password"
|
|
||||||
_ = sess.Save(c.Request(), c.Response())
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Redirect(http.StatusSeeOther, "/home")
|
|
||||||
|
|
||||||
case c.Request().Method == "GET":
|
|
||||||
switch {
|
|
||||||
case err != nil:
|
|
||||||
return renderErrorPage(
|
|
||||||
c,
|
|
||||||
http.StatusInternalServerError,
|
|
||||||
http.StatusText(http.StatusInternalServerError),
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
case f:
|
|
||||||
return c.Redirect(http.StatusSeeOther, "/home")
|
|
||||||
}
|
|
||||||
|
|
||||||
csrf := c.Get("csrf").(string)
|
|
||||||
p := newPage()
|
|
||||||
|
|
||||||
p.Title = "Initial password change"
|
|
||||||
p.Current = "init-password-change"
|
|
||||||
p.CSRF = csrf
|
|
||||||
p.User = u
|
|
||||||
|
|
||||||
err := c.Render(
|
|
||||||
http.StatusOK,
|
|
||||||
"user/init-password-change.tmpl",
|
|
||||||
p,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
c.Logger().Errorf("error: %q", err)
|
|
||||||
|
|
||||||
return renderErrorPage(
|
case f:
|
||||||
c,
|
return c.Redirect(http.StatusSeeOther, "/home")
|
||||||
http.StatusInternalServerError,
|
}
|
||||||
http.StatusText(http.StatusInternalServerError),
|
|
||||||
err.Error(),
|
csrf := c.Get("csrf").(string)
|
||||||
)
|
p := newPage()
|
||||||
}
|
|
||||||
|
p.Title = "Initial password change"
|
||||||
|
p.Current = "init-password-change"
|
||||||
|
p.CSRF = csrf
|
||||||
|
p.User = u
|
||||||
|
p.Name = u.Username
|
||||||
|
|
||||||
|
err = c.Render(
|
||||||
|
http.StatusOK,
|
||||||
|
"user/init-password-change.tmpl",
|
||||||
|
p,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Errorf("error: %q", err)
|
||||||
|
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -4,13 +4,22 @@
|
|||||||
<main class="grow">
|
<main class="grow">
|
||||||
<div class="px-2 md:px-0 place-items-center text-center">
|
<div class="px-2 md:px-0 place-items-center text-center">
|
||||||
{{ if and .Data .Data.flash }}
|
{{ if and .Data .Data.flash }}
|
||||||
<h1 class="text-xl text-pink-600 dark:text-pink-500 py-2">
|
<h1 class="mt-8 text-xl text-pink-600 dark:text-pink-500 py-2">
|
||||||
{{ .Data.flash }}
|
{{ .Data.flash }}
|
||||||
</h1>
|
</h1>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{ if and .Data .Data.info }}
|
||||||
|
<h2 class="mt-4 text-xl font-bold text-green-600 dark:text-green-500 py-2">
|
||||||
|
{{ .Data.info }}
|
||||||
|
</h2>
|
||||||
|
{{- end }}
|
||||||
{{ if .Name -}}
|
{{ if .Name -}}
|
||||||
<h1 class="mt-20 text-2xl text-pink-400 font-bold">
|
<h1 class="mt-14 text-2xl text-pink-400 font-bold">
|
||||||
Welcome, <code>{{.Name}}</code>!<br>
|
Welcome to
|
||||||
|
<code class="p-1 rounded-sm border-l-2 border-fuchsia-200">
|
||||||
|
{{ .AppName }}</code>,
|
||||||
|
<code class="p-1 rounded-sm bg-fuchsia-200">
|
||||||
|
{{.Name}}</code>!
|
||||||
</h1>
|
</h1>
|
||||||
{{if .User}}
|
{{if .User}}
|
||||||
{{if .User.IsAdmin}}
|
{{if .User.IsAdmin}}
|
||||||
|
@ -20,6 +20,11 @@
|
|||||||
<p class="mt-2 text-md text-rose-800 dark:text-rose-500"><span class="font-medium">Error:</span> {{.Data.flash}}</p>
|
<p class="mt-2 text-md text-rose-800 dark:text-rose-500"><span class="font-medium">Error:</span> {{.Data.flash}}</p>
|
||||||
</div>
|
</div>
|
||||||
{{- else -}}{{end}}
|
{{- else -}}{{end}}
|
||||||
|
{{ if and .Data .Data.reauthFlash }}
|
||||||
|
<div class="relative flex items-center mb-4">
|
||||||
|
<p class="mt-2 text-xl text-blue-500 dark:text-blue-400"><span class="italic font-medium">Success:</span> {{.Data.reauthFlash}}</p>
|
||||||
|
</div>
|
||||||
|
{{- else -}}{{end}}
|
||||||
<!-- username field -->
|
<!-- username field -->
|
||||||
<div class="relative flex items-center">
|
<div class="relative flex items-center">
|
||||||
<span class="absolute" role="img" aria-label="person outline icon for username">
|
<span class="absolute" role="img" aria-label="person outline icon for username">
|
||||||
|
@ -10,16 +10,30 @@
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{ if .User -}}
|
{{ if .User -}}
|
||||||
<h1 class="mt-20 text-2xl text-pink-400 font-bold">
|
<h1 class="mt-20 text-2xl text-pink-400 font-bold">
|
||||||
Welcome, <code>{{.Name}}</code>, since you have never changed your password here before, now is the time to do that!<br>
|
Welcome,
|
||||||
|
<code class="p-1 rounded-sm bg-fuchsia-200">
|
||||||
|
{{.Name}}</code>!
|
||||||
</h1>
|
</h1>
|
||||||
|
<h3 class="mt-2 mb-4 text-2xl text-purple-500 dark:text-purple-300 font-bold">
|
||||||
|
Since you have never changed your password here before, now is the time to do that!
|
||||||
|
</h3>
|
||||||
<span class="text-lg text-gray-500 dark:text-gray-300 font-italic">
|
<span class="text-lg text-gray-500 dark:text-gray-300 font-italic">
|
||||||
This is necessary in order to make sure that only you know the password, since the account was pre-created <em>for</em> you.
|
This is necessary in order to make sure that <b>only you</b> know the
|
||||||
|
password, as the account was pre-created <em>for</em> you.
|
||||||
</span>
|
</span>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="mt-8 md:flex md:items-center md:place-items-center md:justify-between">
|
<div class="mt-6 md:flex md:items-center md:place-items-center md:justify-between">
|
||||||
<form method="POST" class="w-full md:w-5xl" action="/user/initial-password-change">
|
<form method="POST" class="w-full md:w-5xl" action="/user/initial-password-change">
|
||||||
<input type="hidden" name="csrf" value="{{- .CSRF -}}">
|
<input type="hidden" name="csrf" value="{{- .CSRF -}}">
|
||||||
<div class="relative flex items-center mt-4">
|
<label class="group relative mt-2 font-bold text-fuchsia-500 dark:text-fuchsia-300 text-sm">
|
||||||
|
Your new password 🛈
|
||||||
|
<span
|
||||||
|
class="absolute hidden group-hover:flex -left-5 -top-2 -translate-y-full w-48 px-2 py-1 bg-gray-700 rounded-lg text-center text-white text-sm after:content-[''] after:absolute after:left-1/2 after:top-[100%] after:-translate-x-1/2 after:border-8 after:border-x-transparent after:border-b-transparent after:border-t-gray-700">
|
||||||
|
Please, save this password into a password manager because after clicking
|
||||||
|
submit you will be logged out and will need to re-login again.
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div class="relative flex items-center">
|
||||||
<span class="absolute" role="img" aria-label="password lock icon">
|
<span class="absolute" role="img" aria-label="password lock icon">
|
||||||
{{ template "svg-password.tmpl" }}
|
{{ template "svg-password.tmpl" }}
|
||||||
</span>
|
</span>
|
||||||
|
Loading…
Reference in New Issue
Block a user