cozy-client 27.7.0 → 27.8.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/CHANGELOG.md +19 -0
- package/dist/CozyClient.js +13 -2
- package/dist/flagship-certification/README.md +96 -0
- package/dist/flagship-certification/flagship-certification.js +163 -0
- package/dist/flagship-certification/store-attestation.android.js +61 -0
- package/dist/flagship-certification/store-attestation.ios.js +67 -0
- package/dist/flagship-certification/store-attestation.js +44 -0
- package/dist/flagship-certification/typedefs.js +24 -0
- package/dist/hooks/useQuery.js +5 -1
- package/dist/hooks/useQuery.spec.jsx +40 -0
- package/package.json +5 -3
- package/types/flagship-certification/flagship-certification.d.ts +2 -0
- package/types/flagship-certification/store-attestation.android.d.ts +1 -0
- package/types/flagship-certification/store-attestation.d.ts +9 -0
- package/types/flagship-certification/store-attestation.ios.d.ts +1 -0
- package/types/flagship-certification/typedefs.d.ts +22 -0
- package/types/models/permission.d.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [27.8.0](https://github.com/cozy/cozy-client/compare/v27.7.0...v27.8.0) (2022-01-11)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Rebuild `permission.d.ts` to fit CI version ([380db52](https://github.com/cozy/cozy-client/commit/380db528004e9a64b244a99bb238351fac4f2a05))
|
|
12
|
+
* UseQuery returns fetch method ([93e7f3d](https://github.com/cozy/cozy-client/commit/93e7f3d217353003795d43dfedf1397bb64815af))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* Add `react-native-google-safetynet` module as peerDependencies ([cd4b37f](https://github.com/cozy/cozy-client/commit/cd4b37fc3353b702035e773b2b423ef7e7aa3156))
|
|
18
|
+
* Add `react-native-ios11-devicecheck` module as peerDependencies ([b9ed2e9](https://github.com/cozy/cozy-client/commit/b9ed2e9c42518a50961f5b233b5f6ae1894ed7d6))
|
|
19
|
+
* Add flagship certification mechanism ([fe8474c](https://github.com/cozy/cozy-client/commit/fe8474c3286393c996eb5704184de652aa8028fe))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
# [27.7.0](https://github.com/cozy/cozy-client/compare/v27.6.5...v27.7.0) (2022-01-03)
|
|
7
26
|
|
|
8
27
|
|
package/dist/CozyClient.js
CHANGED
|
@@ -83,6 +83,8 @@ var _jsonStableStringify = _interopRequireDefault(require("json-stable-stringify
|
|
|
83
83
|
|
|
84
84
|
var _promiseCache = _interopRequireDefault(require("./promise-cache"));
|
|
85
85
|
|
|
86
|
+
var _flagshipCertification = require("./flagship-certification/flagship-certification");
|
|
87
|
+
|
|
86
88
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
|
87
89
|
|
|
88
90
|
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
|
@@ -1958,9 +1960,18 @@ var CozyClient = /*#__PURE__*/function () {
|
|
|
1958
1960
|
return stackClient.register();
|
|
1959
1961
|
|
|
1960
1962
|
case 3:
|
|
1963
|
+
if (!stackClient.oauthOptions.shouldRequireFlagshipPermissions) {
|
|
1964
|
+
_context14.next = 6;
|
|
1965
|
+
break;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
_context14.next = 6;
|
|
1969
|
+
return (0, _flagshipCertification.certifyFlagship)(stackClient.oauthOptions.certificationConfig, this);
|
|
1970
|
+
|
|
1971
|
+
case 6:
|
|
1961
1972
|
return _context14.abrupt("return", this.authorize(openURLCallback));
|
|
1962
1973
|
|
|
1963
|
-
case
|
|
1974
|
+
case 7:
|
|
1964
1975
|
case "end":
|
|
1965
1976
|
return _context14.stop();
|
|
1966
1977
|
}
|
|
@@ -2452,7 +2463,7 @@ var CozyClient = /*#__PURE__*/function () {
|
|
|
2452
2463
|
CozyClient.hooks = CozyClient.hooks || {};
|
|
2453
2464
|
CozyClient.fetchPolicies = _policies.default; //COZY_CLIENT_VERSION_PACKAGE in replaced by babel. See babel config
|
|
2454
2465
|
|
|
2455
|
-
CozyClient.version = "27.
|
|
2466
|
+
CozyClient.version = "27.8.0";
|
|
2456
2467
|
|
|
2457
2468
|
_microee.default.mixin(CozyClient);
|
|
2458
2469
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Flagship certification
|
|
2
|
+
|
|
3
|
+
Flagship certification is the process of verifying that the current running app is a genuine Cozy application.
|
|
4
|
+
|
|
5
|
+
This verification is done by querying an app certificate from the app store (Apple AppStore or Google Play) and sending the resulting certificate to `cozy-stack`. Then `cozy-stack` will be able to an analyze the certificate and conclude if the app is genuine or not.
|
|
6
|
+
|
|
7
|
+
## Nomenclature
|
|
8
|
+
|
|
9
|
+
- `flagship`: refers to the Cozy main application
|
|
10
|
+
- `store certification`: process of verifying app's genuineness through the platform's store
|
|
11
|
+
- `attestation`: result from the `store certification`
|
|
12
|
+
- `challenge`: unique token given to the app by `cozy-stack` that may be encrypted in the `attestation` as a proof of authenticity
|
|
13
|
+
- `nonce`: data type used to store the `challenge` token
|
|
14
|
+
- `SafetyNet`: Google's implementation of the `store certification`
|
|
15
|
+
- `AppAttest`: Apple's implementation of the `store certification`
|
|
16
|
+
|
|
17
|
+
## Android certification
|
|
18
|
+
|
|
19
|
+
Android certification is based on [SafetyNet](https://developer.android.com/training/safetynet/index.html).
|
|
20
|
+
|
|
21
|
+
This process requires to query a `challenge` from `cozy-stack` and to use it to init the `store certification` process through `SafetyNet`. Then the received `attestation` is send to `cozy-stack` for verification.
|
|
22
|
+
|
|
23
|
+
The resulting `attestation` is in the form of a `JSON Web Signature` that embbed the following `JSON`:
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"apkCertificateDigestSha256": [
|
|
27
|
+
"base64 encoded, SHA-256 hash of the certificate used to sign requesting app="
|
|
28
|
+
],
|
|
29
|
+
"apkDigestSha256": "kNv83tJLFqwliYQ/6HUPCeGkBzLLCX/nvT+EF3OEB2I=",
|
|
30
|
+
"apkPackageName": "com.package.name.of.requesting.app",
|
|
31
|
+
"basicIntegrity": true,
|
|
32
|
+
"ctsProfileMatch": true,
|
|
33
|
+
"evaluationType": "BASIC",
|
|
34
|
+
"nonce": "R2Rra24fVm5xa2Mg",
|
|
35
|
+
"timestampMs": 9860437986543
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `attestation`'s content is described in the SafetyNet's documentation: https://developer.android.com/training/safetynet/attestation#use-response-server
|
|
40
|
+
|
|
41
|
+
## iOS certification
|
|
42
|
+
|
|
43
|
+
iOS certification is based on [AppAttest](https://developer.apple.com/documentation/devicecheck).
|
|
44
|
+
|
|
45
|
+
This process requires to query a `challenge` from `cozy-stack` and to use it to init the `store certification` process through `AppAttest`. Then the received `attestation` is send to `cozy-stack` for verification.
|
|
46
|
+
|
|
47
|
+
The resulting `attestation` is in the form of a `base64` token.
|
|
48
|
+
|
|
49
|
+
The `attestation`'s content is described in the `AppAttest`'s documentation: https://developer.apple.com/documentation/devicecheck/validating_apps_that_connect_to_your_server#3576643
|
|
50
|
+
|
|
51
|
+
## Client's configuration
|
|
52
|
+
|
|
53
|
+
In order to configure `flagship` certification on an app, the OAuth property of `cozy-client` must contain `shouldRequireFlagshipPermissions` property set to `true` and `certificationConfig` property filled with required API keys.
|
|
54
|
+
|
|
55
|
+
Example of `cozy-client` configuration:
|
|
56
|
+
```js
|
|
57
|
+
const client = await initClient(uri, {
|
|
58
|
+
scope: [
|
|
59
|
+
'io.cozy.apps'
|
|
60
|
+
],
|
|
61
|
+
oauth: {
|
|
62
|
+
redirectURI: 'REDIRECT_URI',
|
|
63
|
+
softwareID: 'YOUR_APP_ID',
|
|
64
|
+
clientKind: 'mobile',
|
|
65
|
+
clientName: 'YOUR_APP_NAME',
|
|
66
|
+
shouldRequireFlagshipPermissions: true,
|
|
67
|
+
certificationConfig: {
|
|
68
|
+
androidSafetyNetApiKey: 'YOUR_GOOGLE_SAFETY_NET_API_KEY'
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Stacks' configuration
|
|
74
|
+
|
|
75
|
+
In order to certify an app, the `cozy-stack` needs to have the following data:
|
|
76
|
+
- App's package name
|
|
77
|
+
- The app's package name is set on the react-native project
|
|
78
|
+
- It should be put in `flagship.apk_package_names` in the `cozy-stack`'s configuration file
|
|
79
|
+
- iOS app's id
|
|
80
|
+
- The iOS app's id is the concatenation of the developper team's id and the app's package name as defined in the XCode project
|
|
81
|
+
- If team's id is `ABCDEFGHIJ` and package name is `io.cozy.some_app` then the resulting app's id should be `ABCDEFGHIJ.io.cozy.some_app`
|
|
82
|
+
- It should be put in `flagship.apple_app_ids` in the `cozy-stack`'s configuration file
|
|
83
|
+
- Android app's certificate digest
|
|
84
|
+
- The Android certificate digest can be found in the Google Play app's Console, in `Configuration/App integrity` page
|
|
85
|
+
- On local dev environment, the dev certificate location is:
|
|
86
|
+
- On a ReactNative project: `<project_root>/android/app/debug.keystore`
|
|
87
|
+
- On an Android Studio project:
|
|
88
|
+
- On OSX: `~/.android/debug.keystore`
|
|
89
|
+
- On Windows: `%UserProfile%\.android\debug.keystore`
|
|
90
|
+
- On a Xamarin Project:
|
|
91
|
+
- On OSX: `~/.local/share/Xamarin/Mono for Android/debug.keystore`
|
|
92
|
+
- On Windows: `%LocalAppData%\Xamarin\Mono for Android\debug.keystore`
|
|
93
|
+
- To extract the certificat digest from local dev certificate:
|
|
94
|
+
- `keytool -list -v -keystore ./android/app/debug.keystore -alias androiddebugkey -storepass android -keypass android | grep "SHA256: " | cut -d " " -f 3 | xxd -r -p | openssl base64`
|
|
95
|
+
- The certificate digest is the `App signing key certificate` in SHA-256 format converted from HEX to base64
|
|
96
|
+
- It should be put in `flagship.apk_certificate_digests` in the `cozy-stack`'s configuration file
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.certifyFlagship = void 0;
|
|
9
|
+
|
|
10
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
11
|
+
|
|
12
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
13
|
+
|
|
14
|
+
var _CozyClient = _interopRequireDefault(require("../CozyClient"));
|
|
15
|
+
|
|
16
|
+
var _storeAttestation = require("./store-attestation");
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Request a challenge from the Stack that can be used to request the app attestation from the app store
|
|
20
|
+
*
|
|
21
|
+
* @param {CozyClient} client - the CozyClient instance
|
|
22
|
+
* @returns {Promise<string>} - the Nonce string returned by the stack
|
|
23
|
+
*/
|
|
24
|
+
var getStackChallenge = /*#__PURE__*/function () {
|
|
25
|
+
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(client) {
|
|
26
|
+
var stackClient, result;
|
|
27
|
+
return _regenerator.default.wrap(function _callee$(_context) {
|
|
28
|
+
while (1) {
|
|
29
|
+
switch (_context.prev = _context.next) {
|
|
30
|
+
case 0:
|
|
31
|
+
_context.prev = 0;
|
|
32
|
+
stackClient = client.getStackClient();
|
|
33
|
+
_context.next = 4;
|
|
34
|
+
return stackClient.fetchJSON('POST', "/auth/clients/".concat(stackClient.oauthOptions.clientID, "/challenge"), null, {
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: stackClient.registrationAccessTokenToAuthHeader()
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
case 4:
|
|
41
|
+
result = _context.sent;
|
|
42
|
+
return _context.abrupt("return", result.nonce);
|
|
43
|
+
|
|
44
|
+
case 8:
|
|
45
|
+
_context.prev = 8;
|
|
46
|
+
_context.t0 = _context["catch"](0);
|
|
47
|
+
throw new Error('[FLAGSHIP_CERTIFICATION] Something went wrong while requesting a challenge from CozyStack:\n' + _context.t0.message);
|
|
48
|
+
|
|
49
|
+
case 11:
|
|
50
|
+
case "end":
|
|
51
|
+
return _context.stop();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}, _callee, null, [[0, 8]]);
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
return function getStackChallenge(_x) {
|
|
58
|
+
return _ref.apply(this, arguments);
|
|
59
|
+
};
|
|
60
|
+
}();
|
|
61
|
+
/**
|
|
62
|
+
* Give the app attestation to the Stack
|
|
63
|
+
*
|
|
64
|
+
* @param {AttestationResult} appAttestation - the app attestation that was returned by the app store
|
|
65
|
+
* @param {string} nonce - the Nonce string retrieved from the stack
|
|
66
|
+
* @param {CozyClient} client - the CozyClient instance
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
var giveAppAttestationToStack = /*#__PURE__*/function () {
|
|
71
|
+
var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(appAttestation, nonce, client) {
|
|
72
|
+
var platform, attestation, keyId, stackClient;
|
|
73
|
+
return _regenerator.default.wrap(function _callee2$(_context2) {
|
|
74
|
+
while (1) {
|
|
75
|
+
switch (_context2.prev = _context2.next) {
|
|
76
|
+
case 0:
|
|
77
|
+
_context2.prev = 0;
|
|
78
|
+
platform = appAttestation.platform, attestation = appAttestation.attestation, keyId = appAttestation.keyId;
|
|
79
|
+
stackClient = client.getStackClient();
|
|
80
|
+
_context2.next = 5;
|
|
81
|
+
return stackClient.fetchJSON('POST', "/auth/clients/".concat(stackClient.oauthOptions.clientID, "/attestation"), {
|
|
82
|
+
platform: platform,
|
|
83
|
+
attestation: attestation,
|
|
84
|
+
challenge: nonce,
|
|
85
|
+
keyId: keyId
|
|
86
|
+
}, {
|
|
87
|
+
headers: {
|
|
88
|
+
Authorization: stackClient.registrationAccessTokenToAuthHeader()
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
case 5:
|
|
93
|
+
_context2.next = 10;
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case 7:
|
|
97
|
+
_context2.prev = 7;
|
|
98
|
+
_context2.t0 = _context2["catch"](0);
|
|
99
|
+
throw new Error('[FLAGSHIP_CERTIFICATION] Something went wrong while giving attestation to CozyStack:\n' + _context2.t0.message);
|
|
100
|
+
|
|
101
|
+
case 10:
|
|
102
|
+
case "end":
|
|
103
|
+
return _context2.stop();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}, _callee2, null, [[0, 7]]);
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
return function giveAppAttestationToStack(_x2, _x3, _x4) {
|
|
110
|
+
return _ref2.apply(this, arguments);
|
|
111
|
+
};
|
|
112
|
+
}();
|
|
113
|
+
/**
|
|
114
|
+
* Verify app's identity and integrity so the Stack can trust it
|
|
115
|
+
* Verification is done on Stack side by using information from the app's store (Google Play or Apple AppStore)
|
|
116
|
+
*
|
|
117
|
+
* @param {CertificationConfig} certificationConfig - the required configuration to access the stores API
|
|
118
|
+
* @param {CozyClient} client - the CozyClient instance
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
var certifyFlagship = /*#__PURE__*/function () {
|
|
123
|
+
var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(certificationConfig, client) {
|
|
124
|
+
var stackChallengeNonce, appAttestation;
|
|
125
|
+
return _regenerator.default.wrap(function _callee3$(_context3) {
|
|
126
|
+
while (1) {
|
|
127
|
+
switch (_context3.prev = _context3.next) {
|
|
128
|
+
case 0:
|
|
129
|
+
if (certificationConfig) {
|
|
130
|
+
_context3.next = 2;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
throw new Error('[FLAGSHIP_CERTIFICATION] Certification configuration is not set');
|
|
135
|
+
|
|
136
|
+
case 2:
|
|
137
|
+
_context3.next = 4;
|
|
138
|
+
return getStackChallenge(client);
|
|
139
|
+
|
|
140
|
+
case 4:
|
|
141
|
+
stackChallengeNonce = _context3.sent;
|
|
142
|
+
_context3.next = 7;
|
|
143
|
+
return (0, _storeAttestation.getAppAttestationFromStore)(stackChallengeNonce, certificationConfig);
|
|
144
|
+
|
|
145
|
+
case 7:
|
|
146
|
+
appAttestation = _context3.sent;
|
|
147
|
+
_context3.next = 10;
|
|
148
|
+
return giveAppAttestationToStack(appAttestation, stackChallengeNonce, client);
|
|
149
|
+
|
|
150
|
+
case 10:
|
|
151
|
+
case "end":
|
|
152
|
+
return _context3.stop();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}, _callee3);
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
return function certifyFlagship(_x5, _x6) {
|
|
159
|
+
return _ref3.apply(this, arguments);
|
|
160
|
+
};
|
|
161
|
+
}();
|
|
162
|
+
|
|
163
|
+
exports.certifyFlagship = certifyFlagship;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.getAppAttestationFromStore = void 0;
|
|
9
|
+
|
|
10
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
11
|
+
|
|
12
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
13
|
+
|
|
14
|
+
var _reactNativeGoogleSafetynet = _interopRequireDefault(require("react-native-google-safetynet"));
|
|
15
|
+
|
|
16
|
+
//@ts-ignore
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Retrieve the app's attestation from the Google Play store
|
|
20
|
+
*
|
|
21
|
+
* @param {string} nonce - the Nonce string retrieved from the stack
|
|
22
|
+
* @param {CertificationConfig} certificationConfig - Configuration to access the stores certification API
|
|
23
|
+
* @returns {Promise<AttestationResult>} the app's attestation
|
|
24
|
+
*/
|
|
25
|
+
var getAppAttestationFromStore = /*#__PURE__*/function () {
|
|
26
|
+
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(nonce, certificationConfig) {
|
|
27
|
+
var attestationResult;
|
|
28
|
+
return _regenerator.default.wrap(function _callee$(_context) {
|
|
29
|
+
while (1) {
|
|
30
|
+
switch (_context.prev = _context.next) {
|
|
31
|
+
case 0:
|
|
32
|
+
_context.prev = 0;
|
|
33
|
+
_context.next = 3;
|
|
34
|
+
return _reactNativeGoogleSafetynet.default.sendAttestationRequestJWT(nonce, certificationConfig.androidSafetyNetApiKey);
|
|
35
|
+
|
|
36
|
+
case 3:
|
|
37
|
+
attestationResult = _context.sent;
|
|
38
|
+
return _context.abrupt("return", {
|
|
39
|
+
platform: 'android',
|
|
40
|
+
attestation: attestationResult
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
case 7:
|
|
44
|
+
_context.prev = 7;
|
|
45
|
+
_context.t0 = _context["catch"](0);
|
|
46
|
+
throw new Error('[FLAGSHIP_CERTIFICATION] Something went wrong while requesting an attestation from Google Safetynet:\n' + _context.t0.message);
|
|
47
|
+
|
|
48
|
+
case 10:
|
|
49
|
+
case "end":
|
|
50
|
+
return _context.stop();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, _callee, null, [[0, 7]]);
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
return function getAppAttestationFromStore(_x, _x2) {
|
|
57
|
+
return _ref.apply(this, arguments);
|
|
58
|
+
};
|
|
59
|
+
}();
|
|
60
|
+
|
|
61
|
+
exports.getAppAttestationFromStore = getAppAttestationFromStore;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.getAppAttestationFromStore = void 0;
|
|
9
|
+
|
|
10
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
11
|
+
|
|
12
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
13
|
+
|
|
14
|
+
var _reactNativeIos11Devicecheck = _interopRequireDefault(require("react-native-ios11-devicecheck"));
|
|
15
|
+
|
|
16
|
+
//@ts-ignore
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Retrieve the app's attestation from the Apple AppStore
|
|
20
|
+
*
|
|
21
|
+
* @param {string} nonce - the Nonce string retrieved from the stack
|
|
22
|
+
* @param {CertificationConfig} certificationConfig - Configuration to access the stores certification API
|
|
23
|
+
* @returns {Promise<AttestationResult>} the app's attestation
|
|
24
|
+
*/
|
|
25
|
+
var getAppAttestationFromStore = /*#__PURE__*/function () {
|
|
26
|
+
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(nonce, certificationConfig) {
|
|
27
|
+
var keyId, attestKey;
|
|
28
|
+
return _regenerator.default.wrap(function _callee$(_context) {
|
|
29
|
+
while (1) {
|
|
30
|
+
switch (_context.prev = _context.next) {
|
|
31
|
+
case 0:
|
|
32
|
+
_context.prev = 0;
|
|
33
|
+
_context.next = 3;
|
|
34
|
+
return _reactNativeIos11Devicecheck.default.generateKey();
|
|
35
|
+
|
|
36
|
+
case 3:
|
|
37
|
+
keyId = _context.sent;
|
|
38
|
+
_context.next = 6;
|
|
39
|
+
return _reactNativeIos11Devicecheck.default.attestKey(keyId, nonce);
|
|
40
|
+
|
|
41
|
+
case 6:
|
|
42
|
+
attestKey = _context.sent;
|
|
43
|
+
return _context.abrupt("return", {
|
|
44
|
+
platform: 'ios',
|
|
45
|
+
attestation: attestKey,
|
|
46
|
+
keyId: keyId
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
case 10:
|
|
50
|
+
_context.prev = 10;
|
|
51
|
+
_context.t0 = _context["catch"](0);
|
|
52
|
+
throw new Error('[FLAGSHIP_CERTIFICATION] Something went wrong while requesting an attestation from Apple DeviceCheck:\n' + _context.t0.message);
|
|
53
|
+
|
|
54
|
+
case 13:
|
|
55
|
+
case "end":
|
|
56
|
+
return _context.stop();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}, _callee, null, [[0, 10]]);
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
return function getAppAttestationFromStore(_x, _x2) {
|
|
63
|
+
return _ref.apply(this, arguments);
|
|
64
|
+
};
|
|
65
|
+
}();
|
|
66
|
+
|
|
67
|
+
exports.getAppAttestationFromStore = getAppAttestationFromStore;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.getAppAttestationFromStore = void 0;
|
|
9
|
+
|
|
10
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
11
|
+
|
|
12
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Retrieve the app's attestation from the app's store
|
|
16
|
+
* /!\ This is a mock implementation that should never be called
|
|
17
|
+
*
|
|
18
|
+
* @param {string} nonce - the Nonce string retrieved from the stack
|
|
19
|
+
* @param {CertificationConfig} certificationConfig - Configuration to access the stores certification API
|
|
20
|
+
* @returns {Promise<AttestationResult>} the app's attestation
|
|
21
|
+
*/
|
|
22
|
+
var validateAppMock = /*#__PURE__*/function () {
|
|
23
|
+
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(nonce, certificationConfig) {
|
|
24
|
+
return _regenerator.default.wrap(function _callee$(_context) {
|
|
25
|
+
while (1) {
|
|
26
|
+
switch (_context.prev = _context.next) {
|
|
27
|
+
case 0:
|
|
28
|
+
throw new Error("validateApp can only be called from a React Native container");
|
|
29
|
+
|
|
30
|
+
case 1:
|
|
31
|
+
case "end":
|
|
32
|
+
return _context.stop();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}, _callee);
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
return function validateAppMock(_x, _x2) {
|
|
39
|
+
return _ref.apply(this, arguments);
|
|
40
|
+
};
|
|
41
|
+
}();
|
|
42
|
+
|
|
43
|
+
var getAppAttestationFromStore = validateAppMock;
|
|
44
|
+
exports.getAppAttestationFromStore = getAppAttestationFromStore;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A JSON Web Signature
|
|
3
|
+
* @typedef {string} jws
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A JSON Web Signature
|
|
8
|
+
* @typedef {string} base64string
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* An app attestation from the app store
|
|
13
|
+
* @typedef {object} AttestationResult
|
|
14
|
+
* @property {string} platform
|
|
15
|
+
* @property {jws|base64string} attestation
|
|
16
|
+
* @property {string} [keyId]
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Configuration to access the stores certification API
|
|
21
|
+
* @typedef {object} CertificationConfig
|
|
22
|
+
* @property {string} androidSafetyNetApiKey
|
|
23
|
+
*/
|
|
24
|
+
"use strict";
|
package/dist/hooks/useQuery.js
CHANGED
|
@@ -103,8 +103,12 @@ var useQuery = function useQuery(queryDefinition, options) {
|
|
|
103
103
|
as: as
|
|
104
104
|
});
|
|
105
105
|
}, [as, client]);
|
|
106
|
+
var fetch = (0, _react.useCallback)(function () {
|
|
107
|
+
return client.query(definition, options);
|
|
108
|
+
}, [client, definition, options]);
|
|
106
109
|
return _objectSpread(_objectSpread({}, queryState), {}, {
|
|
107
|
-
fetchMore: fetchMore
|
|
110
|
+
fetchMore: fetchMore,
|
|
111
|
+
fetch: fetch
|
|
108
112
|
});
|
|
109
113
|
};
|
|
110
114
|
|
|
@@ -52,6 +52,46 @@ describe('use query', () => {
|
|
|
52
52
|
})
|
|
53
53
|
})
|
|
54
54
|
|
|
55
|
+
it('should share the same API than ObservableQuery', () => {
|
|
56
|
+
const {
|
|
57
|
+
hookResult: {
|
|
58
|
+
result: { current }
|
|
59
|
+
}
|
|
60
|
+
} = setupQuery({
|
|
61
|
+
queryOptions: {
|
|
62
|
+
as: 'simpsons'
|
|
63
|
+
},
|
|
64
|
+
queryDefinition: () => Q('io.cozy.simpsons')
|
|
65
|
+
})
|
|
66
|
+
expect(current).toMatchObject({
|
|
67
|
+
data: [
|
|
68
|
+
expect.objectContaining({ name: 'Homer' }),
|
|
69
|
+
expect.objectContaining({ name: 'Marge' })
|
|
70
|
+
],
|
|
71
|
+
definition: expect.objectContaining({
|
|
72
|
+
doctype: 'io.cozy.simpsons'
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
expect(Object.keys(current)).toEqual(
|
|
77
|
+
expect.objectContaining([
|
|
78
|
+
'id',
|
|
79
|
+
'definition',
|
|
80
|
+
'fetchStatus',
|
|
81
|
+
'lastFetch',
|
|
82
|
+
'lastUpdate',
|
|
83
|
+
'lastErrorUpdate',
|
|
84
|
+
'lastError',
|
|
85
|
+
'hasMore',
|
|
86
|
+
'count',
|
|
87
|
+
'data',
|
|
88
|
+
'bookmark',
|
|
89
|
+
'options',
|
|
90
|
+
'fetchMore',
|
|
91
|
+
'fetch'
|
|
92
|
+
])
|
|
93
|
+
)
|
|
94
|
+
})
|
|
55
95
|
it('should query through the client in context', () => {
|
|
56
96
|
const { client } = setupQuery({
|
|
57
97
|
queryOptions: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-client",
|
|
3
|
-
"version": "27.
|
|
3
|
+
"version": "27.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.node.js",
|
|
6
6
|
"browser": "dist/index.js",
|
|
@@ -66,8 +66,10 @@
|
|
|
66
66
|
"cozy-ui": ">34.0.0",
|
|
67
67
|
"react": "^16.7.0",
|
|
68
68
|
"react-native": "^0.65.0",
|
|
69
|
-
"react-native-
|
|
69
|
+
"react-native-google-safetynet": "0.0.4",
|
|
70
|
+
"react-native-inappbrowser-reborn": "^3.5.1",
|
|
71
|
+
"react-native-ios11-devicecheck": "https://github.com/cozy/react-native-devicecheck#app-attest-v0.1"
|
|
70
72
|
},
|
|
71
73
|
"sideEffects": false,
|
|
72
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "503b86d3aef51c8f9b684dbcffcdad49bd41ca5e"
|
|
73
75
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function getAppAttestationFromStore(nonce: string, certificationConfig: CertificationConfig): Promise<AttestationResult>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieve the app's attestation from the app's store
|
|
3
|
+
* /!\ This is a mock implementation that should never be called
|
|
4
|
+
*
|
|
5
|
+
* @param {string} nonce - the Nonce string retrieved from the stack
|
|
6
|
+
* @param {CertificationConfig} certificationConfig - Configuration to access the stores certification API
|
|
7
|
+
* @returns {Promise<AttestationResult>} the app's attestation
|
|
8
|
+
*/
|
|
9
|
+
export function getAppAttestationFromStore(nonce: string, certificationConfig: CertificationConfig): Promise<AttestationResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function getAppAttestationFromStore(nonce: string, certificationConfig: CertificationConfig): Promise<AttestationResult>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A JSON Web Signature
|
|
3
|
+
*/
|
|
4
|
+
type jws = string;
|
|
5
|
+
/**
|
|
6
|
+
* A JSON Web Signature
|
|
7
|
+
*/
|
|
8
|
+
type base64string = string;
|
|
9
|
+
/**
|
|
10
|
+
* An app attestation from the app store
|
|
11
|
+
*/
|
|
12
|
+
type AttestationResult = {
|
|
13
|
+
platform: string;
|
|
14
|
+
attestation: jws | base64string;
|
|
15
|
+
keyId?: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Configuration to access the stores certification API
|
|
19
|
+
*/
|
|
20
|
+
type CertificationConfig = {
|
|
21
|
+
androidSafetyNetApiKey: string;
|
|
22
|
+
};
|
|
@@ -52,7 +52,7 @@ export type Document = {
|
|
|
52
52
|
_type: string;
|
|
53
53
|
type: string;
|
|
54
54
|
};
|
|
55
|
-
export type PermissionVerb = "
|
|
55
|
+
export type PermissionVerb = "POST" | "DELETE" | "PUT" | "GET" | "ALL" | "PATCH";
|
|
56
56
|
export type PermissionItem = {
|
|
57
57
|
/**
|
|
58
58
|
* - ALL, GET, PUT, PATCH, DELETE, POST…
|