From 9abb17742700dfc3aebbadfdb513390b72888550 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Tue, 20 Oct 2020 23:00:30 +0100 Subject: [PATCH] use typescript for form.html in separate file, allow customization of requirement strings Password requirement text is now loaded by the typescript, and can be customized by changing the validationStrings variable. See wiki for more info. --- data/templates/form.html | 127 ++++++----------------------- go.mod | 2 +- go.sum | 2 + main.go | 10 +-- pwval.go | 38 ++++----- ts/form.ts | 172 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 220 insertions(+), 131 deletions(-) create mode 100644 ts/form.ts diff --git a/data/templates/form.html b/data/templates/form.html index 45bbe19..c7f113a 100644 --- a/data/templates/form.html +++ b/data/templates/form.html @@ -102,8 +102,8 @@
@@ -116,107 +116,30 @@
+ diff --git a/go.mod b/go.mod index 34fd6bc..f6cd6bc 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 github.com/swaggo/gin-swagger v1.2.0 - github.com/swaggo/swag v1.6.8 // indirect + github.com/swaggo/swag v1.6.9 // indirect github.com/ugorji/go v1.1.9 // indirect github.com/urfave/cli/v2 v2.2.0 // indirect golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect diff --git a/go.sum b/go.sum index 0d0b5b7..ab3db0f 100644 --- a/go.sum +++ b/go.sum @@ -210,6 +210,8 @@ github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s= github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc= github.com/swaggo/swag v1.6.8 h1:z3ZNcpJs/NLMpZcKqXUsBELmmY2Ocy09JXKx5gu3L4M= github.com/swaggo/swag v1.6.8/go.mod h1:a0IpNeMfGidNOcm2TsqODUh9JHdHu3kxDA0UlGbBKjI= +github.com/swaggo/swag v1.6.9 h1:BukKRwZjnEcUxQt7Xgfrt9fpav0hiWw9YimdNO9wssw= +github.com/swaggo/swag v1.6.9/go.mod h1:a0IpNeMfGidNOcm2TsqODUh9JHdHu3kxDA0UlGbBKjI= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= diff --git a/main.go b/main.go index 11b1b82..353cca0 100644 --- a/main.go +++ b/main.go @@ -447,11 +447,11 @@ func start(asDaemon, firstCall bool) { app.loadStrftime() validatorConf := ValidatorConf{ - "characters": app.config.Section("password_validation").Key("min_length").MustInt(0), - "uppercase characters": app.config.Section("password_validation").Key("upper").MustInt(0), - "lowercase characters": app.config.Section("password_validation").Key("lower").MustInt(0), - "numbers": app.config.Section("password_validation").Key("number").MustInt(0), - "special characters": app.config.Section("password_validation").Key("special").MustInt(0), + "length": app.config.Section("password_validation").Key("min_length").MustInt(0), + "uppercase": app.config.Section("password_validation").Key("upper").MustInt(0), + "lowercase": app.config.Section("password_validation").Key("lower").MustInt(0), + "number": app.config.Section("password_validation").Key("number").MustInt(0), + "special": app.config.Section("password_validation").Key("special").MustInt(0), } if !app.config.Section("password_validation").Key("enabled").MustBool(false) { for key := range validatorConf { diff --git a/pwval.go b/pwval.go index efa76d7..abe3415 100644 --- a/pwval.go +++ b/pwval.go @@ -1,8 +1,6 @@ package main import ( - "fmt" - "strings" "unicode" ) @@ -21,11 +19,11 @@ func (vd *Validator) init(criteria ValidatorConf) { // This isn't used, its for swagger type PasswordValidation struct { - Characters bool `json:"characters,omitempty"` // Number of characters - Lowercase bool `json:"lowercase characters,omitempty"` // Number of lowercase characters - Uppercase bool `json:"uppercase characters,omitempty"` // Number of uppercase characters - Numbers bool `json:"numbers,omitempty"` // Number of numbers - Specials bool `json:"special characters,omitempty"` // Number of special characters + Characters bool `json:"length,omitempty"` // Number of characters + Lowercase bool `json:"lowercase,omitempty"` // Number of lowercase characters + Uppercase bool `json:"uppercase,omitempty"` // Number of uppercase characters + Numbers bool `json:"number,omitempty"` // Number of numbers + Specials bool `json:"special,omitempty"` // Number of special characters } func (vd *Validator) validate(password string) map[string]bool { @@ -34,17 +32,17 @@ func (vd *Validator) validate(password string) map[string]bool { count[key] = 0 } for _, c := range password { - count["characters"] += 1 + count["length"] += 1 if unicode.IsUpper(c) { - count["uppercase characters"] += 1 + count["uppercase"] += 1 } else if unicode.IsLower(c) { - count["lowercase characters"] += 1 + count["lowercase"] += 1 } else if unicode.IsNumber(c) { count["numbers"] += 1 } else { for _, s := range vd.specialChars { if c == s { - count["special characters"] += 1 + count["special"] += 1 } } } @@ -60,18 +58,12 @@ func (vd *Validator) validate(password string) map[string]bool { return results } -func (vd *Validator) getCriteria() map[string]string { - lines := map[string]string{} - for criterion, min := range vd.criteria { - if min > 0 { - text := fmt.Sprintf("Must have at least %d ", min) - if min == 1 { - text += strings.TrimSuffix(criterion, "s") - } else { - text += criterion - } - lines[criterion] = text +func (vd *Validator) getCriteria() ValidatorConf { + criteria := ValidatorConf{} + for key, num := range vd.criteria { + if num != 0 { + criteria[key] = num } } - return lines + return criteria } diff --git a/ts/form.ts b/ts/form.ts new file mode 100644 index 0000000..d5ecf0a --- /dev/null +++ b/ts/form.ts @@ -0,0 +1,172 @@ +interface pwValString { + singular: string; + plural: string; +} + +interface pwValStrings { + length, uppercase, lowercase, number, special: pwValString; +} + +const _post = (url: string, data: Object, onreadystatechange: () => void): void => { + let req = new XMLHttpRequest(); + req.open("POST", url, true); + req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + req.responseType = 'json'; + req.onreadystatechange = onreadystatechange; + req.send(JSON.stringify(data)); +}; + +const toggleSpinner = (): void => { + const submitButton = document.getElementById('submitButton') as HTMLButtonElement; + if (document.getElementById('createAccountSpinner')) { + submitButton.innerHTML = `Create Account`; + submitButton.disabled = false; + } else { + submitButton.innerHTML = ` + Creating... + `; + } +}; + +const rmAttr = (el: HTMLElement, attr: string): void => { + if (el.classList.contains(attr)) { + el.classList.remove(attr); + } +}; + +const addAttr = (el: HTMLElement, attr: string): void => el.classList.add(attr); + +var validationStrings: pwValStrings; +var bsVersion: number; + +var defaultPwValStrings: pwValStrings = { + length: { + singular: "Must have at least {n} character", + plural: "Must have a least {n} characters" + }, + uppercase: { + singular: "Must have at least {n} uppercase character", + plural: "Must have at least {n} uppercase characters" + }, + lowercase: { + singular: "Must have at least {n} lowercase character", + plural: "Must have at least {n} lowercase characters" + }, + number: { + singular: "Must have at least {n} number", + plural: "Must have at least {n} numbers" + }, + special: { + singular: "Must have at least {n} special character", + plural: "Must have at least {n} special characters" + } +} + +for (let key in validationStrings) { + console.log(key); + if (validationStrings[key].singular == "" || !(validationStrings[key].plural.includes("{n}"))) { + validationStrings[key].singular = defaultPwValStrings[key].singular; + } + if (validationStrings[key].plural == "" || !(validationStrings[key].plural.includes("{n}"))) { + validationStrings[key].plural = defaultPwValStrings[key].plural; + } + let el = document.getElementById(key) as HTMLUListElement; + if (el) { + const min: number = +el.getAttribute("min"); + let text = ""; + if (min == 1) { + text = validationStrings[key].singular.replace("{n}", "1"); + } else { + text = validationStrings[key].plural.replace("{n}", min.toString()); + } + (document.getElementById(key).children[0] as HTMLDivElement).textContent = text; + } +} + +interface Modal { + show: () => void; + hide: () => void; +} + +var successBox: Modal; + +if (bsVersion == 5) { + var bootstrap: any; + successBox = new bootstrap.Modal(document.getElementById('successBox')); +} else if (bsVersion == 4) { + successBox = { + show: (): void => { + ($('#successBox') as any).modal('show'); + }, + hide: (): void => { + ($('#successBox') as any).modal('hide'); + } + }; +} + +var code = window.location.href.split('/').pop(); +var usernameEnabled: boolean; + +(document.getElementById('accountForm') as HTMLFormElement).addEventListener('submit', (event: any): boolean => { + event.preventDefault(); + const el = document.getElementById('errorMessage'); + if (el) { + el.remove(); + } + toggleSpinner(); + let send: Object = serializeForm('accountForm'); + send["code"] = code; + if (!usernameEnabled) { + send["email"] = send["username"]; + } + _post("/newUser", send, function (): void { + if (this.readyState == 4) { + toggleSpinner(); + let data: Object = this.response; + const errorGiven = ("error" in data) + if (errorGiven || data["success"] === false) { + let errorMessage = "Unknown Error"; + if (errorGiven && errorGiven != true) { + errorMessage = data["error"]; + } + document.getElementById('errorBox').innerHTML += ` + + `; + } else { + let valid = true; + for (let key in data) { + if (data.hasOwnProperty(key)) { + const criterion = document.getElementById(key); + if (criterion) { + if (data[key] === false) { + valid = false; + addAttr(criterion, "list-group-item-danger"); + rmAttr(criterion, "list-group-item-success"); + } else { + addAttr(criterion, "list-group-item-success"); + rmAttr(criterion, "list-group-item-danger"); + } + } + } + } + if (valid) { + successBox.show(); + } + } + } + }); + return false; +}); + + + + + + + + + + + + +