@getpara/react-native-wallet 1.7.1 → 2.0.0-alpha.3
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/dist/AsyncStorage.d.ts +5 -2
- package/dist/AsyncStorage.js +14 -37
- package/dist/KeychainStorage.d.ts +3 -0
- package/dist/KeychainStorage.js +28 -35
- package/dist/react-native/ParaMobile.d.ts +5 -23
- package/dist/react-native/ParaMobile.js +21 -81
- package/package.json +9 -9
- package/src/AsyncStorage.ts +14 -32
- package/src/KeychainStorage.ts +31 -34
- package/src/react-native/ParaMobile.ts +26 -75
package/dist/AsyncStorage.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { StorageUtils } from '@getpara/web-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Implements `StorageUtils` using React Native Async Storage.
|
|
4
|
+
*/
|
|
2
5
|
export declare class AsyncStorage implements StorageUtils {
|
|
6
|
+
clear(prefix: string): Promise<void>;
|
|
3
7
|
get(key: string): Promise<string | null>;
|
|
4
|
-
set(key: string, value: string): Promise<void>;
|
|
5
8
|
removeItem(key: string): Promise<void>;
|
|
6
|
-
|
|
9
|
+
set(key: string, value: string): Promise<void>;
|
|
7
10
|
}
|
package/dist/AsyncStorage.js
CHANGED
|
@@ -8,56 +8,33 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import RNAsyncStorage from '@react-native-async-storage/async-storage';
|
|
11
|
+
/**
|
|
12
|
+
* Implements `StorageUtils` using React Native Async Storage.
|
|
13
|
+
*/
|
|
11
14
|
export class AsyncStorage {
|
|
12
|
-
|
|
15
|
+
clear(prefix) {
|
|
13
16
|
return __awaiter(this, void 0, void 0, function* () {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return null;
|
|
17
|
+
const keys = yield RNAsyncStorage.getAllKeys();
|
|
18
|
+
for (const key of keys) {
|
|
19
|
+
if (key.startsWith(prefix)) {
|
|
20
|
+
yield RNAsyncStorage.removeItem(key);
|
|
21
|
+
}
|
|
20
22
|
}
|
|
21
23
|
});
|
|
22
24
|
}
|
|
23
|
-
|
|
25
|
+
get(key) {
|
|
24
26
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
-
|
|
26
|
-
yield RNAsyncStorage.setItem(key, value);
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
console.warn(`Error storing key ${key}:`, error);
|
|
30
|
-
}
|
|
27
|
+
return RNAsyncStorage.getItem(key);
|
|
31
28
|
});
|
|
32
29
|
}
|
|
33
30
|
removeItem(key) {
|
|
34
31
|
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
-
|
|
36
|
-
yield RNAsyncStorage.removeItem(key);
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
console.warn(`Error removing key ${key}:`, error);
|
|
40
|
-
}
|
|
32
|
+
yield RNAsyncStorage.removeItem(key);
|
|
41
33
|
});
|
|
42
34
|
}
|
|
43
|
-
|
|
35
|
+
set(key, value) {
|
|
44
36
|
return __awaiter(this, void 0, void 0, function* () {
|
|
45
|
-
|
|
46
|
-
const keys = yield RNAsyncStorage.getAllKeys();
|
|
47
|
-
for (const key of keys) {
|
|
48
|
-
if (key.startsWith(prefix)) {
|
|
49
|
-
try {
|
|
50
|
-
yield RNAsyncStorage.removeItem(key);
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
console.warn(`Error clearing key ${key}:`, error);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
console.warn(`Error getting keys for prefix ${prefix}:`, error);
|
|
60
|
-
}
|
|
37
|
+
yield RNAsyncStorage.setItem(key, value);
|
|
61
38
|
});
|
|
62
39
|
}
|
|
63
40
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { StorageUtils } from '@getpara/web-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Implements `StorageUtils` using React Native `Keychain`.
|
|
4
|
+
*/
|
|
2
5
|
export declare class KeychainStorage implements StorageUtils {
|
|
3
6
|
get(key: string): Promise<string | null>;
|
|
4
7
|
set(key: string, value: string): Promise<void>;
|
package/dist/KeychainStorage.js
CHANGED
|
@@ -9,6 +9,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import Keychain from 'react-native-keychain';
|
|
11
11
|
const USERNAME = '@CAPSULE';
|
|
12
|
+
const KEYCHAIN_USER_CANCELLED_ERRORS = [
|
|
13
|
+
'user canceled the operation',
|
|
14
|
+
'error: code: 13, msg: cancel',
|
|
15
|
+
'error: code: 10, msg: fingerprint operation canceled by the user',
|
|
16
|
+
];
|
|
17
|
+
function isUserCancelledError(error) {
|
|
18
|
+
return KEYCHAIN_USER_CANCELLED_ERRORS.some(userCancelledError => error.toString().toLowerCase().includes(userCancelledError));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Implements `StorageUtils` using React Native `Keychain`.
|
|
22
|
+
*/
|
|
12
23
|
export class KeychainStorage {
|
|
13
24
|
get(key) {
|
|
14
25
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -22,57 +33,39 @@ export class KeychainStorage {
|
|
|
22
33
|
return item.password;
|
|
23
34
|
}
|
|
24
35
|
catch (error) {
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
if (error instanceof Error && !isUserCancelledError(error)) {
|
|
37
|
+
// triggered when biometry verification fails and user cancels the action
|
|
38
|
+
throw new Error('Error retrieving stored item ' + error.message);
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
27
41
|
}
|
|
28
42
|
});
|
|
29
43
|
}
|
|
30
44
|
set(key, value) {
|
|
31
45
|
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (!result) {
|
|
40
|
-
console.warn(`Failed to store key ${key}`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
console.warn(`Error storing key ${key}:`, error);
|
|
46
|
+
const result = yield Keychain.setGenericPassword(USERNAME, value, {
|
|
47
|
+
service: key,
|
|
48
|
+
accessible: Keychain.ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
|
|
49
|
+
securityLevel: Keychain.SECURITY_LEVEL.ANY,
|
|
50
|
+
});
|
|
51
|
+
if (!result) {
|
|
52
|
+
throw new Error('Failed to store key ' + key);
|
|
45
53
|
}
|
|
46
54
|
});
|
|
47
55
|
}
|
|
48
56
|
removeItem(key) {
|
|
49
57
|
return __awaiter(this, void 0, void 0, function* () {
|
|
50
|
-
|
|
51
|
-
yield Keychain.resetGenericPassword({ service: key });
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
console.warn(`Error removing key ${key}:`, error);
|
|
55
|
-
}
|
|
58
|
+
yield Keychain.resetGenericPassword({ service: key });
|
|
56
59
|
});
|
|
57
60
|
}
|
|
58
61
|
clear(prefix) {
|
|
59
62
|
return __awaiter(this, void 0, void 0, function* () {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
yield Keychain.resetGenericPassword({ service: key });
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
console.warn(`Error clearing key ${key}:`, error);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
63
|
+
const services = yield Keychain.getAllGenericPasswordServices();
|
|
64
|
+
for (const key of services) {
|
|
65
|
+
if (key && key.startsWith(prefix)) {
|
|
66
|
+
yield Keychain.resetGenericPassword({ service: key });
|
|
71
67
|
}
|
|
72
68
|
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
console.warn(`Error getting services for prefix ${prefix}:`, error);
|
|
75
|
-
}
|
|
76
69
|
});
|
|
77
70
|
}
|
|
78
71
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ConstructorOpts, ParaCore, Environment, PlatformUtils } from '@getpara/web-sdk';
|
|
2
|
-
import { Auth } from '@getpara/user-management-client';
|
|
1
|
+
import { AuthStateSignup, ConstructorOpts, ParaCore, Environment, PlatformUtils } from '@getpara/web-sdk';
|
|
3
2
|
/**
|
|
4
3
|
* Represents a mobile implementation of the Para SDK.
|
|
5
4
|
* @extends ParaCore
|
|
@@ -8,6 +7,7 @@ import { Auth } from '@getpara/user-management-client';
|
|
|
8
7
|
* const para = new ParaMobile(Environment.BETA, "api_key");
|
|
9
8
|
*/
|
|
10
9
|
export declare class ParaMobile extends ParaCore {
|
|
10
|
+
isNativePasskey: boolean;
|
|
11
11
|
private relyingPartyId;
|
|
12
12
|
/**
|
|
13
13
|
* Creates an instance of ParaMobile.
|
|
@@ -16,37 +16,19 @@ export declare class ParaMobile extends ParaCore {
|
|
|
16
16
|
* @param {string} [relyingPartyId] - The relying party ID for WebAuthn.
|
|
17
17
|
* @param {ConstructorOpts} [opts] - Additional constructor options.
|
|
18
18
|
*/
|
|
19
|
-
constructor(env: Environment, apiKey
|
|
19
|
+
constructor(env: Environment, apiKey: string, relyingPartyId?: string, opts?: ConstructorOpts);
|
|
20
20
|
protected getPlatformUtils(): PlatformUtils;
|
|
21
|
-
/**
|
|
22
|
-
* Verifies an email and returns the biometrics ID.
|
|
23
|
-
* @param {string} verificationCode - The verification code sent to the email.
|
|
24
|
-
* @returns {Promise<string>} The biometrics ID.
|
|
25
|
-
*/
|
|
26
|
-
verifyEmailBiometricsId({ verificationCode }: {
|
|
27
|
-
verificationCode: string;
|
|
28
|
-
}): Promise<string>;
|
|
29
|
-
/**
|
|
30
|
-
* Verifies a phone number and returns the biometrics ID.
|
|
31
|
-
* @param {string} verificationCode - The verification code sent to the phone.
|
|
32
|
-
* @returns {Promise<string>} The biometrics ID.
|
|
33
|
-
*/
|
|
34
|
-
verifyPhoneBiometricsId({ verificationCode }: {
|
|
35
|
-
verificationCode: string;
|
|
36
|
-
}): Promise<string>;
|
|
37
21
|
/**
|
|
38
22
|
* Registers a passkey for the user.
|
|
39
23
|
* @param {Auth<'email'> | Auth<'phone'>} auth - The user's authentication details
|
|
40
24
|
* @param {string} biometricsId - The biometrics ID obtained from verification.
|
|
41
25
|
* @returns {Promise<void>}
|
|
42
26
|
*/
|
|
43
|
-
registerPasskey(
|
|
44
|
-
biometricsId: string;
|
|
45
|
-
} & (Auth<'email'> | Auth<'phone'>)): Promise<void>;
|
|
27
|
+
registerPasskey(authState: AuthStateSignup): Promise<void>;
|
|
46
28
|
/**
|
|
47
29
|
* Logs in the user using their authentication credentials.
|
|
48
30
|
* @param {AuthParams} params - The authentication parameters.
|
|
49
31
|
* @returns {Promise<void>}
|
|
50
32
|
*/
|
|
51
|
-
login(
|
|
33
|
+
login(): Promise<void>;
|
|
52
34
|
}
|
|
@@ -7,22 +7,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
11
|
-
var t = {};
|
|
12
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
13
|
-
t[p] = s[p];
|
|
14
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
15
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
16
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
17
|
-
t[p[i]] = s[p[i]];
|
|
18
|
-
}
|
|
19
|
-
return t;
|
|
20
|
-
};
|
|
21
10
|
import { ParaCore, Environment, decryptPrivateKeyAndDecryptShare, encryptPrivateKey, getAsymmetricKeyPair, getDerivedPrivateKeyAndDecrypt, getPublicKeyHex, getSHA256HashHex, parseCredentialCreationRes, } from '@getpara/web-sdk';
|
|
22
11
|
import * as Sentry from '@sentry/react-native';
|
|
23
12
|
import { ReactNativeUtils } from './ReactNativeUtils.js';
|
|
24
13
|
import { Passkey, } from 'react-native-passkey';
|
|
25
|
-
import {
|
|
14
|
+
import { PublicKeyStatus } from '@getpara/user-management-client';
|
|
26
15
|
import { setEnv } from '../config.js';
|
|
27
16
|
import base64url from 'base64url';
|
|
28
17
|
import { webcrypto } from 'crypto';
|
|
@@ -45,6 +34,7 @@ export class ParaMobile extends ParaCore {
|
|
|
45
34
|
*/
|
|
46
35
|
constructor(env, apiKey, relyingPartyId, opts) {
|
|
47
36
|
super(env, apiKey, opts);
|
|
37
|
+
this.isNativePasskey = true;
|
|
48
38
|
// starting with non-prod to see what kind of errors we get and if sensitive data is tracked
|
|
49
39
|
// will turn on in prod after monitoring
|
|
50
40
|
if (env !== Environment.PROD && env !== Environment.DEV) {
|
|
@@ -76,56 +66,25 @@ export class ParaMobile extends ParaCore {
|
|
|
76
66
|
getPlatformUtils() {
|
|
77
67
|
return new ReactNativeUtils();
|
|
78
68
|
}
|
|
79
|
-
/**
|
|
80
|
-
* Verifies an email and returns the biometrics ID.
|
|
81
|
-
* @param {string} verificationCode - The verification code sent to the email.
|
|
82
|
-
* @returns {Promise<string>} The biometrics ID.
|
|
83
|
-
*/
|
|
84
|
-
verifyEmailBiometricsId(_a) {
|
|
85
|
-
const _super = Object.create(null, {
|
|
86
|
-
verifyEmail: { get: () => super.verifyEmail }
|
|
87
|
-
});
|
|
88
|
-
return __awaiter(this, arguments, void 0, function* ({ verificationCode }) {
|
|
89
|
-
const webAuthCreateUrl = yield _super.verifyEmail.call(this, { verificationCode });
|
|
90
|
-
const segments = webAuthCreateUrl.split('/');
|
|
91
|
-
const segments2 = segments[segments.length - 1].split('?');
|
|
92
|
-
const biometricsId = segments2[0];
|
|
93
|
-
return biometricsId;
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Verifies a phone number and returns the biometrics ID.
|
|
98
|
-
* @param {string} verificationCode - The verification code sent to the phone.
|
|
99
|
-
* @returns {Promise<string>} The biometrics ID.
|
|
100
|
-
*/
|
|
101
|
-
verifyPhoneBiometricsId(_a) {
|
|
102
|
-
const _super = Object.create(null, {
|
|
103
|
-
verifyPhone: { get: () => super.verifyPhone }
|
|
104
|
-
});
|
|
105
|
-
return __awaiter(this, arguments, void 0, function* ({ verificationCode }) {
|
|
106
|
-
const webAuthCreateUrl = yield _super.verifyPhone.call(this, { verificationCode });
|
|
107
|
-
const segments = webAuthCreateUrl.split('/');
|
|
108
|
-
const segments2 = segments[segments.length - 1].split('?');
|
|
109
|
-
const biometricsId = segments2[0];
|
|
110
|
-
return biometricsId;
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
69
|
/**
|
|
114
70
|
* Registers a passkey for the user.
|
|
115
71
|
* @param {Auth<'email'> | Auth<'phone'>} auth - The user's authentication details
|
|
116
72
|
* @param {string} biometricsId - The biometrics ID obtained from verification.
|
|
117
73
|
* @returns {Promise<void>}
|
|
118
74
|
*/
|
|
119
|
-
registerPasskey(
|
|
75
|
+
registerPasskey(authState) {
|
|
120
76
|
return __awaiter(this, void 0, void 0, function* () {
|
|
121
|
-
|
|
77
|
+
if (!authState.passkeyId) {
|
|
78
|
+
throw new Error('Passkey ID not found. Make sure you have enabled passkey logins in the Para Developer Portal.');
|
|
79
|
+
}
|
|
80
|
+
const userId = this.assertUserId();
|
|
81
|
+
const authInfo = this.assertIsAuthSet();
|
|
122
82
|
if (!webcrypto || !webcrypto.getRandomValues) {
|
|
123
83
|
throw new Error('Web crypto is not available. Ensure you have imported the shim from @getpara/react-native-wallet.');
|
|
124
84
|
}
|
|
125
85
|
const userHandle = new Uint8Array(32);
|
|
126
86
|
webcrypto.getRandomValues(userHandle);
|
|
127
87
|
const userHandleEncoded = base64url.encode(userHandle);
|
|
128
|
-
const { identifier: displayIdentifier } = extractAuthInfo(auth, { isRequired: true });
|
|
129
88
|
const requestJson = {
|
|
130
89
|
authenticatorSelection: {
|
|
131
90
|
authenticatorAttachment: 'platform',
|
|
@@ -139,8 +98,8 @@ export class ParaMobile extends ParaCore {
|
|
|
139
98
|
},
|
|
140
99
|
user: {
|
|
141
100
|
id: userHandleEncoded,
|
|
142
|
-
name:
|
|
143
|
-
displayName:
|
|
101
|
+
name: authInfo.identifier,
|
|
102
|
+
displayName: authInfo.identifier,
|
|
144
103
|
},
|
|
145
104
|
pubKeyCredParams: [
|
|
146
105
|
{
|
|
@@ -169,15 +128,15 @@ export class ParaMobile extends ParaCore {
|
|
|
169
128
|
const publicKeyHex = getPublicKeyHex(keyPair);
|
|
170
129
|
const encryptionKeyHash = getSHA256HashHex(userHandleEncoded);
|
|
171
130
|
const encryptedPrivateKeyHex = yield encryptPrivateKey(keyPair, userHandleEncoded);
|
|
172
|
-
const
|
|
173
|
-
yield this.ctx.client.patchSessionPublicKey(
|
|
131
|
+
const { partnerId } = yield this.ctx.client.touchSession();
|
|
132
|
+
yield this.ctx.client.patchSessionPublicKey(partnerId, userId, authState.passkeyId, {
|
|
174
133
|
publicKey: resultJson.id,
|
|
175
134
|
sigDerivedPublicKey: publicKeyHex,
|
|
176
135
|
cosePublicKey,
|
|
177
136
|
clientDataJSON,
|
|
178
137
|
status: PublicKeyStatus.COMPLETE,
|
|
179
138
|
});
|
|
180
|
-
yield this.ctx.client.uploadEncryptedWalletPrivateKey(
|
|
139
|
+
yield this.ctx.client.uploadEncryptedWalletPrivateKey(userId, encryptedPrivateKeyHex, encryptionKeyHash, resultJson.id);
|
|
181
140
|
});
|
|
182
141
|
}
|
|
183
142
|
/**
|
|
@@ -185,12 +144,11 @@ export class ParaMobile extends ParaCore {
|
|
|
185
144
|
* @param {AuthParams} params - The authentication parameters.
|
|
186
145
|
* @returns {Promise<void>}
|
|
187
146
|
*/
|
|
188
|
-
login(
|
|
147
|
+
login() {
|
|
189
148
|
return __awaiter(this, void 0, void 0, function* () {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
const { challenge, allowedPublicKeys } = yield this.ctx.client.getWebChallenge(authInfo.auth);
|
|
149
|
+
this.assertIsAuthSet();
|
|
150
|
+
const userId = this.assertUserId();
|
|
151
|
+
const { challenge, allowedPublicKeys } = yield this.ctx.client.getWebChallenge({ userId });
|
|
194
152
|
const requestJson = {
|
|
195
153
|
challenge,
|
|
196
154
|
timeout: 60000,
|
|
@@ -205,9 +163,9 @@ export class ParaMobile extends ParaCore {
|
|
|
205
163
|
else {
|
|
206
164
|
resultJson = result;
|
|
207
165
|
}
|
|
208
|
-
const
|
|
166
|
+
const { partnerId } = yield this.ctx.client.touchSession();
|
|
209
167
|
const publicKey = resultJson.id;
|
|
210
|
-
const verifyWebChallengeResult = yield this.ctx.client.verifyWebChallenge(
|
|
168
|
+
const verifyWebChallengeResult = yield this.ctx.client.verifyWebChallenge(partnerId, {
|
|
211
169
|
publicKey,
|
|
212
170
|
signature: {
|
|
213
171
|
clientDataJSON: resultJson.response.clientDataJSON,
|
|
@@ -215,17 +173,8 @@ export class ParaMobile extends ParaCore {
|
|
|
215
173
|
signature: resultJson.response.signature,
|
|
216
174
|
},
|
|
217
175
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const { user } = yield this.ctx.client.getUser(userId);
|
|
221
|
-
if (user.phone) {
|
|
222
|
-
yield this.setPhoneNumber(user.phone.number, user.phone.countryCode);
|
|
223
|
-
}
|
|
224
|
-
if (user.email) {
|
|
225
|
-
yield this.setEmail(user.email);
|
|
226
|
-
}
|
|
227
|
-
if (user.farcasterUsername) {
|
|
228
|
-
yield this.setFarcasterUsername(user.farcasterUsername);
|
|
176
|
+
if (userId !== verifyWebChallengeResult.userId) {
|
|
177
|
+
throw new Error('User ID mismatch');
|
|
229
178
|
}
|
|
230
179
|
const encryptedSharesResult = yield this.ctx.client.getBiometricKeyshares(userId, resultJson.id);
|
|
231
180
|
const encryptionKeyHash = getSHA256HashHex(resultJson.response.userHandle);
|
|
@@ -254,16 +203,7 @@ export class ParaMobile extends ParaCore {
|
|
|
254
203
|
type: desiredWallet.type || undefined,
|
|
255
204
|
};
|
|
256
205
|
}
|
|
257
|
-
const currentWalletIds = {};
|
|
258
|
-
for (const wallet of Object.values(walletsToInsert)) {
|
|
259
|
-
const { id, type } = wallet;
|
|
260
|
-
const currentIdsForType = currentWalletIds[type || 'EVM'] || [];
|
|
261
|
-
currentWalletIds[type || 'EVM'] = [...currentIdsForType, id];
|
|
262
|
-
}
|
|
263
206
|
yield this.setWallets(walletsToInsert);
|
|
264
|
-
yield this.setCurrentWalletIds(currentWalletIds, {
|
|
265
|
-
sessionLookupId: session.data.sessionLookupId,
|
|
266
|
-
});
|
|
267
207
|
});
|
|
268
208
|
}
|
|
269
209
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpara/react-native-wallet",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-alpha.3",
|
|
4
4
|
"description": "Para Wallet for React Native",
|
|
5
5
|
"homepage": "https://getpara.com",
|
|
6
6
|
"author": "Para Team <hello@getpara.com> (https://getpara.com)",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"compile-signer": "bash ./scripts/compileSigner.sh"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@getpara/core-sdk": "
|
|
26
|
-
"@getpara/user-management-client": "
|
|
27
|
-
"@getpara/web-sdk": "
|
|
25
|
+
"@getpara/core-sdk": "2.0.0-alpha.3",
|
|
26
|
+
"@getpara/user-management-client": "2.0.0-alpha.3",
|
|
27
|
+
"@getpara/web-sdk": "2.0.0-alpha.3",
|
|
28
28
|
"@peculiar/webcrypto": "^1.5.0",
|
|
29
29
|
"@sentry/react-native": "^6.7.0",
|
|
30
30
|
"node-forge": "1.3.1",
|
|
@@ -33,16 +33,16 @@
|
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@craftzdog/react-native-buffer": "6.0.5",
|
|
36
|
-
"@react-native-async-storage/async-storage": "2.1.
|
|
36
|
+
"@react-native-async-storage/async-storage": "2.1.0",
|
|
37
37
|
"@types/node-forge": "1.3.1",
|
|
38
38
|
"@types/react": "^18.0.31",
|
|
39
39
|
"@types/react-native": "0.70.0",
|
|
40
40
|
"@types/text-encoding": "0.0.39",
|
|
41
|
-
"react-native-keychain": "
|
|
41
|
+
"react-native-keychain": "9.2.2",
|
|
42
42
|
"react-native-modpow": "1.1.0",
|
|
43
|
-
"react-native-passkey": "3.
|
|
43
|
+
"react-native-passkey": "3.0.0",
|
|
44
44
|
"react-native-quick-base64": "2.1.2",
|
|
45
|
-
"react-native-quick-crypto": "0.7.
|
|
45
|
+
"react-native-quick-crypto": "0.7.11",
|
|
46
46
|
"typescript": "^5.4.3"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"publishConfig": {
|
|
61
61
|
"access": "public"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "77a1e04b06258842ca9c81e3db2a2b0092517659"
|
|
64
64
|
}
|
package/src/AsyncStorage.ts
CHANGED
|
@@ -1,46 +1,28 @@
|
|
|
1
1
|
import { StorageUtils } from '@getpara/web-sdk';
|
|
2
2
|
import RNAsyncStorage from '@react-native-async-storage/async-storage';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Implements `StorageUtils` using React Native Async Storage.
|
|
6
|
+
*/
|
|
4
7
|
export class AsyncStorage implements StorageUtils {
|
|
5
|
-
async
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
async clear(prefix: string): Promise<void> {
|
|
9
|
+
const keys = await RNAsyncStorage.getAllKeys();
|
|
10
|
+
for (const key of keys) {
|
|
11
|
+
if (key.startsWith(prefix)) {
|
|
12
|
+
await RNAsyncStorage.removeItem(key);
|
|
13
|
+
}
|
|
11
14
|
}
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
async
|
|
15
|
-
|
|
16
|
-
await RNAsyncStorage.setItem(key, value);
|
|
17
|
-
} catch (error) {
|
|
18
|
-
console.warn(`Error storing key ${key}:`, error);
|
|
19
|
-
}
|
|
17
|
+
async get(key: string): Promise<string | null> {
|
|
18
|
+
return RNAsyncStorage.getItem(key);
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
async removeItem(key: string): Promise<void> {
|
|
23
|
-
|
|
24
|
-
await RNAsyncStorage.removeItem(key);
|
|
25
|
-
} catch (error) {
|
|
26
|
-
console.warn(`Error removing key ${key}:`, error);
|
|
27
|
-
}
|
|
22
|
+
await RNAsyncStorage.removeItem(key);
|
|
28
23
|
}
|
|
29
24
|
|
|
30
|
-
async
|
|
31
|
-
|
|
32
|
-
const keys = await RNAsyncStorage.getAllKeys();
|
|
33
|
-
for (const key of keys) {
|
|
34
|
-
if (key.startsWith(prefix)) {
|
|
35
|
-
try {
|
|
36
|
-
await RNAsyncStorage.removeItem(key);
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.warn(`Error clearing key ${key}:`, error);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
} catch (error) {
|
|
43
|
-
console.warn(`Error getting keys for prefix ${prefix}:`, error);
|
|
44
|
-
}
|
|
25
|
+
async set(key: string, value: string): Promise<void> {
|
|
26
|
+
await RNAsyncStorage.setItem(key, value);
|
|
45
27
|
}
|
|
46
28
|
}
|
package/src/KeychainStorage.ts
CHANGED
|
@@ -2,7 +2,21 @@ import { StorageUtils } from '@getpara/web-sdk';
|
|
|
2
2
|
import Keychain from 'react-native-keychain';
|
|
3
3
|
|
|
4
4
|
const USERNAME = '@CAPSULE';
|
|
5
|
+
const KEYCHAIN_USER_CANCELLED_ERRORS = [
|
|
6
|
+
'user canceled the operation',
|
|
7
|
+
'error: code: 13, msg: cancel',
|
|
8
|
+
'error: code: 10, msg: fingerprint operation canceled by the user',
|
|
9
|
+
];
|
|
5
10
|
|
|
11
|
+
function isUserCancelledError(error: Error) {
|
|
12
|
+
return KEYCHAIN_USER_CANCELLED_ERRORS.some(userCancelledError =>
|
|
13
|
+
error.toString().toLowerCase().includes(userCancelledError),
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Implements `StorageUtils` using React Native `Keychain`.
|
|
19
|
+
*/
|
|
6
20
|
export class KeychainStorage implements StorageUtils {
|
|
7
21
|
async get(key: string): Promise<string | null> {
|
|
8
22
|
try {
|
|
@@ -14,49 +28,32 @@ export class KeychainStorage implements StorageUtils {
|
|
|
14
28
|
}
|
|
15
29
|
return item.password;
|
|
16
30
|
} catch (error) {
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
if (error instanceof Error && !isUserCancelledError(error)) {
|
|
32
|
+
// triggered when biometry verification fails and user cancels the action
|
|
33
|
+
throw new Error('Error retrieving stored item ' + error.message);
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
19
36
|
}
|
|
20
37
|
}
|
|
21
|
-
|
|
22
38
|
async set(key: string, value: string): Promise<void> {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!result) {
|
|
31
|
-
console.warn(`Failed to store key ${key}`);
|
|
32
|
-
}
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.warn(`Error storing key ${key}:`, error);
|
|
39
|
+
const result = await Keychain.setGenericPassword(USERNAME, value, {
|
|
40
|
+
service: key,
|
|
41
|
+
accessible: Keychain.ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
|
|
42
|
+
securityLevel: Keychain.SECURITY_LEVEL.ANY,
|
|
43
|
+
});
|
|
44
|
+
if (!result) {
|
|
45
|
+
throw new Error('Failed to store key ' + key);
|
|
35
46
|
}
|
|
36
47
|
}
|
|
37
|
-
|
|
38
48
|
async removeItem(key: string): Promise<void> {
|
|
39
|
-
|
|
40
|
-
await Keychain.resetGenericPassword({ service: key });
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.warn(`Error removing key ${key}:`, error);
|
|
43
|
-
}
|
|
49
|
+
await Keychain.resetGenericPassword({ service: key });
|
|
44
50
|
}
|
|
45
|
-
|
|
46
51
|
async clear(prefix: string): Promise<void> {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
await Keychain.resetGenericPassword({ service: key });
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.warn(`Error clearing key ${key}:`, error);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
52
|
+
const services = await Keychain.getAllGenericPasswordServices();
|
|
53
|
+
for (const key of services) {
|
|
54
|
+
if (key && key.startsWith(prefix)) {
|
|
55
|
+
await Keychain.resetGenericPassword({ service: key });
|
|
57
56
|
}
|
|
58
|
-
} catch (error) {
|
|
59
|
-
console.warn(`Error getting services for prefix ${prefix}:`, error);
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
59
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AuthStateSignup,
|
|
2
3
|
ConstructorOpts,
|
|
3
4
|
ParaCore,
|
|
4
5
|
Environment,
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
PasskeyGetRequest,
|
|
23
24
|
PasskeyGetResult,
|
|
24
25
|
} from 'react-native-passkey';
|
|
25
|
-
import {
|
|
26
|
+
import { PublicKeyStatus, WalletScheme } from '@getpara/user-management-client';
|
|
26
27
|
import { setEnv } from '../config.js';
|
|
27
28
|
import base64url from 'base64url';
|
|
28
29
|
import { webcrypto } from 'crypto';
|
|
@@ -38,6 +39,8 @@ const RS256_ALGORITHM = -257;
|
|
|
38
39
|
* const para = new ParaMobile(Environment.BETA, "api_key");
|
|
39
40
|
*/
|
|
40
41
|
export class ParaMobile extends ParaCore {
|
|
42
|
+
isNativePasskey = true;
|
|
43
|
+
|
|
41
44
|
private relyingPartyId: string;
|
|
42
45
|
/**
|
|
43
46
|
* Creates an instance of ParaMobile.
|
|
@@ -46,7 +49,7 @@ export class ParaMobile extends ParaCore {
|
|
|
46
49
|
* @param {string} [relyingPartyId] - The relying party ID for WebAuthn.
|
|
47
50
|
* @param {ConstructorOpts} [opts] - Additional constructor options.
|
|
48
51
|
*/
|
|
49
|
-
constructor(env: Environment, apiKey
|
|
52
|
+
constructor(env: Environment, apiKey: string, relyingPartyId?: string, opts?: ConstructorOpts) {
|
|
50
53
|
super(env, apiKey, opts);
|
|
51
54
|
|
|
52
55
|
// starting with non-prod to see what kind of errors we get and if sensitive data is tracked
|
|
@@ -83,40 +86,20 @@ export class ParaMobile extends ParaCore {
|
|
|
83
86
|
return new ReactNativeUtils();
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
/**
|
|
87
|
-
* Verifies an email and returns the biometrics ID.
|
|
88
|
-
* @param {string} verificationCode - The verification code sent to the email.
|
|
89
|
-
* @returns {Promise<string>} The biometrics ID.
|
|
90
|
-
*/
|
|
91
|
-
async verifyEmailBiometricsId({ verificationCode }: { verificationCode: string }): Promise<string> {
|
|
92
|
-
const webAuthCreateUrl = await super.verifyEmail({ verificationCode });
|
|
93
|
-
const segments = webAuthCreateUrl.split('/');
|
|
94
|
-
const segments2 = segments[segments.length - 1]!.split('?');
|
|
95
|
-
const biometricsId = segments2[0]!;
|
|
96
|
-
|
|
97
|
-
return biometricsId;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Verifies a phone number and returns the biometrics ID.
|
|
102
|
-
* @param {string} verificationCode - The verification code sent to the phone.
|
|
103
|
-
* @returns {Promise<string>} The biometrics ID.
|
|
104
|
-
*/
|
|
105
|
-
async verifyPhoneBiometricsId({ verificationCode }: { verificationCode: string }): Promise<string> {
|
|
106
|
-
const webAuthCreateUrl = await super.verifyPhone({ verificationCode });
|
|
107
|
-
const segments = webAuthCreateUrl.split('/');
|
|
108
|
-
const segments2 = segments[segments.length - 1]!.split('?');
|
|
109
|
-
const biometricsId = segments2[0]!;
|
|
110
|
-
|
|
111
|
-
return biometricsId;
|
|
112
|
-
}
|
|
113
89
|
/**
|
|
114
90
|
* Registers a passkey for the user.
|
|
115
91
|
* @param {Auth<'email'> | Auth<'phone'>} auth - The user's authentication details
|
|
116
92
|
* @param {string} biometricsId - The biometrics ID obtained from verification.
|
|
117
93
|
* @returns {Promise<void>}
|
|
118
94
|
*/
|
|
119
|
-
async registerPasskey(
|
|
95
|
+
async registerPasskey(authState: AuthStateSignup) {
|
|
96
|
+
if (!authState.passkeyId) {
|
|
97
|
+
throw new Error('Passkey ID not found. Make sure you have enabled passkey logins in the Para Developer Portal.');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const userId = this.assertUserId();
|
|
101
|
+
const authInfo = this.assertIsAuthSet();
|
|
102
|
+
|
|
120
103
|
if (!webcrypto || !webcrypto.getRandomValues) {
|
|
121
104
|
throw new Error('Web crypto is not available. Ensure you have imported the shim from @getpara/react-native-wallet.');
|
|
122
105
|
}
|
|
@@ -124,8 +107,6 @@ export class ParaMobile extends ParaCore {
|
|
|
124
107
|
webcrypto.getRandomValues(userHandle);
|
|
125
108
|
const userHandleEncoded = base64url.encode(userHandle as any);
|
|
126
109
|
|
|
127
|
-
const { identifier: displayIdentifier } = extractAuthInfo(auth, { isRequired: true });
|
|
128
|
-
|
|
129
110
|
const requestJson: PasskeyCreateRequest = {
|
|
130
111
|
authenticatorSelection: {
|
|
131
112
|
authenticatorAttachment: 'platform' as any,
|
|
@@ -139,8 +120,8 @@ export class ParaMobile extends ParaCore {
|
|
|
139
120
|
},
|
|
140
121
|
user: {
|
|
141
122
|
id: userHandleEncoded,
|
|
142
|
-
name:
|
|
143
|
-
displayName:
|
|
123
|
+
name: authInfo.identifier,
|
|
124
|
+
displayName: authInfo.identifier,
|
|
144
125
|
},
|
|
145
126
|
pubKeyCredParams: [
|
|
146
127
|
{
|
|
@@ -174,8 +155,8 @@ export class ParaMobile extends ParaCore {
|
|
|
174
155
|
const encryptionKeyHash = getSHA256HashHex(userHandleEncoded);
|
|
175
156
|
const encryptedPrivateKeyHex = await encryptPrivateKey(keyPair, userHandleEncoded);
|
|
176
157
|
|
|
177
|
-
const
|
|
178
|
-
await this.ctx.client.patchSessionPublicKey(
|
|
158
|
+
const { partnerId } = await this.ctx.client.touchSession();
|
|
159
|
+
await this.ctx.client.patchSessionPublicKey(partnerId, userId, authState.passkeyId, {
|
|
179
160
|
publicKey: resultJson.id,
|
|
180
161
|
sigDerivedPublicKey: publicKeyHex,
|
|
181
162
|
cosePublicKey,
|
|
@@ -183,12 +164,7 @@ export class ParaMobile extends ParaCore {
|
|
|
183
164
|
status: PublicKeyStatus.COMPLETE,
|
|
184
165
|
});
|
|
185
166
|
|
|
186
|
-
await this.ctx.client.uploadEncryptedWalletPrivateKey(
|
|
187
|
-
this.getUserId()!,
|
|
188
|
-
encryptedPrivateKeyHex,
|
|
189
|
-
encryptionKeyHash,
|
|
190
|
-
resultJson.id,
|
|
191
|
-
);
|
|
167
|
+
await this.ctx.client.uploadEncryptedWalletPrivateKey(userId, encryptedPrivateKeyHex, encryptionKeyHash, resultJson.id);
|
|
192
168
|
}
|
|
193
169
|
|
|
194
170
|
/**
|
|
@@ -196,12 +172,11 @@ export class ParaMobile extends ParaCore {
|
|
|
196
172
|
* @param {AuthParams} params - The authentication parameters.
|
|
197
173
|
* @returns {Promise<void>}
|
|
198
174
|
*/
|
|
199
|
-
async login(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const authInfo = extractAuthInfo(auth, { isRequired: true });
|
|
175
|
+
async login(): Promise<void> {
|
|
176
|
+
this.assertIsAuthSet();
|
|
177
|
+
const userId = this.assertUserId();
|
|
203
178
|
|
|
204
|
-
const { challenge, allowedPublicKeys } = await this.ctx.client.getWebChallenge(
|
|
179
|
+
const { challenge, allowedPublicKeys } = await this.ctx.client.getWebChallenge({ userId });
|
|
205
180
|
|
|
206
181
|
const requestJson: PasskeyGetRequest = {
|
|
207
182
|
challenge,
|
|
@@ -220,9 +195,9 @@ export class ParaMobile extends ParaCore {
|
|
|
220
195
|
resultJson = result;
|
|
221
196
|
}
|
|
222
197
|
|
|
223
|
-
const
|
|
198
|
+
const { partnerId } = await this.ctx.client.touchSession();
|
|
224
199
|
const publicKey = resultJson.id;
|
|
225
|
-
const verifyWebChallengeResult = await this.ctx.client.verifyWebChallenge(
|
|
200
|
+
const verifyWebChallengeResult = await this.ctx.client.verifyWebChallenge(partnerId, {
|
|
226
201
|
publicKey,
|
|
227
202
|
signature: {
|
|
228
203
|
clientDataJSON: resultJson.response.clientDataJSON,
|
|
@@ -231,22 +206,8 @@ export class ParaMobile extends ParaCore {
|
|
|
231
206
|
},
|
|
232
207
|
});
|
|
233
208
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
await this.setUserId(userId);
|
|
237
|
-
|
|
238
|
-
const { user } = await this.ctx.client.getUser(userId);
|
|
239
|
-
|
|
240
|
-
if (user.phone) {
|
|
241
|
-
await this.setPhoneNumber(user.phone.number, user.phone.countryCode);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (user.email) {
|
|
245
|
-
await this.setEmail(user.email);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (user.farcasterUsername) {
|
|
249
|
-
await this.setFarcasterUsername(user.farcasterUsername);
|
|
209
|
+
if (userId !== verifyWebChallengeResult.userId) {
|
|
210
|
+
throw new Error('User ID mismatch');
|
|
250
211
|
}
|
|
251
212
|
|
|
252
213
|
const encryptedSharesResult = await this.ctx.client.getBiometricKeyshares(userId, resultJson.id);
|
|
@@ -293,16 +254,6 @@ export class ParaMobile extends ParaCore {
|
|
|
293
254
|
};
|
|
294
255
|
}
|
|
295
256
|
|
|
296
|
-
const currentWalletIds: CurrentWalletIds = {};
|
|
297
|
-
for (const wallet of Object.values(walletsToInsert)) {
|
|
298
|
-
const { id, type } = wallet;
|
|
299
|
-
const currentIdsForType = currentWalletIds[type || 'EVM'] || [];
|
|
300
|
-
currentWalletIds[type || 'EVM'] = [...currentIdsForType, id];
|
|
301
|
-
}
|
|
302
|
-
|
|
303
257
|
await this.setWallets(walletsToInsert);
|
|
304
|
-
await this.setCurrentWalletIds(currentWalletIds, {
|
|
305
|
-
sessionLookupId: session.data.sessionLookupId,
|
|
306
|
-
});
|
|
307
258
|
}
|
|
308
259
|
}
|