dev work
This commit is contained in:
parent
6e7977997e
commit
e97ed713c5
21 changed files with 286 additions and 197 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import Cap, { type ChallengeData } from "@cap.js/server";
|
||||
import type { AstroSession } from "astro";
|
||||
|
||||
export function createCap(session: AstroSession<any> | null) {
|
||||
export function createCap(session: AstroSession | null) {
|
||||
if (!session) {
|
||||
throw new Error("Session context is required");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,10 @@
|
|||
import { ofetch } from "ofetch";
|
||||
import { ProxyAgent } from "undici";
|
||||
|
||||
const wireguardDispatcher = new ProxyAgent("http://wireguard:8888");
|
||||
const TIMEOUT = 5000;
|
||||
|
||||
const httpFetchClient = {
|
||||
get: async (url: string, headers: Record<string, string>) => {
|
||||
const response = await ofetch(url, {
|
||||
method: "GET",
|
||||
headers,
|
||||
dispatcher: wireguardDispatcher,
|
||||
timeout: TIMEOUT,
|
||||
});
|
||||
|
||||
return response;
|
||||
|
|
@ -20,8 +14,6 @@ const httpFetchClient = {
|
|||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
dispatcher: wireguardDispatcher,
|
||||
timeout: TIMEOUT,
|
||||
});
|
||||
|
||||
return response;
|
||||
|
|
@ -31,8 +23,6 @@ const httpFetchClient = {
|
|||
method: "PUT",
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
dispatcher: wireguardDispatcher,
|
||||
timeout: TIMEOUT,
|
||||
});
|
||||
|
||||
return response;
|
||||
|
|
@ -42,8 +32,6 @@ const httpFetchClient = {
|
|||
method: "PATCH",
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
dispatcher: wireguardDispatcher,
|
||||
timeout: TIMEOUT,
|
||||
});
|
||||
|
||||
return response;
|
||||
|
|
@ -52,8 +40,6 @@ const httpFetchClient = {
|
|||
const response = await ofetch(url, {
|
||||
method: "DELETE",
|
||||
headers,
|
||||
dispatcher: wireguardDispatcher,
|
||||
timeout: TIMEOUT,
|
||||
});
|
||||
|
||||
return response;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,44 @@
|
|||
import { authenticator } from "otplib";
|
||||
import { createHash } from "crypto";
|
||||
|
||||
const submissionTimestamps = new Map();
|
||||
const otpRequestTimestamps = new Map();
|
||||
const submissionTimestamps = new Map<string, number[]>();
|
||||
const otpRequestTimestamps = new Map<string, number[]>();
|
||||
const ONE_WEEK_IN_MS: number = 7 * 24 * 60 * 60 * 1000;
|
||||
const ONE_HOUR_IN_MS: number = 60 * 60 * 1000;
|
||||
const MAX_OTP_REQUESTS_PER_HOUR: number = 3;
|
||||
const MAX_MESSAGES_PER_WEEK: number = 3;
|
||||
const OTP_STEP_IN_SEC: number = 300;
|
||||
const VALID_PAST_OTP_STEPS: number = 1;
|
||||
const VALID_FUTURE_OTP_STEPS: number = 1;
|
||||
const VALID_FUTURE_OTP_STEPS: number = 0;
|
||||
const OTP_NUM_DIGITS: number = 6;
|
||||
const MAX_GLOBAL_OTP_SENDS_PER_HOUR: number = 10;
|
||||
let globalOtpSendTimestamps: number[] = [];
|
||||
|
||||
export function isRateLimitedGlobally(): boolean {
|
||||
const now = Date.now();
|
||||
globalOtpSendTimestamps = globalOtpSendTimestamps.filter(
|
||||
(t) => now - t < ONE_HOUR_IN_MS,
|
||||
);
|
||||
return globalOtpSendTimestamps.length >= MAX_GLOBAL_OTP_SENDS_PER_HOUR;
|
||||
}
|
||||
|
||||
export function recordGlobalOtpSend() {
|
||||
globalOtpSendTimestamps.push(Date.now());
|
||||
}
|
||||
|
||||
function cleanupStaleEntries(map: Map<string, number[]>, windowMs: number) {
|
||||
const now = Date.now();
|
||||
for (const [key, timestamps] of map) {
|
||||
const recent = timestamps.filter((t) => now - t < windowMs);
|
||||
if (recent.length === 0) map.delete(key);
|
||||
else if (recent.length !== timestamps.length) map.set(key, recent);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
cleanupStaleEntries(submissionTimestamps, ONE_WEEK_IN_MS);
|
||||
cleanupStaleEntries(otpRequestTimestamps, ONE_HOUR_IN_MS);
|
||||
}, ONE_HOUR_IN_MS).unref?.();
|
||||
|
||||
authenticator.options = {
|
||||
step: OTP_STEP_IN_SEC,
|
||||
|
|
@ -30,15 +58,14 @@ function getUserSecret(phoneNumber: string, salt: string): string {
|
|||
}
|
||||
|
||||
export function normalizePhone(phone: string) {
|
||||
const result = phone.replace(/[^\d]/g, "").trim().startsWith("1")
|
||||
? phone.substring(1)
|
||||
: phone;
|
||||
|
||||
if (result.length !== 10) {
|
||||
let digits = phone.replace(/\D/g, "");
|
||||
if (digits.length === 11 && digits.startsWith("1")) {
|
||||
digits = digits.slice(1);
|
||||
}
|
||||
if (digits.length !== 10) {
|
||||
throw new Error("Invalid phone number.");
|
||||
}
|
||||
|
||||
return result;
|
||||
return digits;
|
||||
}
|
||||
|
||||
export function isValidPhone(phone: string): boolean {
|
||||
|
|
@ -46,9 +73,13 @@ export function isValidPhone(phone: string): boolean {
|
|||
const match = phone.match(/(\d{3})(\d{3})(\d{4})/);
|
||||
const [, prefix, exchange, station] = match ?? [];
|
||||
const isValidNANPFormat =
|
||||
/^[2-7][0-8][0-9]$/.test(prefix) && /^[2-9][0-9]{2}$/.test(exchange);
|
||||
/^[2-9][0-9]{2}$/.test(prefix) && /^[2-9][0-9]{2}$/.test(exchange);
|
||||
const isNotAllSameDigit = !/^(.)\1{6}$/.test(exchange + station);
|
||||
const isNot911Number = prefix !== "911" && exchange !== "911";
|
||||
const isNotTollFreeNumber = !(
|
||||
/^[8-9][0-9]{2}$/.test(prefix) &&
|
||||
/^(99|88|77|66|55|44|33|22|11|00)$/.test(prefix.slice(1, 3))
|
||||
);
|
||||
const isNot555Number = prefix !== "555" && exchange !== "555";
|
||||
const isNotPopSongNumber = exchange !== "867" && station !== "5309";
|
||||
|
||||
|
|
@ -57,6 +88,7 @@ export function isValidPhone(phone: string): boolean {
|
|||
isNotAllSameDigit &&
|
||||
isNot911Number &&
|
||||
isNot555Number &&
|
||||
isNotTollFreeNumber &&
|
||||
isNotPopSongNumber
|
||||
);
|
||||
}
|
||||
|
|
@ -153,4 +185,6 @@ export default {
|
|||
recordMsgSubmission,
|
||||
isRateLimitedForOtp,
|
||||
isRateLimitedForMsgs,
|
||||
isRateLimitedGlobally,
|
||||
recordGlobalOtpSend,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue