epitoulouse-bot/src/verifier.ts

155 lines
3.9 KiB
TypeScript

import { v1 as uuid } from "https://deno.land/std@0.213.0/uuid/mod.ts";
import { channel, log_from, Resolver, SimpleResult, split_promise, wait } from "./utils.ts";
const log = log_from(import.meta.url);
export type WebVerifierConf = {
port: number;
url_prefix: string;
};
export type LinkToSend = {
discord_id: string;
cri_login: string;
link: string;
};
export class WebVerifier {
conf;
awaiting;
link_channel;
public constructor(conf: WebVerifierConf) {
this.conf = conf;
this.awaiting = new Map<string, Resolver<SimpleResult>>();
this.link_channel = channel<LinkToSend>();
}
public async start() {
const { promise, resolver } = split_promise<void>();
Deno.serve({
hostname: "0.0.0.0",
port: this.conf.port,
onListen: () => resolver(),
}, (request) => this.serve(request));
return await promise;
}
public async verification(discord_id: string, cri_login: string) {
const { link, uid } = this.create_verif_link();
const { promise, resolver } = split_promise<SimpleResult>();
this.awaiting.set(uid, resolver);
log(`Created verification link '${link}' for discord id '${discord_id}' to cri login '${cri_login}'.`);
this.link_channel.send({ cri_login, discord_id, link });
this.start_peremption(uid);
return await promise;
}
public async *links_to_send() {
while (true) yield await this.link_channel.receive();
}
public url() {
return `http://${this.conf.url_prefix}`;
}
private serve(request: Request): Response {
const url = new URL(request.url);
if (url.pathname.startsWith("/verify/")) return this.serve_verification_page(url);
if (url.pathname.startsWith("/confirm/")) return this.serve_confirmation(url);
return response_failure("Chemin invalide.");
}
private serve_verification_page(url: URL) {
const [uid] = url.pathname.slice("/verify/".length).split("/");
const exists = this.awaiting.has(uid);
if (!exists) return response_failure("Lien de vérification invalide.");
return response_verification_page(this.create_confirm_link(uid));
}
private serve_confirmation(url: URL) {
const [uid] = url.pathname.slice("/confirm/".length).split("/");
const resolver = this.awaiting.get(uid);
if (resolver === undefined) return response_failure("Lien de vérification invalide.");
resolver(true);
this.awaiting.delete(uid);
log(`Verified link ${uid} successfully.`);
return response_success();
}
private async serve_result_page() {
//
}
private create_verif_link() {
const uid = uuid.generate() as string;
const link = `http://${this.conf.url_prefix}/verify/${uid}`;
return { uid, link };
}
private create_confirm_link(uid: string) {
return `http://${this.conf.url_prefix}/confirm/${uid}`;
}
private async start_peremption(uid: string) {
await wait(5 * 60 * 1000); // 5 mins
const resolver = this.awaiting.get(uid);
if (resolver === undefined) return;
resolver("Verification link timeout.");
this.awaiting.delete(uid);
log(`Link '${uid}' timed out.`);
}
}
function response_failure(message: string) {
const body = html_pre(`
EPITLS BOT
==========
❌ Échec
---------
${message}
`);
return new Response(body, { headers: { "Content-Type": "text/html" } });
}
function response_verification_page(confirm_link: string) {
const body = html_pre(`
EPITLS BOT
==========
🟡 Vérification
----------------
Vous êtes sur le point de confirmer l'association.
<form action="${confirm_link}">
<input type="submit" value="Confirmer">
</form>
`);
return new Response(body, { headers: { "Content-Type": "text/html" } });
}
function response_success() {
const body = html_pre(`
EPITLS BOT
==========
✅ Vérifié avec succès
-----------------------
`);
return new Response(body, { headers: { "Content-Type": "text/html" } });
}
function html_pre(content: string) {
return `<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Epitls • Verification</title>
</head>
<body>
<pre>
${content}
</pre>
</body>
</html>
`;
}