85dda211aed3

add PasskeyData sub-object + entry.passkey field to VaultEntry, hasPasskey/isPasskeyOnly helpers, keep flat fields as deprecated for migration

time
2026-04-28 21:59 UTC · 6 months ago
machine
koh2
parent
a86e59d61c04
1 file src/shared/types.ts+62 −4
src/shared/types.ts+62 −4
@@ -24,9 +24,46 @@2424 2525 export type CardNetwork = "visa" | "mastercard" | "amex" | "discover" | "unknown";2626 27+/**28+ * Passkey credential data attached to a VaultEntry. New in v2.11.0 — passkeys29+ * are no longer a separate entry type; instead they hang off any login entry.30+ * A passkey-only entry is a `type:"login"` entry with no username/password31+ * but a `passkey` sub-object set. A login that ALSO has a passkey is just a32+ * normal login with `passkey` populated.33+ *34+ * privateKeyJwk is present only on Chrome-created passkeys, where Peach is35+ * the authenticator. Firefox-created passkeys delegate to the platform36+ * authenticator, so the field is absent — `browser` distinguishes the two.37+ */38+export interface PasskeyData {39+  rpId: string;40+  rpName: string;41+  credentialId: string;42+  publicKeyCose: string;43+  privateKeyJwk?: JsonWebKey;44+  userHandle: string;45+  userName: string;46+  userDisplayName: string;47+  algorithm: -7 | -8;48+  signCount: number;49+  aaguid: string;50+  backupEligible: boolean;51+  backupState: boolean;52+  lastUsedAt?: number;53+  browser?: "chrome" | "firefox";54+  transports?: string[];55+  /** When the credential was first registered with Peach. */56+  createdAt: number;57+}58+2759 export interface VaultEntry {2860   id: string; // crypto.randomUUID()29-  type?: "login" | "note" | "passkey" | "card"; // defaults to "login" for existing entries61+  /**62+   * Entry type. As of v2.11.0 passkeys are stored on `type:"login"` entries63+   * via the `passkey` sub-object — `"passkey"` here is a legacy value still64+   * accepted on read but rewritten to `"login"` on next vault save.65+   */66+  type?: "login" | "note" | "passkey" | "card";3067   title: string;3168   username?: string; // only for login entries3269   password?: string; // only for login entries@@ -52,6 +89,12 @@5289   modifiedAt: number;5390   deletedAt?: number;5491   tags?: string[];92+  /** Passkey credential attached to this entry, if any. */93+  passkey?: PasskeyData;94+  // ── Legacy passkey flat fields (pre-v2.11.0). Populated by vault read95+  // migration so that the old import/sanitize paths keep typing; new code96+  // should always read `entry.passkey?.*` instead. The migration drops these97+  // on next vault save. Do not introduce new readers of these fields.5598   rpId?: string;5699   rpName?: string;57100   credentialId?: string;@@ -66,9 +109,6 @@66109   backupEligible?: boolean;67110   backupState?: boolean;68111   lastUsedAt?: number;69-  // Passkeys are not portable between browsers: a Chrome passkey holds its70-  // private key in Peach's vault, while a Firefox passkey delegates to the71-  // platform authenticator. Tag each entry so the UI can warn cross-browser.72112   browser?: "chrome" | "firefox";73113   transports?: string[];74114   // Card entry fields@@ -80,6 +120,24 @@80120   cardNickname?: string;81121   network?: CardNetwork;82122   last4?: string;123+}124+125+/**126+ * Returns true when an entry has a passkey credential attached. After v2.11.0127+ * migration this is the canonical check; pre-migration entries with a flat128+ * `rpId`/`credentialId` shape get rewritten by the vault read pass before129+ * any consumer sees them.130+ */131+export function hasPasskey(entry: VaultEntry): boolean {132+  return entry.passkey !== undefined && entry.passkey.credentialId.length > 0;133+}134+135+/**136+ * Returns true when an entry is "passkey-only" — has a passkey but no137+ * username or password. Such entries render with login fields hidden.138+ */139+export function isPasskeyOnly(entry: VaultEntry): boolean {140+  return hasPasskey(entry) && !entry.username && !entry.password;83141 }84142 85143 export interface LegacyVaultEntry {

view file