mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-09-22 00:44:52 +02:00
Address code review feedback - improve error handling and documentation
Co-authored-by: hbel <7416029+hbel@users.noreply.github.com>
This commit is contained in:
co-authored by
hbel
parent
3f4d92f3ff
commit
6d70ea0eed
@@ -205,10 +205,14 @@ export class UnifiedAppointmentCrypto {
|
||||
if (!challengeResponse.ok) {
|
||||
if (challengeResponse.status === 429) {
|
||||
const errorData = await challengeResponse.json();
|
||||
const retryAfterSeconds = Math.ceil((errorData.retryAfterMs || 0) / 1000);
|
||||
throw new Error(
|
||||
`Too many failed attempts. Please try again in ${retryAfterSeconds} seconds.`,
|
||||
);
|
||||
const retryAfterSeconds = Math.ceil((errorData.retryAfterMs || 60000) / 1000);
|
||||
if (retryAfterSeconds > 0) {
|
||||
throw new Error(
|
||||
`Too many failed attempts. Please try again in ${retryAfterSeconds} seconds.`,
|
||||
);
|
||||
} else {
|
||||
throw new Error("Too many failed attempts. Please try again later.");
|
||||
}
|
||||
}
|
||||
throw new Error("Challenge could not be retrieved");
|
||||
}
|
||||
@@ -240,11 +244,15 @@ export class UnifiedAppointmentCrypto {
|
||||
if (!verificationResponse.ok) {
|
||||
const errorData = await verificationResponse.json();
|
||||
console.error("❌ Challenge verification failed:", errorData);
|
||||
if (verificationResponse.status === 429 && errorData.retryAfterMs) {
|
||||
const retryAfterSeconds = Math.ceil(errorData.retryAfterMs / 1000);
|
||||
throw new Error(
|
||||
`Too many failed attempts. Please try again in ${retryAfterSeconds} seconds.`,
|
||||
);
|
||||
if (verificationResponse.status === 429) {
|
||||
const retryAfterSeconds = Math.ceil((errorData.retryAfterMs || 60000) / 1000);
|
||||
if (retryAfterSeconds > 0) {
|
||||
throw new Error(
|
||||
`Too many failed attempts. Please try again in ${retryAfterSeconds} seconds.`,
|
||||
);
|
||||
} else {
|
||||
throw new Error("Too many failed attempts. Please try again later.");
|
||||
}
|
||||
}
|
||||
throw new Error("Challenge verification failed");
|
||||
}
|
||||
|
||||
@@ -20,11 +20,17 @@ interface ThrottleResult {
|
||||
failedAttempts: number;
|
||||
}
|
||||
|
||||
// Throttle reset duration: 1 hour
|
||||
const THROTTLE_RESET_DURATION_MS = 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Calculate exponential backoff delay for PIN challenges
|
||||
* First 2 retries: wait for a few seconds
|
||||
* Third retry: wait for one minute
|
||||
* Scale up to thirty minutes
|
||||
* Calculate escalating delay for PIN challenges
|
||||
* Uses a fixed escalation pattern that increases with each failed attempt:
|
||||
* - 1st failure: 2 seconds
|
||||
* - 2nd failure: 10 seconds
|
||||
* - 3rd failure: 1 minute
|
||||
* - 4th failure: 5 minutes
|
||||
* - 5+ failures: 30 minutes
|
||||
*/
|
||||
function calculatePinThrottleDelay(failedAttempts: number): number {
|
||||
if (failedAttempts === 0) return 0;
|
||||
@@ -184,7 +190,7 @@ class ChallengeThrottleService {
|
||||
|
||||
if (records.length === 0) {
|
||||
// Create new throttle record
|
||||
const resetAt = new Date(now.getTime() + 60 * 60 * 1000); // 1 hour from now
|
||||
const resetAt = new Date(now.getTime() + THROTTLE_RESET_DURATION_MS);
|
||||
await db.insert(challengeThrottle).values({
|
||||
id: identifier,
|
||||
failedAttempts: 1,
|
||||
@@ -219,7 +225,7 @@ class ChallengeThrottleService {
|
||||
|
||||
if (records.length === 0) {
|
||||
// Create new throttle record
|
||||
const resetAt = new Date(now.getTime() + 60 * 60 * 1000); // 1 hour from now
|
||||
const resetAt = new Date(now.getTime() + THROTTLE_RESET_DURATION_MS);
|
||||
await db.insert(passkeyChallengeThrottle).values({
|
||||
id: identifier,
|
||||
failedAttempts: 1,
|
||||
|
||||
+26
-21
@@ -49,34 +49,39 @@ export const fetchChallenge = async (email: string) => {
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
let data;
|
||||
try {
|
||||
const data = await resp.json();
|
||||
data = await resp.json();
|
||||
} catch (error) {
|
||||
logger.error("Failed to parse challenge response", { email, error });
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle throttling
|
||||
if (resp.status === 429) {
|
||||
const retryAfterSeconds = Math.ceil((data.retryAfterMs || 60000) / 1000);
|
||||
logger.error("Challenge request throttled", {
|
||||
email,
|
||||
retryAfterSeconds,
|
||||
});
|
||||
// Handle throttling
|
||||
if (resp.status === 429) {
|
||||
const retryAfterSeconds = Math.ceil((data.retryAfterMs || 60000) / 1000);
|
||||
logger.error("Challenge request throttled", {
|
||||
email,
|
||||
retryAfterSeconds,
|
||||
});
|
||||
if (retryAfterSeconds > 0) {
|
||||
throw new Error(
|
||||
`Too many failed attempts. Please try again in ${retryAfterSeconds} seconds.`,
|
||||
);
|
||||
} else {
|
||||
throw new Error("Too many failed attempts. Please try again later.");
|
||||
}
|
||||
|
||||
if (!resp.ok) {
|
||||
logger.error("Failed to fetch challenge", { email, status: resp.status });
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: data.rpId,
|
||||
challenge: data.challenge,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error("Failed to fetch challenge", { email, status: resp.status, error });
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!resp.ok) {
|
||||
logger.error("Failed to fetch challenge", { email, status: resp.status });
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: data.rpId,
|
||||
challenge: data.challenge,
|
||||
};
|
||||
};
|
||||
|
||||
export const getCredentialOptions = ({
|
||||
|
||||
Reference in New Issue
Block a user