summaryrefslogtreecommitdiff
path: root/components/auth/Auth.tsx
blob: 40512cacf5e02463b39eb186aa2f8edcac2fa020 (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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"use client";

import PrimaryButton from "../PrimaryButton";
import { SymbolView, SymbolViewProps, SymbolWeight } from "expo-symbols";
import { useState } from "react";
import { Platform, StyleProp, ViewStyle } from "react-native";

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";

export function Passkee() {
  const [isLoading, setIsLoading] = useState(false);
  async function handleCreatePasskey() {
    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;
    }
  }
  return (
    <PrimaryButton
      label="Create Passkey"
      onPress={handleCreatePasskey}
      isLoading={isLoading}
      style={{ marginBottom: 16 }}
    />
  );
}