Harden 2FA disable and website icon privacy

This commit is contained in:
shuaiplus
2026-07-06 18:45:54 +08:00
parent 5c8f01be59
commit c6438747e3
10 changed files with 52 additions and 18 deletions
+1 -9
View File
@@ -1161,16 +1161,8 @@ export async function handleDisableTwoFactorProvider(request: Request, env: Env,
return errorResponse('Two-factor provider is not supported by this server.', 400);
}
const key = normalizeTotpSecret(readBodyString(body, ['key', 'Key']));
const userVerificationToken = readBodyString(body, ['userVerificationToken', 'UserVerificationToken']);
const secret = readBodyString(body, ['masterPasswordHash', 'MasterPasswordHash', 'otp', 'OTP', 'secret', 'Secret']);
let verified = false;
if (key && userVerificationToken) {
verified = await verifyTotpUserVerificationToken(env, user, key, userVerificationToken);
}
if (!verified) {
verified = await verifyUserSecret(auth, user, secret);
}
const verified = await verifyUserSecret(auth, user, secret);
if (!verified) return errorResponse('User verification failed.', 400);
if (type === TWO_FACTOR_PROVIDER_AUTHENTICATOR) {
+12 -2
View File
@@ -46,6 +46,11 @@ export interface WebBootstrapResponse {
jwtSecretMinLength: number;
registrationInviteRequired: boolean;
webAuthnAllowedOrigins: string[];
websiteIconsEnabled: boolean;
}
function isWebsiteIconProxyEnabled(env: Env): boolean {
return ['1', 'true', 'yes', 'on'].includes(String(env.WEBSITE_ICONS_ENABLED || '').trim().toLowerCase());
}
function isSameOriginWriteRequest(request: Request): boolean {
@@ -257,7 +262,11 @@ function iconResponse(body: BodyInit | null, contentType: string | null): Respon
});
}
async function handleWebsiteIcon(host: string, fallbackMode: 'default' | 'not-found' = 'default'): Promise<Response> {
async function handleWebsiteIcon(env: Env, host: string, fallbackMode: 'default' | 'not-found' = 'default'): Promise<Response> {
if (!isWebsiteIconProxyEnabled(env)) {
return fallbackMode === 'not-found' ? handleMissingWebsiteIcon() : handleNwFavicon();
}
const normalizedHost = normalizeIconHost(host);
if (!normalizedHost) return fallbackMode === 'not-found' ? handleMissingWebsiteIcon() : handleNwFavicon();
@@ -325,6 +334,7 @@ export async function buildWebBootstrapResponse(env: Env): Promise<WebBootstrapR
jwtSecretMinLength: LIMITS.auth.jwtSecretMinLength,
registrationInviteRequired: userCount > 0,
webAuthnAllowedOrigins: getConfiguredWebAuthnAllowedOrigins(env),
websiteIconsEnabled: isWebsiteIconProxyEnabled(env),
};
}
@@ -375,7 +385,7 @@ export async function handlePublicRoute(
const blocked = await enforcePublicRateLimit('public-icon', LIMITS.rateLimit.publicIconRequestsPerMinute);
if (blocked) return blocked;
const fallbackMode = new URL(request.url).searchParams.get('fallback') === '404' ? 'not-found' : 'default';
return handleWebsiteIcon(iconMatch[1], fallbackMode);
return handleWebsiteIcon(env, iconMatch[1], fallbackMode);
}
const publicAttachmentMatch = path.match(/^\/api\/attachments\/([a-f0-9-]+)\/([a-f0-9-]+)$/i);
+1
View File
@@ -20,6 +20,7 @@ export interface Env {
'globalSettings__yubico__clientId'?: string;
'globalSettings__yubico__key'?: string;
'globalSettings__yubico__validationUrls'?: string;
WEBSITE_ICONS_ENABLED?: string;
}
export type UserRole = 'admin' | 'user';