Ctrl K
ComunidadGuías…

shared/utils/passkeys.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * Utility functions for working with passkeys and WebAuthn.
 */

export type DesktopAppPlatform = "macOS" | "Windows";

interface ParsedUserAgent {
  browser?: string;
  os?: string;
  device?: string;
}

export enum PasskeyBrand {
  AliasVault = "a11a5faa-9f32-4b8c-8c5d-2f7d13e8c942",
  GooglePasswordManager = "ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4",
  ChromeOnMac = "adce0002-35bc-c60a-648b-0b25f1f05503",
  WindowsHello1 = "08987058-cadc-4b81-b6e1-30de50dcbe96",
  WindowsHello2 = "9ddd1817-af5a-4672-a2b9-3e3dd95000a9",
  WindowsHello3 = "6028b017-b1d4-4c02-b4b3-afcdafc96bb2",
  ICloudKeychainManaged = "dd4ec289-e01d-41c9-bb89-70fa845d4bf2",
  Dashlane = "531126d6-e717-415c-9320-3d9aa6981239",
  OnePassword = "bada5566-a7aa-401f-bd96-45619a55120d",
  NordPass = "b84e4048-15dc-4dd0-8640-f4f60813c8af",
  Keeper = "0ea242b4-43c4-4a1b-8b17-dd6d0b6baec6",
  Sesame = "891494da-2c90-4d31-a9cd-4eab0aed1309",
  Enpass = "f3809540-7f14-49c1-a8b3-8f813b225541",
  ChromiumBrowser = "b5397666-4885-aa6b-cebf-e52262a439a2",
  EdgeOnMac = "771b48fd-d3d4-4f74-9232-fc157ab0507a",
  IDmelon = "39a5647e-1853-446c-a1f6-a79bae9f5bc7",
  Bitwarden = "d548826e-79b4-db40-a3d8-11116f7e8349",
  ApplePasswords = "fbfc3007-154e-4ecc-8c0b-6e020557d7bd",
  SamsungPass = "53414d53-554e-4700-0000-000000000000",
  ThalesBioIOS = "66a0ccb3-bd6a-191f-ee06-e375c50b9846",
  ThalesBioAndroid = "8836336a-f590-0921-301d-46427531eee6",
  ThalesPINAndroid = "cd69adb5-3c7a-deb9-3177-6800ea6cb72a",
  ThalesPINIOS = "17290f1e-c212-34d0-1423-365d729f09d9",
  ProtonPass = "50726f74-6f6e-5061-7373-50726f746f6e",
  KeePassXC = "fdb141b2-5d84-443e-8a35-4698c205a502",
  KeePassDX = "eaecdef2-1c31-5634-8639-f1cbd9c00a08",
  ToothPicPasskeyProvider = "cc45f64e-52a2-451b-831a-4edd8022a202",
  IPasswords = "bfc748bb-3429-4faa-b9f9-7cfa9f3b76d0",
  ZohoVault = "b35a26b2-8f6e-4697-ab1d-d44db4da28c6",
  LastPass = "b78a0a55-6ef8-d246-a042-ba0f6d55050c",
  Devolutions = "de503f9c-21a4-4f76-b4b7-558eb55c6f89",
  LogMeOnce = "22248c4c-7a12-46e2-9a41-44291b373a4d",
  KasperskyPasswordManager = "a10c6dd9-465e-4226-8198-c7c44b91c555",
  PwSafe = "d350af52-0351-4ba2-acd3-dfeeadc3f764",
  MicrosoftPasswordManager = "d3452668-01fd-4c12-926c-83a4204853aa",
  Initial = "6d212b28-a2c1-4638-b375-5932070f62e9",
  HeimlaneVault = "d49b2120-b865-4191-8cea-be84a52b0485",
}

export const PasskeyBrandNames: Record<PasskeyBrand, string> = {
  [PasskeyBrand.AliasVault]: "AliasVault",
  [PasskeyBrand.GooglePasswordManager]: "Google Password Manager",
  [PasskeyBrand.ChromeOnMac]: "Chrome on Mac",
  [PasskeyBrand.WindowsHello1]: "Windows Hello",
  [PasskeyBrand.WindowsHello2]: "Windows Hello",
  [PasskeyBrand.WindowsHello3]: "Windows Hello",
  [PasskeyBrand.ICloudKeychainManaged]: "iCloud Keychain (Managed)",
  [PasskeyBrand.Dashlane]: "Dashlane",
  [PasskeyBrand.OnePassword]: "1Password",
  [PasskeyBrand.NordPass]: "NordPass",
  [PasskeyBrand.Keeper]: "Keeper",
  [PasskeyBrand.Sesame]: "Sésame",
  [PasskeyBrand.Enpass]: "Enpass",
  [PasskeyBrand.ChromiumBrowser]: "Chromium Browser",
  [PasskeyBrand.EdgeOnMac]: "Edge on Mac",
  [PasskeyBrand.IDmelon]: "IDmelon",
  [PasskeyBrand.Bitwarden]: "Bitwarden",
  [PasskeyBrand.ApplePasswords]: "Apple Passwords",
  [PasskeyBrand.SamsungPass]: "Samsung Pass",
  [PasskeyBrand.ThalesBioIOS]: "Thales Bio iOS SDK",
  [PasskeyBrand.ThalesBioAndroid]: "Thales Bio Android SDK",
  [PasskeyBrand.ThalesPINAndroid]: "Thales PIN Android SDK",
  [PasskeyBrand.ThalesPINIOS]: "Thales PIN iOS SDK",
  [PasskeyBrand.ProtonPass]: "Proton Pass",
  [PasskeyBrand.KeePassXC]: "KeePassXC",
  [PasskeyBrand.KeePassDX]: "KeePassDX",
  [PasskeyBrand.ToothPicPasskeyProvider]: "ToothPic Passkey Provider",
  [PasskeyBrand.IPasswords]: "iPasswords",
  [PasskeyBrand.ZohoVault]: "Zoho Vault",
  [PasskeyBrand.LastPass]: "LastPass",
  [PasskeyBrand.Devolutions]: "Devolutions",
  [PasskeyBrand.LogMeOnce]: "LogMeOnce",
  [PasskeyBrand.KasperskyPasswordManager]: "Kaspersky Password Manager",
  [PasskeyBrand.PwSafe]: "pwSafe",
  [PasskeyBrand.MicrosoftPasswordManager]: "Microsoft Password Manager",
  [PasskeyBrand.Initial]: "initial",
  [PasskeyBrand.HeimlaneVault]: "Heimlane Vault",
};

/** Authenticators built into the OS or Chromium that the desktop app can use. */
const platformAuthenticators = new Set<string>([
  PasskeyBrand.ChromiumBrowser,
  PasskeyBrand.WindowsHello1,
  PasskeyBrand.WindowsHello2,
  PasskeyBrand.WindowsHello3,
]);

/**
 * Returns the platform of the desktop app when a passkey was created inside it
 * with the built-in platform authenticator.
 *
 * @param aaguid the authenticator AAGUID from the registration response.
 * @param userAgent the user agent string from the registration request.
 * @returns the desktop app platform, or undefined if not created in the desktop app.
 */
export function getDesktopAppPlatform(
  aaguid?: string | null,
  userAgent?: string | null
): DesktopAppPlatform | undefined {
  if (
    !aaguid ||
    !platformAuthenticators.has(aaguid) ||
    !userAgent?.toLowerCase().includes("electron/")
  ) {
    return undefined;
  }

  const { os } = parseUserAgent(userAgent);
  if (os === "macOS" || os === "Windows") {
    return os;
  }

  return undefined;
}

/**
 * Parses a user agent string to extract browser, OS, and device information.
 *
 * @param userAgent the user agent string to parse.
 * @returns parsed information about the browser, OS, and device.
 */
function parseUserAgent(userAgent: string): ParsedUserAgent {
  if (!userAgent) {
    return {};
  }

  const ua = userAgent.toLowerCase();
  const result: ParsedUserAgent = {};

  // Detect browser
  if (ua.includes("edg/") || ua.includes("edga/") || ua.includes("edgios/")) {
    result.browser = "Edge";
  } else if (ua.includes("opr/") || ua.includes("opera")) {
    result.browser = "Opera";
  } else if (ua.includes("chrome") && !ua.includes("edg")) {
    result.browser = "Chrome";
  } else if (ua.includes("safari") && !ua.includes("chrome")) {
    result.browser = "Safari";
  } else if (ua.includes("firefox")) {
    result.browser = "Firefox";
  }

  // Detect OS (check iPhone/iPad before macOS since they contain "Mac OS X")
  if (ua.includes("windows")) {
    result.os = "Windows";
  } else if (ua.includes("iphone")) {
    result.os = "iOS";
    result.device = "iPhone";
  } else if (ua.includes("ipad")) {
    result.os = "iOS";
    result.device = "iPad";
  } else if (ua.includes("mac os x") || ua.includes("macos")) {
    result.os = "macOS";
  } else if (ua.includes("android")) {
    result.os = "Android";
    if (ua.includes("mobile")) {
      result.device = "Android Phone";
    } else {
      result.device = "Android Tablet";
    }
  } else if (ua.includes("linux")) {
    result.os = "Linux";
  } else if (ua.includes("cros")) {
    result.os = "Chrome OS";
  }

  return result;
}

/**
 * Determines the type of authenticator based on transports.
 *
 * @param transports array of authenticator transports.
 * @returns a human-readable authenticator type or undefined.
 */
function getAuthenticatorType(transports?: string[]): string | undefined {
  if (!transports || transports.length === 0) {
    return undefined;
  }

  // Platform authenticators typically use "internal"
  if (transports.includes("internal")) {
    return undefined;
  }

  // Security keys typically use USB, NFC, or BLE
  if (
    transports.includes("usb") ||
    transports.includes("nfc") ||
    transports.includes("ble")
  ) {
    return "Security Key";
  }

  // Hybrid authenticators use hybrid transport (phone as authenticator)
  if (transports.includes("hybrid")) {
    return "Phone";
  }

  return undefined;
}

/**
 * Generates a default passkey name when user agent parsing fails.
 *
 * @param transports optional array of authenticator transports.
 * @returns a default passkey name.
 */
function generateDefaultName(transports?: string[]): string {
  const authenticatorType = getAuthenticatorType(transports);

  if (authenticatorType) {
    return authenticatorType;
  }

  return "Passkey";
}

/**
 * Generates a friendly name for a passkey based on AAGUID, user agent, and transport information.
 *
 * @param aaguid the authenticator AAGUID from the registration response.
 * @param userAgent the user agent string from the registration request.
 * @param transports optional array of authenticator transports.
 * @returns a human-readable name for the passkey.
 */
export function generatePasskeyName(
  aaguid?: string | PasskeyBrand,
  userAgent?: string,
  transports?: string[]
): string {
  const desktopPlatform = getDesktopAppPlatform(aaguid, userAgent);
  if (desktopPlatform) {
    return `${desktopPlatform} App`;
  }

  // Prefer known brand from AAGUID over user agent sniffing
  if (aaguid && PasskeyBrandNames[aaguid as PasskeyBrand]) {
    return PasskeyBrandNames[aaguid as PasskeyBrand];
  }

  // Fall back to user agent parsing if AAGUID is not recognized
  if (!userAgent) {
    return generateDefaultName(transports);
  }

  const parsed = parseUserAgent(userAgent);
  const parts: string[] = [];

  // Prioritize device name if available (e.g., "iPhone", "iPad")
  if (parsed.device) {
    parts.push(parsed.device);
  } else {
    // Otherwise use browser and OS
    if (parsed.browser) {
      parts.push(parsed.browser);
    }
    if (parsed.os) {
      parts.push(`on ${parsed.os}`);
    }
  }

  // Add authenticator type hint if available
  const authenticatorType = getAuthenticatorType(transports);
  if (authenticatorType) {
    parts.push(`(${authenticatorType})`);
  }

  if (parts.length === 0) {
    return generateDefaultName(transports);
  }

  return parts.join(" ");
}