duct-tape google recaptcha
This commit is contained in:
parent
f24ea3365a
commit
021f72162d
|
@ -6332,6 +6332,12 @@
|
|||
"@types/express": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/grecaptcha": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/grecaptcha/-/grecaptcha-3.0.9.tgz",
|
||||
"integrity": "sha512-fFxMtjAvXXMYTzDFK5NpcVB7WHnrHVLl00QzEGpuFxSAC789io6M+vjcn+g5FTEamIJtJr/IHkCDsqvJxeWDyw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/http-errors": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
|
||||
|
@ -16104,6 +16110,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/vite-react": "^3.0.0",
|
||||
"@types/grecaptcha": "^3.0.9",
|
||||
"@types/lodash.throttle": "^4.1.9",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/vite-react": "^3.0.0",
|
||||
"@types/grecaptcha": "^3.0.9",
|
||||
"@types/lodash.throttle": "^4.1.9",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
|
|
|
@ -62,6 +62,7 @@ export const InfoSidebar = () => {
|
|||
</div>
|
||||
</Button>
|
||||
<b>Build {__COMMIT_HASH__}</b>
|
||||
<div id="grecaptcha-badge"></div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from "@sc07-canvas/lib/src/net";
|
||||
import { toast } from "react-toastify";
|
||||
import { handleAlert, handleDismiss } from "./alerts";
|
||||
import { Recaptcha } from "./recaptcha";
|
||||
|
||||
export interface INetworkEvents {
|
||||
connected: () => void;
|
||||
|
@ -91,6 +92,14 @@ class Network extends EventEmitter<INetworkEvents> {
|
|||
console.log("Reconnect failed");
|
||||
});
|
||||
|
||||
this.socket.on("recaptcha", (site_key) => {
|
||||
Recaptcha.load(site_key);
|
||||
});
|
||||
|
||||
this.socket.on("recaptcha_challenge", (ack) => {
|
||||
Recaptcha.executeChallenge(ack);
|
||||
});
|
||||
|
||||
this.socket.on("user", (user) => {
|
||||
this.emit("user", user);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
class Recaptcha_ {
|
||||
load(site_key: string) {
|
||||
const script = document.createElement("script");
|
||||
script.setAttribute(
|
||||
"src",
|
||||
`https://www.google.com/recaptcha/api.js?render=explicit`
|
||||
);
|
||||
document.head.appendChild(script);
|
||||
|
||||
script.onload = () => {
|
||||
grecaptcha.ready(() => {
|
||||
grecaptcha.render("grecaptcha-badge", {
|
||||
sitekey: site_key,
|
||||
badge: "inline",
|
||||
size: "invisible",
|
||||
});
|
||||
|
||||
console.log("Google Recaptcha Loaded!");
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
executeChallenge(ack: (token: string) => void) {
|
||||
console.log("[Recaptcha] Received challenge request...");
|
||||
grecaptcha.execute().then((token) => {
|
||||
console.log("[Recaptcha] Sending challenge token back");
|
||||
ack(token as any);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const Recaptcha = new Recaptcha_();
|
|
@ -23,6 +23,9 @@ export interface ServerToClientEvents {
|
|||
alert: (alert: IAlert) => void;
|
||||
alert_dismiss: (id: string) => void;
|
||||
|
||||
recaptcha: (site_key: string) => void;
|
||||
recaptcha_challenge: (ack: (token: string) => void) => void;
|
||||
|
||||
/* --- subscribe events --- */
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,6 +38,7 @@ export const LoggerType = createEnum([
|
|||
"JOB_WORKER",
|
||||
"CANVAS_WORK",
|
||||
"WORKER_ROOT",
|
||||
"RECAPTCHA",
|
||||
]);
|
||||
|
||||
export const getLogger = (module?: keyof typeof LoggerType) =>
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
import { Socket } from "socket.io";
|
||||
import { User } from "../models/User";
|
||||
import { getLogger } from "./Logger";
|
||||
import {
|
||||
ClientToServerEvents,
|
||||
ServerToClientEvents,
|
||||
} from "@sc07-canvas/lib/src/net";
|
||||
|
||||
const Logger = getLogger("RECAPTCHA");
|
||||
|
||||
class Recaptcha_ {
|
||||
disabled = false;
|
||||
chance: number | null = null;
|
||||
|
||||
constructor() {
|
||||
this.disabled =
|
||||
!process.env.RECAPTCHA_SITE_KEY ||
|
||||
!process.env.RECAPTCHA_SECRET_KEY ||
|
||||
!process.env.RECAPTCHA_PIXEL_CHANCE;
|
||||
|
||||
if (!process.env.RECAPTCHA_PIXEL_CHANCE) {
|
||||
Logger.warn("No RECAPTCHA_PIXEL_CHANCE set, captchas will not be sent!");
|
||||
} else {
|
||||
this.chance = parseFloat(process.env.RECAPTCHA_PIXEL_CHANCE);
|
||||
|
||||
if (this.chance > 1 || this.chance < 0) {
|
||||
this.chance = null;
|
||||
this.disabled = true;
|
||||
Logger.warn("RECAPTCHA_PIXEL_CHANCE is not within (0<x<1)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
maybeChallenge(
|
||||
socket: Socket<ClientToServerEvents, ServerToClientEvents>
|
||||
): boolean {
|
||||
if (this.disabled || !this.chance) return false;
|
||||
|
||||
if (Math.random() > this.chance) {
|
||||
socket.emitWithAck("recaptcha_challenge").then((token) => {
|
||||
this.verifyToken(token).then(async (data) => {
|
||||
if (!data.success) {
|
||||
this.notifyStaffOfError(data).then(() => {});
|
||||
} else {
|
||||
if (data.score < 0.5 || true) {
|
||||
try {
|
||||
const user = (await User.fromAuthSession(
|
||||
socket.request.session.user!
|
||||
))!;
|
||||
this.notifyStaff(user, data.score).then(() => {});
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async verifyToken(
|
||||
token: string
|
||||
): Promise<
|
||||
| { success: true; challenge_ts: string; hostname: string; score: number }
|
||||
| { success: false; "error-codes": string[] }
|
||||
> {
|
||||
return await fetch(
|
||||
`https://www.google.com/recaptcha/api/siteverify?secret=${process.env.RECAPTCHA_SECRET_KEY!}&response=${token}`,
|
||||
{
|
||||
method: "POST",
|
||||
}
|
||||
).then((a) => a.json());
|
||||
}
|
||||
|
||||
async notifyStaff(user: User, score: number) {
|
||||
return await fetch(process.env.DISCORD_WEBHOOK!, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: `User ${user.sub} got a low score ${score}`,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
async notifyStaffOfError(obj: any) {
|
||||
return await fetch(process.env.DISCORD_WEBHOOK!, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content:
|
||||
"Error while verifying captcha\n```\n" +
|
||||
JSON.stringify(obj, null, 2) +
|
||||
"\n```",
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const Recaptcha = new Recaptcha_();
|
|
@ -15,6 +15,7 @@ import { prisma } from "./prisma";
|
|||
import { getLogger } from "./Logger";
|
||||
import { Redis } from "./redis";
|
||||
import { User } from "../models/User";
|
||||
import { Recaptcha } from "./Recaptcha";
|
||||
|
||||
const Logger = getLogger("SOCKET");
|
||||
|
||||
|
@ -192,6 +193,9 @@ export class SocketServer {
|
|||
);
|
||||
}
|
||||
|
||||
if (process.env.RECAPTCHA_SITE_KEY)
|
||||
socket.emit("recaptcha", process.env.RECAPTCHA_SITE_KEY);
|
||||
|
||||
socket.emit("config", getClientConfig());
|
||||
{
|
||||
let _clientNotifiedAboutCache = false;
|
||||
|
@ -296,6 +300,8 @@ export class SocketServer {
|
|||
return;
|
||||
}
|
||||
|
||||
Recaptcha.maybeChallenge(socket);
|
||||
|
||||
await user.modifyStack(-1);
|
||||
await Canvas.setPixel(
|
||||
user,
|
||||
|
|
|
@ -60,6 +60,12 @@ declare global {
|
|||
MATRIX_HOMESERVER: string;
|
||||
ELEMENT_HOST: string;
|
||||
MATRIX_GENERAL_ALIAS: string;
|
||||
|
||||
RECAPTCHA_SITE_KEY?: string;
|
||||
RECAPTCHA_SECRET_KEY?: string;
|
||||
RECAPTCHA_PIXEL_CHANCE?: string;
|
||||
|
||||
DISCORD_WEBHOOK?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue