@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# @symbiote-native/secure-store
|
|
2
|
+
|
|
3
|
+
A wrapper package for [SymbioteNative](../../README.md) that makes
|
|
4
|
+
[`expo-secure-store`](https://github.com/expo/expo/tree/main/packages/expo-secure-store) —
|
|
5
|
+
encrypted key-value storage in the iOS Keychain and the Android Keystore, optionally gated behind
|
|
6
|
+
the device's own biometrics — usable from **every** adapter, React, Vue, and Angular, not just
|
|
7
|
+
React. Built the same way as [`@symbiote-native/local-auth`](../local-auth): an
|
|
8
|
+
`expo-modules-core`-based wrapper (see the `symbiote-expo-native-module` project skill for the
|
|
9
|
+
full mechanism — why `expo-modules-core` is depended on directly and never the `expo`
|
|
10
|
+
meta-package, why the upstream JS is hand-ported into `core/` rather than imported, and how
|
|
11
|
+
autolinking picks up the native module).
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @symbiote-native/secure-store
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`expo-secure-store` and `expo-modules-core` come along as regular, pinned dependencies — never
|
|
20
|
+
install either yourself, and never add the `expo` meta-package to this project (it bundles its
|
|
21
|
+
own Metro/Babel pipeline that conflicts with this project's own).
|
|
22
|
+
|
|
23
|
+
### Required one-time step: native autolinking wiring
|
|
24
|
+
|
|
25
|
+
Unlike a plain RN native module, `expo-secure-store`'s native code is discovered by
|
|
26
|
+
`expo-modules-autolinking` — this needs wiring into the native host app **once**, covering this
|
|
27
|
+
package and every other `expo-modules-core` package with zero further changes:
|
|
28
|
+
|
|
29
|
+
| Platform | Touches |
|
|
30
|
+
|---|---|
|
|
31
|
+
| iOS | `ios/Podfile` — add `use_expo_modules!` |
|
|
32
|
+
| iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
|
|
33
|
+
| Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
|
|
34
|
+
| Android | `MainApplication.kt` — Expo's bootstrap hook, plus a native-module name map |
|
|
35
|
+
|
|
36
|
+
Full mechanics live in the `symbiote-expo-native-module` skill. The per-package half of that
|
|
37
|
+
table — the Gradle dependency, the module map entry, the `NSFaceIDUsageDescription` string, and
|
|
38
|
+
the Android backup rules below — is generated by
|
|
39
|
+
[`@symbiote-native/expo-modules-link`](../expo-modules-link) from this package's
|
|
40
|
+
`native-link.json` on every install.
|
|
41
|
+
|
|
42
|
+
### Android: Auto Backup must exclude the store
|
|
43
|
+
|
|
44
|
+
`native-link.json` asks the linker to set two attributes on your app's `<application>` element:
|
|
45
|
+
|
|
46
|
+
```xml
|
|
47
|
+
android:fullBackupContent="@xml/secure_store_backup_rules"
|
|
48
|
+
android:dataExtractionRules="@xml/secure_store_data_extraction_rules"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Both rule files ship inside `expo-secure-store` itself and merge in automatically; only the two
|
|
52
|
+
attributes have to live in your own manifest. They matter: Android Auto Backup would otherwise
|
|
53
|
+
upload the encrypted entries while leaving the Keystore keys that decrypt them behind, and a
|
|
54
|
+
restore onto a new device would hand the app values it can no longer read. If your app already
|
|
55
|
+
sets either attribute, the linker keeps yours and prints a notice — merge the rules yourself in
|
|
56
|
+
that case (Expo's own [SecureStore docs](https://docs.expo.dev/versions/latest/sdk/securestore/)
|
|
57
|
+
describe the rule files).
|
|
58
|
+
|
|
59
|
+
## Shape
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
src/core/ the whole API: seven keychain-accessibility constants plus the
|
|
63
|
+
get/set/delete surface. native-module.ts resolves ExpoSecureStore
|
|
64
|
+
through expo-modules-core's requireNativeModule.
|
|
65
|
+
src/react/ @symbiote-native/secure-store/react
|
|
66
|
+
src/vue/ @symbiote-native/secure-store/vue
|
|
67
|
+
src/angular/ @symbiote-native/secure-store/angular
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
All three adapter entries are plain re-exports of `core/`. Upstream ships free functions and
|
|
71
|
+
constants — no per-instance state, no event stream — so there is nothing for a hook, composable,
|
|
72
|
+
or service to wrap, the same reason [`@symbiote-native/local-auth`](../local-auth) re-exports
|
|
73
|
+
rather than wraps. Import from `@symbiote-native/secure-store` directly if you don't care which
|
|
74
|
+
adapter you're on; the per-adapter subpaths exist so every wrapper package has the same import
|
|
75
|
+
surface.
|
|
76
|
+
|
|
77
|
+
## Use it
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import * as SecureStore from '@symbiote-native/secure-store';
|
|
81
|
+
|
|
82
|
+
await SecureStore.setItemAsync('session-token', token);
|
|
83
|
+
const stored = await SecureStore.getItemAsync('session-token'); // string | null
|
|
84
|
+
await SecureStore.deleteItemAsync('session-token');
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Behind the device's own biometrics or passcode:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
if (SecureStore.canUseBiometricAuthentication()) {
|
|
91
|
+
await SecureStore.setItemAsync('session-token', token, {
|
|
92
|
+
requireAuthentication: true,
|
|
93
|
+
authenticationPrompt: 'Unlock your saved session',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The prompt fires at different moments per platform: Android authenticates on every operation, iOS
|
|
99
|
+
only when reading or updating an entry that already exists. A simulator or emulator does not
|
|
100
|
+
enforce it at all — this option can only be verified on a real device.
|
|
101
|
+
|
|
102
|
+
Values are strings. JSON-encode anything else:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
await SecureStore.setItemAsync('profile', JSON.stringify(profile));
|
|
106
|
+
const profile = JSON.parse((await SecureStore.getItemAsync('profile')) ?? 'null');
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## API
|
|
110
|
+
|
|
111
|
+
| Export | Signature | Notes |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `isAvailableAsync` | `() => Promise<boolean>` | `true` on Android and iOS. Says nothing about permissions. |
|
|
114
|
+
| `getItemAsync` | `(key, options?) => Promise<string \| null>` | `null` when there is no entry, or when the key has been invalidated. |
|
|
115
|
+
| `getItem` | `(key, options?) => string \| null` | Blocks the JS thread. |
|
|
116
|
+
| `setItemAsync` | `(key, value, options?) => Promise<void>` | Rejects if the value cannot be stored. |
|
|
117
|
+
| `setItem` | `(key, value, options?) => void` | Blocks the JS thread. |
|
|
118
|
+
| `deleteItemAsync` | `(key, options?) => Promise<void>` | |
|
|
119
|
+
| `canUseBiometricAuthentication` | `() => boolean` | Whether `requireAuthentication` can be used at all. |
|
|
120
|
+
| `AFTER_FIRST_UNLOCK`, `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`, `ALWAYS`, `ALWAYS_THIS_DEVICE_ONLY`, `WHEN_PASSCODE_SET_THIS_DEVICE_ONLY`, `WHEN_UNLOCKED`, `WHEN_UNLOCKED_THIS_DEVICE_ONLY` | `IKeychainAccessibilityConstant \| undefined` | Values for `options.keychainAccessible`. iOS only — Android's native module declares none of them, so they read `undefined` there. `ALWAYS` and `ALWAYS_THIS_DEVICE_ONLY` are deprecated upstream. |
|
|
121
|
+
|
|
122
|
+
`ISecureStoreOptions`: `keychainService`, `requireAuthentication`, `authenticationPrompt`,
|
|
123
|
+
`keychainAccessible` (iOS), `accessGroup` (iOS).
|
|
124
|
+
|
|
125
|
+
## Notes
|
|
126
|
+
|
|
127
|
+
- **Keys are validated before the native call.** Alphanumerics plus `.`, `-` and `_`, non-empty —
|
|
128
|
+
anything else throws with a readable message rather than failing inside the keychain.
|
|
129
|
+
- **An invalidated key is gone for good.** The system invalidates entries stored with
|
|
130
|
+
`requireAuthentication` whenever enrolled biometrics change (a new fingerprint, a re-registered
|
|
131
|
+
face). `getItemAsync` then resolves `null` — treat it as "the user must sign in again", not as
|
|
132
|
+
an error to retry.
|
|
133
|
+
- **`requireAuthentication` does not combine with a shared `keychainService`.** The full behavior
|
|
134
|
+
needs a freshly generated key, so reuse of a service already holding non-authenticated entries
|
|
135
|
+
gives partial behavior. Upstream documents the same limitation.
|
|
136
|
+
|
|
137
|
+
## Test it
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pnpm vitest run packages/secure-store
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The core tests fake the native module in place of `requireNativeModule`'s runtime resolution —
|
|
144
|
+
`ExpoSecureStore` only exists on a device, so a headless run would otherwise throw at import.
|
|
145
|
+
Real storage, biometrics, and the backup rules can only be verified on a device or emulator.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { AFTER_FIRST_UNLOCK, AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY, ALWAYS, ALWAYS_THIS_DEVICE_ONLY, WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, WHEN_UNLOCKED, WHEN_UNLOCKED_THIS_DEVICE_ONLY, isAvailableAsync, getItemAsync, getItem, setItemAsync, setItem, deleteItemAsync, canUseBiometricAuthentication, } from './secure-store';
|
|
2
|
+
export type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AFTER_FIRST_UNLOCK, AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY, ALWAYS, ALWAYS_THIS_DEVICE_ONLY, WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, WHEN_UNLOCKED, WHEN_UNLOCKED_THIS_DEVICE_ONLY, isAvailableAsync, getItemAsync, getItem, setItemAsync, setItem, deleteItemAsync, canUseBiometricAuthentication, } from './secure-store';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
|
|
2
|
+
export type INativeSecureStoreModule = {
|
|
3
|
+
AFTER_FIRST_UNLOCK?: IKeychainAccessibilityConstant;
|
|
4
|
+
AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
|
|
5
|
+
ALWAYS?: IKeychainAccessibilityConstant;
|
|
6
|
+
ALWAYS_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
|
|
7
|
+
WHEN_PASSCODE_SET_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
|
|
8
|
+
WHEN_UNLOCKED?: IKeychainAccessibilityConstant;
|
|
9
|
+
WHEN_UNLOCKED_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
|
|
10
|
+
getValueWithKeyAsync?(key: string, options: ISecureStoreOptions): Promise<string | null>;
|
|
11
|
+
getValueWithKeySync?(key: string, options: ISecureStoreOptions): string | null;
|
|
12
|
+
setValueWithKeyAsync?(value: string, key: string, options: ISecureStoreOptions): Promise<boolean>;
|
|
13
|
+
setValueWithKeySync?(value: string, key: string, options: ISecureStoreOptions): boolean;
|
|
14
|
+
deleteValueWithKeyAsync?(key: string, options: ISecureStoreOptions): Promise<void>;
|
|
15
|
+
canUseBiometricAuthentication?(): boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare const expoSecureStore: INativeSecureStoreModule;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The item cannot be read after a restart until the device has been unlocked once. Useful for
|
|
4
|
+
* data the app needs while the phone is locked.
|
|
5
|
+
* @platform ios
|
|
6
|
+
*/
|
|
7
|
+
export declare const AFTER_FIRST_UNLOCK: IKeychainAccessibilityConstant | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Like `AFTER_FIRST_UNLOCK`, except the entry does not migrate to a new device when restoring
|
|
10
|
+
* from a backup.
|
|
11
|
+
* @platform ios
|
|
12
|
+
*/
|
|
13
|
+
export declare const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* The item can always be read, locked device or not. The least secure option.
|
|
16
|
+
* @deprecated Use a level that offers some user protection, such as `AFTER_FIRST_UNLOCK`.
|
|
17
|
+
* @platform ios
|
|
18
|
+
*/
|
|
19
|
+
export declare const ALWAYS: IKeychainAccessibilityConstant | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Like `ALWAYS`, except the entry does not migrate to a new device when restoring from a backup.
|
|
22
|
+
* @deprecated Use a level that offers some user protection, such as
|
|
23
|
+
* `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`.
|
|
24
|
+
* @platform ios
|
|
25
|
+
*/
|
|
26
|
+
export declare const ALWAYS_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Like `WHEN_UNLOCKED_THIS_DEVICE_ONLY`, except the user must have set a passcode to store an
|
|
29
|
+
* entry at all. Removing the passcode deletes the entry.
|
|
30
|
+
* @platform ios
|
|
31
|
+
*/
|
|
32
|
+
export declare const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* The item can only be read while the device is unlocked.
|
|
35
|
+
* @platform ios
|
|
36
|
+
*/
|
|
37
|
+
export declare const WHEN_UNLOCKED: IKeychainAccessibilityConstant | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Like `WHEN_UNLOCKED`, except the entry does not migrate to a new device when restoring from a
|
|
40
|
+
* backup.
|
|
41
|
+
* @platform ios
|
|
42
|
+
*/
|
|
43
|
+
export declare const WHEN_UNLOCKED_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the SecureStore API is usable on this device. Says nothing about app permissions.
|
|
46
|
+
* Resolves `true` on Android and iOS.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isAvailableAsync(): Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Read the value stored under `key`.
|
|
51
|
+
*
|
|
52
|
+
* Resolves `null` when there is no entry for the key, or when the key has been invalidated —
|
|
53
|
+
* the system invalidates keys stored with `requireAuthentication` whenever enrolled biometrics
|
|
54
|
+
* change (a new fingerprint, a re-registered face), and an invalidated value can never be read
|
|
55
|
+
* again. Rejects if reading itself fails.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getItemAsync(key: string, options?: ISecureStoreOptions): Promise<string | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Read the value stored under `key`, synchronously.
|
|
60
|
+
*
|
|
61
|
+
* > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
|
|
62
|
+
* > until the user authenticates.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getItem(key: string, options?: ISecureStoreOptions): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Store a key–value pair. Keys may contain alphanumeric characters, `.`, `-` and `_`.
|
|
67
|
+
* Rejects if the value cannot be stored on the device.
|
|
68
|
+
*/
|
|
69
|
+
export declare function setItemAsync(key: string, value: string, options?: ISecureStoreOptions): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Store a key–value pair, synchronously.
|
|
72
|
+
*
|
|
73
|
+
* > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
|
|
74
|
+
* > until the user authenticates.
|
|
75
|
+
*/
|
|
76
|
+
export declare function setItem(key: string, value: string, options?: ISecureStoreOptions): void;
|
|
77
|
+
/** Delete the value stored under `key`. Rejects if the value cannot be deleted. */
|
|
78
|
+
export declare function deleteItemAsync(key: string, options?: ISecureStoreOptions): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Whether a value can be stored with `requireAuthentication` — `true` when the device supports
|
|
81
|
+
* biometric authentication and the enrolled method is strong enough.
|
|
82
|
+
*/
|
|
83
|
+
export declare function canUseBiometricAuthentication(): boolean;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
2
|
+
import { expoSecureStore } from './native-module';
|
|
3
|
+
const NATIVE_MODULE_NAME = 'expo-secure-store';
|
|
4
|
+
// Alphanumerics plus ".", "-" and "_": the same set the native side validates against, so an
|
|
5
|
+
// invalid key fails here with a readable message instead of deep inside the keychain call.
|
|
6
|
+
const VALID_KEY_PATTERN = /^[\w.-]+$/;
|
|
7
|
+
/**
|
|
8
|
+
* The item cannot be read after a restart until the device has been unlocked once. Useful for
|
|
9
|
+
* data the app needs while the phone is locked.
|
|
10
|
+
* @platform ios
|
|
11
|
+
*/
|
|
12
|
+
export const AFTER_FIRST_UNLOCK = expoSecureStore.AFTER_FIRST_UNLOCK;
|
|
13
|
+
/**
|
|
14
|
+
* Like `AFTER_FIRST_UNLOCK`, except the entry does not migrate to a new device when restoring
|
|
15
|
+
* from a backup.
|
|
16
|
+
* @platform ios
|
|
17
|
+
*/
|
|
18
|
+
export const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY = expoSecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;
|
|
19
|
+
/**
|
|
20
|
+
* The item can always be read, locked device or not. The least secure option.
|
|
21
|
+
* @deprecated Use a level that offers some user protection, such as `AFTER_FIRST_UNLOCK`.
|
|
22
|
+
* @platform ios
|
|
23
|
+
*/
|
|
24
|
+
export const ALWAYS = expoSecureStore.ALWAYS;
|
|
25
|
+
/**
|
|
26
|
+
* Like `ALWAYS`, except the entry does not migrate to a new device when restoring from a backup.
|
|
27
|
+
* @deprecated Use a level that offers some user protection, such as
|
|
28
|
+
* `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`.
|
|
29
|
+
* @platform ios
|
|
30
|
+
*/
|
|
31
|
+
export const ALWAYS_THIS_DEVICE_ONLY = expoSecureStore.ALWAYS_THIS_DEVICE_ONLY;
|
|
32
|
+
/**
|
|
33
|
+
* Like `WHEN_UNLOCKED_THIS_DEVICE_ONLY`, except the user must have set a passcode to store an
|
|
34
|
+
* entry at all. Removing the passcode deletes the entry.
|
|
35
|
+
* @platform ios
|
|
36
|
+
*/
|
|
37
|
+
export const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY = expoSecureStore.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY;
|
|
38
|
+
/**
|
|
39
|
+
* The item can only be read while the device is unlocked.
|
|
40
|
+
* @platform ios
|
|
41
|
+
*/
|
|
42
|
+
export const WHEN_UNLOCKED = expoSecureStore.WHEN_UNLOCKED;
|
|
43
|
+
/**
|
|
44
|
+
* Like `WHEN_UNLOCKED`, except the entry does not migrate to a new device when restoring from a
|
|
45
|
+
* backup.
|
|
46
|
+
* @platform ios
|
|
47
|
+
*/
|
|
48
|
+
export const WHEN_UNLOCKED_THIS_DEVICE_ONLY = expoSecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY;
|
|
49
|
+
/**
|
|
50
|
+
* Whether the SecureStore API is usable on this device. Says nothing about app permissions.
|
|
51
|
+
* Resolves `true` on Android and iOS.
|
|
52
|
+
*/
|
|
53
|
+
export async function isAvailableAsync() {
|
|
54
|
+
return !!expoSecureStore.getValueWithKeyAsync;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read the value stored under `key`.
|
|
58
|
+
*
|
|
59
|
+
* Resolves `null` when there is no entry for the key, or when the key has been invalidated —
|
|
60
|
+
* the system invalidates keys stored with `requireAuthentication` whenever enrolled biometrics
|
|
61
|
+
* change (a new fingerprint, a re-registered face), and an invalidated value can never be read
|
|
62
|
+
* again. Rejects if reading itself fails.
|
|
63
|
+
*/
|
|
64
|
+
export async function getItemAsync(key, options = {}) {
|
|
65
|
+
ensureValidKey(key);
|
|
66
|
+
if (!expoSecureStore.getValueWithKeyAsync) {
|
|
67
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getItemAsync');
|
|
68
|
+
}
|
|
69
|
+
return expoSecureStore.getValueWithKeyAsync(key, options);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read the value stored under `key`, synchronously.
|
|
73
|
+
*
|
|
74
|
+
* > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
|
|
75
|
+
* > until the user authenticates.
|
|
76
|
+
*/
|
|
77
|
+
export function getItem(key, options = {}) {
|
|
78
|
+
ensureValidKey(key);
|
|
79
|
+
if (!expoSecureStore.getValueWithKeySync) {
|
|
80
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getItem');
|
|
81
|
+
}
|
|
82
|
+
return expoSecureStore.getValueWithKeySync(key, options);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Store a key–value pair. Keys may contain alphanumeric characters, `.`, `-` and `_`.
|
|
86
|
+
* Rejects if the value cannot be stored on the device.
|
|
87
|
+
*/
|
|
88
|
+
export async function setItemAsync(key, value, options = {}) {
|
|
89
|
+
ensureValidKey(key);
|
|
90
|
+
ensureValidValue(value);
|
|
91
|
+
if (!expoSecureStore.setValueWithKeyAsync) {
|
|
92
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setItemAsync');
|
|
93
|
+
}
|
|
94
|
+
await expoSecureStore.setValueWithKeyAsync(value, key, options);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Store a key–value pair, synchronously.
|
|
98
|
+
*
|
|
99
|
+
* > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
|
|
100
|
+
* > until the user authenticates.
|
|
101
|
+
*/
|
|
102
|
+
export function setItem(key, value, options = {}) {
|
|
103
|
+
ensureValidKey(key);
|
|
104
|
+
ensureValidValue(value);
|
|
105
|
+
if (!expoSecureStore.setValueWithKeySync) {
|
|
106
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setItem');
|
|
107
|
+
}
|
|
108
|
+
expoSecureStore.setValueWithKeySync(value, key, options);
|
|
109
|
+
}
|
|
110
|
+
/** Delete the value stored under `key`. Rejects if the value cannot be deleted. */
|
|
111
|
+
export async function deleteItemAsync(key, options = {}) {
|
|
112
|
+
ensureValidKey(key);
|
|
113
|
+
if (!expoSecureStore.deleteValueWithKeyAsync) {
|
|
114
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'deleteItemAsync');
|
|
115
|
+
}
|
|
116
|
+
await expoSecureStore.deleteValueWithKeyAsync(key, options);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Whether a value can be stored with `requireAuthentication` — `true` when the device supports
|
|
120
|
+
* biometric authentication and the enrolled method is strong enough.
|
|
121
|
+
*/
|
|
122
|
+
export function canUseBiometricAuthentication() {
|
|
123
|
+
if (!expoSecureStore.canUseBiometricAuthentication) {
|
|
124
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'canUseBiometricAuthentication');
|
|
125
|
+
}
|
|
126
|
+
return expoSecureStore.canUseBiometricAuthentication();
|
|
127
|
+
}
|
|
128
|
+
function ensureValidKey(key) {
|
|
129
|
+
if (typeof key !== 'string' || !VALID_KEY_PATTERN.test(key)) {
|
|
130
|
+
throw new Error('Invalid key provided to SecureStore. Keys must not be empty and contain only alphanumeric characters, ".", "-", and "_".');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function ensureValidValue(value) {
|
|
134
|
+
if (typeof value !== 'string') {
|
|
135
|
+
throw new Error('Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
export type ISecureStoreOptions = {
|
|
8
|
+
/**
|
|
9
|
+
* - Android: equivalent of the public/private key pair `Alias`.
|
|
10
|
+
* - iOS: the item's service, equivalent to [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice/).
|
|
11
|
+
*
|
|
12
|
+
* An item stored with a `keychainService` needs the same one to be read back.
|
|
13
|
+
*/
|
|
14
|
+
keychainService?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Require the device's own authentication (biometrics or passcode) to reach the stored value.
|
|
17
|
+
* - Android: [`setUserAuthenticationRequired(true)`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean)).
|
|
18
|
+
* - iOS: [`biometryCurrentSet`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/2937192-biometrycurrentset).
|
|
19
|
+
*
|
|
20
|
+
* The two platforms prompt at different moments: Android authenticates on every operation,
|
|
21
|
+
* iOS only when reading or updating an existing value, never when creating one. The full
|
|
22
|
+
* behavior needs a freshly generated key, so it does not combine with a `keychainService`
|
|
23
|
+
* already used for non-authenticated entries.
|
|
24
|
+
*
|
|
25
|
+
* > Emulators and simulators do not enforce the prompt when retrieving a secret — testing this
|
|
26
|
+
* > option means testing on a real device.
|
|
27
|
+
*/
|
|
28
|
+
requireAuthentication?: boolean;
|
|
29
|
+
/** Message shown to the user in the prompt raised by `requireAuthentication`. */
|
|
30
|
+
authenticationPrompt?: string;
|
|
31
|
+
/**
|
|
32
|
+
* When the stored entry is accessible, via iOS's `kSecAttrAccessible` property.
|
|
33
|
+
* @default WHEN_UNLOCKED
|
|
34
|
+
* @platform ios
|
|
35
|
+
*/
|
|
36
|
+
keychainAccessible?: IKeychainAccessibilityConstant;
|
|
37
|
+
/**
|
|
38
|
+
* The [access group](https://developer.apple.com/documentation/security/sharing-access-to-keychain-items-among-a-collection-of-apps)
|
|
39
|
+
* the stored entry belongs to.
|
|
40
|
+
* @platform ios
|
|
41
|
+
*/
|
|
42
|
+
accessGroup?: string;
|
|
43
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -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 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// @symbiote-native/secure-store/angular: the Angular entry over the framework-agnostic core.
|
|
2
|
+
// Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in a
|
|
3
|
+
// service, so this is a plain re-export.
|
|
4
|
+
export * from '../core';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,+FAA+F;AAC/F,yCAAyC;AACzC,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { AFTER_FIRST_UNLOCK, AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY, ALWAYS, ALWAYS_THIS_DEVICE_ONLY, WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, WHEN_UNLOCKED, WHEN_UNLOCKED_THIS_DEVICE_ONLY, isAvailableAsync, getItemAsync, getItem, setItemAsync, setItem, deleteItemAsync, canUseBiometricAuthentication, } from './secure-store';
|
|
2
|
+
export type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { AFTER_FIRST_UNLOCK, AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY, ALWAYS, ALWAYS_THIS_DEVICE_ONLY, WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, WHEN_UNLOCKED, WHEN_UNLOCKED_THIS_DEVICE_ONLY, isAvailableAsync, getItemAsync, getItem, setItemAsync, setItem, deleteItemAsync, canUseBiometricAuthentication, } from './secure-store';
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,mCAAmC,EACnC,MAAM,EACN,uBAAuB,EACvB,kCAAkC,EAClC,aAAa,EACb,8BAA8B,EAC9B,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,YAAY,EACZ,OAAO,EACP,eAAe,EACf,6BAA6B,GAC9B,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
|
|
2
|
+
export type INativeSecureStoreModule = {
|
|
3
|
+
AFTER_FIRST_UNLOCK?: IKeychainAccessibilityConstant;
|
|
4
|
+
AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
|
|
5
|
+
ALWAYS?: IKeychainAccessibilityConstant;
|
|
6
|
+
ALWAYS_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
|
|
7
|
+
WHEN_PASSCODE_SET_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
|
|
8
|
+
WHEN_UNLOCKED?: IKeychainAccessibilityConstant;
|
|
9
|
+
WHEN_UNLOCKED_THIS_DEVICE_ONLY?: IKeychainAccessibilityConstant;
|
|
10
|
+
getValueWithKeyAsync?(key: string, options: ISecureStoreOptions): Promise<string | null>;
|
|
11
|
+
getValueWithKeySync?(key: string, options: ISecureStoreOptions): string | null;
|
|
12
|
+
setValueWithKeyAsync?(value: string, key: string, options: ISecureStoreOptions): Promise<boolean>;
|
|
13
|
+
setValueWithKeySync?(value: string, key: string, options: ISecureStoreOptions): boolean;
|
|
14
|
+
deleteValueWithKeyAsync?(key: string, options: ISecureStoreOptions): Promise<void>;
|
|
15
|
+
canUseBiometricAuthentication?(): boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare const expoSecureStore: INativeSecureStoreModule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,MAAM,6BAA6B,GAAG,iBAAiB,CAAC;AAyBxD,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAChD,6BAA6B,CAC9B,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { IKeychainAccessibilityConstant, ISecureStoreOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The item cannot be read after a restart until the device has been unlocked once. Useful for
|
|
4
|
+
* data the app needs while the phone is locked.
|
|
5
|
+
* @platform ios
|
|
6
|
+
*/
|
|
7
|
+
export declare const AFTER_FIRST_UNLOCK: IKeychainAccessibilityConstant | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Like `AFTER_FIRST_UNLOCK`, except the entry does not migrate to a new device when restoring
|
|
10
|
+
* from a backup.
|
|
11
|
+
* @platform ios
|
|
12
|
+
*/
|
|
13
|
+
export declare const AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* The item can always be read, locked device or not. The least secure option.
|
|
16
|
+
* @deprecated Use a level that offers some user protection, such as `AFTER_FIRST_UNLOCK`.
|
|
17
|
+
* @platform ios
|
|
18
|
+
*/
|
|
19
|
+
export declare const ALWAYS: IKeychainAccessibilityConstant | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Like `ALWAYS`, except the entry does not migrate to a new device when restoring from a backup.
|
|
22
|
+
* @deprecated Use a level that offers some user protection, such as
|
|
23
|
+
* `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY`.
|
|
24
|
+
* @platform ios
|
|
25
|
+
*/
|
|
26
|
+
export declare const ALWAYS_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Like `WHEN_UNLOCKED_THIS_DEVICE_ONLY`, except the user must have set a passcode to store an
|
|
29
|
+
* entry at all. Removing the passcode deletes the entry.
|
|
30
|
+
* @platform ios
|
|
31
|
+
*/
|
|
32
|
+
export declare const WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* The item can only be read while the device is unlocked.
|
|
35
|
+
* @platform ios
|
|
36
|
+
*/
|
|
37
|
+
export declare const WHEN_UNLOCKED: IKeychainAccessibilityConstant | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Like `WHEN_UNLOCKED`, except the entry does not migrate to a new device when restoring from a
|
|
40
|
+
* backup.
|
|
41
|
+
* @platform ios
|
|
42
|
+
*/
|
|
43
|
+
export declare const WHEN_UNLOCKED_THIS_DEVICE_ONLY: IKeychainAccessibilityConstant | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the SecureStore API is usable on this device. Says nothing about app permissions.
|
|
46
|
+
* Resolves `true` on Android and iOS.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isAvailableAsync(): Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Read the value stored under `key`.
|
|
51
|
+
*
|
|
52
|
+
* Resolves `null` when there is no entry for the key, or when the key has been invalidated —
|
|
53
|
+
* the system invalidates keys stored with `requireAuthentication` whenever enrolled biometrics
|
|
54
|
+
* change (a new fingerprint, a re-registered face), and an invalidated value can never be read
|
|
55
|
+
* again. Rejects if reading itself fails.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getItemAsync(key: string, options?: ISecureStoreOptions): Promise<string | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Read the value stored under `key`, synchronously.
|
|
60
|
+
*
|
|
61
|
+
* > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
|
|
62
|
+
* > until the user authenticates.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getItem(key: string, options?: ISecureStoreOptions): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Store a key–value pair. Keys may contain alphanumeric characters, `.`, `-` and `_`.
|
|
67
|
+
* Rejects if the value cannot be stored on the device.
|
|
68
|
+
*/
|
|
69
|
+
export declare function setItemAsync(key: string, value: string, options?: ISecureStoreOptions): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Store a key–value pair, synchronously.
|
|
72
|
+
*
|
|
73
|
+
* > Blocks the JavaScript thread. With `requireAuthentication` on, the app stays unresponsive
|
|
74
|
+
* > until the user authenticates.
|
|
75
|
+
*/
|
|
76
|
+
export declare function setItem(key: string, value: string, options?: ISecureStoreOptions): void;
|
|
77
|
+
/** Delete the value stored under `key`. Rejects if the value cannot be deleted. */
|
|
78
|
+
export declare function deleteItemAsync(key: string, options?: ISecureStoreOptions): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Whether a value can be stored with `requireAuthentication` — `true` when the device supports
|
|
81
|
+
* biometric authentication and the enrolled method is strong enough.
|
|
82
|
+
*/
|
|
83
|
+
export declare function canUseBiometricAuthentication(): boolean;
|