@oxy.so/services 13.3.0 → 13.5.0
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/README.md +12 -11
- package/lib/commonjs/ui/session/nativeSecureStorage.js +26 -18
- package/lib/commonjs/ui/session/nativeSecureStorage.js.map +1 -1
- package/lib/commonjs/ui/session/sharedDeviceCredentialStore.js +25 -16
- package/lib/commonjs/ui/session/sharedDeviceCredentialStore.js.map +1 -1
- package/lib/module/ui/session/nativeSecureStorage.js +26 -19
- package/lib/module/ui/session/nativeSecureStorage.js.map +1 -1
- package/lib/module/ui/session/sharedDeviceCredentialStore.js +25 -16
- package/lib/module/ui/session/sharedDeviceCredentialStore.js.map +1 -1
- package/lib/typescript/commonjs/ui/session/nativeSecureStorage.d.ts +9 -4
- package/lib/typescript/commonjs/ui/session/nativeSecureStorage.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/sharedDeviceCredentialStore.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/nativeSecureStorage.d.ts +9 -4
- package/lib/typescript/module/ui/session/nativeSecureStorage.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/sharedDeviceCredentialStore.d.ts.map +1 -1
- package/package.json +4 -4
- package/plugins/withOxyReleaseSigning.js +277 -0
- package/plugins/withOxySharedPermissions.js +8 -1
- package/src/ui/session/nativeSecureStorage.ts +31 -25
- package/src/ui/session/sharedDeviceCredentialStore.ts +30 -23
package/README.md
CHANGED
|
@@ -121,21 +121,22 @@ bun add @oxy.so/services @oxy.so/core
|
|
|
121
121
|
|
|
122
122
|
### Peer Dependencies
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
The native modules `@oxy.so/services` needs on iOS and Android (Expo SDK 54 to 58;
|
|
125
|
+
`package.json` has the exact ranges):
|
|
125
126
|
|
|
126
|
-
- react: >=18, react-native: >=0.76
|
|
127
|
-
- react-native-reanimated: >=3.16, react-native-gesture-handler: >=2.16
|
|
128
|
-
- react-native-safe-area-context: ^5.4.0, react-native-svg: >=13
|
|
129
|
-
- Expo projects: expo, expo-font, expo-image, expo-linear-gradient
|
|
130
|
-
- Navigation (if you use the provided screens): @react-navigation/native
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
Example for Expo:
|
|
134
127
|
```bash
|
|
135
|
-
|
|
136
|
-
|
|
128
|
+
npx expo install expo-crypto expo-secure-store expo-file-system expo-image @expo/app-integrity \
|
|
129
|
+
@react-native-async-storage/async-storage @react-native-community/netinfo \
|
|
130
|
+
react-native-reanimated react-native-gesture-handler react-native-keyboard-controller \
|
|
131
|
+
react-native-safe-area-context react-native-svg
|
|
137
132
|
```
|
|
138
133
|
|
|
134
|
+
Every native module above is a **required** peer: the app must list each one
|
|
135
|
+
itself. Expo's autolinking skips optional peers and installs nothing an app does
|
|
136
|
+
not name at its SDK's version, so an app without `expo-crypto` builds and then
|
|
137
|
+
fails at launch (`Cannot find native module 'ExpoCrypto'`: no CSPRNG, no
|
|
138
|
+
sign-in). `bunx @oxy.so/doctor --ci` fails an app that is missing one.
|
|
139
|
+
|
|
139
140
|
### React Native/Expo Setup
|
|
140
141
|
|
|
141
142
|
For React Native and Expo projects, add the polyfill import at the very top of your entry file:
|
|
@@ -16,33 +16,41 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
16
16
|
* different backings — a device credential in SecureStore and a pin in
|
|
17
17
|
* AsyncStorage would resolve differently after a keystore reset.
|
|
18
18
|
*
|
|
19
|
-
* `expo-secure-store` is
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* `expo-secure-store` is a REQUIRED peer of this package (13.5.0): without its
|
|
20
|
+
* native module the device credential would sit in plaintext AsyncStorage. It
|
|
21
|
+
* is still loaded lazily, as a direct statement of a `try` block (the shape
|
|
22
|
+
* Metro marks optional, `optionalPeerLoads.test.ts`), so a build whose native
|
|
23
|
+
* module is missing (react-native-macos, an app that has not rebuilt) degrades
|
|
24
|
+
* to AsyncStorage instead of crashing. The specifier is a LITERAL: the old
|
|
25
|
+
* `const m = 'expo-secure-store'; import(m)` never hid it from Metro (Babel
|
|
26
|
+
* evaluates the constant, so Metro resolved it anyway) and only hid it from the
|
|
27
|
+
* gates that read this source.
|
|
23
28
|
*/
|
|
24
|
-
// Variable indirection so Metro's static analyzer never traces expo-secure-store
|
|
25
|
-
// into the web bundle; the module is native-only and optional.
|
|
26
|
-
const SECURE_STORE_MODULE = 'expo-secure-store';
|
|
27
29
|
let secureStorePromise = null;
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Resolve the `expo-secure-store` module once (memoised), or `null` when it is
|
|
31
33
|
* unavailable — callers then fall back to AsyncStorage. Never throws.
|
|
32
34
|
*/
|
|
35
|
+
async function importSecureStore() {
|
|
36
|
+
let mod;
|
|
37
|
+
try {
|
|
38
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require('expo-secure-store')));
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (typeof mod.getItemAsync === 'function' && typeof mod.setItemAsync === 'function' && typeof mod.deleteItemAsync === 'function') {
|
|
43
|
+
return {
|
|
44
|
+
getItemAsync: mod.getItemAsync.bind(mod),
|
|
45
|
+
setItemAsync: mod.setItemAsync.bind(mod),
|
|
46
|
+
deleteItemAsync: mod.deleteItemAsync.bind(mod)
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
33
51
|
function loadSecureStore() {
|
|
34
52
|
if (!secureStorePromise) {
|
|
35
|
-
|
|
36
|
-
secureStorePromise = (specifier => new Promise(r => r(`${specifier}`)).then(s => _interopRequireWildcard(require(s))))(moduleName).then(mod => {
|
|
37
|
-
if (typeof mod.getItemAsync === 'function' && typeof mod.setItemAsync === 'function' && typeof mod.deleteItemAsync === 'function') {
|
|
38
|
-
return {
|
|
39
|
-
getItemAsync: mod.getItemAsync.bind(mod),
|
|
40
|
-
setItemAsync: mod.setItemAsync.bind(mod),
|
|
41
|
-
deleteItemAsync: mod.deleteItemAsync.bind(mod)
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
}).catch(() => null);
|
|
53
|
+
secureStorePromise = importSecureStore();
|
|
46
54
|
}
|
|
47
55
|
return secureStorePromise;
|
|
48
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_storageHelpers","require","_interopRequireWildcard","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","
|
|
1
|
+
{"version":3,"names":["_storageHelpers","require","_interopRequireWildcard","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","secureStorePromise","importSecureStore","mod","Promise","resolve","then","getItemAsync","setItemAsync","deleteItemAsync","bind","loadSecureStore","createNativeSecureKeyValueStorage","asyncStorage","getAsyncStorage","createPlatformStorage","getItem","key","secure","setItem","value","removeItem"],"sourceRoot":"../../../../src","sources":["ui/session/nativeSecureStorage.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -65,10 +65,6 @@ const IOS_DEVICE_SESSION_KEYCHAIN_SERVICE = exports.IOS_DEVICE_SESSION_KEYCHAIN_
|
|
|
65
65
|
/** The single key holding the JSON-encoded `{deviceId, deviceSecret}` pair. */
|
|
66
66
|
const SHARED_DEVICE_SESSION_STORAGE_KEY = exports.SHARED_DEVICE_SESSION_STORAGE_KEY = 'oxy_shared_device_session_v1';
|
|
67
67
|
|
|
68
|
-
// Variable indirection so Metro's static analyzer never traces expo-secure-store
|
|
69
|
-
// into the web bundle; the module is native-only and optional.
|
|
70
|
-
const SECURE_STORE_MODULE = 'expo-secure-store';
|
|
71
|
-
|
|
72
68
|
/**
|
|
73
69
|
* The subset of `expo-secure-store` this module needs. Unlike the adapter in
|
|
74
70
|
* `nativeSecureStorage.ts` it must pass OPTIONS — the access group and the
|
|
@@ -77,20 +73,33 @@ const SECURE_STORE_MODULE = 'expo-secure-store';
|
|
|
77
73
|
*/
|
|
78
74
|
|
|
79
75
|
let keychainStorePromise = null;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* `expo-secure-store`, a required peer of this package, loaded lazily as a
|
|
79
|
+
* direct statement of a `try` block with a LITERAL specifier (see
|
|
80
|
+
* `nativeSecureStorage.ts` for why the old runtime-computed one hid nothing
|
|
81
|
+
* from Metro). A build without its native module reads `unsupported`.
|
|
82
|
+
*/
|
|
83
|
+
async function importKeychainGroupStore() {
|
|
84
|
+
let mod;
|
|
85
|
+
try {
|
|
86
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require('expo-secure-store')));
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (typeof mod.getItemAsync === 'function' && typeof mod.setItemAsync === 'function' && typeof mod.deleteItemAsync === 'function' && typeof mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY === 'number') {
|
|
91
|
+
return {
|
|
92
|
+
getItemAsync: mod.getItemAsync.bind(mod),
|
|
93
|
+
setItemAsync: mod.setItemAsync.bind(mod),
|
|
94
|
+
deleteItemAsync: mod.deleteItemAsync.bind(mod),
|
|
95
|
+
WHEN_UNLOCKED_THIS_DEVICE_ONLY: mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
80
100
|
function loadKeychainGroupStore() {
|
|
81
101
|
if (!keychainStorePromise) {
|
|
82
|
-
|
|
83
|
-
keychainStorePromise = (specifier => new Promise(r => r(`${specifier}`)).then(s => _interopRequireWildcard(require(s))))(moduleName).then(mod => {
|
|
84
|
-
if (typeof mod.getItemAsync === 'function' && typeof mod.setItemAsync === 'function' && typeof mod.deleteItemAsync === 'function' && typeof mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY === 'number') {
|
|
85
|
-
return {
|
|
86
|
-
getItemAsync: mod.getItemAsync.bind(mod),
|
|
87
|
-
setItemAsync: mod.setItemAsync.bind(mod),
|
|
88
|
-
deleteItemAsync: mod.deleteItemAsync.bind(mod),
|
|
89
|
-
WHEN_UNLOCKED_THIS_DEVICE_ONLY: mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
return null;
|
|
93
|
-
}).catch(() => null);
|
|
102
|
+
keychainStorePromise = importKeychainGroupStore();
|
|
94
103
|
}
|
|
95
104
|
return keychainStorePromise;
|
|
96
105
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","_expoModulesCore","_session","_interopRequireWildcard","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","IOS_KEYCHAIN_ACCESS_GROUP","exports","IOS_DEVICE_SESSION_KEYCHAIN_SERVICE","SHARED_DEVICE_SESSION_STORAGE_KEY","
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_expoModulesCore","_session","_interopRequireWildcard","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","IOS_KEYCHAIN_ACCESS_GROUP","exports","IOS_DEVICE_SESSION_KEYCHAIN_SERVICE","SHARED_DEVICE_SESSION_STORAGE_KEY","keychainStorePromise","importKeychainGroupStore","mod","Promise","resolve","then","getItemAsync","setItemAsync","deleteItemAsync","WHEN_UNLOCKED_THIS_DEVICE_ONLY","bind","loadKeychainGroupStore","parseStoredCredential","raw","parsed","JSON","parse","candidate","deviceId","length","deviceSecret","createKeychainGroupCredentialStore","readOptions","keychainService","keychainAccessGroup","read","store","state","error","cause","credential","Error","publish","value","stringify","keychainAccessible","clear","brokerModule","loadBrokerModule","undefined","native","requireOptionalNativeModule","write","createBrokerCredentialStore","broker","normalizeSharedDeviceSessionRead","createPlatformSharedDeviceCredentialStore","Platform","OS"],"sourceRoot":"../../../../src","sources":["ui/session/sharedDeviceCredentialStore.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -11,36 +11,43 @@
|
|
|
11
11
|
* different backings — a device credential in SecureStore and a pin in
|
|
12
12
|
* AsyncStorage would resolve differently after a keystore reset.
|
|
13
13
|
*
|
|
14
|
-
* `expo-secure-store` is
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* `expo-secure-store` is a REQUIRED peer of this package (13.5.0): without its
|
|
15
|
+
* native module the device credential would sit in plaintext AsyncStorage. It
|
|
16
|
+
* is still loaded lazily, as a direct statement of a `try` block (the shape
|
|
17
|
+
* Metro marks optional, `optionalPeerLoads.test.ts`), so a build whose native
|
|
18
|
+
* module is missing (react-native-macos, an app that has not rebuilt) degrades
|
|
19
|
+
* to AsyncStorage instead of crashing. The specifier is a LITERAL: the old
|
|
20
|
+
* `const m = 'expo-secure-store'; import(m)` never hid it from Metro (Babel
|
|
21
|
+
* evaluates the constant, so Metro resolved it anyway) and only hid it from the
|
|
22
|
+
* gates that read this source.
|
|
18
23
|
*/
|
|
19
24
|
|
|
20
25
|
import { createPlatformStorage } from "../utils/storageHelpers.js";
|
|
21
|
-
|
|
22
|
-
// Variable indirection so Metro's static analyzer never traces expo-secure-store
|
|
23
|
-
// into the web bundle; the module is native-only and optional.
|
|
24
|
-
const SECURE_STORE_MODULE = 'expo-secure-store';
|
|
25
26
|
let secureStorePromise = null;
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Resolve the `expo-secure-store` module once (memoised), or `null` when it is
|
|
29
30
|
* unavailable — callers then fall back to AsyncStorage. Never throws.
|
|
30
31
|
*/
|
|
32
|
+
async function importSecureStore() {
|
|
33
|
+
let mod;
|
|
34
|
+
try {
|
|
35
|
+
mod = await import('expo-secure-store');
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (typeof mod.getItemAsync === 'function' && typeof mod.setItemAsync === 'function' && typeof mod.deleteItemAsync === 'function') {
|
|
40
|
+
return {
|
|
41
|
+
getItemAsync: mod.getItemAsync.bind(mod),
|
|
42
|
+
setItemAsync: mod.setItemAsync.bind(mod),
|
|
43
|
+
deleteItemAsync: mod.deleteItemAsync.bind(mod)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
31
48
|
function loadSecureStore() {
|
|
32
49
|
if (!secureStorePromise) {
|
|
33
|
-
|
|
34
|
-
secureStorePromise = import(moduleName).then(mod => {
|
|
35
|
-
if (typeof mod.getItemAsync === 'function' && typeof mod.setItemAsync === 'function' && typeof mod.deleteItemAsync === 'function') {
|
|
36
|
-
return {
|
|
37
|
-
getItemAsync: mod.getItemAsync.bind(mod),
|
|
38
|
-
setItemAsync: mod.setItemAsync.bind(mod),
|
|
39
|
-
deleteItemAsync: mod.deleteItemAsync.bind(mod)
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
43
|
-
}).catch(() => null);
|
|
50
|
+
secureStorePromise = importSecureStore();
|
|
44
51
|
}
|
|
45
52
|
return secureStorePromise;
|
|
46
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["createPlatformStorage","
|
|
1
|
+
{"version":3,"names":["createPlatformStorage","secureStorePromise","importSecureStore","mod","getItemAsync","setItemAsync","deleteItemAsync","bind","loadSecureStore","createNativeSecureKeyValueStorage","asyncStorage","getAsyncStorage","getItem","key","secure","setItem","value","removeItem"],"sourceRoot":"../../../../src","sources":["ui/session/nativeSecureStorage.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,qBAAqB,QAAQ,4BAAyB;AAQ/D,IAAIC,kBAA0D,GAAG,IAAI;;AAErE;AACA;AACA;AACA;AACA,eAAeC,iBAAiBA,CAAA,EAAoC;EAClE,IAAIC,GAA6B;EACjC,IAAI;IACFA,GAAG,GAAI,MAAM,MAAM,CAAC,mBAAmB,CAAyC;EAClF,CAAC,CAAC,MAAM;IACN,OAAO,IAAI;EACb;EACA,IACE,OAAOA,GAAG,CAACC,YAAY,KAAK,UAAU,IACtC,OAAOD,GAAG,CAACE,YAAY,KAAK,UAAU,IACtC,OAAOF,GAAG,CAACG,eAAe,KAAK,UAAU,EACzC;IACA,OAAO;MACLF,YAAY,EAAED,GAAG,CAACC,YAAY,CAACG,IAAI,CAACJ,GAAG,CAAC;MACxCE,YAAY,EAAEF,GAAG,CAACE,YAAY,CAACE,IAAI,CAACJ,GAAG,CAAC;MACxCG,eAAe,EAAEH,GAAG,CAACG,eAAe,CAACC,IAAI,CAACJ,GAAG;IAC/C,CAAC;EACH;EACA,OAAO,IAAI;AACb;AAEA,SAASK,eAAeA,CAAA,EAAoC;EAC1D,IAAI,CAACP,kBAAkB,EAAE;IACvBA,kBAAkB,GAAGC,iBAAiB,CAAC,CAAC;EAC1C;EACA,OAAOD,kBAAkB;AAC3B;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASQ,iCAAiCA,CAAA,EAA0B;EACzE,IAAIC,YAAsE,GAAG,IAAI;EACjF,MAAMC,eAAe,GAAG,MAAAA,CAAA,KAAY;IAClC,IAAI,CAACD,YAAY,EAAE;MACjBA,YAAY,GAAG,MAAMV,qBAAqB,CAAC,CAAC;IAC9C;IACA,OAAOU,YAAY;EACrB,CAAC;EACD,OAAO;IACLE,OAAO,EAAE,MAAOC,GAAG,IAAK;MACtB,MAAMC,MAAM,GAAG,MAAMN,eAAe,CAAC,CAAC;MACtC,IAAIM,MAAM,EAAE;QACV,OAAOA,MAAM,CAACV,YAAY,CAACS,GAAG,CAAC;MACjC;MACA,OAAO,CAAC,MAAMF,eAAe,CAAC,CAAC,EAAEC,OAAO,CAACC,GAAG,CAAC;IAC/C,CAAC;IACDE,OAAO,EAAE,MAAAA,CAAOF,GAAG,EAAEG,KAAK,KAAK;MAC7B,MAAMF,MAAM,GAAG,MAAMN,eAAe,CAAC,CAAC;MACtC,IAAIM,MAAM,EAAE;QACV,MAAMA,MAAM,CAACT,YAAY,CAACQ,GAAG,EAAEG,KAAK,CAAC;QACrC;MACF;MACA,MAAM,CAAC,MAAML,eAAe,CAAC,CAAC,EAAEI,OAAO,CAACF,GAAG,EAAEG,KAAK,CAAC;IACrD,CAAC;IACDC,UAAU,EAAE,MAAOJ,GAAG,IAAK;MACzB,MAAMC,MAAM,GAAG,MAAMN,eAAe,CAAC,CAAC;MACtC,IAAIM,MAAM,EAAE;QACV,MAAMA,MAAM,CAACR,eAAe,CAACO,GAAG,CAAC;QACjC;MACF;MACA,MAAM,CAAC,MAAMF,eAAe,CAAC,CAAC,EAAEM,UAAU,CAACJ,GAAG,CAAC;IACjD;EACF,CAAC;AACH","ignoreList":[]}
|
|
@@ -61,10 +61,6 @@ export const IOS_DEVICE_SESSION_KEYCHAIN_SERVICE = 'oxy_device_session';
|
|
|
61
61
|
/** The single key holding the JSON-encoded `{deviceId, deviceSecret}` pair. */
|
|
62
62
|
export const SHARED_DEVICE_SESSION_STORAGE_KEY = 'oxy_shared_device_session_v1';
|
|
63
63
|
|
|
64
|
-
// Variable indirection so Metro's static analyzer never traces expo-secure-store
|
|
65
|
-
// into the web bundle; the module is native-only and optional.
|
|
66
|
-
const SECURE_STORE_MODULE = 'expo-secure-store';
|
|
67
|
-
|
|
68
64
|
/**
|
|
69
65
|
* The subset of `expo-secure-store` this module needs. Unlike the adapter in
|
|
70
66
|
* `nativeSecureStorage.ts` it must pass OPTIONS — the access group and the
|
|
@@ -73,20 +69,33 @@ const SECURE_STORE_MODULE = 'expo-secure-store';
|
|
|
73
69
|
*/
|
|
74
70
|
|
|
75
71
|
let keychainStorePromise = null;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* `expo-secure-store`, a required peer of this package, loaded lazily as a
|
|
75
|
+
* direct statement of a `try` block with a LITERAL specifier (see
|
|
76
|
+
* `nativeSecureStorage.ts` for why the old runtime-computed one hid nothing
|
|
77
|
+
* from Metro). A build without its native module reads `unsupported`.
|
|
78
|
+
*/
|
|
79
|
+
async function importKeychainGroupStore() {
|
|
80
|
+
let mod;
|
|
81
|
+
try {
|
|
82
|
+
mod = await import('expo-secure-store');
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (typeof mod.getItemAsync === 'function' && typeof mod.setItemAsync === 'function' && typeof mod.deleteItemAsync === 'function' && typeof mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY === 'number') {
|
|
87
|
+
return {
|
|
88
|
+
getItemAsync: mod.getItemAsync.bind(mod),
|
|
89
|
+
setItemAsync: mod.setItemAsync.bind(mod),
|
|
90
|
+
deleteItemAsync: mod.deleteItemAsync.bind(mod),
|
|
91
|
+
WHEN_UNLOCKED_THIS_DEVICE_ONLY: mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
76
96
|
function loadKeychainGroupStore() {
|
|
77
97
|
if (!keychainStorePromise) {
|
|
78
|
-
|
|
79
|
-
keychainStorePromise = import(moduleName).then(mod => {
|
|
80
|
-
if (typeof mod.getItemAsync === 'function' && typeof mod.setItemAsync === 'function' && typeof mod.deleteItemAsync === 'function' && typeof mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY === 'number') {
|
|
81
|
-
return {
|
|
82
|
-
getItemAsync: mod.getItemAsync.bind(mod),
|
|
83
|
-
setItemAsync: mod.setItemAsync.bind(mod),
|
|
84
|
-
deleteItemAsync: mod.deleteItemAsync.bind(mod),
|
|
85
|
-
WHEN_UNLOCKED_THIS_DEVICE_ONLY: mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
return null;
|
|
89
|
-
}).catch(() => null);
|
|
98
|
+
keychainStorePromise = importKeychainGroupStore();
|
|
90
99
|
}
|
|
91
100
|
return keychainStorePromise;
|
|
92
101
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Platform","requireOptionalNativeModule","normalizeSharedDeviceSessionRead","IOS_KEYCHAIN_ACCESS_GROUP","IOS_DEVICE_SESSION_KEYCHAIN_SERVICE","SHARED_DEVICE_SESSION_STORAGE_KEY","
|
|
1
|
+
{"version":3,"names":["Platform","requireOptionalNativeModule","normalizeSharedDeviceSessionRead","IOS_KEYCHAIN_ACCESS_GROUP","IOS_DEVICE_SESSION_KEYCHAIN_SERVICE","SHARED_DEVICE_SESSION_STORAGE_KEY","keychainStorePromise","importKeychainGroupStore","mod","getItemAsync","setItemAsync","deleteItemAsync","WHEN_UNLOCKED_THIS_DEVICE_ONLY","bind","loadKeychainGroupStore","parseStoredCredential","raw","parsed","JSON","parse","candidate","deviceId","length","deviceSecret","createKeychainGroupCredentialStore","readOptions","keychainService","keychainAccessGroup","read","store","state","error","cause","credential","Error","publish","value","stringify","keychainAccessible","clear","brokerModule","loadBrokerModule","undefined","native","write","createBrokerCredentialStore","broker","createPlatformSharedDeviceCredentialStore","OS"],"sourceRoot":"../../../../src","sources":["ui/session/sharedDeviceCredentialStore.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -9,10 +9,15 @@
|
|
|
9
9
|
* different backings — a device credential in SecureStore and a pin in
|
|
10
10
|
* AsyncStorage would resolve differently after a keystore reset.
|
|
11
11
|
*
|
|
12
|
-
* `expo-secure-store` is
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* `expo-secure-store` is a REQUIRED peer of this package (13.5.0): without its
|
|
13
|
+
* native module the device credential would sit in plaintext AsyncStorage. It
|
|
14
|
+
* is still loaded lazily, as a direct statement of a `try` block (the shape
|
|
15
|
+
* Metro marks optional, `optionalPeerLoads.test.ts`), so a build whose native
|
|
16
|
+
* module is missing (react-native-macos, an app that has not rebuilt) degrades
|
|
17
|
+
* to AsyncStorage instead of crashing. The specifier is a LITERAL: the old
|
|
18
|
+
* `const m = 'expo-secure-store'; import(m)` never hid it from Metro (Babel
|
|
19
|
+
* evaluates the constant, so Metro resolved it anyway) and only hid it from the
|
|
20
|
+
* gates that read this source.
|
|
16
21
|
*/
|
|
17
22
|
import type { NativeKeyValueStorage } from '@oxy.so/core/session';
|
|
18
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nativeSecureStorage.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/nativeSecureStorage.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"nativeSecureStorage.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/nativeSecureStorage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AA2ClE;;;;;GAKG;AACH,wBAAgB,iCAAiC,IAAI,qBAAqB,CAiCzE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedDeviceCredentialStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/sharedDeviceCredentialStore.ts"],"names":[],"mappings":"AAyCA,OAAO,EAIL,KAAK,2BAA2B,EACjC,MAAM,sBAAsB,CAAC;AAE9B;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,mCAAmC,uBAAuB,CAAC;AAExE,+EAA+E;AAC/E,eAAO,MAAM,iCAAiC,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"sharedDeviceCredentialStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/sharedDeviceCredentialStore.ts"],"names":[],"mappings":"AAyCA,OAAO,EAIL,KAAK,2BAA2B,EACjC,MAAM,sBAAsB,CAAC;AAE9B;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,mCAAmC,uBAAuB,CAAC;AAExE,+EAA+E;AAC/E,eAAO,MAAM,iCAAiC,iCAAiC,CAAC;AAqPhF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yCAAyC,IAAI,2BAA2B,GAAG,IAAI,CAO9F"}
|
|
@@ -9,10 +9,15 @@
|
|
|
9
9
|
* different backings — a device credential in SecureStore and a pin in
|
|
10
10
|
* AsyncStorage would resolve differently after a keystore reset.
|
|
11
11
|
*
|
|
12
|
-
* `expo-secure-store` is
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* `expo-secure-store` is a REQUIRED peer of this package (13.5.0): without its
|
|
13
|
+
* native module the device credential would sit in plaintext AsyncStorage. It
|
|
14
|
+
* is still loaded lazily, as a direct statement of a `try` block (the shape
|
|
15
|
+
* Metro marks optional, `optionalPeerLoads.test.ts`), so a build whose native
|
|
16
|
+
* module is missing (react-native-macos, an app that has not rebuilt) degrades
|
|
17
|
+
* to AsyncStorage instead of crashing. The specifier is a LITERAL: the old
|
|
18
|
+
* `const m = 'expo-secure-store'; import(m)` never hid it from Metro (Babel
|
|
19
|
+
* evaluates the constant, so Metro resolved it anyway) and only hid it from the
|
|
20
|
+
* gates that read this source.
|
|
16
21
|
*/
|
|
17
22
|
import type { NativeKeyValueStorage } from '@oxy.so/core/session';
|
|
18
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nativeSecureStorage.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/nativeSecureStorage.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"nativeSecureStorage.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/nativeSecureStorage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AA2ClE;;;;;GAKG;AACH,wBAAgB,iCAAiC,IAAI,qBAAqB,CAiCzE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedDeviceCredentialStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/sharedDeviceCredentialStore.ts"],"names":[],"mappings":"AAyCA,OAAO,EAIL,KAAK,2BAA2B,EACjC,MAAM,sBAAsB,CAAC;AAE9B;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,mCAAmC,uBAAuB,CAAC;AAExE,+EAA+E;AAC/E,eAAO,MAAM,iCAAiC,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"sharedDeviceCredentialStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/sharedDeviceCredentialStore.ts"],"names":[],"mappings":"AAyCA,OAAO,EAIL,KAAK,2BAA2B,EACjC,MAAM,sBAAsB,CAAC;AAE9B;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,mCAAmC,uBAAuB,CAAC;AAExE,+EAA+E;AAC/E,eAAO,MAAM,iCAAiC,iCAAiC,CAAC;AAqPhF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yCAAyC,IAAI,2BAA2B,GAAG,IAAI,CAO9F"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxy.so/services",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.5.0",
|
|
4
4
|
"description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -178,6 +178,7 @@
|
|
|
178
178
|
"copyfiles": "^2.4.1",
|
|
179
179
|
"expo-clipboard": "~58.0.4",
|
|
180
180
|
"expo-constants": "~58.0.13",
|
|
181
|
+
"expo-crypto": "~58.0.7",
|
|
181
182
|
"expo-file-system": "~58.0.11",
|
|
182
183
|
"expo-haptics": "~58.0.7",
|
|
183
184
|
"expo-image": "~58.0.17",
|
|
@@ -227,6 +228,7 @@
|
|
|
227
228
|
"expo": ">=54.0.0 <59.0.0",
|
|
228
229
|
"expo-clipboard": ">=8.0.0 <59.0.0",
|
|
229
230
|
"expo-constants": ">=18.0.0 <59.0.0",
|
|
231
|
+
"expo-crypto": ">=15.0.0 <59.0.0",
|
|
230
232
|
"expo-document-picker": ">=14.0.0 <59.0.0",
|
|
231
233
|
"expo-file-system": ">=19.0.0 <59.0.0",
|
|
232
234
|
"expo-haptics": ">=15.0.0 <59.0.0",
|
|
@@ -236,6 +238,7 @@
|
|
|
236
238
|
"expo-linear-gradient": ">=15.0.0 <59.0.0",
|
|
237
239
|
"expo-modules-core": "<59.0.0",
|
|
238
240
|
"expo-notifications": ">=0.32.0 <59.0.0",
|
|
241
|
+
"expo-secure-store": ">=15.0.0 <59.0.0",
|
|
239
242
|
"expo-web-browser": ">=15.0.0 <59.0.0",
|
|
240
243
|
"nativewind": ">=5.0.0-preview.3 <6.0.0",
|
|
241
244
|
"react-native-css": "^3.0.6 || 3.1.0-rc.0",
|
|
@@ -263,9 +266,6 @@
|
|
|
263
266
|
"@types/react": {
|
|
264
267
|
"optional": true
|
|
265
268
|
},
|
|
266
|
-
"react-native-keyboard-controller": {
|
|
267
|
-
"optional": true
|
|
268
|
-
},
|
|
269
269
|
"expo": {
|
|
270
270
|
"optional": true
|
|
271
271
|
},
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config plugin: withOxyReleaseSigning (EVERY Oxy Android app, through
|
|
3
|
+
* `withOxySharedPermissions`; listed on its own only by an app that declares no
|
|
4
|
+
* Oxy permission).
|
|
5
|
+
*
|
|
6
|
+
* An app that declares the Oxy signature permissions (`so.oxy.permission.IDENTITY`,
|
|
7
|
+
* `DEVICE_SESSION`) is only safe to install when it carries the ONE Oxy ecosystem
|
|
8
|
+
* certificate. Signed with anything else it takes ownership of both permissions,
|
|
9
|
+
* after which Android refuses to install every correctly signed Oxy app next to it
|
|
10
|
+
* (`INSTALL_FAILED_DUPLICATE_PERMISSION`), and Commons refuses it as a caller
|
|
11
|
+
* anyway. That is why the plugin that declares the permissions applies this one:
|
|
12
|
+
* an app cannot get the permissions without the signing rule.
|
|
13
|
+
*
|
|
14
|
+
* It edits `android/app/build.gradle` in three places:
|
|
15
|
+
*
|
|
16
|
+
* 1. Before `android {`: resolve the keystore from Gradle properties (set in
|
|
17
|
+
* `~/.gradle/gradle.properties`, with `-P`, or as `ORG_GRADLE_PROJECT_<name>`
|
|
18
|
+
* environment variables; never in the repo):
|
|
19
|
+
* - `OXY_UPLOAD_STORE_FILE`: the keystore (`~/` is expanded).
|
|
20
|
+
* - `OXY_UPLOAD_STORE_PASSWORD`, or `OXY_UPLOAD_STORE_PASSWORD_FILE`: a file
|
|
21
|
+
* holding it (trimmed).
|
|
22
|
+
* - `OXY_UPLOAD_KEY_ALIAS`: `oxy` for the ecosystem key.
|
|
23
|
+
* - `OXY_UPLOAD_KEY_PASSWORD` / `OXY_UPLOAD_KEY_PASSWORD_FILE`: optional,
|
|
24
|
+
* defaults to the store password (a PKCS12 keystore has one password).
|
|
25
|
+
* 2. `signingConfigs.release` from those, and the `release` buildType signed by
|
|
26
|
+
* it ALWAYS (the `debug` buildType only with `OXY_UPLOAD_SIGN_DEBUG`, below).
|
|
27
|
+
* Expo's template signs release with the debug key; nothing here falls back
|
|
28
|
+
* to it.
|
|
29
|
+
* 3. `oxyVerifyReleaseSigning`, a task every release variant's `pre…ReleaseBuild`
|
|
30
|
+
* and `validateSigning…Release` depend on. It opens the release buildType's
|
|
31
|
+
* EFFECTIVE signing config and fails the build unless the key's certificate
|
|
32
|
+
* SHA-256 is the ecosystem one, so it holds however the keystore arrived
|
|
33
|
+
* (these properties, EAS's injected `credentials.json`, anything else) and it
|
|
34
|
+
* refuses a debug key, a per-app key and a wrong alias alike. Debug builds
|
|
35
|
+
* never run it.
|
|
36
|
+
*
|
|
37
|
+
* Opt-in for debug: `OXY_UPLOAD_SIGN_DEBUG=true` signs the debug buildType with
|
|
38
|
+
* the same key and runs the same guard before every debug variant, so a dev
|
|
39
|
+
* client can sit next to real Oxy apps and be granted the permissions (Willo's
|
|
40
|
+
* `withOxyDebugSigning` did this with `OXY_DEBUG_*`). Without it debug keeps
|
|
41
|
+
* Expo's debug key and is never checked.
|
|
42
|
+
*
|
|
43
|
+
* There is deliberately no opt-out for release: no CI job builds an Android
|
|
44
|
+
* release without the key, and a release signed with anything else must not
|
|
45
|
+
* exist. An app that declares no Oxy permission (Authenticator) lists this
|
|
46
|
+
* plugin directly to get the same release signing.
|
|
47
|
+
*
|
|
48
|
+
* `android/` is generated by `expo prebuild`, so these edits live here or they
|
|
49
|
+
* are lost on the next prebuild.
|
|
50
|
+
*/
|
|
51
|
+
// The app's own Expo SDK copy, never one this package ships (Expo's guidance for library plugins).
|
|
52
|
+
const { withAppBuildGradle } = require('expo/config-plugins');
|
|
53
|
+
|
|
54
|
+
/** SHA-256 of the Oxy ecosystem release certificate (alias `oxy`), lowercase hex. */
|
|
55
|
+
const ECOSYSTEM_CERT_SHA256 = 'b8ab3746466ce4569d7a6a0ffa1eed91bc375174d503613987ca09cb33d6a58d';
|
|
56
|
+
|
|
57
|
+
const MARKER = '@generated oxy-release-signing';
|
|
58
|
+
const PLUGIN = '[@oxy.so/services withOxyReleaseSigning]';
|
|
59
|
+
|
|
60
|
+
/** Gradle property names holding the release signing material. */
|
|
61
|
+
const PROPERTIES = {
|
|
62
|
+
storeFile: 'OXY_UPLOAD_STORE_FILE',
|
|
63
|
+
storePassword: 'OXY_UPLOAD_STORE_PASSWORD',
|
|
64
|
+
keyAlias: 'OXY_UPLOAD_KEY_ALIAS',
|
|
65
|
+
keyPassword: 'OXY_UPLOAD_KEY_PASSWORD',
|
|
66
|
+
signDebug: 'OXY_UPLOAD_SIGN_DEBUG',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** Resolves the OXY_UPLOAD_* properties; inserted before `android {`. */
|
|
70
|
+
const RESOLVE_PROPERTIES = `// ${MARKER}: the Oxy ecosystem release key (@oxy.so/services
|
|
71
|
+
// withOxyReleaseSigning). Edit the plugin, never this file.
|
|
72
|
+
def oxyHomePath = { String path -> path.startsWith('~/') ? System.getProperty('user.home') + path.substring(1) : path }
|
|
73
|
+
def oxySigningProblems = []
|
|
74
|
+
def oxySigningSecret = { String name ->
|
|
75
|
+
def value = findProperty(name)
|
|
76
|
+
if (value != null) return value.toString()
|
|
77
|
+
def path = findProperty(name + '_FILE')
|
|
78
|
+
if (path == null) return null
|
|
79
|
+
def secretFile = file(oxyHomePath(path.toString()))
|
|
80
|
+
if (!secretFile.isFile()) {
|
|
81
|
+
oxySigningProblems << (name + '_FILE names ' + secretFile + ', which is not a file')
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
return secretFile.getText('UTF-8').trim()
|
|
85
|
+
}
|
|
86
|
+
def oxyStoreFilePath = findProperty('${PROPERTIES.storeFile}')
|
|
87
|
+
def oxyStoreFile = oxyStoreFilePath == null ? null : file(oxyHomePath(oxyStoreFilePath.toString()))
|
|
88
|
+
def oxyStorePassword = oxySigningSecret('${PROPERTIES.storePassword}')
|
|
89
|
+
def oxyKeyAlias = findProperty('${PROPERTIES.keyAlias}')?.toString()
|
|
90
|
+
def oxyKeyPassword = oxySigningSecret('${PROPERTIES.keyPassword}') ?: oxyStorePassword
|
|
91
|
+
if (oxyStoreFile == null) oxySigningProblems << '${PROPERTIES.storeFile} is not set'
|
|
92
|
+
if (oxyStorePassword == null && findProperty('${PROPERTIES.storePassword}_FILE') == null) oxySigningProblems << '${PROPERTIES.storePassword} (or ${PROPERTIES.storePassword}_FILE) is not set'
|
|
93
|
+
if (oxyKeyAlias == null) oxySigningProblems << '${PROPERTIES.keyAlias} is not set'
|
|
94
|
+
def oxyUploadSigning = oxyStoreFile != null && oxyStorePassword != null && oxyKeyAlias != null
|
|
95
|
+
// Opt-in: sign debug with the ecosystem key too (a dev client next to real Oxy apps),
|
|
96
|
+
// and then refuse a debug build without it exactly like a release.
|
|
97
|
+
def oxySignDebug = (findProperty('${PROPERTIES.signDebug}') ?: 'false').toString().toBoolean()
|
|
98
|
+
def oxySignedBuildTypes = oxySignDebug ? ['release', 'debug'] : ['release']
|
|
99
|
+
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
const RELEASE_SIGNING_CONFIG = ` release {
|
|
103
|
+
// ${MARKER}: the Oxy ecosystem key from the OXY_UPLOAD_* properties.
|
|
104
|
+
// Without them it stays empty and oxyVerifyReleaseSigning fails the build.
|
|
105
|
+
if (oxyUploadSigning) {
|
|
106
|
+
storeFile oxyStoreFile
|
|
107
|
+
storePassword oxyStorePassword
|
|
108
|
+
keyAlias oxyKeyAlias
|
|
109
|
+
keyPassword oxyKeyPassword
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
|
|
114
|
+
const RELEASE_SIGNING_LINE = 'signingConfig signingConfigs.release';
|
|
115
|
+
const DEBUG_SIGNING_LINE = 'signingConfig signingConfigs.debug';
|
|
116
|
+
const OPT_IN_DEBUG_SIGNING_LINE =
|
|
117
|
+
'signingConfig oxySignDebug ? signingConfigs.release : signingConfigs.debug';
|
|
118
|
+
|
|
119
|
+
/** The fail-closed guard; appended to the file. */
|
|
120
|
+
const VERIFY_TASK = String.raw`
|
|
121
|
+
// ${MARKER}: every release build must carry the Oxy ecosystem certificate.
|
|
122
|
+
// Signed with anything else, the app declares so.oxy.permission.IDENTITY and
|
|
123
|
+
// DEVICE_SESSION under the wrong certificate: Android then refuses to install the
|
|
124
|
+
// correctly signed Oxy apps next to it (INSTALL_FAILED_DUPLICATE_PERMISSION), and
|
|
125
|
+
// Commons refuses it. This checks the release buildType's EFFECTIVE signing config.
|
|
126
|
+
def oxyVerifyReleaseSigning = tasks.register('oxyVerifyReleaseSigning') {
|
|
127
|
+
group = 'verification'
|
|
128
|
+
description = 'Fails unless release (and, with OXY_UPLOAD_SIGN_DEBUG, debug) is signed with the Oxy ecosystem certificate.'
|
|
129
|
+
notCompatibleWithConfigurationCache('reads the android extension when it runs')
|
|
130
|
+
doLast {
|
|
131
|
+
def expected = '${ECOSYSTEM_CERT_SHA256}'
|
|
132
|
+
def fail = { String reason ->
|
|
133
|
+
throw new GradleException([
|
|
134
|
+
'Oxy release signing: ' + reason,
|
|
135
|
+
'',
|
|
136
|
+
'Every Oxy Android release is signed with the ONE ecosystem key (certificate SHA-256 ' + expected + ').',
|
|
137
|
+
'Provide it as Gradle properties, in ~/.gradle/gradle.properties, as -P<name>=<value>,',
|
|
138
|
+
'or as ORG_GRADLE_PROJECT_<name> environment variables:',
|
|
139
|
+
' ${PROPERTIES.storeFile}=~/.config/oxy/oxy-ecosystem-release.keystore',
|
|
140
|
+
' ${PROPERTIES.storePassword}_FILE=~/.config/oxy/oxy-ecosystem-release-keystore.pass (or ${PROPERTIES.storePassword})',
|
|
141
|
+
' ${PROPERTIES.keyAlias}=oxy',
|
|
142
|
+
' ${PROPERTIES.keyPassword}[_FILE] only when the key password differs from the store password',
|
|
143
|
+
'Debug builds need none of this unless ${PROPERTIES.signDebug}=true.',
|
|
144
|
+
].join('\n'))
|
|
145
|
+
}
|
|
146
|
+
oxySignedBuildTypes.each { buildType ->
|
|
147
|
+
def config = android.buildTypes.getByName(buildType).signingConfig
|
|
148
|
+
if (config == null || config.storeFile == null) {
|
|
149
|
+
fail('the ' + buildType + ' build has no keystore' + (oxySigningProblems ? ': ' + oxySigningProblems.join('; ') : '') + '.')
|
|
150
|
+
}
|
|
151
|
+
def keyStore = null
|
|
152
|
+
try {
|
|
153
|
+
keyStore = java.security.KeyStore.getInstance(config.storeFile, config.storePassword?.toCharArray())
|
|
154
|
+
} catch (Exception e) {
|
|
155
|
+
fail('could not open ' + config.storeFile + ' (' + e.getClass().getSimpleName() + ': ' + e.getMessage() + ').')
|
|
156
|
+
}
|
|
157
|
+
def certificate = keyStore.getCertificate(config.keyAlias)
|
|
158
|
+
if (certificate == null) {
|
|
159
|
+
fail(config.storeFile.toString() + ' has no certificate under the alias "' + config.keyAlias + '".')
|
|
160
|
+
}
|
|
161
|
+
def actual = java.security.MessageDigest.getInstance('SHA-256').digest(certificate.getEncoded()).encodeHex().toString()
|
|
162
|
+
if (actual != expected) {
|
|
163
|
+
fail('the ' + buildType + ' build is signed by "' + config.name + '" (' + config.storeFile + ', alias ' + config.keyAlias + '), certificate SHA-256 ' + actual + ', not the Oxy ecosystem one.')
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
tasks.configureEach { task ->
|
|
169
|
+
if (task.name ==~ (oxySignDebug ? /pre\w*(Release|Debug)Build|validateSigning\w*(Release|Debug)/ : /pre\w*ReleaseBuild|validateSigning\w*Release/)) {
|
|
170
|
+
task.dependsOn(oxyVerifyReleaseSigning)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
`;
|
|
174
|
+
|
|
175
|
+
function fail(message) {
|
|
176
|
+
throw new Error(`${PLUGIN} ${message}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Apply the edits to the text of a Groovy `android/app/build.gradle`.
|
|
181
|
+
* Idempotent; refuses a file another plugin already gave a release signing config.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} contents
|
|
184
|
+
* @returns {string}
|
|
185
|
+
*/
|
|
186
|
+
function applyReleaseSigning(contents) {
|
|
187
|
+
if (contents.includes(MARKER)) return contents;
|
|
188
|
+
|
|
189
|
+
if (contents.includes('OXY_UPLOAD_')) {
|
|
190
|
+
fail(
|
|
191
|
+
'app/build.gradle already has an OXY_UPLOAD_* signing config written by another plugin: ' +
|
|
192
|
+
"`@oxy.so/app-preset/plugin/withOxyAndroidRelease` (app-preset 4 or earlier) or an app's " +
|
|
193
|
+
"own copy (such as Mention's plugins/withAndroidReleaseBuild.js). Remove it from the app " +
|
|
194
|
+
'config: withOxySharedPermissions signs every Oxy release.',
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (contents.includes('OXY_DEBUG_')) {
|
|
199
|
+
fail(
|
|
200
|
+
"app/build.gradle already has an OXY_DEBUG_* debug signing config (such as Willo's " +
|
|
201
|
+
'plugins/withOxyDebugSigning.js). Remove that plugin and set the Gradle property ' +
|
|
202
|
+
`${PROPERTIES.signDebug}=true to sign debug builds with the ecosystem key.`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const androidBlock = /^android \{$/m;
|
|
207
|
+
if (!androidBlock.test(contents)) {
|
|
208
|
+
fail(
|
|
209
|
+
'Could not find the top-level `android {` block in app/build.gradle. The Expo template changed: update this plugin.',
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
let next = contents.replace(androidBlock, `${RESOLVE_PROPERTIES}android {`);
|
|
213
|
+
|
|
214
|
+
// Declare signingConfigs.release immediately after the generated debug entry.
|
|
215
|
+
const debugSigningBlock =
|
|
216
|
+
/(signingConfigs\s*\{\s*\n\s*debug\s*\{[\s\S]*?\n\s{8}\}\n)(\s*release\s*\{)?/;
|
|
217
|
+
const debugMatch = next.match(debugSigningBlock);
|
|
218
|
+
if (!debugMatch) {
|
|
219
|
+
fail(
|
|
220
|
+
'Could not find the generated signingConfigs.debug block in app/build.gradle. The Expo template changed: update this plugin.',
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (debugMatch[2]) {
|
|
224
|
+
fail(
|
|
225
|
+
'app/build.gradle already declares signingConfigs.release. Remove the plugin that writes it: withOxySharedPermissions signs every Oxy release.',
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
next = next.replace(debugSigningBlock, `$1${RELEASE_SIGNING_CONFIG}`);
|
|
229
|
+
|
|
230
|
+
// Point the release buildType at it. The debug buildType has its own
|
|
231
|
+
// `signingConfig signingConfigs.debug`, so only the line inside `release {` is replaced.
|
|
232
|
+
const buildTypes = next.indexOf('buildTypes {');
|
|
233
|
+
const releaseBuildType = buildTypes === -1 ? -1 : next.indexOf('release {', buildTypes);
|
|
234
|
+
if (releaseBuildType === -1) {
|
|
235
|
+
fail('Could not find the release buildType in app/build.gradle.');
|
|
236
|
+
}
|
|
237
|
+
const releaseBody = next.slice(releaseBuildType);
|
|
238
|
+
const signingLine = releaseBody.match(/signingConfig [^\n]+/);
|
|
239
|
+
const closing = releaseBody.indexOf('\n }');
|
|
240
|
+
if (
|
|
241
|
+
!signingLine ||
|
|
242
|
+
signingLine.index === undefined ||
|
|
243
|
+
(closing !== -1 && signingLine.index > closing)
|
|
244
|
+
) {
|
|
245
|
+
fail('Could not find the signingConfig line of the release buildType in app/build.gradle.');
|
|
246
|
+
}
|
|
247
|
+
const at = releaseBuildType + signingLine.index;
|
|
248
|
+
next = next.slice(0, at) + RELEASE_SIGNING_LINE + next.slice(at + signingLine[0].length);
|
|
249
|
+
|
|
250
|
+
// The debug buildType keeps Expo's debug key unless OXY_UPLOAD_SIGN_DEBUG asks.
|
|
251
|
+
const debugBuildType = next.indexOf('debug {', next.indexOf('buildTypes {'));
|
|
252
|
+
const debugLine = debugBuildType === -1 ? -1 : next.indexOf(DEBUG_SIGNING_LINE, debugBuildType);
|
|
253
|
+
if (debugLine === -1 || debugLine > next.indexOf('release {', next.indexOf('buildTypes {'))) {
|
|
254
|
+
fail('Could not find the signingConfig line of the debug buildType in app/build.gradle.');
|
|
255
|
+
}
|
|
256
|
+
next =
|
|
257
|
+
next.slice(0, debugLine) +
|
|
258
|
+
OPT_IN_DEBUG_SIGNING_LINE +
|
|
259
|
+
next.slice(debugLine + DEBUG_SIGNING_LINE.length);
|
|
260
|
+
|
|
261
|
+
return `${next.replace(/\n*$/, '\n')}${VERIFY_TASK}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function withOxyReleaseSigning(config) {
|
|
265
|
+
return withAppBuildGradle(config, (modConfig) => {
|
|
266
|
+
if (modConfig.modResults.language !== 'groovy') {
|
|
267
|
+
fail(`Expected a Groovy app/build.gradle, got ${modConfig.modResults.language}.`);
|
|
268
|
+
}
|
|
269
|
+
modConfig.modResults.contents = applyReleaseSigning(modConfig.modResults.contents);
|
|
270
|
+
return modConfig;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = withOxyReleaseSigning;
|
|
275
|
+
module.exports.applyReleaseSigning = applyReleaseSigning;
|
|
276
|
+
module.exports.ECOSYSTEM_CERT_SHA256 = ECOSYSTEM_CERT_SHA256;
|
|
277
|
+
module.exports.PROPERTIES = PROPERTIES;
|
|
@@ -29,9 +29,16 @@
|
|
|
29
29
|
*
|
|
30
30
|
* `signature` is the whole trust boundary at the manifest level; each provider
|
|
31
31
|
* also checks the calling package and its certificate itself.
|
|
32
|
+
*
|
|
33
|
+
* Declaring these permissions is only safe under the ONE Oxy ecosystem
|
|
34
|
+
* certificate, so this plugin also applies `withOxyReleaseSigning`: every
|
|
35
|
+
* release build is signed with the ecosystem key from the `OXY_UPLOAD_*` Gradle
|
|
36
|
+
* properties and FAILS unless its certificate is the ecosystem one. Apps list
|
|
37
|
+
* neither that plugin nor any signing plugin of their own.
|
|
32
38
|
*/
|
|
33
39
|
// The app's own Expo SDK copy, never one this package ships (Expo's guidance for library plugins).
|
|
34
40
|
const { withAndroidManifest } = require('expo/config-plugins');
|
|
41
|
+
const withOxyReleaseSigning = require('./withOxyReleaseSigning');
|
|
35
42
|
|
|
36
43
|
const IDENTITY_PERMISSION = 'so.oxy.permission.IDENTITY';
|
|
37
44
|
const DEVICE_SESSION_PERMISSION = 'so.oxy.permission.DEVICE_SESSION';
|
|
@@ -52,7 +59,7 @@ const HOST_AUTHORITIES = [
|
|
|
52
59
|
];
|
|
53
60
|
|
|
54
61
|
function withOxySharedPermissions(config) {
|
|
55
|
-
return withAndroidManifest(config, (modConfig) => {
|
|
62
|
+
return withAndroidManifest(withOxyReleaseSigning(config), (modConfig) => {
|
|
56
63
|
const manifest = modConfig.modResults.manifest;
|
|
57
64
|
|
|
58
65
|
manifest.permission = manifest.permission ?? [];
|
|
@@ -9,18 +9,19 @@
|
|
|
9
9
|
* different backings — a device credential in SecureStore and a pin in
|
|
10
10
|
* AsyncStorage would resolve differently after a keystore reset.
|
|
11
11
|
*
|
|
12
|
-
* `expo-secure-store` is
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* `expo-secure-store` is a REQUIRED peer of this package (13.5.0): without its
|
|
13
|
+
* native module the device credential would sit in plaintext AsyncStorage. It
|
|
14
|
+
* is still loaded lazily, as a direct statement of a `try` block (the shape
|
|
15
|
+
* Metro marks optional, `optionalPeerLoads.test.ts`), so a build whose native
|
|
16
|
+
* module is missing (react-native-macos, an app that has not rebuilt) degrades
|
|
17
|
+
* to AsyncStorage instead of crashing. The specifier is a LITERAL: the old
|
|
18
|
+
* `const m = 'expo-secure-store'; import(m)` never hid it from Metro (Babel
|
|
19
|
+
* evaluates the constant, so Metro resolved it anyway) and only hid it from the
|
|
20
|
+
* gates that read this source.
|
|
16
21
|
*/
|
|
17
22
|
import type { NativeKeyValueStorage } from '@oxy.so/core/session';
|
|
18
23
|
import { createPlatformStorage } from '../utils/storageHelpers';
|
|
19
24
|
|
|
20
|
-
// Variable indirection so Metro's static analyzer never traces expo-secure-store
|
|
21
|
-
// into the web bundle; the module is native-only and optional.
|
|
22
|
-
const SECURE_STORE_MODULE = 'expo-secure-store';
|
|
23
|
-
|
|
24
25
|
interface SecureStoreLike {
|
|
25
26
|
getItemAsync(key: string): Promise<string | null>;
|
|
26
27
|
setItemAsync(key: string, value: string): Promise<void>;
|
|
@@ -33,25 +34,30 @@ let secureStorePromise: Promise<SecureStoreLike | null> | null = null;
|
|
|
33
34
|
* Resolve the `expo-secure-store` module once (memoised), or `null` when it is
|
|
34
35
|
* unavailable — callers then fall back to AsyncStorage. Never throws.
|
|
35
36
|
*/
|
|
37
|
+
async function importSecureStore(): Promise<SecureStoreLike | null> {
|
|
38
|
+
let mod: Partial<SecureStoreLike>;
|
|
39
|
+
try {
|
|
40
|
+
mod = (await import('expo-secure-store')) as unknown as Partial<SecureStoreLike>;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (
|
|
45
|
+
typeof mod.getItemAsync === 'function' &&
|
|
46
|
+
typeof mod.setItemAsync === 'function' &&
|
|
47
|
+
typeof mod.deleteItemAsync === 'function'
|
|
48
|
+
) {
|
|
49
|
+
return {
|
|
50
|
+
getItemAsync: mod.getItemAsync.bind(mod),
|
|
51
|
+
setItemAsync: mod.setItemAsync.bind(mod),
|
|
52
|
+
deleteItemAsync: mod.deleteItemAsync.bind(mod),
|
|
53
|
+
} satisfies SecureStoreLike;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
function loadSecureStore(): Promise<SecureStoreLike | null> {
|
|
37
59
|
if (!secureStorePromise) {
|
|
38
|
-
|
|
39
|
-
secureStorePromise = import(moduleName)
|
|
40
|
-
.then((mod: Partial<SecureStoreLike>) => {
|
|
41
|
-
if (
|
|
42
|
-
typeof mod.getItemAsync === 'function' &&
|
|
43
|
-
typeof mod.setItemAsync === 'function' &&
|
|
44
|
-
typeof mod.deleteItemAsync === 'function'
|
|
45
|
-
) {
|
|
46
|
-
return {
|
|
47
|
-
getItemAsync: mod.getItemAsync.bind(mod),
|
|
48
|
-
setItemAsync: mod.setItemAsync.bind(mod),
|
|
49
|
-
deleteItemAsync: mod.deleteItemAsync.bind(mod),
|
|
50
|
-
} satisfies SecureStoreLike;
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
})
|
|
54
|
-
.catch(() => null);
|
|
60
|
+
secureStorePromise = importSecureStore();
|
|
55
61
|
}
|
|
56
62
|
return secureStorePromise;
|
|
57
63
|
}
|
|
@@ -64,10 +64,6 @@ export const IOS_DEVICE_SESSION_KEYCHAIN_SERVICE = 'oxy_device_session';
|
|
|
64
64
|
/** The single key holding the JSON-encoded `{deviceId, deviceSecret}` pair. */
|
|
65
65
|
export const SHARED_DEVICE_SESSION_STORAGE_KEY = 'oxy_shared_device_session_v1';
|
|
66
66
|
|
|
67
|
-
// Variable indirection so Metro's static analyzer never traces expo-secure-store
|
|
68
|
-
// into the web bundle; the module is native-only and optional.
|
|
69
|
-
const SECURE_STORE_MODULE = 'expo-secure-store';
|
|
70
|
-
|
|
71
67
|
/**
|
|
72
68
|
* The subset of `expo-secure-store` this module needs. Unlike the adapter in
|
|
73
69
|
* `nativeSecureStorage.ts` it must pass OPTIONS — the access group and the
|
|
@@ -83,27 +79,38 @@ interface KeychainGroupStore {
|
|
|
83
79
|
|
|
84
80
|
let keychainStorePromise: Promise<KeychainGroupStore | null> | null = null;
|
|
85
81
|
|
|
82
|
+
/**
|
|
83
|
+
* `expo-secure-store`, a required peer of this package, loaded lazily as a
|
|
84
|
+
* direct statement of a `try` block with a LITERAL specifier (see
|
|
85
|
+
* `nativeSecureStorage.ts` for why the old runtime-computed one hid nothing
|
|
86
|
+
* from Metro). A build without its native module reads `unsupported`.
|
|
87
|
+
*/
|
|
88
|
+
async function importKeychainGroupStore(): Promise<KeychainGroupStore | null> {
|
|
89
|
+
let mod: Partial<KeychainGroupStore>;
|
|
90
|
+
try {
|
|
91
|
+
mod = (await import('expo-secure-store')) as unknown as Partial<KeychainGroupStore>;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
if (
|
|
96
|
+
typeof mod.getItemAsync === 'function' &&
|
|
97
|
+
typeof mod.setItemAsync === 'function' &&
|
|
98
|
+
typeof mod.deleteItemAsync === 'function' &&
|
|
99
|
+
typeof mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY === 'number'
|
|
100
|
+
) {
|
|
101
|
+
return {
|
|
102
|
+
getItemAsync: mod.getItemAsync.bind(mod),
|
|
103
|
+
setItemAsync: mod.setItemAsync.bind(mod),
|
|
104
|
+
deleteItemAsync: mod.deleteItemAsync.bind(mod),
|
|
105
|
+
WHEN_UNLOCKED_THIS_DEVICE_ONLY: mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
|
106
|
+
} satisfies KeychainGroupStore;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
86
111
|
function loadKeychainGroupStore(): Promise<KeychainGroupStore | null> {
|
|
87
112
|
if (!keychainStorePromise) {
|
|
88
|
-
|
|
89
|
-
keychainStorePromise = import(moduleName)
|
|
90
|
-
.then((mod: Partial<KeychainGroupStore>) => {
|
|
91
|
-
if (
|
|
92
|
-
typeof mod.getItemAsync === 'function' &&
|
|
93
|
-
typeof mod.setItemAsync === 'function' &&
|
|
94
|
-
typeof mod.deleteItemAsync === 'function' &&
|
|
95
|
-
typeof mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY === 'number'
|
|
96
|
-
) {
|
|
97
|
-
return {
|
|
98
|
-
getItemAsync: mod.getItemAsync.bind(mod),
|
|
99
|
-
setItemAsync: mod.setItemAsync.bind(mod),
|
|
100
|
-
deleteItemAsync: mod.deleteItemAsync.bind(mod),
|
|
101
|
-
WHEN_UNLOCKED_THIS_DEVICE_ONLY: mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
|
|
102
|
-
} satisfies KeychainGroupStore;
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
})
|
|
106
|
-
.catch(() => null);
|
|
113
|
+
keychainStorePromise = importKeychainGroupStore();
|
|
107
114
|
}
|
|
108
115
|
return keychainStorePromise;
|
|
109
116
|
}
|