Ctrl K
ComunidadGuías…

tests/webauthn-real.spec.ts

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

Archivo original ↗Preguntar a KiBot
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
import { createInterface } from "node:readline";
import { expect, test } from "@playwright/test";

interface FixtureInfo {
  origin: string;
  username: string;
  temporaryPassword: string;
}

async function startFixture(): Promise<{ process: ChildProcessWithoutNullStreams; info: FixtureInfo }> {
  const child = spawn("bun", ["tests/fixtures/webauthn-server.ts"], {
    cwd: process.cwd(),
    stdio: ["pipe", "pipe", "pipe"],
  });
  const lines = createInterface({ input: child.stdout });
  const info = await new Promise<FixtureInfo>((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error("WebAuthn fixture startup timed out.")), 10_000);
    lines.once("line", (line) => {
      clearTimeout(timer);
      try { resolve(JSON.parse(line) as FixtureInfo); } catch (error) { reject(error); }
    });
    child.once("exit", (code) => reject(new Error(`WebAuthn fixture exited with ${code}.`)));
  });
  lines.close();
  return { process: child, info };
}

test("a Chromium virtual authenticator completes the real WebAuthn library ceremony", async ({ page }) => {
  const fixture = await startFixture();
  try {
    const cdp = await page.context().newCDPSession(page);
    await cdp.send("WebAuthn.enable");
    await cdp.send("WebAuthn.addVirtualAuthenticator", { options: {
      protocol: "ctap2",
      ctap2Version: "ctap2_1",
      transport: "internal",
      hasResidentKey: true,
      hasUserVerification: true,
      isUserVerified: true,
      automaticPresenceSimulation: true,
    } });
    await page.goto(`${fixture.info.origin}/healthz`);
    const result = await page.evaluate(async ({ username, temporaryPassword }) => {
      const call = async (path: string, init: RequestInit = {}, key?: string) => {
        const response = await fetch(path, {
          ...init,
          headers: {
            ...(init.body ? { "content-type": "application/json" } : {}),
            ...(key ? { authorization: `Bearer ${key}` } : {}),
            ...init.headers,
          },
        });
        const body = await response.json();
        if (!response.ok) throw new Error(`${path} returned ${response.status}: ${JSON.stringify(body)}`);
        return body.data as Record<string, any>;
      };
      const bytes = (value: string) => {
        const binary = atob(value.replaceAll("-", "+").replaceAll("_", "/")
          .padEnd(Math.ceil(value.length / 4) * 4, "="));
        return Uint8Array.from(binary, (character) => character.charCodeAt(0));
      };
      const encoded = (value: ArrayBuffer) => {
        let binary = "";
        for (const byte of new Uint8Array(value)) binary += String.fromCharCode(byte);
        return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
      };
      const password = "real browser passkey password";
      const temporary = await call("/v1/dashboard-sessions", {
        method: "POST", body: JSON.stringify({ username, password: temporaryPassword }),
      });
      await call("/v1/operator-accounts/current/password", {
        method: "PATCH", body: JSON.stringify({ password }),
      }, temporary.api_key);
      const initial = await call("/v1/dashboard-sessions", {
        method: "POST", body: JSON.stringify({ username, password }),
      });
      const registration = await call("/v1/operator-accounts/current/passkeys/options", {
        method: "POST", body: "{}",
      }, initial.api_key);
      const creation = registration.options as any;
      const created = await navigator.credentials.create({ publicKey: {
        ...creation,
        challenge: bytes(creation.challenge),
        user: { ...creation.user, id: bytes(creation.user.id) },
        excludeCredentials: creation.excludeCredentials?.map((credential: any) => ({
          ...credential, id: bytes(credential.id),
        })),
      } as PublicKeyCredentialCreationOptions }) as PublicKeyCredential;
      const attestation = created.response as AuthenticatorAttestationResponse;
      const registered = await call("/v1/operator-accounts/current/passkeys", {
        method: "POST",
        body: JSON.stringify({
          challenge_id: registration.challenge_id,
          label: "Virtual authenticator",
          response: {
            id: created.id,
            rawId: encoded(created.rawId),
            response: {
              attestationObject: encoded(attestation.attestationObject),
              clientDataJSON: encoded(attestation.clientDataJSON),
              transports: attestation.getTransports(),
            },
            type: created.type,
            clientExtensionResults: created.getClientExtensionResults(),
            authenticatorAttachment: created.authenticatorAttachment,
          },
        }),
      }, initial.api_key);
      const staged = await call("/v1/dashboard-sessions", {
        method: "POST", body: JSON.stringify({ username, password }),
      });
      const authentication = await call("/v1/dashboard-sessions/current/passkey-options", {
        method: "POST", body: "{}",
      }, staged.api_key);
      const request = authentication.options as any;
      const asserted = await navigator.credentials.get({ publicKey: {
        ...request,
        challenge: bytes(request.challenge),
        allowCredentials: request.allowCredentials?.map((credential: any) => ({
          ...credential, id: bytes(credential.id),
        })),
      } as PublicKeyCredentialRequestOptions }) as PublicKeyCredential;
      const assertion = asserted.response as AuthenticatorAssertionResponse;
      const completed = await call("/v1/dashboard-sessions/current/passkey", {
        method: "POST",
        body: JSON.stringify({
          challenge_id: authentication.challenge_id,
          response: {
            id: asserted.id,
            rawId: encoded(asserted.rawId),
            response: {
              authenticatorData: encoded(assertion.authenticatorData),
              clientDataJSON: encoded(assertion.clientDataJSON),
              signature: encoded(assertion.signature),
              userHandle: assertion.userHandle ? encoded(assertion.userHandle) : undefined,
            },
            type: asserted.type,
            clientExtensionResults: asserted.getClientExtensionResults(),
            authenticatorAttachment: asserted.authenticatorAttachment,
          },
        }),
      }, staged.api_key);
      return {
        recoveryCodes: registered.recovery_codes.length,
        staged: staged.auth_stage,
        completed: completed.auth_stage,
        scopes: completed.scopes,
      };
    }, fixture.info);
    expect(result).toEqual({
      recoveryCodes: 8,
      staged: "second_factor",
      completed: "full",
      scopes: ["views:compile"],
    });
  } finally {
    fixture.process.kill("SIGTERM");
  }
});