Skip to content

PushSubscription validation #591

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions packages/firebase/app/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ declare namespace firebase.auth {
applyActionCode(code: string): Promise<any>;
checkActionCode(code: string): Promise<any>;
confirmPasswordReset(code: string, newPassword: string): Promise<any>;
createUserAndRetrieveDataWithEmailAndPassword(
email: string,
password: string
): Promise<any>;
createUserAndRetrieveDataWithEmailAndPassword(
email: string,
password: string
): Promise<any>;
createUserWithEmailAndPassword(
email: string,
password: string
Expand Down Expand Up @@ -202,7 +202,10 @@ declare namespace firebase.auth {
signInWithCustomToken(token: string): Promise<any>;
signInAndRetrieveDataWithCustomToken(token: string): Promise<any>;
signInWithEmailAndPassword(email: string, password: string): Promise<any>;
signInAndRetrieveDataWithEmailAndPassword(email: string, password: string): Promise<any>;
signInAndRetrieveDataWithEmailAndPassword(
email: string,
password: string
): Promise<any>;
signInWithPhoneNumber(
phoneNumber: string,
applicationVerifier: firebase.auth.ApplicationVerifier
Expand Down
32 changes: 23 additions & 9 deletions packages/messaging/src/controllers/controller-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ export default class ControllerInterface {
publicVapidKey: Uint8Array,
tokenDetails: Object
): Promise<string> {
const isTokenValid = await this.isTokenStillValid(tokenDetails);
const isTokenValid = this.isTokenStillValid(
pushSubscription,
publicVapidKey,
tokenDetails
);
if (isTokenValid) {
const now = Date.now();
if (now < tokenDetails['createTime'] + TOKEN_EXPIRATION_MILLIS) {
Expand All @@ -138,14 +142,24 @@ export default class ControllerInterface {
/*
* Checks if the tokenDetails match the details provided in the clients.
*/
private isTokenStillValid(tokenDetails: Object): Promise<Boolean> {
// TODO Validate rest of the details.
return this.getPublicVapidKey_().then(publicKey => {
if (arrayBufferToBase64(publicKey) !== tokenDetails['vapidKey']) {
return false;
}
return true;
});
private isTokenStillValid(
pushSubscription: PushSubscription,
publicVapidKey: Uint8Array,
tokenDetails: Object
): Boolean {
if (arrayBufferToBase64(publicVapidKey) !== tokenDetails['vapidKey']) {
return false;
}

// getKey() isn't defined in the PushSubscription externs file, hence
// subscription['getKey']('<key name>').
return (
pushSubscription.endpoint === tokenDetails['endpoint'] &&
arrayBufferToBase64(pushSubscription['getKey']('auth')) ===
tokenDetails['auth'] &&
arrayBufferToBase64(pushSubscription['getKey']('p256dh')) ===
tokenDetails['p256dh']
);
}

private async updateToken(
Expand Down
90 changes: 87 additions & 3 deletions packages/messaging/test/controller-get-token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
const sandbox = sinon.sandbox.create();
const now = Date.now();
const expiredDate = new Date(now - ONE_DAY);
const FAKE_SUBSCRIPTION = makeFakeSubscription();

const EXAMPLE_FCM_TOKEN = 'ExampleFCMToken1337';
const EXAMPLE_SENDER_ID = '1234567890';
Expand All @@ -52,7 +53,9 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
const EXAMPLE_TOKEN_DETAILS_DEFAULT_VAPID = {
swScope: '/example-scope',
vapidKey: DEFAULT_VAPID_KEY,
subscription: makeFakeSubscription(),
endpoint: FAKE_SUBSCRIPTION.endpoint,
auth: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('auth')),
p256dh: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('p256dh')),
fcmSenderId: EXAMPLE_SENDER_ID,
fcmToken: 'qwerty1',
fcmPushSet: '87654321',
Expand All @@ -61,7 +64,9 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
const EXAMPLE_TOKEN_DETAILS_CUSTOM_VAPID = {
swScope: '/example-scope',
vapidKey: CUSTOM_VAPID_KEY,
subscription: makeFakeSubscription(),
endpoint: FAKE_SUBSCRIPTION.endpoint,
auth: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('auth')),
p256dh: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('p256dh')),
fcmSenderId: EXAMPLE_SENDER_ID,
fcmToken: 'qwerty2',
fcmPushSet: '7654321',
Expand All @@ -70,7 +75,9 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
const EXAMPLE_EXPIRED_TOKEN_DETAILS = {
swScope: '/example-scope',
vapidKey: DEFAULT_VAPID_KEY,
subscription: makeFakeSubscription(),
endpoint: FAKE_SUBSCRIPTION.endpoint,
auth: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('auth')),
p256dh: arrayBufferToBase64(FAKE_SUBSCRIPTION['getKey']('p256dh')),
fcmSenderId: EXAMPLE_SENDER_ID,
fcmToken: 'qwerty3',
fcmPushSet: '654321',
Expand Down Expand Up @@ -399,6 +406,83 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
assert.equal(tokenModelArgs.fcmPushSet, TOKEN_DETAILS['pushSet']);
});
});

it('should get a new token in ${ServiceClass.name} if PushSubscription details have changed', function() {
// Stubs
const deleteTokenStub = sandbox.stub(
TokenDetailsModel.prototype,
'deleteToken'
);
const saveTokenDetailsStub = sandbox.stub(
TokenDetailsModel.prototype,
'saveTokenDetails'
);
saveTokenDetailsStub.callsFake(async () => {});

sandbox
.stub(ControllerInterface.prototype, 'getNotificationPermission_')
.callsFake(() => NotificationPermission.granted);

const existingTokenDetails = VapidSetup['details'];
let vapidKeyToUse = FCMDetails.DEFAULT_PUBLIC_VAPID_KEY;
if (VapidSetup['name'] === 'custom') {
vapidKeyToUse = base64ToArrayBuffer(CUSTOM_VAPID_KEY);
}
sandbox
.stub(ServiceClass.prototype, 'getPublicVapidKey_')
.callsFake(() => Promise.resolve(vapidKeyToUse));

sandbox
.stub(VapidDetailsModel.prototype, 'saveVapidDetails')
.callsFake(async () => {});

const GET_TOKEN_RESPONSE = {
token: 'new-token',
pushSet: 'new-pushSet'
};
sandbox
.stub(IIDModel.prototype, 'getToken')
.callsFake(() => Promise.resolve(GET_TOKEN_RESPONSE));
sandbox
.stub(IIDModel.prototype, 'deleteToken')
.callsFake(async () => {});

const registration = generateFakeReg(Promise.resolve(null));
mockGetReg(Promise.resolve(registration));

const options = {
endpoint: 'https://different-push-endpoint.com/',
auth: 'another-auth-secret',
p256dh: 'another-user-public-key'
};
const newPS = makeFakeSubscription(options);

deleteTokenStub.callsFake(token => {
assert.equal(token, existingTokenDetails.fcmToken);
return Promise.resolve(existingTokenDetails);
});
// The push subscription has changed since we saved the token.
sandbox
.stub(ServiceClass.prototype, 'getPushSubscription')
.callsFake(() => Promise.resolve(newPS));
sandbox
.stub(TokenDetailsModel.prototype, 'getTokenDetailsFromSWScope')
.callsFake(() => Promise.resolve(existingTokenDetails));

const serviceInstance = new ServiceClass(app);
return serviceInstance.getToken().then(token => {
// make sure we call getToken and retrieve the new token.
assert.equal('new-token', token);
// make sure the existing token is deleted.
assert.equal(deleteTokenStub.callCount, 1);
// make sure the new details are saved.
assert.equal(saveTokenDetailsStub.callCount, 1);
assert.equal(
VapidDetailsModel.prototype.saveVapidDetails['callCount'],
1
);
});
});
});
});

Expand Down