Skip to content

Commit 9da493b

Browse files
committed
chore: added password inline validation
1 parent 3f704e7 commit 9da493b

9 files changed

Lines changed: 77 additions & 36 deletions

File tree

cmd/web/handlers:login.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/bit8bytes/gearberg/internal/httperr"
1111
"github.com/bit8bytes/gearberg/internal/locale"
1212
"github.com/bit8bytes/gearberg/internal/orgs/settings"
13+
"github.com/bit8bytes/gearberg/internal/templates/fragments"
1314
"github.com/bit8bytes/gearberg/internal/templates/pages"
1415
"github.com/bit8bytes/gearberg/pkg/htmx"
1516
"github.com/segmentio/ksuid"
@@ -213,6 +214,15 @@ func (app *application) postResetPassword(w http.ResponseWriter, r *http.Request
213214
return nil
214215
}
215216

217+
func (app *application) postValidatePassword(w http.ResponseWriter, r *http.Request) *httperr.Error {
218+
if err := r.ParseForm(); err != nil {
219+
return nil
220+
}
221+
data := app.html.TemplateData(r)
222+
data.Form = accounts.ValidatePassword(r.PostForm.Get("password"))
223+
return app.html.RenderFragment(w, r, http.StatusOK, fragments.PasswordValidation, data)
224+
}
225+
216226
func (app *application) postSignOut(w http.ResponseWriter, r *http.Request) *httperr.Error {
217227
if err := app.session.Destroy(r.Context()); err != nil {
218228
tmplData := app.html.TemplateData(r)

cmd/web/routes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func (app *application) routes() (http.Handler, error) {
3232
mux.Handle("GET /forgot-password/success", app.withGuest(app.html.Handle(app.getForgotPasswordSuccess)))
3333
mux.Handle("GET /reset-password", app.withGuest(app.html.Handle(app.getResetPassword)))
3434
mux.Handle("POST /reset-password", app.withGuest(app.html.Handle(app.postResetPassword)))
35+
mux.Handle("POST /validate/password", app.html.Handle(app.postValidatePassword))
3536

3637
// TODO: withLogin needs to be replaced with api specific bearer tokens.
3738
mux.Handle("/api/v1/", app.withLogin(apiServer))

internal/accounts/form.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,29 @@ func ParseResetPasswordForm(r *http.Request) (ResetPasswordForm, error) {
138138
}, nil
139139
}
140140

141+
// PasswordValidationForm is used by the HTMX inline password validation endpoint.
142+
// It has the same Password and Errors fields as SignUpForm and ResetPasswordForm,
143+
// so the password-validation fragment template works with all three.
144+
type PasswordValidationForm struct {
145+
Password string
146+
validator.Validator
147+
}
148+
149+
// ValidatePassword validates a single password value and returns a form with any errors set.
150+
func ValidatePassword(password string) *PasswordValidationForm {
151+
f := &PasswordValidationForm{Password: password}
152+
if password == "" {
153+
return f
154+
}
155+
f.Check(validator.MinChars(password, passwordMinLength), "password",
156+
fmt.Sprintf("Must be at least %d characters long", passwordMinLength))
157+
f.Check(validator.MaxChars(password, passwordMaxLength), "password",
158+
fmt.Sprintf("Must be less than %d characters long", passwordMaxLength))
159+
result := zxcvbn.PasswordStrength(password, nil)
160+
f.Check(result.Score >= passwordMinStrengthScore, "password", "Password is too weak or common")
161+
return f
162+
}
163+
141164
// PasswordMinLength returns the minimum allowed password length for use in templates.
142165
func (f *ResetPasswordForm) PasswordMinLength() int { return passwordMinLength }
143166

internal/assets/dist/index.css

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8279,9 +8279,6 @@
82798279
.text-center {
82808280
text-align: center;
82818281
}
8282-
.text-left {
8283-
text-align: left;
8284-
}
82858282
.text-right {
82868283
text-align: right;
82878284
}

internal/templates/components/login.tmpl

Lines changed: 0 additions & 31 deletions
This file was deleted.

internal/templates/fragments/fragments.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ const (
99
EquipmentPartOf = "equipment-part-of"
1010
WarehouseLocations = "warehouse-locations"
1111
OrgCurrency = "org-currency"
12+
PasswordValidation = "password-validation"
1213
)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{{ define "password-validation" }}
2+
{{ $valid := and .Form.Password (not .Form.Errors.password) }}
3+
<div id="password-validation" class="flex flex-col gap-1">
4+
<div class="relative">
5+
<label class="floating-label w-full">
6+
<input
7+
id="password"
8+
name="password"
9+
type="password"
10+
class="input w-full {{ if .Form.Errors.password }}input-error{{ else if $valid }}input-success{{ end }}"
11+
placeholder="Password"
12+
{{ if .Form.Password }}value="{{ .Form.Password }}"{{ end }}
13+
autocomplete="new-password"
14+
hx-post="/validate/password"
15+
hx-target="#password-validation"
16+
hx-swap="outerHTML"
17+
hx-trigger="input changed delay:500ms"
18+
{{ if .Form.Errors.password }}aria-describedby="password-error" aria-invalid="true"{{ end }}
19+
/>
20+
<span>Password</span>
21+
</label>
22+
<button
23+
type="button"
24+
data-pw-toggle="password"
25+
class="absolute right-2 top-1/2 -translate-y-1/2 btn btn-ghost btn-xs text-secondary-content"
26+
aria-label="Show password"
27+
aria-pressed="false"
28+
>Show</button>
29+
</div>
30+
{{ if .Form.Errors.password }}
31+
<div class="label">
32+
<span id="password-error" class="label-text-alt text-error">{{ .Form.Errors.password }}</span>
33+
</div>
34+
{{ else if $valid }}
35+
<div class="label">
36+
<span class="label-text-alt text-success">Password looks good</span>
37+
</div>
38+
{{ end }}
39+
</div>
40+
{{ end }}

internal/templates/pages/login/reset-password.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<input type="hidden" name="token" value="{{ .Form.Token }}" />
1313

1414
<div class="flex flex-col gap-4">
15-
{{ template "password-input" . }}
15+
{{ template "password-validation" . }}
1616

1717
<div class="flex flex-col gap-1">
1818
<label class="floating-label w-full">

internal/templates/pages/login/signup.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
{{ end }}
3232
</div>
3333

34-
{{ template "password-input" . }}
34+
{{ template "password-validation" . }}
3535

3636
<div class="flex flex-col gap-1">
3737
<label class="floating-label w-full">

0 commit comments

Comments
 (0)