mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 17:10:10 +00:00
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.
This commit is contained in:
parent
2f9965bcda
commit
9abb177427
@ -102,8 +102,8 @@
|
||||
<div class="card-body">
|
||||
<ul class="list-group">
|
||||
{{ range $key, $value := .requirements }}
|
||||
<li id="{{ $key }}" class="list-group-item list-group-item-danger">
|
||||
<div> {{ $value }}</div>
|
||||
<li id="{{ $key }}" min="{{ $value }}" class="list-group-item list-group-item-danger">
|
||||
<div></div>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
@ -116,107 +116,30 @@
|
||||
</div>
|
||||
<script src="serialize.js"></script>
|
||||
<script>
|
||||
{{ if .bs5 }}
|
||||
var bsVersion = 5;
|
||||
{{ else }}
|
||||
var bsVersion = 4;
|
||||
{{ end }}
|
||||
if (bsVersion == 5) {
|
||||
var successBox = new bootstrap.Modal(document.getElementById('successBox'));
|
||||
} else if (bsVersion == 4) {
|
||||
var successBox = {
|
||||
show : function() {
|
||||
return $('#successBox').modal('show');
|
||||
var usernameEnabled = {{ .username }}
|
||||
var validationStrings = {
|
||||
"length": {
|
||||
"singular": "Must have at least {n} character",
|
||||
"plural": "Must have a least {n} characters"
|
||||
},
|
||||
hide : function() {
|
||||
return $('#successBox').modal('hide');
|
||||
"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"
|
||||
}
|
||||
};
|
||||
};
|
||||
var code = window.location.href.split('/').pop();
|
||||
function toggleSpinner () {
|
||||
var submitButton = document.getElementById('submitButton');
|
||||
var oldSpan = document.getElementById('createAccount');
|
||||
var newSpan = document.createElement('span');
|
||||
newSpan.id = 'createAccount';
|
||||
if (document.getElementById('createAccountSpinner')) {
|
||||
newSpan.appendChild(document.createTextNode('Create Account'));
|
||||
submitButton.disabled = false;
|
||||
} else {
|
||||
var spinner = document.createElement('span');
|
||||
spinner.id = 'createAccountSpinner';
|
||||
spinner.classList.add('spinner-border', 'spinner-border-sm');
|
||||
spinner.setAttribute('role', 'status');
|
||||
spinner.setAttribute('aria-hidden', 'true');
|
||||
var text = document.createTextNode(' Creating...');
|
||||
newSpan.appendChild(spinner);
|
||||
newSpan.appendChild(text);
|
||||
submitButton.disabled = true;
|
||||
}
|
||||
submitButton.replaceChild(newSpan, oldSpan);
|
||||
};
|
||||
document.getElementById('accountForm').onsubmit = function() {
|
||||
if (document.getElementById('errorMessage')) {
|
||||
document.getElementById('errorMessage').remove();
|
||||
}
|
||||
toggleSpinner();
|
||||
var send = serializeForm('accountForm');
|
||||
send['code'] = code;
|
||||
{{ if not .username }}
|
||||
send['email'] = send['username'];
|
||||
{{ end }}
|
||||
send = JSON.stringify(send);
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("POST", "/newUser", true);
|
||||
req.responseType = 'json';
|
||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
req.onreadystatechange = function() {
|
||||
if (this.readyState == 4) {
|
||||
toggleSpinner();
|
||||
var data = this.response;
|
||||
if ('error' in data || data['success'] == false) {
|
||||
if (typeof(data['error']) != 'undefined') {
|
||||
var errorMessage = data['error'];
|
||||
} else {
|
||||
var errorMessage = 'Unknown Error';
|
||||
}
|
||||
var text = document.createTextNode(errorMessage);
|
||||
var error = document.createElement('button');
|
||||
error.classList.add('btn', 'btn-outline-danger');
|
||||
error.setAttribute('disabled', '');
|
||||
error.appendChild(text);
|
||||
error.id = 'errorMessage';
|
||||
document.getElementById('errorBox').appendChild(error);
|
||||
} else {
|
||||
var valid = true
|
||||
for (var key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
var criterion = document.getElementById(key);
|
||||
if (criterion) {
|
||||
if (data[key] == false) {
|
||||
valid = false;
|
||||
if (criterion.classList.contains('list-group-item-success')) {
|
||||
criterion.classList.remove('list-group-item-success');
|
||||
criterion.classList.add('list-group-item-danger');
|
||||
};
|
||||
} else {
|
||||
if (criterion.classList.contains('list-group-item-danger')) {
|
||||
criterion.classList.remove('list-group-item-danger');
|
||||
criterion.classList.add('list-group-item-success');
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (valid == true) {
|
||||
successBox.show();
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
req.send(send);
|
||||
return false;
|
||||
};
|
||||
</script>
|
||||
<script src="form.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
2
go.mod
2
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
|
||||
|
2
go.sum
2
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=
|
||||
|
10
main.go
10
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 {
|
||||
|
38
pwval.go
38
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
|
||||
}
|
||||
|
172
ts/form.ts
Normal file
172
ts/form.ts
Normal file
@ -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 = `<span>Create Account</span>`;
|
||||
submitButton.disabled = false;
|
||||
} else {
|
||||
submitButton.innerHTML = `
|
||||
<span id="createAccountSpinner" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>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 += `
|
||||
<button id="errorMessage" class="btn btn-outline-danger" disabled>${errorMessage}</button>
|
||||
`;
|
||||
} 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;
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user