implemented PKCE auth (#921)

* implemented PKCE auth

* removed node-jose

* added PKCE tests
This commit is contained in:
Danny Coates 2018-09-14 08:00:33 -07:00 committed by Donovan Preston
parent 20528eb0d1
commit 7ccf462bf8
18 changed files with 331 additions and 263 deletions

View file

@ -1,96 +0,0 @@
const { URLSearchParams } = require('url');
const fetch = require('node-fetch');
const config = require('../config');
const pages = require('./pages');
const KEY_SCOPE = 'https://identity.mozilla.com/apps/send';
let fxaConfig = null;
let lastConfigRefresh = 0;
async function getFxaConfig() {
if (fxaConfig && Date.now() - lastConfigRefresh < 1000 * 60 * 5) {
return fxaConfig;
}
const res = await fetch(`${config.fxa_url}/.well-known/openid-configuration`);
fxaConfig = await res.json();
lastConfigRefresh = Date.now();
return fxaConfig;
}
module.exports = {
login: async function(req, res) {
const query = req.query;
if (!query || !query.keys_jwk) {
return res.sendStatus(400);
}
const c = await getFxaConfig();
const params = new URLSearchParams({
client_id: config.fxa_client_id,
redirect_uri: `${config.base_url}/api/fxa/oauth`,
state: 'todo',
scope: `profile ${KEY_SCOPE}`,
action: 'email',
keys_jwk: query.keys_jwk
});
res.redirect(`${c.authorization_endpoint}?${params.toString()}`);
},
oauth: async function(req, res) {
const query = req.query;
if (!query || !query.code || !query.state || !query.action) {
return res.sendStatus(400);
}
const c = await getFxaConfig();
const x = await fetch(c.token_endpoint, {
method: 'POST',
body: JSON.stringify({
code: query.code,
client_id: config.fxa_client_id,
client_secret: config.fxa_client_secret
}),
headers: {
'content-type': 'application/json'
}
});
const zzz = await x.json();
console.error(zzz);
const p = await fetch(c.userinfo_endpoint, {
method: 'GET',
headers: {
authorization: `Bearer ${zzz.access_token}`
}
});
const userInfo = await p.json();
userInfo.keys_jwe = zzz.keys_jwe;
userInfo.access_token = zzz.access_token;
req.userInfo = userInfo;
pages.index(req, res);
},
verify: async function(token) {
if (!token) {
return null;
}
const c = await getFxaConfig();
try {
const verifyUrl = c.jwks_uri.replace('jwks', 'verify');
const result = await fetch(verifyUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token })
});
const info = await result.json();
if (
info.scope &&
Array.isArray(info.scope) &&
info.scope.includes(KEY_SCOPE)
) {
return info.user;
}
} catch (e) {
// gulp
}
return null;
}
};

View file

@ -6,7 +6,6 @@ const config = require('../config');
const auth = require('../middleware/auth');
const language = require('../middleware/language');
const pages = require('./pages');
const fxa = require('./fxa');
const filelist = require('./filelist');
const IS_DEV = config.env === 'development';
@ -34,6 +33,8 @@ module.exports = function(app) {
'wss://*.dev.lcip.org',
'wss://*.mozaws.net',
'wss://send.firefox.com',
'https://*.dev.lcip.org',
'https://*.accounts.firefox.com',
'https://sentry.prod.mozaws.net',
'https://www.google-analytics.com'
],
@ -80,8 +81,7 @@ module.exports = function(app) {
);
app.get(`/api/exists/:id${ID_REGEX}`, require('./exists'));
app.get(`/api/metadata/:id${ID_REGEX}`, auth.hmac, require('./metadata'));
app.get('/api/fxa/login', fxa.login);
app.get('/api/fxa/oauth', fxa.oauth);
app.get('/api/fxa/oauth', pages.blank);
app.get('/api/filelist', auth.fxa, filelist.get);
app.post('/api/filelist', auth.fxa, filelist.post);
app.post('/api/upload', auth.fxa, require('./upload'));

View file

@ -1,4 +1,5 @@
const config = require('../config');
const { getFxaConfig } = require('../fxa');
let sentry = '';
if (config.sentry_id) {
@ -27,33 +28,35 @@ if (config.analytics_id) {
ga = `var GOOGLE_ANALYTICS_ID = '${config.analytics_id}';`;
}
/* eslint-disable no-useless-escape */
const jsconfig = `
var isIE = /trident\\\/7\.|msie/i.test(navigator.userAgent);
var isUnsupportedPage = /\\\/unsupported/.test(location.pathname);
if (isIE && !isUnsupportedPage) {
window.location.replace('/unsupported/ie');
}
var LIMITS = {
ANON: {
MAX_FILE_SIZE: ${config.anon_max_file_size},
MAX_DOWNLOADS: ${config.anon_max_downloads},
MAX_EXPIRE_SECONDS: ${config.anon_max_expire_seconds},
},
MAX_FILE_SIZE: ${config.max_file_size},
MAX_DOWNLOADS: ${config.max_downloads},
MAX_EXPIRE_SECONDS: ${config.max_expire_seconds},
MAX_FILES_PER_ARCHIVE: ${config.max_files_per_archive},
MAX_ARCHIVES_PER_USER: ${config.max_archives_per_user}
};
var DEFAULTS = {
EXPIRE_SECONDS: ${config.default_expire_seconds}
};
${ga}
${sentry}
`;
module.exports = function(req, res) {
module.exports = async function(req, res) {
const fxaConfig = await getFxaConfig();
fxaConfig.client_id = config.fxa_client_id;
/* eslint-disable no-useless-escape */
const jsconfig = `
var isIE = /trident\\\/7\.|msie/i.test(navigator.userAgent);
var isUnsupportedPage = /\\\/unsupported/.test(location.pathname);
if (isIE && !isUnsupportedPage) {
window.location.replace('/unsupported/ie');
}
var LIMITS = {
ANON: {
MAX_FILE_SIZE: ${config.anon_max_file_size},
MAX_DOWNLOADS: ${config.anon_max_downloads},
MAX_EXPIRE_SECONDS: ${config.anon_max_expire_seconds},
},
MAX_FILE_SIZE: ${config.max_file_size},
MAX_DOWNLOADS: ${config.max_downloads},
MAX_EXPIRE_SECONDS: ${config.max_expire_seconds},
MAX_FILES_PER_ARCHIVE: ${config.max_files_per_archive},
MAX_ARCHIVES_PER_USER: ${config.max_archives_per_user}
};
var DEFAULTS = {
EXPIRE_SECONDS: ${config.default_expire_seconds}
};
var AUTH_CONFIG = ${JSON.stringify(fxaConfig)};
${ga}
${sentry}
`;
res.set('Content-Type', 'application/javascript');
res.send(jsconfig);
};

View file

@ -5,7 +5,7 @@ const mozlog = require('../log');
const Limiter = require('../limiter');
const Parser = require('../streamparser');
const wsStream = require('websocket-stream/stream');
const fxa = require('./fxa');
const fxa = require('../fxa');
const log = mozlog('send.upload');