Skip to content

Commit 5aa11e8

Browse files
fix: address PR review comments
- Fix misleading log message: 'failing open' -> 'failing closed (503)' in LogCaptchaServiceUnavailable (hCaptcha unavailable returns 503, not open) - Remove dead ArgumentException/FormatException catch blocks from sitemap validation; EnsureSitemapHealthy only throws InvalidOperationException - Remove duplicate top-level ForwardedHeaders section from appsettings.json (two keys at the same path; keep the one with both TrustedProxyCidrs and TrustedProxies) - Fix captcha challenge timeout: start 90s timer after widget is ready, not before — previously script-load time ate into the challenge window; also increases from 15s to 90s to accommodate interactive challenges
1 parent b334c0c commit 5aa11e8

4 files changed

Lines changed: 38 additions & 46 deletions

File tree

EssentialCSharp.Web/Controllers/ChatController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ public async Task StreamMessage([FromBody] ChatMessageRequest request, Cancellat
290290
}
291291
}
292292

293-
[LoggerMessage(Level = LogLevel.Warning, Message = "hCaptcha service unavailable during chat request — failing open")]
293+
[LoggerMessage(Level = LogLevel.Warning, Message = "hCaptcha service unavailable during chat request — failing closed (503)")]
294294
private static partial void LogCaptchaServiceUnavailable(ILogger<ChatController> logger);
295295

296296
[LoggerMessage(Level = LogLevel.Warning, Message = "hCaptcha validation failed for chat request — error codes: {ErrorCodes}")]

EssentialCSharp.Web/Program.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -616,16 +616,6 @@ await McpJsonRpcResponseWriter.WriteErrorAsync(
616616
LogSitemapValidationFailed(logger, ex);
617617
// Continue startup even if sitemap validation fails
618618
}
619-
catch (ArgumentException ex)
620-
{
621-
LogSitemapValidationFailed(logger, ex);
622-
// Continue startup even if sitemap validation fails
623-
}
624-
catch (FormatException ex)
625-
{
626-
LogSitemapValidationFailed(logger, ex);
627-
// Continue startup even if sitemap validation fails
628-
}
629619

630620
app.Run();
631621
}

EssentialCSharp.Web/appsettings.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
"ConnectionStrings": {
33
"PostgresVectorStore": "your-postgres-connection-string-here"
44
},
5-
"ForwardedHeaders": {
6-
"TrustedProxyCidrs": []
7-
},
85
"Logging": {
96
"LogLevel": {
107
"Default": "Warning",

EssentialCSharp.Web/wwwroot/js/chat-module.js

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ let captchaTokenResolve = null;
2020
let captchaTokenReject = null;
2121
let captchaPending = false; // prevents concurrent token requests overwriting promise callbacks
2222
let captchaRequestGeneration = 0;
23-
const CAPTCHA_TIMEOUT_MS = 15_000;
23+
const CAPTCHA_TIMEOUT_MS = 90_000;
2424

2525
// Resolves once the widget has rendered. getCaptchaToken() awaits this so a user who
2626
// submits before hCaptcha finishes loading waits (up to 15 s) rather than getting a 403.
@@ -68,49 +68,54 @@ function initCaptchaWidget() {
6868
* Returns a fresh hCaptcha token, or null if captcha is not configured.
6969
* Waits for the widget to finish rendering if it has not yet (handles slow script loads).
7070
* Rejects with 'captcha-concurrent' if a token request is already in-flight.
71-
* Rejects with 'captcha-timeout' if the widget does not respond within 15 seconds.
71+
* Rejects with 'captcha-timeout' if the challenge does not complete within 90 seconds
72+
* after the widget is ready (the timer starts after widget load, not before).
7273
*/
7374
function getCaptchaToken() {
7475
if (!captchaSiteKey) return Promise.resolve(null);
7576
if (captchaPending) return Promise.reject(new Error('captcha-concurrent'));
7677
captchaPending = true;
7778
const requestGeneration = ++captchaRequestGeneration;
7879

79-
let timeoutId;
80-
8180
// Chain onto captchaWidgetReady so calls made before the widget finishes loading
8281
// wait rather than immediately returning null and causing a 403.
83-
const tokenPromise = captchaWidgetReady.then(() => new Promise((resolve, reject) => {
82+
// The challenge timeout starts only after the widget is ready so script-load time
83+
// does not eat into the window available for completing an interactive challenge.
84+
return captchaWidgetReady.then(() => {
8485
if (requestGeneration !== captchaRequestGeneration) {
85-
reject(new Error('captcha-stale'));
86-
return;
87-
}
88-
captchaTokenResolve = (token) => {
89-
captchaPending = false;
90-
clearTimeout(timeoutId); // cancel lingering timer so it can't corrupt the next call
91-
resolve(token);
92-
};
93-
captchaTokenReject = (err) => {
9486
captchaPending = false;
95-
clearTimeout(timeoutId);
96-
reject(err);
97-
};
98-
window.hcaptcha.execute(captchaWidgetId);
99-
}));
100-
101-
const timeoutPromise = new Promise((_, reject) => {
102-
// timeoutId is assigned synchronously here, before captchaTokenResolve can fire
103-
timeoutId = setTimeout(() => {
104-
if (requestGeneration !== captchaRequestGeneration) return;
105-
captchaRequestGeneration++;
106-
captchaPending = false;
107-
captchaTokenResolve = null;
108-
captchaTokenReject = null;
109-
reject(new Error('captcha-timeout'));
110-
}, CAPTCHA_TIMEOUT_MS);
111-
});
87+
return Promise.reject(new Error('captcha-stale'));
88+
}
89+
90+
let timeoutId;
11291

113-
return Promise.race([tokenPromise, timeoutPromise]);
92+
const tokenPromise = new Promise((resolve, reject) => {
93+
captchaTokenResolve = (token) => {
94+
captchaPending = false;
95+
clearTimeout(timeoutId); // cancel lingering timer so it can't corrupt the next call
96+
resolve(token);
97+
};
98+
captchaTokenReject = (err) => {
99+
captchaPending = false;
100+
clearTimeout(timeoutId);
101+
reject(err);
102+
};
103+
window.hcaptcha.execute(captchaWidgetId);
104+
});
105+
106+
const timeoutPromise = new Promise((_, reject) => {
107+
timeoutId = setTimeout(() => {
108+
if (requestGeneration !== captchaRequestGeneration) return;
109+
captchaRequestGeneration++;
110+
captchaPending = false;
111+
captchaTokenResolve = null;
112+
captchaTokenReject = null;
113+
reject(new Error('captcha-timeout'));
114+
}, CAPTCHA_TIMEOUT_MS);
115+
});
116+
117+
return Promise.race([tokenPromise, timeoutPromise]);
118+
});
114119
}
115120

116121
function resetCaptchaWidget() {

0 commit comments

Comments
 (0)