diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a710db52d9..1dcf5ba35b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,12 +14,10 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - - # caches yarn dependencies in the yarn cache (not npm_modules) - # should help to speed up yarn install and be safe - # across changes to the repo and platforms - - name: Cache Yarn Deps - uses: c-hive/gha-yarn-cache@v1 + # caches yarn dependencies in the yarn cache (not npm_modules) + # should help to speed up yarn install and be safe + # across changes to the repo and platforms + cache: 'yarn' - run: echo "github.ref = ${{ github.ref }}" @@ -60,7 +58,7 @@ jobs: - name: Build App Image run: | cd docker - docker-compose build + docker compose build - name: Push App Images run: | @@ -85,9 +83,7 @@ jobs: with: node-version: 18 registry-url: https://registry.npmjs.org/ - - - name: Cache Yarn Deps - uses: c-hive/gha-yarn-cache@v1 + cache: 'yarn' # frozen lockfile should make the cache more effective # and our tests more predictable diff --git a/packages/app/obojobo-express/package.json b/packages/app/obojobo-express/package.json index 21cbdd7046..ac67f3331c 100644 --- a/packages/app/obojobo-express/package.json +++ b/packages/app/obojobo-express/package.json @@ -51,7 +51,7 @@ "ejs": "^3.1.6", "eventemitter": "^0.3.3", "express": "~4.18.2", - "express-ims-lti": "https://github.com/ucfcdl/express-ims-lti.git#879f752758250b81429f75f62bb5602f1b06a564", + "express-ims-lti": "https://github.com/ucfcdl/express-ims-lti.git#d2eb98631b89cf9356e0053eeb0944d4f3dd4849", "express-session": "^1.17.1", "express-validator": "^6.10.0", "file-type": "^16.3.0", diff --git a/packages/obonode/obojobo-chunks-materia/package.json b/packages/obonode/obojobo-chunks-materia/package.json index a920d6579d..b8b9b8849d 100644 --- a/packages/obonode/obojobo-chunks-materia/package.json +++ b/packages/obonode/obojobo-chunks-materia/package.json @@ -27,7 +27,10 @@ "singleQuote": true }, "dependencies": { + "jsonwebtoken": "^9.0.2", + "node-jose": "^2.2.0", "oauth-signature": "^1.5.0", + "simple-oauth2": "^5.1.0", "uuid": "^8.3.2", "xml2js": "0.5.0" }, diff --git a/packages/obonode/obojobo-chunks-materia/server/config/materia-lti.json b/packages/obonode/obojobo-chunks-materia/server/config/materia-lti.json index 1b13695270..d6fc49879b 100644 --- a/packages/obonode/obojobo-chunks-materia/server/config/materia-lti.json +++ b/packages/obonode/obojobo-chunks-materia/server/config/materia-lti.json @@ -1,21 +1,35 @@ { "default": { + "materiaLtiVersion": "1.3", "oauthKey": "materia-lti-key", "oauthSecret": "materia-lti-secret", "oboFamilyCode": "obojobo-next", - "oboName": "Obojobo Next", - "oboGuid": "obojobo.next.default.guid" + "oboGuid": "obojobo.next.default.guid", + "oboJwtKey": "obojobo-jwt-key", + "oboLtiClientId": "1234", + "oboLtiUuid": "00000000-0000-0000-0000-000000000000", + "oboLtiDeploymentId": "obojobo-deploy-id", + "oboName": "Obojobo Next" }, "development": { - "clientMateriaHost": "https://localhost", - "optionalOboServerHost": "https://host.docker.internal:8080", - "oboGuid": "obojobo.next.local.dev" + "clientMateriaHost": "http://localhost:420", + "materiaLtiVersion": {"ENV": "MATERIA_LTI_VERSION"}, + "oboGuid": "obojobo.next.local.dev.guid", + "oboLtiUuid": "00000000-0000-0000-0000-000000000000", + "oboPrivateRsaKey": {"ENV": "OBO_PRIVATE_RSA_KEY"}, + "optionalOboServerHost": "https://host.docker.internal:8080" }, "production": { + "clientMateriaHost": {"ENV": "MATERIA_HOST"}, + "materiaLtiVersion": {"ENV": "MATERIA_LTI_VERSION"}, "oauthKey": {"ENV": "MATERIA_OAUTH_KEY"}, "oauthSecret": {"ENV": "MATERIA_OAUTH_SECRET"}, - "clientMateriaHost": {"ENV": "MATERIA_HOST"}, - "oboGuid": {"ENV": "OBO_LTI_GUID"} + "oboGuid": {"ENV": "OBO_LTI_GUID"}, + "oboJwtKey": {"ENV": "OBO_JWT_KEY"}, + "oboLtiClientId": {"ENV": "OBO_LTI_CLIENTID"}, + "oboLtiUuid": {"ENV": "OBO_LTI_UUID"}, + "oboLtiDeploymentId": {"ENV": "OBO_LTI_DEPLOYMENTID"}, + "oboPrivateRsaKey": {"ENV": "OBO_PRIVATE_RSA_KEY"} } } diff --git a/packages/obonode/obojobo-chunks-materia/server/index.js b/packages/obonode/obojobo-chunks-materia/server/index.js index f475cec2b8..d4c881a314 100644 --- a/packages/obonode/obojobo-chunks-materia/server/index.js +++ b/packages/obonode/obojobo-chunks-materia/server/index.js @@ -1,10 +1,12 @@ +const jose = require('node-jose') +const jwt = require('jsonwebtoken') const router = require('express').Router() //eslint-disable-line new-cap const logger = require('obojobo-express/server/logger') const uuid = require('uuid').v4 const bodyParser = require('body-parser') const oboEvents = require('obojobo-express/server/obo_events') -const Visit = require('obojobo-express/server/models/visit') const Draft = require('obojobo-express/server/models/draft') +const Visit = require('obojobo-express/server/models/visit') const config = require('obojobo-express/server/config').materiaLti const { widgetLaunchParams, @@ -29,6 +31,9 @@ oboEvents.on('EDITOR_SETTINGS', event => { } }) +const base64encode = str => Buffer.from(str).toString('base64') +const base64decode = str => Buffer.from(str, 'base64').toString() + // util to get a baseUrl to build urls for for this server // option `isForServerRequest` indicates the url will be // used by materia server to communicate with obo server @@ -39,6 +44,17 @@ const baseUrl = (req, isForServerRequest = true) => { return `${req.protocol}://${req.get('host')}` } +// util to get a csrf token from an existing request +// feels like a bit of a hack but seems to work +const csrfCookie = req => { + const cookies = req.headers.cookie.split(';') + let cookieCSRFToken = cookies.find(cookie => cookie.includes('csrftoken')) + if (cookieCSRFToken) { + cookieCSRFToken = cookieCSRFToken.split('=')[1] + } + return cookieCSRFToken +} + const renderError = (res, title, message) => { res.set('Content-Type', 'text/html') res.send( @@ -62,152 +78,359 @@ const renderLtiLaunch = (paramsIn, method, endpoint, res) => { `) } -// receives scores passed back from Materia -router - .route('/materia-lti-score-passback') - .post(bodyParser.text({ type: '*/*' })) - .post(async (req, res) => { - let success - let visit = {} - let passBackData = {} - let sourcedIdData = {} - const messageId = uuid() - - try { - const verified = verifyScorePassback(req.headers, req.body, req.originalUrl, baseUrl(req)) - if (!verified) throw Error('Signature verification failed') - passBackData = await getValuesFromPassbackXML(req.body) - sourcedIdData = expandLisResultSourcedId(passBackData.sourcedId) - visit = await Visit.fetchById(sourcedIdData.visitId) - success = true - } catch (e) { - logger.error(e) - success = false - } +if (config.materiaLtiVersion === '1.3') { + // route to launch a materia widget + // the viewer component sends the widget url + // to this url and we build a page with all the params + // and signed oauth signature that the client's browser + // will post for us - taking them to the widget + router + .route('/materia-lti-launch') + .get([requireCurrentUser, requireCurrentVisit]) + .get(async (req, res) => { + // use the visitId to get the src from the materia chunk + const currentDocument = await req.currentVisit.draftDocument + const materiaNode = currentDocument.getChildNodeById(req.query.nodeId) - await materiaEvent.insertLtiScorePassbackEvent({ - userId: visit.user_id, - draftId: visit.draft_id, - contentId: visit.draft_content_id, - resourceLinkId: sourcedIdData.nodeId, - messageRefId: passBackData.messageId, - lisResultSourcedId: passBackData.sourcedId, - messageId, - success, - ip: req.ip, - score: passBackData.score, - materiaHost: config.clientMateriaHost, - isPreview: visit.is_preview, - visitId: sourcedIdData.visitId - }) + if (!materiaNode) { + renderError( + res, + 'Materia Widget Not Found', + `The Materia node id ${req.query.nodeId} was not found in the current draft: ${currentDocument.id} v.${currentDocument.contentId}.` + ) + return + } - const xml = createPassbackResult({ success, messageId, messageRefId: passBackData.messageId }) + const materiaOboNodeId = materiaNode.node.id + const widgetEndpoint = materiaNode.node.content.src - res.status(success ? 200 : 500) - res.type('application/xml') - res.send(xml) - }) + // verify the endpoint is the configured materia server + if (!widgetEndpoint.startsWith(config.clientMateriaHost)) { + renderError( + res, + 'Materia Widget Url Restricted', + `The widget url ${widgetEndpoint} does not match the configured Materia server located at ${config.clientMateriaHost}.` + ) + return + } -// route to launch a materia widget -// the viewer component sends the widget url -// to this url and we build a page with all the params -// and signed oauth signature that the client's browser -// will post for us - taking them to the widget -router - .route('/materia-lti-launch') - .get([requireCurrentUser, requireCurrentVisit]) - .get(async (req, res) => { - // use the visitId to get the src from the materia chunk - const currentDocument = await req.currentVisit.draftDocument - const materiaNode = currentDocument.getChildNodeById(req.query.nodeId) - const method = 'POST' - - if (!materiaNode) { - renderError( - res, - 'Materia Widget Not Found', - `The Materia node id ${req.query.nodeId} was not found in the current draft: ${currentDocument.id} v.${currentDocument.contentId}.` + await materiaEvent.insertLtiLaunchWidgetEvent({ + userId: req.currentUser.id, + draftId: currentDocument.draftId, + contentId: currentDocument.contentId, + visitId: req.currentVisit.id, + isPreview: req.currentVisit.is_preview, + lisResultSourcedId: `${req.currentVisit.id}__${materiaOboNodeId}`, + resourceLinkId: `${req.currentVisit.resource_link_id}__${req.currentVisit.draft_id}__${materiaOboNodeId}`, + widgetEndpoint, + ip: req.ip + }) + const endpoint = `${config.clientMateriaHost}/ltilaunch/` + + const loginHintObj = { + nodeId: materiaOboNodeId, + widgetEndpoint + } + const loginHint = base64encode(JSON.stringify(loginHintObj)) + const ltiMessageHint = 'resource' + res.redirect( + `${config.clientMateriaHost}/init/${config.oboLtiUuid}/?iss=${baseUrl(req)}&client_id=${ + config.oboLtiClientId + }&target_link_uri=${endpoint}&login_hint=${loginHint}<i_message_hint=${ltiMessageHint}` ) - return + }) + + router.route('/materia-lti-picker-return').post(async (req, res) => { + // our Materia integration relies on postmessage + // this is only here for Materia to redirect to + // once a resource is selected. Normally, + // the client will close the browser before this loads + const materia_jwks = await fetch(`${config.clientMateriaHost}/.well-known/jwks.json`).then(r => + r.json() + ) + const keystore = jose.JWK.createKeyStore() + for (const jwk of materia_jwks.keys) { + await keystore.add(jwk) + } + const result = await jose.JWS.createVerify(keystore).verify(req.body.JWT) + const payload = JSON.parse(result.payload.toString()) + + if (payload.type === 'ltiResourceLink' && req.url) { + res.type('text/html') + res.send(`Materia Widget Selection Complete`) } + }) + + router + .route('/materia-lti-picker-launch') + .get([requireCurrentUser, requireCanViewEditor]) + .get(async (req, res) => { + const { draftId, contentId, nodeId } = req.query + const currentDocument = await Draft.fetchDraftByVersion(draftId, contentId) + const endpoint = `${config.clientMateriaHost}/ltilaunch/` + + await materiaEvent.insertLtiPickerLaunchEvent({ + userId: req.currentUser.id, + draftId, + contentId, + nodeId, + endpoint, + ip: req.ip + }) - const materiaOboNodeId = materiaNode.node.id - const endpoint = materiaNode.node.content.src + const loginHintObj = { + nodeId: nodeId, + documentTitle: currentDocument.getTitle() + } - // verify the endpoint is the configured materia server - if (!endpoint.startsWith(config.clientMateriaHost)) { - renderError( - res, - 'Materia Widget Url Restricted', - `The widget url ${endpoint} does not match the configured Materia server located at ${config.clientMateriaHost}.` + const loginHint = base64encode(JSON.stringify(loginHintObj)) + const ltiMessageHint = 'picker' + res.redirect( + `${config.clientMateriaHost}/init/${config.oboLtiUuid}/?iss=${baseUrl(req)}&client_id=${ + config.oboLtiClientId + }&target_link_uri=${endpoint}&login_hint=${loginHint}<i_message_hint=${ltiMessageHint}` ) - return - } + }) - const launchParams = widgetLaunchParams( - currentDocument, - req.currentVisit, - req.currentUser, - materiaOboNodeId, - baseUrl(req) - ) + router + .route('/materia-lti-auth') + .get([requireCurrentUser]) + .get(async (req, res) => { + const { client_id, redirect_uri, login_hint, lti_message_hint, nonce, state } = req.query + + if (lti_message_hint === 'picker' && !req.currentUser.hasPermission('canViewEditor')) { + renderError( + res, + 'Action Not Allowed', + 'Widget picker event launched by user lacking editor rights.' + ) + return + } + + const now = Math.floor(Date.now() / 1000) + + const nodeContext = JSON.parse(base64decode(login_hint)) - await materiaEvent.insertLtiLaunchWidgetEvent({ - userId: req.currentUser.id, - draftId: currentDocument.draftId, - contentId: currentDocument.contentId, - visitId: req.currentVisit.id, - isPreview: req.currentVisit.is_preview, - lisResultSourcedId: launchParams.lis_result_sourcedid, - resourceLinkId: launchParams.resource_link_id, - endpoint, - ip: req.ip + const payload = { + iss: baseUrl(req), + aud: client_id, + iat: now, + exp: now + 300, + nonce, + sub: req.currentUser.username, // this... may not be necessary? + email: req.currentUser.email, + given_name: req.currentUser.firstName, + family_name: req.currentUser.lastName, + 'https://purl.imsglobal.org/spec/lti/claim/lis': { + person_sourcedid: req.currentUser.username + }, + 'https://purl.imsglobal.org/spec/lti/claim/version': '1.3.0', + 'https://purl.imsglobal.org/spec/lti/claim/message_type': + lti_message_hint === 'picker' ? 'LtiDeepLinkingRequest' : 'LtiResourceLinkRequest', + 'https://purl.imsglobal.org/spec/lti/claim/deployment_id': config.oboLtiDeploymentId, + 'https://purl.imsglobal.org/spec/lti/claim/target_link_uri': + lti_message_hint === 'picker' ? redirect_uri : nodeContext.widgetEndpoint, + // transporting the node ID of the Materia node being embedded via the login hint + // there may be a more intelligent way of doing this? + 'https://purl.imsglobal.org/spec/lti/claim/resource_link': { id: nodeContext.nodeId }, + 'https://purl.imsglobal.org/spec/lti/claim/roles': [ + // this may be a bit naive, but we can probably assume that + // students will not be able to use the draft editor, so anybody + // getting this far is an instructor + req.currentUser.hasPermission('canViewEditor') + ? 'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor' + : 'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner' + ], + // need to somehow address this + 'https://purl.imsglobal.org/spec/lti/claim/context': { + id: nodeContext.nodeId, + title: nodeContext.draftTitle + } + } + if (lti_message_hint === 'picker') { + payload['https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings'] = { + deep_link_return_url: `${baseUrl(req)}/materia-lti-picker-return`, + accept_types: ['ltiResourceLink'], + accept_presentation_document_targets: ['iframe', 'window', 'embed'] + } + } + + const idToken = jwt.sign(payload, config.oboPrivateRsaKey, { + algorithm: 'RS256', + keyid: config.oboJwtKey + }) + + res.set('Content-Type', 'text/html') + res.send(` +
+ + + + +
+ + `) }) - renderLtiLaunch(launchParams, method, endpoint, res) + + // this might make more sense somewhere else, but currently it only matters for the materia integration + router.route('/.well-known/jwks.json').get(async (req, res) => { + const key = await jose.JWK.asKey(config.oboPrivateRsaKey, 'pem') + + const jwk = key.toJSON() + jwk.use = 'sig' + jwk.alg = 'RS256' + jwk.kid = config.oboJwtKey + + res.json({ keys: [jwk] }) }) +} else if (config.materiaLtiVersion === '1.1') { + // receives scores passed back from Materia + router + .route('/materia-lti-score-passback') + .post(bodyParser.text({ type: '*/*' })) + .post(async (req, res) => { + let success + let visit = {} + let passBackData = {} + let sourcedIdData = {} + const messageId = uuid() -router.route('/materia-lti-picker-return').all((req, res) => { - // our Materia integration relies on postmessage - // this is only here for Materia to redirect to - // once a resource is selected. Normally, - // the client will close the browser before this loads - // In the future, this will have to receive & validate - // a normal LTI ContentItemSelectionRequest results and - // pass it to the client - if (req.query.embed_type && req.query.url) { - res.type('text/html') - res.send(`Materia Widget Selection Complete`) - } -}) + try { + const verified = verifyScorePassback(req.headers, req.body, req.originalUrl, baseUrl(req)) + if (!verified) throw Error('Signature verification failed') + passBackData = await getValuesFromPassbackXML(req.body) + sourcedIdData = expandLisResultSourcedId(passBackData.sourcedId) + visit = await Visit.fetchById(sourcedIdData.visitId) + success = true + } catch (e) { + logger.error(e) + success = false + } -router - .route('/materia-lti-picker-launch') - .get([requireCurrentUser, requireCanViewEditor]) - .get(async (req, res) => { - const { draftId, contentId, nodeId } = req.query - const clientBaseUrl = baseUrl(req, false) - const serverBaseUrl = baseUrl(req) - const currentDocument = await Draft.fetchDraftByVersion(draftId, contentId) - const method = 'POST' - const endpoint = `${config.clientMateriaHost}/lti/picker` - const launchParams = contentSelectionParams( - currentDocument, - nodeId, - req.currentUser, - clientBaseUrl, - serverBaseUrl - ) + await materiaEvent.insertLtiScorePassbackEvent({ + userId: visit.user_id, + draftId: visit.draft_id, + contentId: visit.draft_content_id, + resourceLinkId: sourcedIdData.nodeId, + messageRefId: passBackData.messageId, + lisResultSourcedId: passBackData.sourcedId, + messageId, + success, + ip: req.ip, + score: passBackData.score, + materiaHost: config.clientMateriaHost, + isPreview: visit.is_preview, + visitId: sourcedIdData.visitId + }) + + const xml = createPassbackResult({ success, messageId, messageRefId: passBackData.messageId }) + + res.status(success ? 200 : 500) + res.type('application/xml') + res.send(xml) + }) + + // route to launch a materia widget + // the viewer component sends the widget url + // to this url and we build a page with all the params + // and signed oauth signature that the client's browser + // will post for us - taking them to the widget + router + .route('/materia-lti-launch') + .get([requireCurrentUser, requireCurrentVisit]) + .get(async (req, res) => { + // use the visitId to get the src from the materia chunk + const currentDocument = await req.currentVisit.draftDocument + const materiaNode = currentDocument.getChildNodeById(req.query.nodeId) + const method = 'POST' - await materiaEvent.insertLtiPickerLaunchEvent({ - userId: req.currentUser.id, - draftId, - contentId, - nodeId, - endpoint, - ip: req.ip + if (!materiaNode) { + renderError( + res, + 'Materia Widget Not Found', + `The Materia node id ${req.query.nodeId} was not found in the current draft: ${currentDocument.id} v.${currentDocument.contentId}.` + ) + return + } + + const materiaOboNodeId = materiaNode.node.id + const endpoint = materiaNode.node.content.src + + // verify the endpoint is the configured materia server + if (!endpoint.startsWith(config.clientMateriaHost)) { + renderError( + res, + 'Materia Widget Url Restricted', + `The widget url ${endpoint} does not match the configured Materia server located at ${config.clientMateriaHost}.` + ) + return + } + + const launchParams = widgetLaunchParams( + currentDocument, + req.currentVisit, + req.currentUser, + materiaOboNodeId, + baseUrl(req) + ) + + await materiaEvent.insertLtiLaunchWidgetEvent({ + userId: req.currentUser.id, + draftId: currentDocument.draftId, + contentId: currentDocument.contentId, + visitId: req.currentVisit.id, + isPreview: req.currentVisit.is_preview, + lisResultSourcedId: launchParams.lis_result_sourcedid, + resourceLinkId: launchParams.resource_link_id, + endpoint, + ip: req.ip + }) + renderLtiLaunch(launchParams, method, endpoint, res) }) - renderLtiLaunch(launchParams, method, endpoint, res) + router.route('/materia-lti-picker-return').all((req, res) => { + // our Materia integration relies on postmessage + // this is only here for Materia to redirect to + // once a resource is selected. Normally, + // the client will close the browser before this loads + // In the future, this will have to receive & validate + // a normal LTI ContentItemSelectionRequest results and + // pass it to the client + if (req.query.embed_type && req.query.url) { + res.type('text/html') + res.send(`Materia Widget Selection Complete`) + } }) + router + .route('/materia-lti-picker-launch') + .get([requireCurrentUser, requireCanViewEditor]) + .get(async (req, res) => { + const { draftId, contentId, nodeId } = req.query + const clientBaseUrl = baseUrl(req, false) + const serverBaseUrl = baseUrl(req) + const currentDocument = await Draft.fetchDraftByVersion(draftId, contentId) + const method = 'POST' + const endpoint = `${config.clientMateriaHost}/lti/picker` + const launchParams = contentSelectionParams( + currentDocument, + nodeId, + req.currentUser, + clientBaseUrl, + serverBaseUrl + ) + + await materiaEvent.insertLtiPickerLaunchEvent({ + userId: req.currentUser.id, + draftId, + contentId, + nodeId, + endpoint, + ip: req.ip + }) + + renderLtiLaunch(launchParams, method, endpoint, res) + }) +} + module.exports = router diff --git a/yarn.lock b/yarn.lock index d2a92d117f..a0da960a82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1198,6 +1198,44 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@hapi/boom@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-10.0.1.tgz#ebb14688275ae150aa6af788dbe482e6a6062685" + integrity sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA== + dependencies: + "@hapi/hoek" "^11.0.2" + +"@hapi/bourne@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-3.0.0.tgz#f11fdf7dda62fe8e336fa7c6642d9041f30356d7" + integrity sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w== + +"@hapi/hoek@^11.0.2", "@hapi/hoek@^11.0.4": + version "11.0.7" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.7.tgz#56a920793e0a42d10e530da9a64cc0d3919c4002" + integrity sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ== + +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@hapi/wreck@^18.0.0": + version "18.1.0" + resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-18.1.0.tgz#68e631fc7568ebefc6252d5b86cb804466c8dbe6" + integrity sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/bourne" "^3.0.0" + "@hapi/hoek" "^11.0.2" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2340,6 +2378,23 @@ dependencies: "@octokit/openapi-types" "^5.3.2" +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -4128,6 +4183,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64url@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -4334,6 +4394,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-equal-constant-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -4357,6 +4422,14 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + builtins@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" @@ -5995,6 +6068,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + editorconfig@^0.15.3: version "0.15.3" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" @@ -6225,6 +6305,11 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es6-promise@^4.2.8: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -6511,11 +6596,11 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" -"express-ims-lti@https://github.com/ucfcdl/express-ims-lti.git#879f752758250b81429f75f62bb5602f1b06a564": +"express-ims-lti@https://github.com/ucfcdl/express-ims-lti.git#d2eb98631b89cf9356e0053eeb0944d4f3dd4849": version "0.2.4" - resolved "https://github.com/ucfcdl/express-ims-lti.git#879f752758250b81429f75f62bb5602f1b06a564" + resolved "https://github.com/ucfcdl/express-ims-lti.git#d2eb98631b89cf9356e0053eeb0944d4f3dd4849" dependencies: - ims-lti "https://github.com/ucfcdl/ims-lti.git#d051afa35ebfa8c3570a176f976e7f28dae9ebf8" + ims-lti "https://github.com/ucfcdl/ims-lti.git#3445a6af23597aa5ead38743a2a5c8b3347fb9f2" express-list-endpoints@^5.0.0: version "5.0.0" @@ -7741,9 +7826,9 @@ import-local@^3.0.2: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" -"ims-lti@https://github.com/ucfcdl/ims-lti.git#d051afa35ebfa8c3570a176f976e7f28dae9ebf8": +"ims-lti@https://github.com/ucfcdl/ims-lti.git#3445a6af23597aa5ead38743a2a5c8b3347fb9f2": version "3.0.2" - resolved "https://github.com/ucfcdl/ims-lti.git#d051afa35ebfa8c3570a176f976e7f28dae9ebf8" + resolved "https://github.com/ucfcdl/ims-lti.git#3445a6af23597aa5ead38743a2a5c8b3347fb9f2" dependencies: uuid "~3.1.0" xml2js "~0.4.0" @@ -8802,6 +8887,17 @@ jiti@^1.18.2: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.1.tgz#fa99e4b76a23053e0e7cde098efe1704a14c16f1" integrity sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg== +joi@^17.6.4: + version "17.13.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + js-base64@^2.4.9: version "2.6.4" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" @@ -8980,6 +9076,22 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= +jsonwebtoken@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -8998,6 +9110,23 @@ jsprim@^1.2.2: array-includes "^3.1.2" object.assign "^4.1.2" +jwa@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" + integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== + dependencies: + buffer-equal-constant-time "^1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + katex@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/katex/-/katex-0.13.1.tgz#a3f4764f7e513eed554600e0732f5ac2fa202165" @@ -9284,16 +9413,51 @@ lodash.flattendeep@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash.orderby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -9347,6 +9511,11 @@ loglevel@^1.6.8: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== +long@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -10072,6 +10241,11 @@ node-forge@^0.10.0: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== +node-forge@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + node-fs@~0.1.5: version "0.1.7" resolved "https://registry.yarnpkg.com/node-fs/-/node-fs-0.1.7.tgz#32323cccb46c9fbf0fc11812d45021cc31d325bb" @@ -10098,6 +10272,21 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= +node-jose@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-jose/-/node-jose-2.2.0.tgz#b64f3225ad6bec328509a420800de597ba2bf3ed" + integrity sha512-XPCvJRr94SjLrSIm4pbYHKLEaOsDvJCpyFw/6V/KK/IXmyZ6SFBzAUDO9HQf4DB/nTEFcRGH87mNciOP23kFjw== + dependencies: + base64url "^3.0.1" + buffer "^6.0.3" + es6-promise "^4.2.8" + lodash "^4.17.21" + long "^5.2.0" + node-forge "^1.2.1" + pako "^2.0.4" + process "^0.11.10" + uuid "^9.0.0" + node-modules-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" @@ -10740,6 +10929,11 @@ pacote@^11.2.6: ssri "^8.0.1" tar "^6.1.0" +pako@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -11241,6 +11435,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -12603,6 +12802,16 @@ simple-get@^4.0.0, simple-get@^4.0.1: once "^1.3.1" simple-concat "^1.0.0" +simple-oauth2@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/simple-oauth2/-/simple-oauth2-5.1.0.tgz#1398fe2b8f4b4066298d63c155501b31b42238f2" + integrity sha512-gWDa38Ccm4MwlG5U7AlcJxPv3lvr80dU7ARJWrGdgvOKyzSj1gr3GBPN1rABTedAYvC/LsGYoFuFxwDBPtGEbw== + dependencies: + "@hapi/hoek" "^11.0.4" + "@hapi/wreck" "^18.0.0" + debug "^4.3.4" + joi "^17.6.4" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -14246,6 +14455,11 @@ uuid@^8.3.0, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + uuid@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"