@symbiote-native/secure-store 0.0.1
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/LICENSE +21 -0
- package/README.md +145 -0
- package/build/angular/index.d.ts +1 -0
- package/build/angular/index.js +4 -0
- package/build/core/index.d.ts +2 -0
- package/build/core/index.js +1 -0
- package/build/core/native-module.d.ts +17 -0
- package/build/core/native-module.js +3 -0
- package/build/core/secure-store.d.ts +83 -0
- package/build/core/secure-store.js +137 -0
- package/build/core/types.d.ts +43 -0
- package/build/core/types.js +1 -0
- package/build/react/index.d.ts +1 -0
- package/build/react/index.js +5 -0
- package/build/vue/index.d.ts +1 -0
- package/build/vue/index.js +4 -0
- package/build-ngc/angular/index.d.ts +1 -0
- package/build-ngc/angular/index.js +5 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/core/index.d.ts +2 -0
- package/build-ngc/core/index.js +2 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +17 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/secure-store.d.ts +83 -0
- package/build-ngc/core/secure-store.js +138 -0
- package/build-ngc/core/secure-store.js.map +1 -0
- package/build-ngc/core/types.d.ts +43 -0
- package/build-ngc/core/types.js +2 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +21 -0
- package/package.json +107 -0
- package/src/angular/index.ts +4 -0
- package/src/core/index.ts +17 -0
- package/src/core/native-module.ts +31 -0
- package/src/core/secure-store.test.ts +146 -0
- package/src/core/secure-store.ts +177 -0
- package/src/core/types.ts +44 -0
- package/src/react/index.ts +5 -0
- package/src/vue/index.ts +4 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
2
|
+
|
|
3
|
+
import { expoSecureStore } from './native-module';
|
|
4
|
+
import type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
|
|
5
|
+
|
|
6
|
+
const NATIVE_MODULE_NAME = 'expo-secure-store';
|
|
7
|
+
|
|
8
|
+
// Alphanumerics plus ".", "-" and "_": the same set the native side validates against, so an
|
|
9
|
+
// invalid key fails here with a readable message instead of deep inside the keychain call.
|
|
10
|
+
const VALID_KEY_PATTERN = /^[\w.-]+$/;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The item cannot be read after a restart until the device has been unlocked once. Useful for
|
|
14
|
+
* data the app needs while the phone is locked.
|
|
15
|
+
* @platform ios
|
|
16
|
+
*/
|
|
17
|
+
export const AFTER_FIRST_UNLOCK: IKeychainAccessibilityConstant | undefined =
|
|
18
|
+
expoSecureStore.AFTER_FIRST_UNLOCK;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Like `AFTER_FIRST_UNLOCK`, except the entry does not migrate to a new device when restoring
|
|
22
|
+
* from a backup.
|
|
23
|
+
* @platform ios
|
|
24
|
+
*/
|
|
25
|
+
export const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined =
|
|
26
|
+
expoSecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The item can always be read, locked device or not. The least secure option.
|
|
30
|
+
* @deprecated Use a level that offers some user protection, such as `AFTER_FIRST_UNLOCK`.
|
|
31
|
+
* @platform ios
|
|
32
|
+
*/
|
|
33
|
+
export const ALWAYS: IKeychainAccessibilityConstant | undefined = expoSecureStore.ALWAYS;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Like `ALWAYS`, except the entry does not migrate to a new device when restoring from a backup.
|
|
37
|
+
* @deprecated Use a level that offers some user protection, such as
|
|
38
|
+
* `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`.
|
|
39
|
+
* @platform ios
|
|
40
|
+
*/
|
|
41
|
+
export const ALWAYS_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined =
|
|
42
|
+
expoSecureStore.ALWAYS_THIS_DEVICE_ONLY;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Like `WHEN_UNLOCKED_THIS_DEVICE_ONLY`, except the user must have set a passcode to store an
|
|
46
|
+
* entry at all. Removing the passcode deletes the entry.
|
|
47
|
+
* @platform ios
|
|
48
|
+
*/
|
|
49
|
+
export const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined =
|
|
50
|
+
expoSecureStore.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The item can only be read while the device is unlocked.
|
|
54
|
+
* @platform ios
|
|
55
|
+
*/
|
|
56
|
+
export const WHEN_UNLOCKED: IKeychainAccessibilityConstant | undefined =
|
|
57
|
+
expoSecureStore.WHEN_UNLOCKED;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Like `WHEN_UNLOCKED`, except the entry does not migrate to a new device when restoring from a
|
|
61
|
+
* backup.
|
|
62
|
+
* @platform ios
|
|
63
|
+
*/
|
|
64
|
+
export const WHEN_UNLOCKED_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined =
|
|
65
|
+
expoSecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether the SecureStore API is usable on this device. Says nothing about app permissions.
|
|
69
|
+
* Resolves `true` on Android and iOS.
|
|
70
|
+
*/
|
|
71
|
+
export async function isAvailableAsync(): Promise<boolean> {
|
|
72
|
+
return !!expoSecureStore.getValueWithKeyAsync;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Read the value stored under `key`.
|
|
77
|
+
*
|
|
78
|
+
* Resolves `null` when there is no entry for the key, or when the key has been invalidated —
|
|
79
|
+
* the system invalidates keys stored with `requireAuthentication` whenever enrolled biometrics
|
|
80
|
+
* change (a new fingerprint, a re-registered face), and an invalidated value can never be read
|
|
81
|
+
* again. Rejects if reading itself fails.
|
|
82
|
+
*/
|
|
83
|
+
export async function getItemAsync(
|
|
84
|
+
key: string,
|
|
85
|
+
options: ISecureStoreOptions = {},
|
|
86
|
+
): Promise<string | null> {
|
|
87
|
+
ensureValidKey(key);
|
|
88
|
+
if (!expoSecureStore.getValueWithKeyAsync) {
|
|
89
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getItemAsync');
|
|
90
|
+
}
|
|
91
|
+
return expoSecureStore.getValueWithKeyAsync(key, options);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Read the value stored under `key`, synchronously.
|
|
96
|
+
*
|
|
97
|
+
* > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
|
|
98
|
+
* > until the user authenticates.
|
|
99
|
+
*/
|
|
100
|
+
export function getItem(key: string, options: ISecureStoreOptions = {}): string | null {
|
|
101
|
+
ensureValidKey(key);
|
|
102
|
+
if (!expoSecureStore.getValueWithKeySync) {
|
|
103
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getItem');
|
|
104
|
+
}
|
|
105
|
+
return expoSecureStore.getValueWithKeySync(key, options);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Store a key–value pair. Keys may contain alphanumeric characters, `.`, `-` and `_`.
|
|
110
|
+
* Rejects if the value cannot be stored on the device.
|
|
111
|
+
*/
|
|
112
|
+
export async function setItemAsync(
|
|
113
|
+
key: string,
|
|
114
|
+
value: string,
|
|
115
|
+
options: ISecureStoreOptions = {},
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
ensureValidKey(key);
|
|
118
|
+
ensureValidValue(value);
|
|
119
|
+
if (!expoSecureStore.setValueWithKeyAsync) {
|
|
120
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setItemAsync');
|
|
121
|
+
}
|
|
122
|
+
await expoSecureStore.setValueWithKeyAsync(value, key, options);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Store a key–value pair, synchronously.
|
|
127
|
+
*
|
|
128
|
+
* > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
|
|
129
|
+
* > until the user authenticates.
|
|
130
|
+
*/
|
|
131
|
+
export function setItem(key: string, value: string, options: ISecureStoreOptions = {}): void {
|
|
132
|
+
ensureValidKey(key);
|
|
133
|
+
ensureValidValue(value);
|
|
134
|
+
if (!expoSecureStore.setValueWithKeySync) {
|
|
135
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setItem');
|
|
136
|
+
}
|
|
137
|
+
expoSecureStore.setValueWithKeySync(value, key, options);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Delete the value stored under `key`. Rejects if the value cannot be deleted. */
|
|
141
|
+
export async function deleteItemAsync(
|
|
142
|
+
key: string,
|
|
143
|
+
options: ISecureStoreOptions = {},
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
ensureValidKey(key);
|
|
146
|
+
if (!expoSecureStore.deleteValueWithKeyAsync) {
|
|
147
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'deleteItemAsync');
|
|
148
|
+
}
|
|
149
|
+
await expoSecureStore.deleteValueWithKeyAsync(key, options);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Whether a value can be stored with `requireAuthentication` — `true` when the device supports
|
|
154
|
+
* biometric authentication and the enrolled method is strong enough.
|
|
155
|
+
*/
|
|
156
|
+
export function canUseBiometricAuthentication(): boolean {
|
|
157
|
+
if (!expoSecureStore.canUseBiometricAuthentication) {
|
|
158
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'canUseBiometricAuthentication');
|
|
159
|
+
}
|
|
160
|
+
return expoSecureStore.canUseBiometricAuthentication();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function ensureValidKey(key: string): void {
|
|
164
|
+
if (typeof key !== 'string' || !VALID_KEY_PATTERN.test(key)) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
'Invalid key provided to SecureStore. Keys must not be empty and contain only alphanumeric characters, ".", "-", and "_".',
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function ensureValidValue(value: string): void {
|
|
172
|
+
if (typeof value !== 'string') {
|
|
173
|
+
throw new Error(
|
|
174
|
+
'Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.',
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An iOS [`kSecAttrAccessible`](https://developer.apple.com/documentation/security/ksecattraccessible/)
|
|
3
|
+
* value, as exposed by the native module. Opaque on purpose — pass one of the constants this
|
|
4
|
+
* package exports rather than a literal.
|
|
5
|
+
*/
|
|
6
|
+
export type IKeychainAccessibilityConstant = number;
|
|
7
|
+
|
|
8
|
+
export type ISecureStoreOptions = {
|
|
9
|
+
/**
|
|
10
|
+
* - Android: equivalent of the public/private key pair `Alias`.
|
|
11
|
+
* - iOS: the item's service, equivalent to [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice/).
|
|
12
|
+
*
|
|
13
|
+
* An item stored with a `keychainService` needs the same one to be read back.
|
|
14
|
+
*/
|
|
15
|
+
keychainService?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Require the device's own authentication (biometrics or passcode) to reach the stored value.
|
|
18
|
+
* - Android: [`setUserAuthenticationRequired(true)`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean)).
|
|
19
|
+
* - iOS: [`biometryCurrentSet`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/2937192-biometrycurrentset).
|
|
20
|
+
*
|
|
21
|
+
* The two platforms prompt at different moments: Android authenticates on every operation,
|
|
22
|
+
* iOS only when reading or updating an existing value, never when creating one. The full
|
|
23
|
+
* behavior needs a freshly generated key, so it does not combine with a `keychainService`
|
|
24
|
+
* already used for non-authenticated entries.
|
|
25
|
+
*
|
|
26
|
+
* > Emulators and simulators do not enforce the prompt when retrieving a secret — testing this
|
|
27
|
+
* > option means testing on a real device.
|
|
28
|
+
*/
|
|
29
|
+
requireAuthentication?: boolean;
|
|
30
|
+
/** Message shown to the user in the prompt raised by `requireAuthentication`. */
|
|
31
|
+
authenticationPrompt?: string;
|
|
32
|
+
/**
|
|
33
|
+
* When the stored entry is accessible, via iOS's `kSecAttrAccessible` property.
|
|
34
|
+
* @default WHEN_UNLOCKED
|
|
35
|
+
* @platform ios
|
|
36
|
+
*/
|
|
37
|
+
keychainAccessible?: IKeychainAccessibilityConstant;
|
|
38
|
+
/**
|
|
39
|
+
* The [access group](https://developer.apple.com/documentation/security/sharing-access-to-keychain-items-among-a-collection-of-apps)
|
|
40
|
+
* the stored entry belongs to.
|
|
41
|
+
* @platform ios
|
|
42
|
+
*/
|
|
43
|
+
accessGroup?: string;
|
|
44
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// @symbiote-native/secure-store/react: the React entry over the framework-agnostic core.
|
|
2
|
+
// Upstream ships free functions and seven constants, no per-instance state and no event stream
|
|
3
|
+
// — there is nothing for a hook to wrap, so this is a plain re-export, the same shape
|
|
4
|
+
// packages/local-auth's React entry has for the same reason.
|
|
5
|
+
export * from '../core';
|
package/src/vue/index.ts
ADDED