mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-08-06 07:00:12 +00:00
fix: align WebAuthn connectors with Bitwarden clients
Add official-compatible mobile and desktop connector flows, preserve exact .html asset paths, and cover the protocol and framing behavior with regression tests. Fixes #326
This commit is contained in:
+18
-1
@@ -8,6 +8,13 @@ export const OFFICIAL_BITWARDEN_BROWSER_EXTENSION_ORIGINS = [
|
||||
'chrome-extension://ccnckbpmaceehanjmeomladnmlffdjgn',
|
||||
] as const;
|
||||
|
||||
// Bitwarden desktop is migrating from file:// to this privileged Electron
|
||||
// origin. Official clients keep the legacy file:// path as a compatibility
|
||||
// fallback while self-hosted servers add CORS support for the new origin.
|
||||
export const OFFICIAL_BITWARDEN_DESKTOP_ORIGINS = [
|
||||
'bw-desktop-file://bundle',
|
||||
] as const;
|
||||
|
||||
export function normalizeOrigin(value: unknown): string | null {
|
||||
const raw = String(value || '').trim();
|
||||
if (!raw) return null;
|
||||
@@ -30,10 +37,20 @@ export function isBrowserExtensionOrigin(origin: unknown): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function isOfficialBitwardenDesktopOrigin(origin: unknown): boolean {
|
||||
const normalized = normalizeOrigin(origin);
|
||||
return !!normalized && OFFICIAL_BITWARDEN_DESKTOP_ORIGINS.includes(
|
||||
normalized as (typeof OFFICIAL_BITWARDEN_DESKTOP_ORIGINS)[number]
|
||||
);
|
||||
}
|
||||
|
||||
export function getConfiguredWebAuthnAllowedOrigins(
|
||||
env: Pick<Env, 'WEBAUTHN_ALLOWED_ORIGINS'>
|
||||
): string[] {
|
||||
const seen = new Set<string>(OFFICIAL_BITWARDEN_BROWSER_EXTENSION_ORIGINS);
|
||||
const seen = new Set<string>([
|
||||
...OFFICIAL_BITWARDEN_BROWSER_EXTENSION_ORIGINS,
|
||||
...OFFICIAL_BITWARDEN_DESKTOP_ORIGINS,
|
||||
]);
|
||||
for (const item of String(env.WEBAUTHN_ALLOWED_ORIGINS || '').split(',')) {
|
||||
const origin = normalizeOrigin(item);
|
||||
if (origin) seen.add(origin);
|
||||
|
||||
+19
-3
@@ -3,6 +3,7 @@ import type { Env } from '../types';
|
||||
import {
|
||||
isBrowserExtensionOrigin,
|
||||
isConfiguredWebAuthnAllowedOrigin,
|
||||
isOfficialBitwardenDesktopOrigin,
|
||||
normalizeOrigin,
|
||||
} from './origins';
|
||||
|
||||
@@ -48,7 +49,10 @@ function getCorsPolicy(request: Request, env: Env): { allowOrigin: string | null
|
||||
if (origin === url.origin) {
|
||||
return { allowOrigin: origin, allowCredentials: true };
|
||||
}
|
||||
if (isBrowserExtensionOrigin(origin) && isConfiguredWebAuthnAllowedOrigin(env, origin)) {
|
||||
if (
|
||||
(isBrowserExtensionOrigin(origin) || isOfficialBitwardenDesktopOrigin(origin))
|
||||
&& isConfiguredWebAuthnAllowedOrigin(env, origin)
|
||||
) {
|
||||
return { allowOrigin: origin, allowCredentials: true };
|
||||
}
|
||||
if (isWildcardCorsPath(url.pathname)) {
|
||||
@@ -100,10 +104,22 @@ export function applyCors(
|
||||
headers.set(k, v);
|
||||
}
|
||||
// Security headers applied to every response.
|
||||
headers.set('X-Frame-Options', 'DENY');
|
||||
headers.set('X-Content-Type-Options', 'nosniff');
|
||||
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
if (!headers.has('Content-Security-Policy')) {
|
||||
const isWebAuthnFrameConnector = new URL(request.url).pathname === '/webauthn-connector.html';
|
||||
if (isWebAuthnFrameConnector) {
|
||||
// Official desktop and browser clients render this exact endpoint inside a
|
||||
// 40px cross-origin iframe. The connector validates its parent before any
|
||||
// WebAuthn request or postMessage, so only this protocol page may be framed.
|
||||
headers.delete('X-Frame-Options');
|
||||
headers.set(
|
||||
'Content-Security-Policy',
|
||||
"default-src 'none'; script-src 'self'; style-src 'unsafe-inline'; connect-src 'self'; base-uri 'none'; form-action 'none'"
|
||||
);
|
||||
} else {
|
||||
headers.set('X-Frame-Options', 'DENY');
|
||||
}
|
||||
if (!isWebAuthnFrameConnector && !headers.has('Content-Security-Policy')) {
|
||||
headers.set('Content-Security-Policy', "frame-ancestors 'none'; img-src 'self' data:");
|
||||
}
|
||||
return new Response(response.body, {
|
||||
|
||||
Reference in New Issue
Block a user