mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 19:33:02 +03:00
Implemented externally verifiable identity tokens
no-issue This adds two new endpoints, one at /ghost/.well-known/jwks.json for exposing a public key, and one on the canary api /identities, which allows the Owner user to fetch a JWT. This token can then be used by external services to verify the domain * Added ghost_{public,private}_key settings This key can be used for generating tokens for communicating with external services on behalf of Ghost * Added .well-known directory to /ghost/.well-known We add a jwks.json file to the .well-known directory which exposes a public JWK which can be used to verify the signatures of JWT's created by Ghost This is added to the /ghost/ path so that it can live on the admin domain, rather than the frontend. This is because most of its uses/functions will be in relation to the admin domain. * Improved settings model tests This removes hardcoded positions in favour of testing that a particular event wasn't emitted which is less brittle and more precise about what's being tested * Fixed parent app unit tests for well-known This updates the parent app unit tests to check that the well-known route is mounted. We all change proxyquire to use `noCallThru` which ensures that the ubderlying modules are not required. This stops the initialisation logic in ./well-known erroring in tests https://github.com/thlorenz/proxyquire/issues/215 * Moved jwt signature to a separate 'token' propery This structure corresponds to other resources and allows to exptend with additional properties in future if needed
This commit is contained in:
parent
318484d737
commit
d246a4761e
36
core/server/api/canary/identities.js
Normal file
36
core/server/api/canary/identities.js
Normal file
@ -0,0 +1,36 @@
|
||||
const settings = require('../../services/settings/cache');
|
||||
const urlUtils = require('../../lib/url-utils');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const jose = require('node-jose');
|
||||
const issuer = urlUtils.urlFor('admin', true);
|
||||
|
||||
const dangerousPrivateKey = settings.get('ghost_private_key');
|
||||
const keyStore = jose.JWK.createKeyStore();
|
||||
const keyStoreReady = keyStore.add(dangerousPrivateKey, 'pem');
|
||||
|
||||
const getKeyID = async () => {
|
||||
const key = await keyStoreReady;
|
||||
return key.kid;
|
||||
};
|
||||
|
||||
const sign = async (claims, options) => {
|
||||
const kid = await getKeyID();
|
||||
return jwt.sign(claims, dangerousPrivateKey, Object.assign({
|
||||
issuer,
|
||||
expiresIn: '5m',
|
||||
algorithm: 'RS256',
|
||||
keyid: kid
|
||||
}, options));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
docName: 'identities',
|
||||
permissions: true,
|
||||
read: {
|
||||
permissions: true,
|
||||
async query(frame) {
|
||||
const token = await sign({sub: frame.user.get('email')});
|
||||
return {token};
|
||||
}
|
||||
}
|
||||
};
|
@ -14,6 +14,10 @@ module.exports = {
|
||||
return shared.pipeline(require('./db'), localUtils);
|
||||
},
|
||||
|
||||
get identities() {
|
||||
return shared.pipeline(require('./identities'), localUtils);
|
||||
},
|
||||
|
||||
get integrations() {
|
||||
return shared.pipeline(require('./integrations'), localUtils);
|
||||
},
|
||||
|
@ -14,8 +14,12 @@ const common = require('../../../lib/common');
|
||||
const nonePublicAuth = (apiConfig, frame) => {
|
||||
debug('check admin permissions');
|
||||
|
||||
const singular = apiConfig.docName.replace(/s$/, '');
|
||||
|
||||
let singular;
|
||||
if (apiConfig.docName.match(/ies$/)) {
|
||||
singular = apiConfig.docName.replace(/ies$/, 'y');
|
||||
} else {
|
||||
singular = apiConfig.docName.replace(/s$/, '');
|
||||
}
|
||||
let permissionIdentifier = frame.options.id;
|
||||
|
||||
// CASE: Target ctrl can override the identifier. The identifier is the unique identifier of the target resource
|
||||
|
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
read(data, apiConfig, frame) {
|
||||
frame.response = {
|
||||
identities: [data]
|
||||
};
|
||||
}
|
||||
};
|
@ -67,6 +67,10 @@ module.exports = {
|
||||
return require('./member-signin_urls');
|
||||
},
|
||||
|
||||
get identities() {
|
||||
return require('./identities');
|
||||
},
|
||||
|
||||
get images() {
|
||||
return require('./images');
|
||||
},
|
||||
|
@ -14,6 +14,12 @@
|
||||
},
|
||||
"theme_session_secret": {
|
||||
"defaultValue": null
|
||||
},
|
||||
"ghost_public_key": {
|
||||
"defaultValue": null
|
||||
},
|
||||
"ghost_private_key": {
|
||||
"defaultValue": null
|
||||
}
|
||||
},
|
||||
"blog": {
|
||||
|
@ -417,6 +417,11 @@
|
||||
"name": "Read member signin urls",
|
||||
"action_type": "read",
|
||||
"object_type": "member_signin_url"
|
||||
},
|
||||
{
|
||||
"name": "Read identities",
|
||||
"action_type": "read",
|
||||
"object_type": "identity"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -23,6 +23,16 @@ const getMembersKey = doBlock(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const getGhostKey = doBlock(() => {
|
||||
let UNO_KEYPAIRINO;
|
||||
return function getGhostKey(type) {
|
||||
if (!UNO_KEYPAIRINO) {
|
||||
UNO_KEYPAIRINO = keypair({bits: 1024});
|
||||
}
|
||||
return UNO_KEYPAIRINO[type];
|
||||
};
|
||||
});
|
||||
|
||||
// For neatness, the defaults file is split into categories.
|
||||
// It's much easier for us to work with it as a single level
|
||||
// instead of iterating those categories every time
|
||||
@ -38,7 +48,9 @@ function parseDefaultSettings() {
|
||||
theme_session_secret: () => crypto.randomBytes(32).toString('hex'),
|
||||
members_public_key: () => getMembersKey('public'),
|
||||
members_private_key: () => getMembersKey('private'),
|
||||
members_email_auth_secret: () => crypto.randomBytes(64).toString('hex')
|
||||
members_email_auth_secret: () => crypto.randomBytes(64).toString('hex'),
|
||||
ghost_public_key: () => getGhostKey('public'),
|
||||
ghost_private_key: () => getGhostKey('private')
|
||||
};
|
||||
|
||||
_.each(defaultSettingsInCategories, function each(settings, categoryName) {
|
||||
|
@ -177,6 +177,9 @@ module.exports = function apiRoutes() {
|
||||
);
|
||||
router.del('/session', mw.authAdminApi, http(apiCanary.session.delete));
|
||||
|
||||
// ## Identity
|
||||
router.get('/identities', mw.authAdminApi, http(apiCanary.identities.read));
|
||||
|
||||
// ## Authentication
|
||||
router.post('/authentication/passwordreset',
|
||||
shared.middlewares.brute.globalReset,
|
||||
|
@ -58,6 +58,7 @@ module.exports = function setupParentApp(options = {}) {
|
||||
adminApp.use(sentry.requestHandler);
|
||||
adminApp.enable('trust proxy'); // required to respect x-forwarded-proto in admin requests
|
||||
adminApp.use('/ghost/api', require('./api')());
|
||||
adminApp.use('/ghost/.well-known', require('./well-known')());
|
||||
adminApp.use('/ghost', require('./admin')());
|
||||
|
||||
// TODO: remove {admin url}/content/* once we're sure the API is not returning relative asset URLs anywhere
|
||||
|
23
core/server/web/well-known.js
Normal file
23
core/server/web/well-known.js
Normal file
@ -0,0 +1,23 @@
|
||||
const express = require('express');
|
||||
const settings = require('../services/settings/cache');
|
||||
const jose = require('node-jose');
|
||||
|
||||
const dangerousPrivateKey = settings.get('ghost_private_key');
|
||||
const keyStore = jose.JWK.createKeyStore();
|
||||
const keyStoreReady = keyStore.add(dangerousPrivateKey, 'pem');
|
||||
|
||||
const getSafePublicJWKS = async () => {
|
||||
await keyStoreReady;
|
||||
return keyStore.toJSON();
|
||||
};
|
||||
|
||||
module.exports = function setupWellKnownApp() {
|
||||
const wellKnownApp = express();
|
||||
|
||||
wellKnownApp.get('/jwks.json', async (req, res) => {
|
||||
const jwks = await getSafePublicJWKS();
|
||||
res.json(jwks);
|
||||
});
|
||||
|
||||
return wellKnownApp;
|
||||
};
|
103
core/test/regression/api/canary/admin/identities_spec.js
Normal file
103
core/test/regression/api/canary/admin/identities_spec.js
Normal file
@ -0,0 +1,103 @@
|
||||
const should = require('should');
|
||||
const supertest = require('supertest');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const jwksClient = require('jwks-rsa');
|
||||
const testUtils = require('../../../../utils');
|
||||
const localUtils = require('./utils');
|
||||
const config = require('../../../../../server/config');
|
||||
|
||||
const ghost = testUtils.startGhost;
|
||||
|
||||
let request;
|
||||
|
||||
const verifyJWKS = (endpoint, token) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const jwksClient = require('jwks-rsa');
|
||||
const client = jwksClient({
|
||||
jwksUri: endpoint
|
||||
});
|
||||
|
||||
function getKey(header, callback){
|
||||
client.getSigningKey(header.kid, (err, key) => {
|
||||
let signingKey = key.publicKey || key.rsaPublicKey;
|
||||
callback(null, signingKey);
|
||||
});
|
||||
}
|
||||
|
||||
jwt.verify(token, getKey, {}, (err, decoded) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
resolve(decoded);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('Identities API', function () {
|
||||
describe('As Owner', function () {
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function () {
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return localUtils.doAuth(request);
|
||||
});
|
||||
});
|
||||
|
||||
it('Can create JWT token and verify it afterwards with public jwks', function () {
|
||||
let identity;
|
||||
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`identities/`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.identities);
|
||||
|
||||
identity = jsonResponse.identities[0];
|
||||
})
|
||||
.then(() => {
|
||||
return verifyJWKS(`${request.app}/ghost/.well-known/jwks.json`, identity.token);
|
||||
})
|
||||
.then((decoded) => {
|
||||
decoded.sub.should.equal('jbloggs@example.com');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('As non-Owner', function () {
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function (_ghostServer) {
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return testUtils.createUser({
|
||||
user: testUtils.DataGenerator.forKnex.createUser({email: 'admin+1@ghost.org'}),
|
||||
role: testUtils.DataGenerator.Content.roles[0].name
|
||||
});
|
||||
})
|
||||
.then(function (admin) {
|
||||
request.user = admin;
|
||||
|
||||
return localUtils.doAuth(request);
|
||||
});
|
||||
});
|
||||
|
||||
it('Cannot read', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`identities/`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
});
|
@ -20,7 +20,7 @@ var should = require('should'),
|
||||
describe('DB version integrity', function () {
|
||||
// Only these variables should need updating
|
||||
const currentSchemaHash = '7cd198f085844aa5725964069b051189';
|
||||
const currentFixturesHash = 'b2e26827d712513907054782a0be5735';
|
||||
const currentFixturesHash = '1e5856f5172a4389bd72a98b388792e6';
|
||||
|
||||
// If this test is failing, then it is likely a change has been made that requires a DB version bump,
|
||||
// and the values above will need updating as confirmation
|
||||
|
@ -114,7 +114,7 @@ describe('Unit: models/settings', function () {
|
||||
return models.Settings.populateDefaults()
|
||||
.then(() => {
|
||||
// 2 events per item - settings.added and settings.[name].added
|
||||
eventSpy.callCount.should.equal(88);
|
||||
eventSpy.callCount.should.equal(92);
|
||||
const eventsEmitted = eventSpy.args.map(args => args[0]);
|
||||
const checkEventEmitted = event => should.ok(eventsEmitted.includes(event), `${event} event should be emitted`);
|
||||
|
||||
@ -136,10 +136,9 @@ describe('Unit: models/settings', function () {
|
||||
|
||||
return models.Settings.populateDefaults()
|
||||
.then(() => {
|
||||
// 2 events per item - settings.added and settings.[name].added
|
||||
eventSpy.callCount.should.equal(86);
|
||||
|
||||
eventSpy.args[13][0].should.equal('settings.logo.added');
|
||||
const eventsEmitted = eventSpy.args.map(args => args[0]);
|
||||
const checkEventNotEmitted = event => should.ok(!eventsEmitted.includes(event), `${event} event should be emitted`);
|
||||
checkEventNotEmitted('settings.description.added');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
const proxyquire = require('proxyquire');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
const configUtils = require('../../utils/configUtils');
|
||||
|
||||
describe('parent app', function () {
|
||||
@ -10,6 +10,7 @@ describe('parent app', function () {
|
||||
let apiSpy;
|
||||
let parentApp;
|
||||
let adminSpy;
|
||||
let wellKnownSpy;
|
||||
let siteSpy;
|
||||
let gatewaySpy;
|
||||
let authPagesSpy;
|
||||
@ -24,6 +25,7 @@ describe('parent app', function () {
|
||||
vhostSpy = sinon.spy();
|
||||
apiSpy = sinon.spy();
|
||||
adminSpy = sinon.spy();
|
||||
wellKnownSpy = sinon.spy();
|
||||
siteSpy = sinon.spy();
|
||||
gatewaySpy = sinon.spy();
|
||||
authPagesSpy = sinon.spy();
|
||||
@ -33,6 +35,7 @@ describe('parent app', function () {
|
||||
'@tryghost/vhost-middleware': vhostSpy,
|
||||
'./api': apiSpy,
|
||||
'./admin': adminSpy,
|
||||
'./well-known': wellKnownSpy,
|
||||
'./site': siteSpy,
|
||||
'../services/members': {
|
||||
gateway: gatewaySpy,
|
||||
@ -54,10 +57,12 @@ describe('parent app', function () {
|
||||
parentApp();
|
||||
|
||||
use.calledWith('/ghost/api').should.be.true();
|
||||
use.calledWith('/ghost/.well-known').should.be.true();
|
||||
use.calledWith('/ghost').should.be.true();
|
||||
use.calledWith('/content/images').should.be.false();
|
||||
|
||||
apiSpy.called.should.be.true();
|
||||
wellKnownSpy.called.should.be.true();
|
||||
adminSpy.called.should.be.true();
|
||||
siteSpy.called.should.be.true();
|
||||
|
||||
|
@ -152,6 +152,7 @@
|
||||
"grunt-shell": "3.0.1",
|
||||
"grunt-subgrunt": "1.3.0",
|
||||
"grunt-update-submodules": "0.4.1",
|
||||
"jwks-rsa": "1.7.0",
|
||||
"matchdep": "2.0.0",
|
||||
"mocha": "7.1.0",
|
||||
"mock-knex": "0.4.7",
|
||||
|
105
yarn.lock
105
yarn.lock
@ -416,6 +416,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.27.tgz#61eb4d75dc6bfbce51cf49ee9bbebe941b2cb5d0"
|
||||
integrity sha512-6BmYWSBea18+tSjjSC3QIyV93ZKAeNWGM7R6aYt1ryTZXrlHF+QLV0G2yV0viEGVyRkyQsWfMoJ0k/YghBX5sQ==
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
|
||||
integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==
|
||||
dependencies:
|
||||
"@types/connect" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/cacheable-request@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976"
|
||||
@ -431,6 +439,45 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
|
||||
|
||||
"@types/connect@*":
|
||||
version "3.4.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546"
|
||||
integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/express-jwt@0.0.42":
|
||||
version "0.0.42"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-jwt/-/express-jwt-0.0.42.tgz#4f04e1fadf9d18725950dc041808a4a4adf7f5ae"
|
||||
integrity sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
"@types/express-unless" "*"
|
||||
|
||||
"@types/express-serve-static-core@*":
|
||||
version "4.17.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz#f6f41fa35d42e79dbf6610eccbb2637e6008a0cf"
|
||||
integrity sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/range-parser" "*"
|
||||
|
||||
"@types/express-unless@*":
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-unless/-/express-unless-0.5.1.tgz#4f440b905e42bbf53382b8207bc337dc5ff9fd1f"
|
||||
integrity sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
|
||||
"@types/express@*":
|
||||
version "4.17.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.2.tgz#a0fb7a23d8855bac31bc01d5a58cadd9b2173e6c"
|
||||
integrity sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==
|
||||
dependencies:
|
||||
"@types/body-parser" "*"
|
||||
"@types/express-serve-static-core" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/http-cache-semantics@*":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a"
|
||||
@ -443,11 +490,21 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/mime@*":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
|
||||
integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
|
||||
|
||||
"@types/node@*":
|
||||
version "12.7.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446"
|
||||
integrity sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==
|
||||
|
||||
"@types/range-parser@*":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
|
||||
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
|
||||
|
||||
"@types/responselike@*":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
|
||||
@ -455,6 +512,14 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/serve-static@*":
|
||||
version "1.13.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
|
||||
integrity sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==
|
||||
dependencies:
|
||||
"@types/express-serve-static-core" "*"
|
||||
"@types/mime" "*"
|
||||
|
||||
"@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
|
||||
@ -4804,6 +4869,19 @@ jwa@^1.4.1:
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jwks-rsa@1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-1.7.0.tgz#5f3362bb428d72a5f40719b5e10785fa094d1a01"
|
||||
integrity sha512-tq7DVJt9J6wTvl9+AQfwZIiPSuY2Vf0F+MovfRTFuBqLB1xgDVhegD33ChEAQ6yBv9zFvUIyj4aiwrSA5VehUw==
|
||||
dependencies:
|
||||
"@types/express-jwt" "0.0.42"
|
||||
debug "^4.1.0"
|
||||
jsonwebtoken "^8.5.1"
|
||||
limiter "^1.1.4"
|
||||
lru-memoizer "^2.0.1"
|
||||
ms "^2.1.2"
|
||||
request "^2.88.0"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
@ -4998,6 +5076,11 @@ liftoff@~2.5.0:
|
||||
rechoir "^0.6.2"
|
||||
resolve "^1.1.7"
|
||||
|
||||
limiter@^1.1.4:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2"
|
||||
integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==
|
||||
|
||||
linkify-it@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf"
|
||||
@ -5251,6 +5334,22 @@ lru-cache@^5.1.1:
|
||||
dependencies:
|
||||
yallist "^3.0.2"
|
||||
|
||||
lru-cache@~4.0.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e"
|
||||
integrity sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=
|
||||
dependencies:
|
||||
pseudomap "^1.0.1"
|
||||
yallist "^2.0.0"
|
||||
|
||||
lru-memoizer@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-memoizer/-/lru-memoizer-2.1.0.tgz#2551b29d5181188695d30f12f6a56fad5b418b61"
|
||||
integrity sha512-oKjxgJhL+m1wfEkez7/a6iyRZUdohej+2u04qCaAQ7BBfx/qD4RH3jOQhPsd8Y3pcm7IhcNtE3kCEIDCMPiJFQ==
|
||||
dependencies:
|
||||
lodash.clonedeep "^4.5.0"
|
||||
lru-cache "~4.0.0"
|
||||
|
||||
lru_map@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
|
||||
@ -5784,7 +5883,7 @@ ms@2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||
|
||||
ms@^2.0.0, ms@^2.1.1:
|
||||
ms@^2.0.0, ms@^2.1.1, ms@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
@ -7036,7 +7135,7 @@ proxyquire@2.1.3:
|
||||
module-not-found-error "^1.0.1"
|
||||
resolve "^1.11.1"
|
||||
|
||||
pseudomap@^1.0.2:
|
||||
pseudomap@^1.0.1, pseudomap@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
|
||||
@ -9214,7 +9313,7 @@ y18n@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
||||
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
|
||||
|
||||
yallist@^2.1.2:
|
||||
yallist@^2.0.0, yallist@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
|
||||
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
|
||||
|
Loading…
Reference in New Issue
Block a user