types.ts

9.8 KB · raw

1/**2 * types.ts — cross-boundary types and the `MessageType` union.3 *4 * `MessageType` mixes background-bound and content-script-bound messages.5 * Background-bound types have a case in `src/background/messages.ts`.6 * Content-script-bound types (PING, SUMMON_AUTOFILL, VAULT_LOCKED,7 * RESTART_AUTO_LOCK, etc.) are sent *to* content scripts or the popup and8 * have no handler in messages.ts by design — that absence is not a missing9 * handler. Add new cross-boundary types here, not inline in call sites.10 */1112export interface ManualSummonRecord {13  firstSummonedAt: number;14  summonCount: number;15  lastSummonedAt: number;16}1718export interface Canary {19  webhookUrl: string;20  label?: string;21  lastPinged?: number;22  pingCount?: number;23}2425export type CardNetwork = "visa" | "mastercard" | "amex" | "discover" | "unknown";2627export interface VaultEntry {28  id: string; // crypto.randomUUID()29  type?: "login" | "note" | "passkey" | "card"; // defaults to "login" for existing entries30  title: string;31  username?: string; // only for login entries32  password?: string; // only for login entries33  url?: string; // only for login entries34  /**35   * Additional sites this entry should match for autofill. Login entries only.36   * Each value is a full origin or URL (e.g. `https://login.foo.com`).37   * The vault domain index keys against the hostname of every entry in this38   * list, in addition to `url`.39   */40  additionalUrls?: string[];41  totp?: string; // base32 seed, stored encrypted — only for login entries42  notes?: string;43  breach?: number; // HIBP exposure count; 0 or undefined = clean — only for login entries44  /**45   * When the password field was last changed. Set on first-write of `password`46   * and refreshed on every subsequent change. Defaults to `createdAt` when47   * legacy entries are read by the advanced health analyser.48   */49  passwordChangedAt?: number;50  canary?: Canary;51  createdAt: number; // Date.now()52  modifiedAt: number;53  deletedAt?: number;54  tags?: string[];55  rpId?: string;56  rpName?: string;57  credentialId?: string;58  publicKeyCose?: string;59  privateKeyJwk?: JsonWebKey;60  userHandle?: string;61  userName?: string;62  userDisplayName?: string;63  algorithm?: -7 | -8;64  signCount?: number;65  aaguid?: string;66  backupEligible?: boolean;67  backupState?: boolean;68  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.72  browser?: "chrome" | "firefox";73  transports?: string[];74  // Card entry fields75  cardholderName?: string;76  cardNumber?: string;       // full PAN, stored encrypted in vault77  expMonth?: string;78  expYear?: string;79  cvc?: string;80  cardNickname?: string;81  network?: CardNetwork;82  last4?: string;83}8485export interface LegacyVaultEntry {86  id: string;87  title: string;88  username: string;89  password: string;90  url: string;91  totp?: string;92  notes?: string;93  breach?: number;94  created: number;95  updated: number;96  deletedAt?: number;97}9899export interface CardEntry extends VaultEntry {100  type: "card";101  cardholderName: string;102  cardNumber: string;103  expMonth: string;104  expYear: string;105  cvc: string;106}107108export interface PasskeyEntry extends VaultEntry {109  type: "passkey";110  rpId: string;111  rpName: string;112  credentialId: string;113  publicKeyCose: string;114  /**115   * Encrypted vault holds the private key only for Chrome-created passkeys,116   * where Peach is the authenticator. Firefox-created passkeys delegate to117   * the platform authenticator, so this field is absent — `browser`118   * distinguishes the two paths.119   */120  privateKeyJwk?: JsonWebKey;121  userHandle: string;122  userName: string;123  userDisplayName: string;124  algorithm: -7 | -8;125  signCount: number;126  aaguid: string;127  backupEligible: boolean;128  backupState: boolean;129  lastUsedAt?: number;130  // Optional on read for pre-v2.10.0 entries; new entries always set it.131  browser?: "chrome" | "firefox";132  transports?: string[];133}134135export interface PendingPasskeyRequest {136  requestId: number;137  type: "create" | "get";138  rpId: string;139  rpName: string;140  userName: string;141  userDisplayName?: string;142  /**143   * Firefox path: when present, the popup performs the navigator.credentials144   * call itself rather than completing a chrome.webAuthenticationProxy145   * request. Absent for Chrome (the existing webAuthenticationProxy flow).146   */147  browser?: "chrome" | "firefox";148  /** Firefox create: base64url challenge minted by the popup-context flow. */149  challenge?: string;150  /** Firefox create: base64url user handle. Generated if absent. */151  userHandle?: string;152}153154export interface PasskeyProviderState {155  supported: boolean;156  enabled: boolean;157  attached: boolean;158  browserLabel: string;159  statusLabel: string;160  conflict?: string;161}162163export interface StoredVault {164  version: 1; // AAD for AES-GCM --- prevents downgrade165  salt: string; // hex --- 16 bytes, CSPRNG, stored not secret166  nonce: string; // hex --- 12 bytes, CSPRNG, fresh per write167  ciphertext: string; // hex --- AES-256-GCM output168  tag: string; // hex --- 16-byte GCM auth tag169}170171// Background-bound message types — sent from popup/content scripts,172// handled in src/background/messages.ts. Everything in this group must173// have a matching case in that file's switch statement.174export type MessageType =175  | "UNLOCK"176  | "LOCK"177  | "IS_UNLOCKED"178  | "HAS_VAULT"179  | "GET_ENTRIES"180  | "ADD_ENTRY"181  | "BATCH_ADD_ENTRIES"182  | "RESTORE_ENTRIES"183  | "CLEAR_VAULT_ENTRIES"184  | "UPDATE_ENTRY"185  | "ADD_ADDITIONAL_URL"186  | "DELETE_ENTRY"187  | "AUTOFILL_QUERY"188  | "COPY"189  | "CREATE_VAULT"190  | "GET_ENTRY_FOR_FILL"191  | "CHECK_BREACH"192  | "CHECK_ALL_BREACHES"193  | "GET_SETTINGS"194  | "UPDATE_SETTINGS"195  | "FIRE_CANARY_PING"196  | "ACTIVATE_LICENSE"197  | "GET_LICENSE"198  | "GET_LICENSE_KEY"199  | "IS_DURESS_ENABLED"200  | "SETUP_DURESS"201  | "DISABLE_DURESS"202  | "VERIFY_MAIN_PASSWORD"203  | "CHANGE_PASSWORD"204  | "GET_SITE_PASSWORD_POLICY"205  | "GET_SYNC_STATE"206  | "TRIGGER_SYNC"207  | "START_PAIRING"208  | "CANCEL_PAIRING"209  | "ACCEPT_PAIRING"210  | "REMOVE_PAIRED_DEVICE"211  | "REMOVE_TRUSTED_NETWORK"212  | "MARK_NETWORK_TRUSTED"213  | "GET_PENDING_PASSKEY"214  | "APPROVE_PASSKEY"215  | "DENY_PASSKEY"216  | "DELETE_PASSKEY"217  | "GET_PASSKEYS"218  | "GET_PASSKEY_PROVIDER_STATE"219  | "SET_PASSKEY_PROVIDER_ENABLED"220  | "GET_DELETED_ENTRIES"221  | "RESTORE_ENTRY"222  | "PURGE_ENTRY"223  | "PURGE_EXPIRED_TRASH"224  | "LIST_VAULT_SNAPSHOTS"225  | "RESTORE_VAULT_SNAPSHOT"226  | "COUNT_DUPLICATES"227  | "DEDUPLICATE_ENTRIES"228  | "RECORD_MANUAL_SUMMON"229  | "DECLINE_CARD_SAVE"230  | "IS_CARD_DECLINED"231  | "SUBMIT_FEEDBACK"232  | "GET_VAULT_STATUS"233  | "GET_ADVANCED_HEALTH_REPORT"234  | "INITIATE_PASSWORD_CHANGE"235  | "DEACTIVATE_LICENSE"236  | "GET_IDENTITIES"237  | "DESTROY_VAULT"238  | "INITIATE_FIREFOX_PASSKEY_CREATE"239  | "COMPLETE_FIREFOX_PASSKEY_CREATE"240  | "PASSKEY_OFFER_DECLINE"241  | "GET_PASSKEY_OFFER_DECLINED"242  | "GET_PASSKEY_FOR_DOMAIN"243  // Content-script-bound message types — sent BY the background TO content244  // scripts. They never reach messages.ts's switch. Kept in this union so245  // existing call sites type-check; do not add cases for them in the246  // background handler.247  | "PING"            // background → content: liveness probe248  | "SUMMON_AUTOFILL" // background → content: open the in-page palette249  | "VAULT_LOCKED"    // background → content/popup: vault just locked250  | "VAULT_UNLOCKED"  // background → content/popup: vault just unlocked251  | "PRO_GATE_TRIGGERED" // background → popup: a Pro feature was attempted on free252  | "PASSKEY_PRO_GATE" // background → content: show passkey Pro gate in inline popup253  | "OPEN_PRO_GATE" // content → background: open popup and navigate to License section254  | "SHOULD_PLAY_CELEBRATION"255  | "MARK_CELEBRATION_PLAYED";256257export type ProFeature =258  | "sync"259  | "passkeys"260  | "advanced_health"261  | "smart_password_change"262  | "ios_app";263264/**265 * License export shape included in PeachScript and encrypted JSON backups.266 * `celebrationPlayed` is intentionally absent — a new device deserves the moment.267 */268export interface PeachLicenseExport {269  key: string;270  activatedAt?: number;271}272273export interface AdvancedHealthEntryReport {274  id: string;275  title: string;276  domain: string;277  url?: string;278  score: number;279  ageDays: number;280  reasons: string[];281  factors: {282    breached: boolean;283    breachCount: number;284    reused: boolean;285    aged: boolean;286    weak: boolean;287    missingTotpOnSensitive: boolean;288  };289}290291export interface AdvancedHealthReport {292  vaultScore: number;293  generatedAt: number;294  entryCount: number;295  reusedGroups: number;296  agedCount: number;297  remediation: AdvancedHealthEntryReport[];298}299300export const ACCENT_COLORS = [301  { name: "peach",    hex: "#F49C70" },302  { name: "rose",     hex: "#FF8AA6" },303  { name: "plum",     hex: "#D597F2" },304  { name: "lavender", hex: "#A6A8FF" },305  { name: "sky",      hex: "#5BC8EE" },306  { name: "mint",     hex: "#5EE0B0" },307  { name: "sage",     hex: "#B5D870" },308  { name: "butter",   hex: "#FFD45A" },309  { name: "sand",     hex: "#EDBC85" },310  { name: "clay",     hex: "#F08660" },311  { name: "slate",    hex: "#80BEDE" },312  { name: "stone",    hex: "#C8B8A8" },313] as const;314315export type AccentColorName = typeof ACCENT_COLORS[number]["name"];316317export interface SavedIdentity {318  value: string;319  type: "email" | "username";320  lastUsed: number;321  useCount: number;322  exampleDomain?: string;323}324325export interface PasswordPolicy {326  minLength?: number;327  maxLength?: number;328  requiresUppercase?: boolean;329  requiresLowercase?: boolean;330  requiresNumbers?: boolean;331  requiresSymbols?: boolean;332  forbidsSymbols?: boolean;333  allowedSymbols?: string;334  /** Characters the site explicitly disallows. Filtered from every pool before generation. */335  forbiddenChars?: string;336}337338export interface Message {339  type: MessageType;340  payload?: unknown;341}342343export interface MessageResponse {344  ok: boolean;345  data?: unknown;346  error?: string;347  unlocked?: boolean;348}