Files
kapteins-daagbok/client/src/utils/passkeyHost.ts
T
elpatron 0caaf681d8 Fix live journal freeze and passkey login on localhost.
Harden live log init with safe per-entry decrypt, stable loading state, and no parallel list scan in live mode. Improve multi-sail picker UX, stop WebAuthn retry after user cancel, redirect 127.0.0.1 to localhost, and tolerate missing appearance prefs table.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 08:49:45 +02:00

70 lines
2.4 KiB
TypeScript

/**
* WebAuthn / Passkeys require a valid domain (see WHATWG valid domain).
* IP addresses such as 127.0.0.1 are rejected by browsers and @simplewebauthn/browser.
*/
export function isPasskeyCompatibleHostname(hostname: string): boolean {
return (
hostname === 'localhost' ||
/^((xn--[a-z0-9-]+|[a-z0-9]+(-[a-z0-9]+)*)\.)+([a-z]{2,}|xn--[a-z0-9-]+)$/i.test(hostname)
)
}
export function isPasskeyCompatibleLocation(loc: Location = window.location): boolean {
return isPasskeyCompatibleHostname(loc.hostname)
}
/** Same page on localhost — for dev links when opened via 127.0.0.1. */
export function toPasskeyCompatibleUrl(href: string): string {
const url = new URL(href)
if (url.hostname === '127.0.0.1' || url.hostname === '[::1]' || url.hostname === '::1') {
url.hostname = 'localhost'
}
return url.toString()
}
/**
* Redirect 127.0.0.1 / ::1 to localhost (dev). Returns true if navigation was started.
*/
export function redirectToPasskeyCompatibleHostIfNeeded(loc: Location = window.location): boolean {
if (isPasskeyCompatibleHostname(loc.hostname)) return false
const target = toPasskeyCompatibleUrl(loc.href)
if (target === loc.href) return false
window.location.replace(target)
return true
}
export function isPasskeyInvalidDomainError(message: string): boolean {
return /is an invalid domain$/i.test(message)
}
export function localizePasskeyHostError(message: string, invalidHostMessage: string): string {
return isPasskeyInvalidDomainError(message) ? invalidHostMessage : message
}
/** User dismissed or denied the platform passkey prompt (do not auto-retry WebAuthn). */
export function isWebAuthnUserAbortError(err: unknown): boolean {
if (!err || typeof err !== 'object') return false
const name = 'name' in err ? String((err as { name: string }).name) : ''
if (name === 'NotAllowedError' || name === 'AbortError') return true
const message = 'message' in err ? String((err as { message: string }).message) : String(err)
return /timed out|not allowed|cancel/i.test(message)
}
export function localizeWebAuthnError(
message: string,
messages: {
invalidHost: string
cancelled: string
invalidRpId?: string
}
): string {
if (isPasskeyInvalidDomainError(message)) return messages.invalidHost
if (/timed out|not allowed|cancel/i.test(message)) return messages.cancelled
if (/invalid for this domain/i.test(message) && messages.invalidRpId) {
return messages.invalidRpId
}
return message
}