Skip to content

Commit

Permalink
form: modularize captcha somewhat
Browse files Browse the repository at this point in the history
  • Loading branch information
hrfee committed Dec 23, 2023
1 parent 6e20576 commit ab05c07
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 49 deletions.
59 changes: 10 additions & 49 deletions ts/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { _get, _post, toggleLoader, addLoader, removeLoader, toDateString } from
import { loadLangSelector } from "./modules/lang.js";
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
import { Captcha } from "./modules/captcha.js";

interface formWindow extends Window {
invalidPassword: string;
Expand Down Expand Up @@ -172,35 +173,7 @@ if (!window.usernameEnabled) { usernameField.parentElement.remove(); usernameFie
const passwordField = document.getElementById("create-password") as HTMLInputElement;
const rePasswordField = document.getElementById("create-reenter-password") as HTMLInputElement;

let captchaVerified = false;
let captchaID = "";
let captchaInput = document.getElementById("captcha-input") as HTMLInputElement;
const captchaCheckbox = document.getElementById("captcha-success") as HTMLSpanElement;
let prevCaptcha = "";

let baseValidator = (oncomplete: (valid: boolean) => void): void => {
if (window.captcha && !window.reCAPTCHA && (captchaInput.value != prevCaptcha)) {
prevCaptcha = captchaInput.value;
_post("/captcha/verify/" + window.code + "/" + captchaID + "/" + captchaInput.value, null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status == 204) {
captchaCheckbox.innerHTML = `<i class="ri-check-line"></i>`;
captchaCheckbox.classList.add("~positive");
captchaCheckbox.classList.remove("~critical");
captchaVerified = true;
} else {
captchaCheckbox.innerHTML = `<i class="ri-close-line"></i>`;
captchaCheckbox.classList.add("~critical");
captchaCheckbox.classList.remove("~positive");
captchaVerified = false;
}
_baseValidator(oncomplete, captchaVerified);
}
});
} else {
_baseValidator(oncomplete, captchaVerified);
}
}
let captcha = new Captcha(window.code, window.captcha, window.reCAPTCHA);

function _baseValidator(oncomplete: (valid: boolean) => void, captchaValid: boolean): void {
if (window.emailRequired) {
Expand Down Expand Up @@ -228,6 +201,8 @@ function _baseValidator(oncomplete: (valid: boolean) => void, captchaValid: bool
oncomplete(true);
}

let baseValidator = captcha.baseValidatorWrapper(_baseValidator);

interface GreCAPTCHA {
render: (container: HTMLDivElement, parameters: {
sitekey?: string,
Expand Down Expand Up @@ -273,29 +248,15 @@ interface sendDTO {
captcha_text?: string;
}

const genCaptcha = () => {
_get("/captcha/gen/"+window.code, null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status == 200) {
captchaID = req.response["id"];
document.getElementById("captcha-img").innerHTML = `
<img class="w-100" src="${window.location.toString().substring(0, window.location.toString().lastIndexOf("/invite"))}/captcha/img/${window.code}/${captchaID}"></img>
`;
captchaInput.value = "";
}
}
});
};

if (window.captcha && !window.reCAPTCHA) {
genCaptcha();
(document.getElementById("captcha-regen") as HTMLSpanElement).onclick = genCaptcha;
captchaInput.onkeyup = validator.validate;
captcha.generate();
(document.getElementById("captcha-regen") as HTMLSpanElement).onclick = captcha.generate;
captcha.input.onkeyup = validator.validate;
}

const create = (event: SubmitEvent) => {
event.preventDefault();
if (window.captcha && !window.reCAPTCHA && !captchaVerified) {
if (window.captcha && !window.reCAPTCHA && !captcha.verified) {

}
addLoader(submitSpan);
Expand Down Expand Up @@ -330,8 +291,8 @@ const create = (event: SubmitEvent) => {
if (window.reCAPTCHA) {
send.captcha_text = grecaptcha.getResponse();
} else {
send.captcha_id = captchaID;
send.captcha_text = captchaInput.value;
send.captcha_id = captcha.captchaID;
send.captcha_text = captcha.input.value;
}
}
_post("/newUser", send, (req: XMLHttpRequest) => {
Expand Down
64 changes: 64 additions & 0 deletions ts/modules/captcha.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { _get, _post } from "./common.js";

export class Captcha {
enabled = true;
verified = false;
captchaID = "";
input = document.getElementById("captcha-input") as HTMLInputElement;
checkbox = document.getElementById("captcha-success") as HTMLSpanElement;
previous = "";
reCAPTCHA = false;
code = "";

get value(): string { return this.input.value; }

hasChanged = (): boolean => { return this.value != this.previous; }

baseValidatorWrapper = (_baseValidator: (oncomplete: (valid: boolean) => void, captchaValid: boolean) => void) => {
return (oncomplete: (valid: boolean) => void): void => {
if (this.enabled && !this.reCAPTCHA && this.hasChanged()) {
this.previous = this.value;
this.verify(() => {
_baseValidator(oncomplete, this.verified);
});
} else {
_baseValidator(oncomplete, this.verified);
}
};
};

verify = (callback: () => void) => _post("/captcha/verify/" + this.code + "/" + this.captchaID + "/" + this.input.value, null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status == 204) {
this.checkbox.innerHTML = `<i class="ri-check-line"></i>`;
this.checkbox.classList.add("~positive");
this.checkbox.classList.remove("~critical");
this.verified = true;
} else {
this.checkbox.innerHTML = `<i class="ri-close-line"></i>`;
this.checkbox.classList.add("~critical");
this.checkbox.classList.remove("~positive");
this.verified = false;
}
callback();
}
});

generate = () => _get("/captcha/gen/"+this.code, null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status == 200) {
this.captchaID = req.response["id"];
document.getElementById("captcha-img").innerHTML = `
<img class="w-100" src="${window.location.toString().substring(0, window.location.toString().lastIndexOf("/invite"))}/captcha/img/${this.code}/${this.captchaID}"></img>
`;
this.input.value = "";
}
}
});

constructor(code: string, enabled: boolean, reCAPTCHA: boolean) {
this.code = code;
this.enabled = enabled;
this.reCAPTCHA = reCAPTCHA;
}
}
3 changes: 3 additions & 0 deletions ts/pwr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ interface formWindow extends Window {
userExpiryHours: number;
userExpiryMinutes: number;
userExpiryMessage: string;
captcha: boolean;
reCAPTCHA: boolean;
reCAPTCHASiteKey: string;
}

loadLangSelector("pwr");
Expand Down
3 changes: 3 additions & 0 deletions views.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
data["telegramEnabled"] = false
data["discordEnabled"] = false
data["matrixEnabled"] = false
data["captcha"] = app.config.Section("captcha").Key("enabled").MustBool(false)
data["reCAPTCHA"] = app.config.Section("captcha").Key("recaptcha").MustBool(false)
data["reCAPTCHASiteKey"] = app.config.Section("captcha").Key("recaptcha_site_key").MustString("")
gcHTML(gc, http.StatusOK, "form-loader.html", data)
return
}
Expand Down

0 comments on commit ab05c07

Please sign in to comment.