58 lines
1.7 KiB
TypeScript
58 lines
1.7 KiB
TypeScript
import { Psql } from "@core/db/psql";
|
|
import { SlidingWindow } from "@core/mq/slidingWindow.ts";
|
|
import { redis } from "@core/db/redis.ts";
|
|
|
|
type seconds = number;
|
|
|
|
export interface CaptchaDifficultyConfig {
|
|
global: boolean;
|
|
duration: seconds;
|
|
threshold: number;
|
|
difficulty: number;
|
|
}
|
|
|
|
export const getCaptchaDifficultyConfigByRoute = async (sql: Psql, route: string): Promise<CaptchaDifficultyConfig[]> => {
|
|
return sql<CaptchaDifficultyConfig[]>`
|
|
SELECT duration, threshold, difficulty, global
|
|
FROM captcha_difficulty_settings
|
|
WHERE CONCAT(method, '-', path) = ${route}
|
|
ORDER BY duration
|
|
`;
|
|
};
|
|
|
|
export const getCaptchaConfigMaxDuration = async (sql: Psql, route: string): Promise<seconds> => {
|
|
const rows = await sql<{max: number}[]>`
|
|
SELECT MAX(duration)
|
|
FROM captcha_difficulty_settings
|
|
WHERE CONCAT(method, '-', path) = ${route}
|
|
`;
|
|
if (rows.length < 1){
|
|
return Number.MAX_SAFE_INTEGER;
|
|
}
|
|
return rows[0].max;
|
|
}
|
|
|
|
|
|
export const getCurrentCaptchaDifficulty = async (sql: Psql, route: string): Promise<number | null> => {
|
|
const configs = await getCaptchaDifficultyConfigByRoute(sql, route);
|
|
if (configs.length < 1) {
|
|
return null
|
|
}
|
|
else if (configs.length == 1) {
|
|
return configs[0].difficulty
|
|
}
|
|
const maxDuration = configs.reduce((max, config) =>
|
|
Math.max(max, config.duration), 0);
|
|
const slidingWindow = new SlidingWindow(redis, maxDuration);
|
|
for (let i = 0; i < configs.length; i++) {
|
|
const config = configs[i];
|
|
const lastConfig = configs[i - 1];
|
|
const count = await slidingWindow.count(`captcha-${route}`, config.duration);
|
|
if (count >= config.threshold) {
|
|
continue;
|
|
}
|
|
return lastConfig.difficulty
|
|
}
|
|
return configs[0].difficulty;
|
|
}
|