jazz-tools 0.19.19 → 0.19.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.svelte-kit/__package__/client.d.ts.map +1 -1
- package/.svelte-kit/__package__/client.js +3 -1
- package/.svelte-kit/__package__/tests/client.test.js +48 -0
- package/.turbo/turbo-build.log +65 -61
- package/dist/better-auth/auth/client.d.ts.map +1 -1
- package/dist/better-auth/auth/client.js +1 -1
- package/dist/better-auth/auth/client.js.map +1 -1
- package/dist/{chunk-PEHQ7TN2.js → chunk-MI24YFCY.js} +31 -4
- package/dist/chunk-MI24YFCY.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/react-core/hooks.d.ts +2 -2
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +4 -78
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-native/chunk-DGUM43GV.js +11 -0
- package/dist/react-native/chunk-DGUM43GV.js.map +1 -0
- package/dist/react-native/crypto.js +2 -0
- package/dist/react-native/crypto.js.map +1 -1
- package/dist/react-native/index.js +540 -29
- package/dist/react-native/index.js.map +1 -1
- package/dist/react-native-core/auth/PasskeyAuth.d.ts +123 -0
- package/dist/react-native-core/auth/PasskeyAuth.d.ts.map +1 -0
- package/dist/react-native-core/auth/PasskeyAuthBasicUI.d.ts +34 -0
- package/dist/react-native-core/auth/PasskeyAuthBasicUI.d.ts.map +1 -0
- package/dist/react-native-core/auth/auth.d.ts +3 -0
- package/dist/react-native-core/auth/auth.d.ts.map +1 -1
- package/dist/react-native-core/auth/passkey-utils.d.ts +16 -0
- package/dist/react-native-core/auth/passkey-utils.d.ts.map +1 -0
- package/dist/react-native-core/auth/usePasskeyAuth.d.ts +48 -0
- package/dist/react-native-core/auth/usePasskeyAuth.d.ts.map +1 -0
- package/dist/react-native-core/chunk-DGUM43GV.js +11 -0
- package/dist/react-native-core/chunk-DGUM43GV.js.map +1 -0
- package/dist/react-native-core/crypto.js +2 -0
- package/dist/react-native-core/crypto.js.map +1 -1
- package/dist/react-native-core/index.js +535 -24
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/tests/PasskeyAuth.test.d.ts +2 -0
- package/dist/react-native-core/tests/PasskeyAuth.test.d.ts.map +1 -0
- package/dist/react-native-core/tests/passkey-utils.test.d.ts +2 -0
- package/dist/react-native-core/tests/passkey-utils.test.d.ts.map +1 -0
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/account.d.ts +5 -1
- package/dist/tools/coValues/account.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +30 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
- package/dist/tools/testing.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/better-auth/auth/client.ts +3 -1
- package/src/better-auth/auth/tests/client.test.ts +66 -2
- package/src/react-core/hooks.ts +12 -103
- package/src/react-native-core/auth/PasskeyAuth.ts +316 -0
- package/src/react-native-core/auth/PasskeyAuthBasicUI.tsx +284 -0
- package/src/react-native-core/auth/auth.ts +3 -0
- package/src/react-native-core/auth/passkey-utils.ts +47 -0
- package/src/react-native-core/auth/usePasskeyAuth.tsx +85 -0
- package/src/react-native-core/tests/PasskeyAuth.test.ts +463 -0
- package/src/react-native-core/tests/passkey-utils.test.ts +144 -0
- package/src/tools/coValues/account.ts +11 -3
- package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +27 -1
- package/src/tools/tests/account.test.ts +2 -1
- package/testSetup.ts +4 -0
- package/vitest.config.ts +1 -0
- package/dist/chunk-PEHQ7TN2.js.map +0 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
Text,
|
|
5
|
+
TextInput,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
View,
|
|
8
|
+
useColorScheme,
|
|
9
|
+
} from "react-native";
|
|
10
|
+
import { usePasskeyAuth } from "./usePasskeyAuth.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A basic UI component for passkey authentication in React Native apps.
|
|
14
|
+
*
|
|
15
|
+
* This component provides a simple sign-up and log-in interface using passkeys.
|
|
16
|
+
* It's designed for quick prototyping and can be customized or replaced with
|
|
17
|
+
* your own authentication UI.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* import { PasskeyAuthBasicUI } from "jazz-tools/react-native-core";
|
|
22
|
+
*
|
|
23
|
+
* function App() {
|
|
24
|
+
* return (
|
|
25
|
+
* <JazzProvider ...>
|
|
26
|
+
* <PasskeyAuthBasicUI
|
|
27
|
+
* appName="My App"
|
|
28
|
+
* rpId="myapp.com"
|
|
29
|
+
* >
|
|
30
|
+
* <MainApp />
|
|
31
|
+
* </PasskeyAuthBasicUI>
|
|
32
|
+
* </JazzProvider>
|
|
33
|
+
* );
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @category Auth Providers
|
|
38
|
+
*/
|
|
39
|
+
export const PasskeyAuthBasicUI = ({
|
|
40
|
+
appName,
|
|
41
|
+
rpId,
|
|
42
|
+
children,
|
|
43
|
+
}: {
|
|
44
|
+
appName: string;
|
|
45
|
+
rpId: string;
|
|
46
|
+
children: React.ReactNode;
|
|
47
|
+
}) => {
|
|
48
|
+
const colorScheme = useColorScheme();
|
|
49
|
+
const darkMode = colorScheme === "dark";
|
|
50
|
+
const [username, setUsername] = useState<string>("");
|
|
51
|
+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
52
|
+
|
|
53
|
+
const auth = usePasskeyAuth({ appName, rpId });
|
|
54
|
+
|
|
55
|
+
const handleSignUp = () => {
|
|
56
|
+
setErrorMessage(null);
|
|
57
|
+
|
|
58
|
+
auth.signUp(username).catch((error) => {
|
|
59
|
+
if (error.cause instanceof Error) {
|
|
60
|
+
setErrorMessage(error.cause.message);
|
|
61
|
+
} else {
|
|
62
|
+
setErrorMessage(error.message);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleLogIn = () => {
|
|
68
|
+
setErrorMessage(null);
|
|
69
|
+
|
|
70
|
+
auth.logIn().catch((error) => {
|
|
71
|
+
if (error.cause instanceof Error) {
|
|
72
|
+
setErrorMessage(error.cause.message);
|
|
73
|
+
} else {
|
|
74
|
+
setErrorMessage(error.message);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (auth.state === "signedIn") {
|
|
80
|
+
return children;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<View
|
|
85
|
+
style={[
|
|
86
|
+
styles.container,
|
|
87
|
+
darkMode ? styles.darkBackground : styles.lightBackground,
|
|
88
|
+
]}
|
|
89
|
+
>
|
|
90
|
+
<View style={styles.formContainer}>
|
|
91
|
+
<Text
|
|
92
|
+
style={[
|
|
93
|
+
styles.headerText,
|
|
94
|
+
darkMode ? styles.darkText : styles.lightText,
|
|
95
|
+
]}
|
|
96
|
+
>
|
|
97
|
+
{appName}
|
|
98
|
+
</Text>
|
|
99
|
+
|
|
100
|
+
{errorMessage && <Text style={styles.errorText}>{errorMessage}</Text>}
|
|
101
|
+
|
|
102
|
+
<TextInput
|
|
103
|
+
placeholder="Display name"
|
|
104
|
+
value={username}
|
|
105
|
+
onChangeText={setUsername}
|
|
106
|
+
placeholderTextColor={darkMode ? "#999" : "#666"}
|
|
107
|
+
style={[
|
|
108
|
+
styles.textInput,
|
|
109
|
+
darkMode ? styles.darkInput : styles.lightInput,
|
|
110
|
+
]}
|
|
111
|
+
autoCapitalize="words"
|
|
112
|
+
autoCorrect={false}
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
<TouchableOpacity
|
|
116
|
+
onPress={handleSignUp}
|
|
117
|
+
style={[
|
|
118
|
+
styles.button,
|
|
119
|
+
darkMode ? styles.darkButton : styles.lightButton,
|
|
120
|
+
]}
|
|
121
|
+
>
|
|
122
|
+
<Text
|
|
123
|
+
style={darkMode ? styles.darkButtonText : styles.lightButtonText}
|
|
124
|
+
>
|
|
125
|
+
Sign Up with Passkey
|
|
126
|
+
</Text>
|
|
127
|
+
</TouchableOpacity>
|
|
128
|
+
|
|
129
|
+
<View style={styles.divider}>
|
|
130
|
+
<View
|
|
131
|
+
style={[
|
|
132
|
+
styles.dividerLine,
|
|
133
|
+
darkMode ? styles.darkDivider : styles.lightDivider,
|
|
134
|
+
]}
|
|
135
|
+
/>
|
|
136
|
+
<Text
|
|
137
|
+
style={[
|
|
138
|
+
styles.dividerText,
|
|
139
|
+
darkMode ? styles.darkText : styles.lightText,
|
|
140
|
+
]}
|
|
141
|
+
>
|
|
142
|
+
or
|
|
143
|
+
</Text>
|
|
144
|
+
<View
|
|
145
|
+
style={[
|
|
146
|
+
styles.dividerLine,
|
|
147
|
+
darkMode ? styles.darkDivider : styles.lightDivider,
|
|
148
|
+
]}
|
|
149
|
+
/>
|
|
150
|
+
</View>
|
|
151
|
+
|
|
152
|
+
<TouchableOpacity
|
|
153
|
+
onPress={handleLogIn}
|
|
154
|
+
style={[
|
|
155
|
+
styles.secondaryButton,
|
|
156
|
+
darkMode ? styles.darkSecondaryButton : styles.lightSecondaryButton,
|
|
157
|
+
]}
|
|
158
|
+
>
|
|
159
|
+
<Text style={darkMode ? styles.darkText : styles.lightText}>
|
|
160
|
+
Log In with Existing Passkey
|
|
161
|
+
</Text>
|
|
162
|
+
</TouchableOpacity>
|
|
163
|
+
</View>
|
|
164
|
+
</View>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const styles = StyleSheet.create({
|
|
169
|
+
container: {
|
|
170
|
+
flex: 1,
|
|
171
|
+
justifyContent: "center",
|
|
172
|
+
alignItems: "center",
|
|
173
|
+
padding: 20,
|
|
174
|
+
},
|
|
175
|
+
formContainer: {
|
|
176
|
+
width: "80%",
|
|
177
|
+
maxWidth: 300,
|
|
178
|
+
alignItems: "center",
|
|
179
|
+
justifyContent: "center",
|
|
180
|
+
},
|
|
181
|
+
headerText: {
|
|
182
|
+
fontSize: 24,
|
|
183
|
+
fontWeight: "600",
|
|
184
|
+
marginBottom: 30,
|
|
185
|
+
},
|
|
186
|
+
errorText: {
|
|
187
|
+
color: "#ff4444",
|
|
188
|
+
marginVertical: 10,
|
|
189
|
+
textAlign: "center",
|
|
190
|
+
fontSize: 14,
|
|
191
|
+
},
|
|
192
|
+
textInput: {
|
|
193
|
+
borderWidth: 1,
|
|
194
|
+
padding: 12,
|
|
195
|
+
marginVertical: 10,
|
|
196
|
+
width: "100%",
|
|
197
|
+
borderRadius: 8,
|
|
198
|
+
fontSize: 16,
|
|
199
|
+
},
|
|
200
|
+
darkInput: {
|
|
201
|
+
borderColor: "#444",
|
|
202
|
+
backgroundColor: "#1a1a1a",
|
|
203
|
+
color: "#fff",
|
|
204
|
+
},
|
|
205
|
+
lightInput: {
|
|
206
|
+
borderColor: "#ddd",
|
|
207
|
+
backgroundColor: "#fff",
|
|
208
|
+
color: "#000",
|
|
209
|
+
},
|
|
210
|
+
button: {
|
|
211
|
+
paddingVertical: 14,
|
|
212
|
+
paddingHorizontal: 10,
|
|
213
|
+
borderRadius: 8,
|
|
214
|
+
width: "100%",
|
|
215
|
+
marginVertical: 10,
|
|
216
|
+
},
|
|
217
|
+
darkButton: {
|
|
218
|
+
backgroundColor: "#0066cc",
|
|
219
|
+
},
|
|
220
|
+
lightButton: {
|
|
221
|
+
backgroundColor: "#007aff",
|
|
222
|
+
},
|
|
223
|
+
darkButtonText: {
|
|
224
|
+
color: "#fff",
|
|
225
|
+
textAlign: "center",
|
|
226
|
+
fontWeight: "600",
|
|
227
|
+
fontSize: 16,
|
|
228
|
+
},
|
|
229
|
+
lightButtonText: {
|
|
230
|
+
color: "#fff",
|
|
231
|
+
textAlign: "center",
|
|
232
|
+
fontWeight: "600",
|
|
233
|
+
fontSize: 16,
|
|
234
|
+
},
|
|
235
|
+
divider: {
|
|
236
|
+
flexDirection: "row",
|
|
237
|
+
alignItems: "center",
|
|
238
|
+
width: "100%",
|
|
239
|
+
marginVertical: 20,
|
|
240
|
+
},
|
|
241
|
+
dividerLine: {
|
|
242
|
+
flex: 1,
|
|
243
|
+
height: 1,
|
|
244
|
+
},
|
|
245
|
+
darkDivider: {
|
|
246
|
+
backgroundColor: "#444",
|
|
247
|
+
},
|
|
248
|
+
lightDivider: {
|
|
249
|
+
backgroundColor: "#ddd",
|
|
250
|
+
},
|
|
251
|
+
dividerText: {
|
|
252
|
+
marginHorizontal: 10,
|
|
253
|
+
fontSize: 14,
|
|
254
|
+
},
|
|
255
|
+
secondaryButton: {
|
|
256
|
+
paddingVertical: 14,
|
|
257
|
+
paddingHorizontal: 10,
|
|
258
|
+
borderRadius: 8,
|
|
259
|
+
width: "100%",
|
|
260
|
+
borderWidth: 1,
|
|
261
|
+
},
|
|
262
|
+
darkSecondaryButton: {
|
|
263
|
+
borderColor: "#444",
|
|
264
|
+
backgroundColor: "transparent",
|
|
265
|
+
},
|
|
266
|
+
lightSecondaryButton: {
|
|
267
|
+
borderColor: "#ddd",
|
|
268
|
+
backgroundColor: "transparent",
|
|
269
|
+
},
|
|
270
|
+
darkText: {
|
|
271
|
+
color: "#fff",
|
|
272
|
+
textAlign: "center",
|
|
273
|
+
},
|
|
274
|
+
lightText: {
|
|
275
|
+
color: "#000",
|
|
276
|
+
textAlign: "center",
|
|
277
|
+
},
|
|
278
|
+
darkBackground: {
|
|
279
|
+
backgroundColor: "#000",
|
|
280
|
+
},
|
|
281
|
+
lightBackground: {
|
|
282
|
+
backgroundColor: "#fff",
|
|
283
|
+
},
|
|
284
|
+
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { KvStoreContext } from "../storage/kv-store-context.js";
|
|
2
2
|
|
|
3
3
|
export * from "./DemoAuthUI.js";
|
|
4
|
+
export * from "./PasskeyAuth.js";
|
|
5
|
+
export * from "./usePasskeyAuth.js";
|
|
6
|
+
export * from "./PasskeyAuthBasicUI.js";
|
|
4
7
|
|
|
5
8
|
export function clearUserCredentials() {
|
|
6
9
|
const kvStore = KvStoreContext.getInstance().getStorage();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for base64url encoding/decoding used by React Native passkey authentication.
|
|
3
|
+
*
|
|
4
|
+
* The react-native-passkey library uses base64url strings, while the browser WebAuthn API
|
|
5
|
+
* uses raw ArrayBuffers. These utilities handle the conversion between formats.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Converts a Uint8Array to a base64url-encoded string.
|
|
10
|
+
* Base64url uses '-' and '_' instead of '+' and '/', and omits padding '='.
|
|
11
|
+
*/
|
|
12
|
+
export function uint8ArrayToBase64Url(bytes: Uint8Array): string {
|
|
13
|
+
// Convert to regular base64 first
|
|
14
|
+
let binary = "";
|
|
15
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
16
|
+
binary += String.fromCharCode(bytes[i]!);
|
|
17
|
+
}
|
|
18
|
+
const base64 = btoa(binary);
|
|
19
|
+
|
|
20
|
+
// Convert to base64url: replace + with -, / with _, remove =
|
|
21
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Converts a base64url-encoded string to a Uint8Array.
|
|
26
|
+
*/
|
|
27
|
+
export function base64UrlToUint8Array(base64url: string): Uint8Array {
|
|
28
|
+
// Convert base64url to regular base64
|
|
29
|
+
let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
|
|
30
|
+
|
|
31
|
+
// Add padding if needed
|
|
32
|
+
const padding = base64.length % 4;
|
|
33
|
+
if (padding > 0) {
|
|
34
|
+
base64 += "=".repeat(4 - padding);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Decode base64 to binary string
|
|
38
|
+
const binary = atob(base64);
|
|
39
|
+
|
|
40
|
+
// Convert binary string to Uint8Array
|
|
41
|
+
const bytes = new Uint8Array(binary.length);
|
|
42
|
+
for (let i = 0; i < binary.length; i++) {
|
|
43
|
+
bytes[i] = binary.charCodeAt(i);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return bytes;
|
|
47
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useAuthSecretStorage,
|
|
3
|
+
useIsAuthenticated,
|
|
4
|
+
useJazzContext,
|
|
5
|
+
} from "jazz-tools/react-core";
|
|
6
|
+
import { useMemo } from "react";
|
|
7
|
+
import { ReactNativePasskeyAuth } from "./PasskeyAuth.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* React hook for passkey (WebAuthn) authentication in React Native apps.
|
|
11
|
+
*
|
|
12
|
+
* This hook provides a simple interface for signing up and logging in with passkeys,
|
|
13
|
+
* using the device's biometric authentication (FaceID/TouchID/fingerprint).
|
|
14
|
+
*
|
|
15
|
+
* **Requirements:**
|
|
16
|
+
* - Install `react-native-passkey` as a peer dependency
|
|
17
|
+
* - Configure your app's associated domains (iOS) and asset links (Android)
|
|
18
|
+
* - Passkeys require HTTPS domain verification
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* import { usePasskeyAuth } from "jazz-tools/react-native-core";
|
|
23
|
+
*
|
|
24
|
+
* function AuthScreen() {
|
|
25
|
+
* const auth = usePasskeyAuth({
|
|
26
|
+
* appName: "My App",
|
|
27
|
+
* rpId: "myapp.com",
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* if (auth.state === "signedIn") {
|
|
31
|
+
* return <MainApp />;
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* return (
|
|
35
|
+
* <View>
|
|
36
|
+
* <Button title="Sign Up" onPress={() => auth.signUp("John Doe")} />
|
|
37
|
+
* <Button title="Log In" onPress={auth.logIn} />
|
|
38
|
+
* </View>
|
|
39
|
+
* );
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @param options.appName - The display name of your app shown during passkey prompts
|
|
44
|
+
* @param options.rpId - The relying party ID (your app's domain, e.g., "myapp.com")
|
|
45
|
+
*
|
|
46
|
+
* @category Auth Providers
|
|
47
|
+
*/
|
|
48
|
+
export function usePasskeyAuth({
|
|
49
|
+
appName,
|
|
50
|
+
rpId,
|
|
51
|
+
}: {
|
|
52
|
+
appName: string;
|
|
53
|
+
rpId: string;
|
|
54
|
+
}) {
|
|
55
|
+
const context = useJazzContext();
|
|
56
|
+
const authSecretStorage = useAuthSecretStorage();
|
|
57
|
+
|
|
58
|
+
if ("guest" in context) {
|
|
59
|
+
throw new Error("Passkey auth is not supported in guest mode");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const authMethod = useMemo(() => {
|
|
63
|
+
return new ReactNativePasskeyAuth(
|
|
64
|
+
context.node.crypto,
|
|
65
|
+
context.authenticate,
|
|
66
|
+
authSecretStorage,
|
|
67
|
+
appName,
|
|
68
|
+
rpId,
|
|
69
|
+
);
|
|
70
|
+
}, [
|
|
71
|
+
appName,
|
|
72
|
+
rpId,
|
|
73
|
+
authSecretStorage,
|
|
74
|
+
context.node.crypto,
|
|
75
|
+
context.authenticate,
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
const isAuthenticated = useIsAuthenticated();
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
state: isAuthenticated ? "signedIn" : "anonymous",
|
|
82
|
+
logIn: authMethod.logIn,
|
|
83
|
+
signUp: authMethod.signUp,
|
|
84
|
+
} as const;
|
|
85
|
+
}
|