Address code review feedback - improve error handling and documentation

Co-authored-by: hbel <7416029+hbel@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-14 14:28:24 +00:00
co-authored by hbel
parent 3f4d92f3ff
commit 6d70ea0eed
3 changed files with 55 additions and 36 deletions
+17 -9
View File
@@ -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");
}
+12 -6
View File
@@ -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
View File
@@ -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 = ({