fix(auth): validate WebAuthn rp.id against current host
Add ensureValidRpId helper to validate and correct rp.id for WebAuthn operations, preventing authentication failures when the configured rp.id doesn't match the current hostname. Also add OAuthProvider type and fix type casting in LoginPage.
This commit is contained in:
@@ -8,6 +8,11 @@
|
||||
*/
|
||||
export type OAuthFlow = 'login' | 'register' | 'link';
|
||||
|
||||
/**
|
||||
* Supported OAuth providers.
|
||||
*/
|
||||
export type OAuthProvider = 'google' | 'github' | 'microsoft';
|
||||
|
||||
/**
|
||||
* Parameters for storing OAuth state.
|
||||
*/
|
||||
|
||||
+18
-1
@@ -45,6 +45,19 @@ export async function isPlatformAuthenticatorAvailable(): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to ensure rp.id is valid for the current origin
|
||||
function ensureValidRpId(rpId: string): string {
|
||||
const currentHost = window.location.hostname;
|
||||
// valid if rpId matches host or is a parent domain (e.g. host ends with .rpId)
|
||||
const isValid = currentHost === rpId || currentHost.endsWith('.' + rpId);
|
||||
|
||||
if (!isValid) {
|
||||
console.warn(`[WebAuthn] Invalid rp.id "${rpId}" for current host "${currentHost}". Overriding with "${currentHost}".`);
|
||||
return currentHost;
|
||||
}
|
||||
return rpId;
|
||||
}
|
||||
|
||||
// Types for WebAuthn API responses
|
||||
export interface WebAuthnRegistrationOptions {
|
||||
rp: {
|
||||
@@ -107,6 +120,10 @@ export async function createRegistrationCredential(
|
||||
): Promise<PublicKeyCredential> {
|
||||
const publicKeyOptions: PublicKeyCredentialCreationOptions = {
|
||||
...options,
|
||||
rp: {
|
||||
...options.rp,
|
||||
id: ensureValidRpId(options.rp.id),
|
||||
},
|
||||
challenge: base64ToBuffer(options.challenge),
|
||||
user: {
|
||||
id: base64ToBuffer(options.user.id),
|
||||
@@ -157,7 +174,7 @@ export async function createLoginAssertion(
|
||||
const publicKeyOptions: PublicKeyCredentialRequestOptions = {
|
||||
challenge: base64ToBuffer(options.challenge),
|
||||
timeout: options.timeout,
|
||||
rpId: options.rpId,
|
||||
rpId: ensureValidRpId(options.rpId),
|
||||
allowCredentials: options.allowCredentials.map((cred) => ({
|
||||
...cred,
|
||||
id: base64ToBuffer(cred.id),
|
||||
|
||||
@@ -218,7 +218,7 @@ export default function LoginPage() {
|
||||
|
||||
try {
|
||||
// Step 1: Get login options from server
|
||||
const options = await api.webauthn.beginLogin(emailToUse) as WebAuthnLoginOptions;
|
||||
const options = await api.webauthn.beginLogin(emailToUse) as unknown as WebAuthnLoginOptions;
|
||||
|
||||
// Step 2: Create assertion using browser WebAuthn API
|
||||
const assertion = await createLoginAssertion(options);
|
||||
@@ -286,7 +286,7 @@ export default function LoginPage() {
|
||||
|
||||
try {
|
||||
// Step 1: Get login options from server
|
||||
const options = await api.webauthn.beginLogin(email) as WebAuthnLoginOptions;
|
||||
const options = await api.webauthn.beginLogin(email) as unknown as WebAuthnLoginOptions;
|
||||
|
||||
// Step 2: Create assertion using browser WebAuthn API
|
||||
const assertion = await createLoginAssertion(options);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"root":["./src/App.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/NavLink.tsx","./src/components/auth/BannerAlert.tsx","./src/components/auth/ComplianceBanner.tsx","./src/components/auth/PasswordStrengthMeter.tsx","./src/components/branding/GatehouseLogo.tsx","./src/components/dev/ApiDevTools.tsx","./src/components/layouts/AuthenticatedLayout.tsx","./src/components/layouts/MfaEnforcementLayout.tsx","./src/components/layouts/ProtectedLayout.tsx","./src/components/layouts/PublicLayout.tsx","./src/components/navigation/AppSidebar.tsx","./src/components/navigation/TopBar.tsx","./src/components/security/AddPasskeyWizard.tsx","./src/components/security/TotpEnrollmentWizard.tsx","./src/components/security/TotpRemoveDialog.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/aspect-ratio.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/carousel.tsx","./src/components/ui/chart.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/command.tsx","./src/components/ui/context-menu.tsx","./src/components/ui/dialog.tsx","./src/components/ui/drawer.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/form.tsx","./src/components/ui/hover-card.tsx","./src/components/ui/input-otp.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/menubar.tsx","./src/components/ui/navigation-menu.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/radio-group.tsx","./src/components/ui/resizable.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/slider.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/toast.tsx","./src/components/ui/toaster.tsx","./src/components/ui/toggle-group.tsx","./src/components/ui/toggle.tsx","./src/components/ui/tooltip.tsx","./src/components/ui/use-toast.ts","./src/contexts/AuthContext.tsx","./src/hooks/use-mobile.tsx","./src/hooks/use-toast.ts","./src/hooks/useOrganizations.ts","./src/lib/api.ts","./src/lib/encoding.ts","./src/lib/oauth.ts","./src/lib/utils.ts","./src/lib/webauthn.ts","./src/pages/Index.tsx","./src/pages/NotFound.tsx","./src/pages/auth/ForgotPasswordPage.tsx","./src/pages/auth/InviteAcceptPage.tsx","./src/pages/auth/LoginPage.tsx","./src/pages/auth/OAuthCallbackPage.tsx","./src/pages/auth/OIDCConsentPage.tsx","./src/pages/auth/OIDCErrorPage.tsx","./src/pages/auth/RegisterPage.tsx","./src/pages/auth/ResetPasswordPage.tsx","./src/pages/auth/VerifyEmailPage.tsx","./src/pages/org/CompliancePage.tsx","./src/pages/org/MembersPage.tsx","./src/pages/org/OIDCClientsPage.tsx","./src/pages/org/OrgAuditPage.tsx","./src/pages/org/OrgOverviewPage.tsx","./src/pages/org/PoliciesPage.tsx","./src/pages/user/ActivityPage.tsx","./src/pages/user/LinkedAccountsPage.tsx","./src/pages/user/ProfilePage.tsx","./src/pages/user/SecurityPage.tsx"],"errors":true,"version":"5.8.3"}
|
||||
@@ -0,0 +1 @@
|
||||
{"root":["./vite.config.ts"],"version":"5.8.3"}
|
||||
Reference in New Issue
Block a user