1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-06-02 23:57:46 +02:00
jfa-go/ts/form.ts
Harvey Tindall 99875b9176
almost complete telegram user verification
When signing up, the user is given a pin code which they send to a
telegram bot. This provides user verification, but more importantly
allows the bot to message the user, as the Telegram API requires the
user to interact with the bot before it can do the opposite.

The bot should recognize the correct language, but a /lang command is
also provided to change it.

The verification process is pretty much functional but ui is still
broken, and it isn't properly integrated yet.
2021-05-07 01:08:12 +01:00

294 lines
10 KiB
TypeScript

import { Modal } from "./modules/modal.js";
import { notificationBox } from "./modules/common.js";
import { _get, _post, toggleLoader, toDateString } from "./modules/common.js";
import { loadLangSelector } from "./modules/lang.js";
interface formWindow extends Window {
validationStrings: pwValStrings;
invalidPassword: string;
successModal: Modal;
telegramModal: Modal;
confirmationModal: Modal
code: string;
messages: { [key: string]: string };
confirmation: boolean;
telegramEnabled: boolean;
telegramRequired: boolean;
telegramPIN: string;
userExpiryEnabled: boolean;
userExpiryMonths: number;
userExpiryDays: number;
userExpiryHours: number;
userExpiryMinutes: number;
userExpiryMessage: string;
}
interface pwValString {
singular: string;
plural: string;
}
interface pwValStrings {
length: pwValString;
uppercase: pwValString;
lowercase: pwValString;
number: pwValString;
special: pwValString;
[ type: string ]: pwValString;
}
loadLangSelector("form");
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement);
window.successModal = new Modal(document.getElementById("modal-success"), true);
if (window.telegramEnabled) {
window.telegramModal = new Modal(document.getElementById("modal-telegram"), window.telegramRequired);
(document.getElementById("link-telegram") as HTMLSpanElement).onclick = () => {
const waiting = document.getElementById("telegram-waiting") as HTMLSpanElement;
toggleLoader(waiting);
window.telegramModal.show();
const checkVerified = () => _get("/invite/" + window.code + "/telegram/verified/" + window.telegramPIN, null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status == 401) {
window.telegramModal.close();
window.notifications.customError("invalidCodeError", window.lang.notif("errorInvalidCode"));
return;
} else if (req.status == 200) {
if (req.response["success"] as boolean) {
toggleLoader(waiting);
window.telegramModal.close()
window.notifications.customSuccess("accountVerified", window.lang.notif("telegramVerified"))
} else {
setTimeout(checkVerified, 1500);
}
}
}
});
checkVerified();
};
}
if (window.confirmation) {
window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true);
}
declare var window: formWindow;
if (window.userExpiryEnabled) {
const messageEl = document.getElementById("user-expiry-message") as HTMLElement;
const calculateTime = () => {
let time = new Date()
time.setMonth(time.getMonth() + window.userExpiryMonths);
time.setDate(time.getDate() + window.userExpiryDays);
time.setHours(time.getHours() + window.userExpiryHours);
time.setMinutes(time.getMinutes() + window.userExpiryMinutes);
messageEl.textContent = window.userExpiryMessage.replace("{date}", toDateString(time));
setTimeout(calculateTime, 1000);
};
calculateTime();
}
var defaultPwValStrings: pwValStrings = {
length: {
singular: "Must have at least {n} character",
plural: "Must have at 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"
}
}
const form = document.getElementById("form-create") as HTMLFormElement;
const submitButton = form.querySelector("input[type=submit]") as HTMLInputElement;
const submitSpan = form.querySelector("span.submit") as HTMLSpanElement;
let usernameField = document.getElementById("create-username") as HTMLInputElement;
const emailField = document.getElementById("create-email") as HTMLInputElement;
if (!window.usernameEnabled) { usernameField.parentElement.remove(); usernameField = emailField; }
const passwordField = document.getElementById("create-password") as HTMLInputElement;
const rePasswordField = document.getElementById("create-reenter-password") as HTMLInputElement;
const checkPasswords = () => {
if (passwordField.value != rePasswordField.value) {
rePasswordField.setCustomValidity(window.invalidPassword);
submitButton.disabled = true;
submitSpan.setAttribute("disabled", "");
} else {
rePasswordField.setCustomValidity("");
submitButton.disabled = false;
submitSpan.removeAttribute("disabled");
}
};
rePasswordField.addEventListener("keyup", checkPasswords);
passwordField.addEventListener("keyup", checkPasswords);
interface respDTO {
response: boolean;
error: string;
}
interface sendDTO {
code: string;
email: string;
username: string;
password: string;
}
const create = (event: SubmitEvent) => {
event.preventDefault();
toggleLoader(submitSpan);
let send: sendDTO = {
code: window.code,
username: usernameField.value,
email: emailField.value,
password: passwordField.value
};
_post("/newUser", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
let vals = req.response as respDTO;
let valid = true;
for (let type in vals) {
if (requirements[type]) { requirements[type].valid = vals[type]; }
if (!vals[type]) { valid = false; }
}
if (req.status == 200 && valid) {
window.successModal.show();
} else {
submitSpan.classList.add("~critical");
submitSpan.classList.remove("~urge");
setTimeout(() => {
submitSpan.classList.add("~urge");
submitSpan.classList.remove("~critical");
}, 1000);
}
}
}, true, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
toggleLoader(submitSpan);
if (req.status == 401) {
if (req.response["error"] as string) {
if (req.response["error"] == "confirmEmail") {
window.confirmationModal.show();
return;
}
const old = submitSpan.textContent;
if (req.response["error"] in window.messages) {
submitSpan.textContent = window.messages[req.response["error"]];
} else {
submitSpan.textContent = req.response["error"];
}
setTimeout(() => { submitSpan.textContent = old; }, 1000);
}
}
}
});
};
form.onsubmit = create;
class Requirement {
private _name: string;
protected _minCount: number;
private _content: HTMLSpanElement;
private _valid: HTMLSpanElement;
private _li: HTMLLIElement;
get valid(): boolean { return this._valid.classList.contains("~positive"); }
set valid(state: boolean) {
if (state) {
this._valid.classList.add("~positive");
this._valid.classList.remove("~critical");
this._valid.innerHTML = `<i class="icon ri-check-line" title="valid"></i>`;
} else {
this._valid.classList.add("~critical");
this._valid.classList.remove("~positive");
this._valid.innerHTML = `<i class="icon ri-close-line" title="invalid"></i>`;
}
}
constructor(name: string, el: HTMLLIElement) {
this._name = name;
this._li = el;
this._content = this._li.querySelector("span.requirement-content") as HTMLSpanElement;
this._valid = this._li.querySelector("span.requirement-valid") as HTMLSpanElement;
this.valid = false;
this._minCount = +this._li.getAttribute("min");
let text = "";
if (this._minCount == 1) {
text = window.validationStrings[this._name].singular.replace("{n}", "1");
} else {
text = window.validationStrings[this._name].plural.replace("{n}", ""+this._minCount);
}
this._content.textContent = text;
}
validate = (count: number) => { this.valid = (count >= this._minCount); }
}
// Incredible code right here
const isInt = (s: string): boolean => { return (s in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]); }
const testStrings = (f: pwValString): boolean => {
const testString = (s: string): boolean => {
if (s == "" || !s.includes("{n}")) { return false; }
return true;
}
return testString(f.singular) && testString(f.plural);
}
interface Validation { [name: string]: number }
const validate = (s: string): Validation => {
let v: Validation = {};
for (let criteria of ["length", "lowercase", "uppercase", "number", "special"]) { v[criteria] = 0; }
v["length"] = s.length;
for (let c of s) {
if (isInt(c)) { v["number"]++; }
else {
const upper = c.toUpperCase();
if (upper == c.toLowerCase()) { v["special"]++; }
else {
if (upper == c) { v["uppercase"]++; }
else if (upper != c) { v["lowercase"]++; }
}
}
}
return v
}
passwordField.addEventListener("keyup", () => {
const v = validate(passwordField.value);
for (let criteria in requirements) {
requirements[criteria].validate(v[criteria]);
}
});
var requirements: { [category: string]: Requirement} = {};
if (!window.validationStrings) {
window.validationStrings = defaultPwValStrings;
} else {
for (let category in window.validationStrings) {
if (!testStrings(window.validationStrings[category])) {
window.validationStrings[category] = defaultPwValStrings[category];
}
const el = document.getElementById("requirement-" + category);
if (el) {
requirements[category] = new Requirement(category, el as HTMLLIElement);
}
}
}