summaryrefslogtreecommitdiff
path: root/lib/passkey.ts
blob: 573c62babdfc42ded03c3e9bc875c73d838af0d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
const PASSKEY_CREDENTIAL_ID_KEY = "urbit_wallet_passkey_id";
const RELYING_PARTY_ID = "wallet.urbit.org"; // Change this to your domain
const RELYING_PARTY_NAME = "Urbit Wallet";

async function createPasskey() {
  const pkok =
    await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
  console.log({ pkok });

  // if (Platform.OS !== "web") {
  //   return false;
  // }
  console.log("creating passkey");

  try {
    // Generate a random user ID
    const userId = new Uint8Array(16);
    crypto.getRandomValues(userId);

    // Generate a random challenge
    const challenge = new Uint8Array(32);
    crypto.getRandomValues(challenge);

    const createCredentialOptions: CredentialCreationOptions = {
      publicKey: {
        // REQUIRED: Random challenge to prevent replay attacks
        // Must be at least 16 bytes, we use 32 for extra security
        challenge,

        // REQUIRED: Relying Party (your website/app)
        rp: {
          // REQUIRED: Human-readable name shown in UI prompts
          name: RELYING_PARTY_NAME,

          // OPTIONAL: Domain that owns this credential
          // If omitted, defaults to current domain
          // Must match or be a registrable suffix of the current domain
          id:
            window.location.hostname === "localhost"
              ? "localhost"
              : RELYING_PARTY_ID,
        },

        // REQUIRED: User information
        user: {
          // REQUIRED: Unique user ID (must not contain PII)
          // Used by authenticator to distinguish between accounts
          id: userId,

          // REQUIRED: Unique username/identifier for this RP
          // Shown in some UI contexts, should be recognizable to user
          name: "urbit-user",

          // REQUIRED: Human-readable name for display
          // Shown in account selectors and prompts
          displayName: "Urbit Wallet User",
        },

        // REQUIRED: List of acceptable public key algorithms
        // Order matters - authenticator will use first supported algorithm
        pubKeyCredParams: [
          { alg: -7, type: "public-key" }, // ES256 (ECDSA with SHA-256) - most common
          { alg: -257, type: "public-key" }, // RS256 (RSASSA-PKCS1-v1_5) - fallback
        ],

        // OPTIONAL: Criteria for authenticator selection
        authenticatorSelection: {
          // OPTIONAL: "platform" (built-in like Touch ID) or "cross-platform" (USB key)
          // Omitting allows both types
          // authenticatorAttachment: "platform",

          // OPTIONAL: User verification requirement
          // "required" - must verify user (biometric/PIN)
          // "preferred" - verify if possible, continue if not (default)
          // "discouraged" - don't verify unless required by RP
          userVerification: "preferred",

          // OPTIONAL: Whether to create a discoverable credential (passkey)
          // "required" - must create discoverable credential
          // "preferred" - create if possible (default)
          // "discouraged" - don't create discoverable credential
          residentKey: "preferred",

          // DEPRECATED: Use residentKey instead
          // Kept for backwards compatibility with older authenticators
          requireResidentKey: false,
        },

        // OPTIONAL: Time limit in milliseconds (default varies by browser)
        // 60 seconds is reasonable for user interaction
        timeout: 60000,

        // OPTIONAL: Attestation preference (proof of authenticator legitimacy)
        // "none" - no attestation needed (default, best for privacy)
        // "indirect" - RP prefers attestation but allows anonymization
        // "direct" - RP needs attestation from authenticator
        // "enterprise" - RP needs attestation and device info (requires permission)
        attestation: "none",

        // OPTIONAL: List of credentials to exclude (prevent duplicates)
        // excludeCredentials: [
        //   { id: existingCredentialId, type: "public-key" }
        // ],

        // OPTIONAL: Extensions for additional features
        // extensions: {
        //   credProps: true,  // Request additional credential properties
        //   hmacCreateSecret: true,  // For symmetric key operations
        // },
      },
    };

    const credential = (await navigator.credentials.create(
      createCredentialOptions,
    )) as PublicKeyCredential;
    console.log({ credential });

    if (credential) {
      // Store the credential ID for later use
      // await SecureStore.setItemAsync(PASSKEY_CREDENTIAL_ID_KEY, credential.id);

      console.log("Passkey created successfully", credential.id);
      return true;
    }

    return false;
  } catch (error) {
    console.error("Error creating passkey:", error);
    return false;
  }
}