Skip to content

Commit bf02676

Browse files
authored
feat(app-check): Expose modular API that matches the Firebase web JS SDK v9 API (#6912)
1 parent 67eb1f6 commit bf02676

5 files changed

Lines changed: 414 additions & 5 deletions

File tree

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
/*
2+
* Copyright (c) 2016-present Invertase Limited & Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this library except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
const jwt = require('jsonwebtoken');
19+
20+
describe('appCheck() modular', function () {
21+
describe('firebase v8 compatibility', function () {
22+
before(function () {
23+
rnfbProvider = firebase.appCheck().newReactNativeFirebaseAppCheckProvider();
24+
rnfbProvider.configure({
25+
android: {
26+
provider: 'debug',
27+
debugToken: '698956B2-187B-49C6-9E25-C3F3530EEBAF',
28+
},
29+
apple: {
30+
provider: 'debug',
31+
debugToken: '698956B2-187B-49C6-9E25-C3F3530EEBAF',
32+
},
33+
web: {
34+
provider: 'debug',
35+
siteKey: 'none',
36+
},
37+
});
38+
39+
// Our tests configure a debug provider with shared secret so we should get a valid token
40+
firebase
41+
.appCheck()
42+
.initializeAppCheck({ provider: rnfbProvider, isTokenAutoRefreshEnabled: false });
43+
});
44+
45+
describe('config', function () {
46+
// This depends on firebase.json containing a false value for token auto refresh, we
47+
// verify here that it was carried in to the Info.plist correctly
48+
// it relies on token auto refresh being left false for local tests where the app is reused, since it is persistent
49+
// but in CI it's fresh every time and would be true if not overridden in Info.plist
50+
it('should configure token auto refresh in Info.plist on ios', async function () {
51+
if (device.getPlatform() === 'ios') {
52+
const tokenRefresh = await NativeModules.RNFBAppCheckModule.isTokenAutoRefreshEnabled(
53+
'[DEFAULT]',
54+
);
55+
tokenRefresh.should.equal(false);
56+
} else {
57+
this.skip();
58+
}
59+
});
60+
});
61+
62+
describe('setTokenAutoRefresh())', function () {
63+
it('should set token refresh', async function () {
64+
firebase.appCheck().setTokenAutoRefreshEnabled(false);
65+
66+
// Only iOS lets us assert on this unfortunately, other platforms have no accessor
67+
if (device.getPlatform() === 'ios') {
68+
let tokenRefresh = await NativeModules.RNFBAppCheckModule.isTokenAutoRefreshEnabled(
69+
'[DEFAULT]',
70+
);
71+
tokenRefresh.should.equal(false);
72+
}
73+
firebase.appCheck().setTokenAutoRefreshEnabled(true);
74+
// Only iOS lets us assert on this unfortunately, other platforms have no accessor
75+
if (device.getPlatform() === 'ios') {
76+
tokenRefresh = await NativeModules.RNFBAppCheckModule.isTokenAutoRefreshEnabled(
77+
'[DEFAULT]',
78+
);
79+
tokenRefresh.should.equal(true);
80+
}
81+
});
82+
});
83+
84+
describe('getToken())', function () {
85+
it('token fetch attempt with configured debug token should work', async function () {
86+
const { token } = await firebase.appCheck().getToken(true);
87+
token.should.not.equal('');
88+
const decodedToken = jwt.decode(token);
89+
decodedToken.aud[1].should.equal('projects/react-native-firebase-testing');
90+
if (decodedToken.exp < Date.now()) {
91+
Promise.reject('Token already expired');
92+
}
93+
94+
// on android if you move too fast, you may not get a fresh token
95+
await Utils.sleep(2000);
96+
97+
// Force refresh should get a different token?
98+
// TODO sometimes fails on android https://github.com/firebase/firebase-android-sdk/issues/2954
99+
const { token: token2 } = await firebase.appCheck().getToken(true);
100+
token2.should.not.equal('');
101+
const decodedToken2 = jwt.decode(token2);
102+
decodedToken2.aud[1].should.equal('projects/react-native-firebase-testing');
103+
if (decodedToken2.exp < Date.now()) {
104+
Promise.reject('Token already expired');
105+
}
106+
(token === token2).should.be.false();
107+
});
108+
109+
it('token fetch with config switch to invalid then valid should fail then work', async function () {
110+
rnfbProvider = firebase.appCheck().newReactNativeFirebaseAppCheckProvider();
111+
rnfbProvider.configure({
112+
android: {
113+
provider: 'playIntegrity',
114+
},
115+
apple: {
116+
provider: 'appAttest',
117+
},
118+
web: {
119+
provider: 'debug',
120+
siteKey: 'none',
121+
},
122+
});
123+
124+
// Our tests configure a debug provider with shared secret so we should get a valid token
125+
firebase
126+
.appCheck()
127+
.initializeAppCheck({ provider: rnfbProvider, isTokenAutoRefreshEnabled: false });
128+
try {
129+
await firebase.appCheck().getToken(true);
130+
return Promise.reject(new Error('should have thrown an error'));
131+
} catch (e) {
132+
e.message.should.containEql('appCheck/token-error');
133+
}
134+
135+
rnfbProvider.configure({
136+
android: {
137+
provider: 'debug',
138+
debugToken: '698956B2-187B-49C6-9E25-C3F3530EEBAF',
139+
},
140+
apple: {
141+
provider: 'debug',
142+
},
143+
web: {
144+
provider: 'debug',
145+
siteKey: 'none',
146+
},
147+
});
148+
firebase
149+
.appCheck()
150+
.initializeAppCheck({ provider: rnfbProvider, isTokenAutoRefreshEnabled: false });
151+
152+
const { token } = await firebase.appCheck().getToken(true);
153+
token.should.not.equal('');
154+
const decodedToken = jwt.decode(token);
155+
decodedToken.aud[1].should.equal('projects/react-native-firebase-testing');
156+
if (decodedToken.exp < Date.now()) {
157+
Promise.reject('Token already expired');
158+
}
159+
});
160+
});
161+
162+
describe('activate())', function () {
163+
it('should activate with default provider and defined token refresh', function () {
164+
firebase
165+
.appCheck()
166+
.activate('ignored', false)
167+
.then(value => expect(value).toBe(undefined))
168+
.catch(e => new Error('app-check activate failed? ' + e));
169+
});
170+
171+
it('should error if activate gets no parameters', async function () {
172+
try {
173+
firebase.appCheck().activate();
174+
return Promise.reject(new Error('should have thrown an error'));
175+
} catch (e) {
176+
e.message.should.containEql('siteKeyOrProvider must be a string value');
177+
}
178+
});
179+
180+
it('should not error if activate called with no token refresh value', async function () {
181+
try {
182+
firebase.appCheck().activate('ignored');
183+
return Promise.resolve(true);
184+
} catch (e) {
185+
return Promise.reject(new Error('should not have thrown an error - ' + e));
186+
}
187+
});
188+
});
189+
});
190+
191+
describe('modular', function () {
192+
var appCheckInstance;
193+
before(async function () {
194+
const { initializeAppCheck } = appCheckModular;
195+
196+
rnfbProvider = firebase.appCheck().newReactNativeFirebaseAppCheckProvider();
197+
rnfbProvider.configure({
198+
android: {
199+
provider: 'debug',
200+
debugToken: '698956B2-187B-49C6-9E25-C3F3530EEBAF',
201+
},
202+
apple: {
203+
provider: 'debug',
204+
debugToken: '698956B2-187B-49C6-9E25-C3F3530EEBAF',
205+
},
206+
web: {
207+
provider: 'debug',
208+
siteKey: 'none',
209+
},
210+
});
211+
212+
// Our tests configure a debug provider with shared secret so we should get a valid token
213+
appCheckInstance = await initializeAppCheck(undefined, {
214+
provider: rnfbProvider,
215+
isTokenAutoRefreshEnabled: false,
216+
});
217+
});
218+
219+
describe('setTokenAutoRefresh())', function () {
220+
it('should set token refresh', async function () {
221+
const { setTokenAutoRefreshEnabled } = appCheckModular;
222+
223+
setTokenAutoRefreshEnabled(appCheckInstance, false);
224+
// Only iOS lets us assert on this unfortunately, other platforms have no accessor
225+
if (device.getPlatform() === 'ios') {
226+
let tokenRefresh = await NativeModules.RNFBAppCheckModule.isTokenAutoRefreshEnabled(
227+
'[DEFAULT]',
228+
);
229+
tokenRefresh.should.equal(false);
230+
}
231+
setTokenAutoRefreshEnabled(appCheckInstance, true);
232+
// Only iOS lets us assert on this unfortunately, other platforms have no accessor
233+
if (device.getPlatform() === 'ios') {
234+
tokenRefresh = await NativeModules.RNFBAppCheckModule.isTokenAutoRefreshEnabled(
235+
'[DEFAULT]',
236+
);
237+
tokenRefresh.should.equal(true);
238+
}
239+
});
240+
});
241+
242+
describe('getToken())', function () {
243+
it('token fetch attempt with configured debug token should work', async function () {
244+
const { getToken } = appCheckModular;
245+
246+
const { token } = await getToken(appCheckInstance, true);
247+
token.should.not.equal('');
248+
const decodedToken = jwt.decode(token);
249+
decodedToken.aud[1].should.equal('projects/react-native-firebase-testing');
250+
if (decodedToken.exp < Date.now()) {
251+
Promise.reject('Token already expired');
252+
}
253+
254+
// on android if you move too fast, you may not get a fresh token
255+
await Utils.sleep(2000);
256+
257+
// Force refresh should get a different token?
258+
// TODO sometimes fails on android https://github.com/firebase/firebase-android-sdk/issues/2954
259+
const { token: token2 } = await getToken(appCheckInstance, true);
260+
token2.should.not.equal('');
261+
const decodedToken2 = jwt.decode(token2);
262+
decodedToken2.aud[1].should.equal('projects/react-native-firebase-testing');
263+
if (decodedToken2.exp < Date.now()) {
264+
Promise.reject('Token already expired');
265+
}
266+
(token === token2).should.be.false();
267+
});
268+
269+
it('token fetch with config switch to invalid then valid should fail then work', async function () {
270+
const { initializeAppCheck, getToken } = appCheckModular;
271+
272+
rnfbProvider = firebase.appCheck().newReactNativeFirebaseAppCheckProvider();
273+
rnfbProvider.configure({
274+
android: {
275+
provider: 'playIntegrity',
276+
},
277+
apple: {
278+
provider: 'appAttest',
279+
},
280+
web: {
281+
provider: 'debug',
282+
siteKey: 'none',
283+
},
284+
});
285+
286+
// Our tests configure a debug provider with shared secret so we should get a valid token
287+
const instance1 = await initializeAppCheck(undefined, {
288+
provider: rnfbProvider,
289+
isTokenAutoRefreshEnabled: false,
290+
});
291+
try {
292+
await getToken(instance1, true);
293+
return Promise.reject(new Error('should have thrown an error'));
294+
} catch (e) {
295+
e.message.should.containEql('appCheck/token-error');
296+
}
297+
298+
rnfbProvider.configure({
299+
android: {
300+
provider: 'debug',
301+
debugToken: '698956B2-187B-49C6-9E25-C3F3530EEBAF',
302+
},
303+
apple: {
304+
provider: 'debug',
305+
},
306+
web: {
307+
provider: 'debug',
308+
siteKey: 'none',
309+
},
310+
});
311+
const instance2 = await initializeAppCheck(undefined, {
312+
provider: rnfbProvider,
313+
isTokenAutoRefreshEnabled: false,
314+
});
315+
316+
const { token } = await getToken(instance2, true);
317+
token.should.not.equal('');
318+
const decodedToken = jwt.decode(token);
319+
decodedToken.aud[1].should.equal('projects/react-native-firebase-testing');
320+
if (decodedToken.exp < Date.now()) {
321+
Promise.reject('Token already expired');
322+
}
323+
});
324+
});
325+
});
326+
});

packages/app-check/lib/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ import ReactNativeFirebaseAppCheckProvider from './ReactNativeFirebaseAppCheckPr
2626

2727
import version from './version';
2828

29+
export {
30+
addTokenListener,
31+
getToken,
32+
initializeAppCheck,
33+
setTokenAutoRefreshEnabled,
34+
} from './modular/index';
35+
2936
const statics = {};
3037

3138
const namespace = 'appCheck';

0 commit comments

Comments
 (0)