Skip to content

Commit a538b83

Browse files
author
pinarx
authored
PushSubscription validation (#591)
* Check pushSubscription details when validating a token: this should enable us to deal with permission changes where new push subscriptions are generated but the token is not validated
1 parent faa54d8 commit a538b83

File tree

2 files changed

+110
-12
lines changed

2 files changed

+110
-12
lines changed

packages/messaging/src/controllers/controller-interface.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@ export default class ControllerInterface {
114114
publicVapidKey: Uint8Array,
115115
tokenDetails: Object
116116
): Promise<string> {
117-
const isTokenValid = await this.isTokenStillValid(tokenDetails);
117+
const isTokenValid = this.isTokenStillValid(
118+
pushSubscription,
119+
publicVapidKey,
120+
tokenDetails
121+
);
118122
if (isTokenValid) {
119123
const now = Date.now();
120124
if (now < tokenDetails['createTime'] + TOKEN_EXPIRATION_MILLIS) {
@@ -138,14 +142,24 @@ export default class ControllerInterface {
138142
/*
139143
* Checks if the tokenDetails match the details provided in the clients.
140144
*/
141-
private isTokenStillValid(tokenDetails: Object): Promise<Boolean> {
142-
// TODO Validate rest of the details.
143-
return this.getPublicVapidKey_().then(publicKey => {
144-
if (arrayBufferToBase64(publicKey) !== tokenDetails['vapidKey']) {
145-
return false;
146-
}
147-
return true;
148-
});
145+
private isTokenStillValid(
146+
pushSubscription: PushSubscription,
147+
publicVapidKey: Uint8Array,
148+
tokenDetails: Object
149+
): Boolean {
150+
if (arrayBufferToBase64(publicVapidKey) !== tokenDetails['vapidKey']) {
151+
return false;
152+
}
153+
154+
// getKey() isn't defined in the PushSubscription externs file, hence
155+
// subscription['getKey']('<key name>').
156+
return (
157+
pushSubscription.endpoint === tokenDetails['endpoint'] &&
158+
arrayBufferToBase64(pushSubscription['getKey']('auth')) ===
159+
tokenDetails['auth'] &&
160+
arrayBufferToBase64(pushSubscription['getKey']('p256dh')) ===
161+
tokenDetails['p256dh']
162+
);
149163
}
150164

151165
private async updateToken(

packages/messaging/test/controller-get-token.test.ts

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
3939
const sandbox = sinon.sandbox.create();
4040
const now = Date.now();
4141
const expiredDate = new Date(now - ONE_DAY);
42+
const FAKE_SUBSCRIPTION = makeFakeSubscription();
4243

4344
const EXAMPLE_FCM_TOKEN = 'ExampleFCMToken1337';
4445
const EXAMPLE_SENDER_ID = '1234567890';
@@ -52,7 +53,9 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
5253
const EXAMPLE_TOKEN_DETAILS_DEFAULT_VAPID = {
5354
swScope: '/example-scope',
5455
vapidKey: DEFAULT_VAPID_KEY,
55-
subscription: makeFakeSubscription(),
56+
endpoint: FAKE_SUBSCRIPTION.endpoint,
57+
auth: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('auth')),
58+
p256dh: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('p256dh')),
5659
fcmSenderId: EXAMPLE_SENDER_ID,
5760
fcmToken: 'qwerty1',
5861
fcmPushSet: '87654321',
@@ -61,7 +64,9 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
6164
const EXAMPLE_TOKEN_DETAILS_CUSTOM_VAPID = {
6265
swScope: '/example-scope',
6366
vapidKey: CUSTOM_VAPID_KEY,
64-
subscription: makeFakeSubscription(),
67+
endpoint: FAKE_SUBSCRIPTION.endpoint,
68+
auth: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('auth')),
69+
p256dh: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('p256dh')),
6570
fcmSenderId: EXAMPLE_SENDER_ID,
6671
fcmToken: 'qwerty2',
6772
fcmPushSet: '7654321',
@@ -70,7 +75,9 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
7075
const EXAMPLE_EXPIRED_TOKEN_DETAILS = {
7176
swScope: '/example-scope',
7277
vapidKey: DEFAULT_VAPID_KEY,
73-
subscription: makeFakeSubscription(),
78+
endpoint: FAKE_SUBSCRIPTION.endpoint,
79+
auth: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('auth')),
80+
p256dh: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('p256dh')),
7481
fcmSenderId: EXAMPLE_SENDER_ID,
7582
fcmToken: 'qwerty3',
7683
fcmPushSet: '654321',
@@ -399,6 +406,83 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
399406
assert.equal(tokenModelArgs.fcmPushSet, TOKEN_DETAILS['pushSet']);
400407
});
401408
});
409+
410+
it('should get a new token in ${ServiceClass.name} if PushSubscription details have changed', function() {
411+
// Stubs
412+
const deleteTokenStub = sandbox.stub(
413+
TokenDetailsModel.prototype,
414+
'deleteToken'
415+
);
416+
const saveTokenDetailsStub = sandbox.stub(
417+
TokenDetailsModel.prototype,
418+
'saveTokenDetails'
419+
);
420+
saveTokenDetailsStub.callsFake(async () => {});
421+
422+
sandbox
423+
.stub(ControllerInterface.prototype, 'getNotificationPermission_')
424+
.callsFake(() => NotificationPermission.granted);
425+
426+
const existingTokenDetails = VapidSetup['details'];
427+
let vapidKeyToUse = FCMDetails.DEFAULT_PUBLIC_VAPID_KEY;
428+
if (VapidSetup['name'] === 'custom') {
429+
vapidKeyToUse = base64ToArrayBuffer(CUSTOM_VAPID_KEY);
430+
}
431+
sandbox
432+
.stub(ServiceClass.prototype, 'getPublicVapidKey_')
433+
.callsFake(() => Promise.resolve(vapidKeyToUse));
434+
435+
sandbox
436+
.stub(VapidDetailsModel.prototype, 'saveVapidDetails')
437+
.callsFake(async () => {});
438+
439+
const GET_TOKEN_RESPONSE = {
440+
token: 'new-token',
441+
pushSet: 'new-pushSet'
442+
};
443+
sandbox
444+
.stub(IIDModel.prototype, 'getToken')
445+
.callsFake(() => Promise.resolve(GET_TOKEN_RESPONSE));
446+
sandbox
447+
.stub(IIDModel.prototype, 'deleteToken')
448+
.callsFake(async () => {});
449+
450+
const registration = generateFakeReg(Promise.resolve(null));
451+
mockGetReg(Promise.resolve(registration));
452+
453+
const options = {
454+
endpoint: 'https://different-push-endpoint.com/',
455+
auth: 'another-auth-secret',
456+
p256dh: 'another-user-public-key'
457+
};
458+
const newPS = makeFakeSubscription(options);
459+
460+
deleteTokenStub.callsFake(token => {
461+
assert.equal(token, existingTokenDetails.fcmToken);
462+
return Promise.resolve(existingTokenDetails);
463+
});
464+
// The push subscription has changed since we saved the token.
465+
sandbox
466+
.stub(ServiceClass.prototype, 'getPushSubscription')
467+
.callsFake(() => Promise.resolve(newPS));
468+
sandbox
469+
.stub(TokenDetailsModel.prototype, 'getTokenDetailsFromSWScope')
470+
.callsFake(() => Promise.resolve(existingTokenDetails));
471+
472+
const serviceInstance = new ServiceClass(app);
473+
return serviceInstance.getToken().then(token => {
474+
// make sure we call getToken and retrieve the new token.
475+
assert.equal('new-token', token);
476+
// make sure the existing token is deleted.
477+
assert.equal(deleteTokenStub.callCount, 1);
478+
// make sure the new details are saved.
479+
assert.equal(saveTokenDetailsStub.callCount, 1);
480+
assert.equal(
481+
VapidDetailsModel.prototype.saveVapidDetails['callCount'],
482+
1
483+
);
484+
});
485+
});
402486
});
403487
});
404488

0 commit comments

Comments
 (0)