@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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +145 -0
  3. package/build/angular/index.d.ts +1 -0
  4. package/build/angular/index.js +4 -0
  5. package/build/core/index.d.ts +2 -0
  6. package/build/core/index.js +1 -0
  7. package/build/core/native-module.d.ts +17 -0
  8. package/build/core/native-module.js +3 -0
  9. package/build/core/secure-store.d.ts +83 -0
  10. package/build/core/secure-store.js +137 -0
  11. package/build/core/types.d.ts +43 -0
  12. package/build/core/types.js +1 -0
  13. package/build/react/index.d.ts +1 -0
  14. package/build/react/index.js +5 -0
  15. package/build/vue/index.d.ts +1 -0
  16. package/build/vue/index.js +4 -0
  17. package/build-ngc/angular/index.d.ts +1 -0
  18. package/build-ngc/angular/index.js +5 -0
  19. package/build-ngc/angular/index.js.map +1 -0
  20. package/build-ngc/core/index.d.ts +2 -0
  21. package/build-ngc/core/index.js +2 -0
  22. package/build-ngc/core/index.js.map +1 -0
  23. package/build-ngc/core/native-module.d.ts +17 -0
  24. package/build-ngc/core/native-module.js +4 -0
  25. package/build-ngc/core/native-module.js.map +1 -0
  26. package/build-ngc/core/secure-store.d.ts +83 -0
  27. package/build-ngc/core/secure-store.js +138 -0
  28. package/build-ngc/core/secure-store.js.map +1 -0
  29. package/build-ngc/core/types.d.ts +43 -0
  30. package/build-ngc/core/types.js +2 -0
  31. package/build-ngc/core/types.js.map +1 -0
  32. package/native-link.json +21 -0
  33. package/package.json +107 -0
  34. package/src/angular/index.ts +4 -0
  35. package/src/core/index.ts +17 -0
  36. package/src/core/native-module.ts +31 -0
  37. package/src/core/secure-store.test.ts +146 -0
  38. package/src/core/secure-store.ts +177 -0
  39. package/src/core/types.ts +44 -0
  40. package/src/react/index.ts +5 -0
  41. 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';
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/secure-store/vue: the Vue entry over the framework-agnostic core. Same
2
+ // reasoning as the React entry — no per-instance state or event stream to wire onto Vue's
3
+ // reactivity, so this is a plain re-export.
4
+ export * from '../core';