Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

Commit

Permalink
feat(oauth): notify push and email on code exchanges
Browse files Browse the repository at this point in the history
Fixes #2880
Fixes #2955
  • Loading branch information
vladikoff committed Mar 28, 2019
1 parent 57f5891 commit 4bb82c1
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 10 deletions.
2 changes: 1 addition & 1 deletion lib/devices.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ module.exports = (log, db, push) => {
deviceName = synthesizeName(deviceInfo);
}
if (credentials.tokenVerified) {
request.app.devices.then(devices => {
db.devices(credentials.uid).then(devices => {
const otherDevices = devices.filter(device => device.id !== result.id);
return push.notifyDeviceConnected(credentials.uid, otherDevices, deviceName);
});
Expand Down
26 changes: 26 additions & 0 deletions lib/oauthdb/check-access-token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict'

const Joi = require('joi')
const validators = require('../routes/validators')

module.exports = (config) => {
return {
path: '/v1/verify',
method: 'POST',
validate: {
payload: {
token: validators.accessToken.required(),
},
response: {
user: Joi.string().required(),
client_id: Joi.string().required(),
scope: Joi.array(),
profile_changed_at: Joi.number().min(0)
}
}
}
}
12 changes: 9 additions & 3 deletions lib/oauthdb/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module.exports = (log, config) => {
grantTokensFromAuthorizationCode: require('./grant-tokens-from-authorization-code')(config),
grantTokensFromRefreshToken: require('./grant-tokens-from-refresh-token')(config),
grantTokensFromCredentials: require('./grant-tokens-from-credentials')(config),
checkAccessToken: require('./check-access-token')(config),
});

const api = new OAuthAPI(config.oauth.url, config.oauth.poolee);
Expand Down Expand Up @@ -115,16 +116,21 @@ module.exports = (log, config) => {
}
},

async checkAccessToken(token) {
try {
return await api.checkAccessToken(token)
} catch (err) {
throw mapOAuthError(log, err)
}
}

/* As we work through the process of merging oauth-server
* into auth-server, future methods we might want to include
* here will be things like the following:
async getClientInstances(account) {
},
async checkAccessToken(token) {
}
async revokeAccessToken(token) {
}
Expand Down
2 changes: 1 addition & 1 deletion lib/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ module.exports = function (
signinUtils,
push
);
const oauth = require('./oauth')(log, config, oauthdb);
const oauth = require('./oauth')(log, config, oauthdb, db, mailer, devicesImpl);
const devicesSessions = require('./devices-and-sessions')(log, db, config, customs, push, pushbox, devicesImpl, oauthdb);
const emails = require('./emails')(log, db, mailer, config, customs, push);
const password = require('./password')(
Expand Down
21 changes: 17 additions & 4 deletions lib/routes/oauth.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
const Joi = require('joi');

const error = require('../error');
const oauthRouteUtils = require('./utils/oauth');

module.exports = (log, config, oauthdb) => {
module.exports = (log, config, oauthdb, db, mailer, devices) => {
const routes = [
{
method: 'GET',
Expand Down Expand Up @@ -110,19 +111,31 @@ module.exports = (log, config, oauthdb) => {
},
handler: async function (request) {
const sessionToken = request.auth.credentials;
let grant;
switch (request.payload.grant_type) {
case 'authorization_code':
return await oauthdb.grantTokensFromAuthorizationCode(request.payload);
grant = await oauthdb.grantTokensFromAuthorizationCode(request.payload);
break;
case 'refresh_token':
return await oauthdb.grantTokensFromRefreshToken(request.payload);
grant = await oauthdb.grantTokensFromRefreshToken(request.payload);
break;
case 'fxa-credentials':
if (! sessionToken) {
throw error.invalidToken();
}
return await oauthdb.grantTokensFromSessionToken(sessionToken, request.payload);
grant = await oauthdb.grantTokensFromSessionToken(sessionToken, request.payload);
break;
default:
throw error.internalValidationError();
}

switch (request.payload.grant_type) {
case 'authorization_code':
case 'fxa-assertion':
await oauthRouteUtils.newTokenNotification(db, oauthdb, mailer, devices, request, sessionToken, grant)
}

return grant;
}
},
];
Expand Down
55 changes: 55 additions & 0 deletions lib/routes/utils/oauth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict'

const ScopeSet = require('fxa-shared').oauth.scopes

// right now we only care about notifications for the following scopes
// if not a match, then we don't notify
const NOTIFICATION_SCOPES = ScopeSet.fromArray(['https://identity.mozilla.com/apps/oldsync'])

module.exports = {
newTokenNotification: async function newTokenNotification (db, oauthdb, mailer, devices, request, credentials, grant) {
const scopeSet = ScopeSet.fromString(grant.scope)

if (! scopeSet.intersects(NOTIFICATION_SCOPES)) {
// right now we only care about notifications for the `oldsync` scope
// if not a match, then we don't do any notifications
return
}

const tokenVerify = await oauthdb.checkAccessToken({
token: grant.access_token
})

if (! credentials) {
credentials = {}
}

const uid = tokenVerify.user
credentials.uid = uid
credentials.tokenVerified = true // XXX TODO: check this?
credentials.refreshTokenId = grant.refresh_token
credentials.client = await oauthdb.getClientInfo(tokenVerify.client_id)

await devices.upsert(request, credentials, {})

const geoData = request.app.geo
const ip = request.app.clientAddress
const service = request.payload.service || request.query.service || tokenVerify.client_id

const emailOptions = {
acceptLanguage: request.app.acceptLanguage,
ip: ip,
location: geoData.location,
service: service,
timeZone: geoData.timeZone,
uid: credentials.uid
}

const account = await db.account(uid)
await mailer.sendNewDeviceLoginNotification(account.emails, account, emailOptions)
}
}
12 changes: 12 additions & 0 deletions test/client/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,18 @@ module.exports = config => {
);
};

ClientApi.prototype.grantTokensFromSessionToken = function (sessionTokenHex, oauthParams) {
return tokens.SessionToken.fromHex(sessionTokenHex)
.then((token) => {
return this.doRequest(
'POST',
`${this.baseURL}/oauth/token`,
token,
oauthParams
)
})
}

ClientApi.heartbeat = function (origin) {
return (new ClientApi(origin)).doRequest('GET', `${origin }/__heartbeat__`);
};
Expand Down
6 changes: 5 additions & 1 deletion test/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -692,5 +692,9 @@ module.exports = config => {
return this.api.grantOAuthTokens(oauthParams);
};

Client.prototype.grantTokensFromSessionToken = function (oauthParams) {
return this.api.grantTokensFromSessionToken(this.sessionToken, oauthParams);
}

return Client;
};
}

0 comments on commit 4bb82c1

Please sign in to comment.