From 89753f70944d45b96837a24165e522023e29cae9 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Mon, 15 Jan 2024 11:21:13 +0100 Subject: [PATCH] Draft tests, implement email verification (#1) The email verification is stateless (without database), using JSON Web Token. On successful verification, another JSON Web Token is created, proving that email is verified. It should be saved in person's pod in some discoverable settings. --- .env.sample | 11 + .github/workflows/test.yml | 3 + .gitignore | 2 + package.json | 5 + src/app.ts | 96 ++++--- src/config/index.ts | 21 +- src/controllers/integration.ts | 217 ++++---------- src/middlewares/authorizeGroup.ts | 55 ++++ src/middlewares/solidAuth.ts | 1 + src/test/integration-finish.spec.ts | 94 ++---- src/test/integration-start.spec.ts | 109 +++---- src/test/notification.spec.ts | 9 +- src/test/status.spec.ts | 43 +-- src/test/testSetup.spec.ts | 84 +++--- yarn.lock | 430 +++++++++++++++++++++++++++- 15 files changed, 741 insertions(+), 439 deletions(-) create mode 100644 src/middlewares/authorizeGroup.ts diff --git a/.env.sample b/.env.sample index 6629592..3d9de52 100644 --- a/.env.sample +++ b/.env.sample @@ -13,6 +13,10 @@ BEHIND_PROXY= MAILER_IDENTITY_EMAIL=bot@example MAILER_IDENTITY_PASSWORD=password MAILER_IDENTITY_PROVIDER=http://localhost:3456 +MAILER_IDENTITY_WEBID=http://localhost:3456/bot/profile/card#me + +# link to group of users who are allowed to use the service +ALLOWED_GROUPS= # SMTP Transport for sending emails # string or undefined @@ -39,3 +43,10 @@ DB_USERNAME= DB_PASSWORD= DB_HOST= DB_PORT= + +# JWT +# path to JWT (private) key +# you can use the command `openssl ecparam -name prime256v1 -genkey -noout -out ecdsa-p256-private.pem` to generate one +JWT_KEY=./ecdsa-p256-private.pem +# jwt algorithm +JWT_ALG=ES256 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index db43f41..1d4e982 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,5 +18,8 @@ jobs: - name: Install dependencies run: yarn install --frozen-lockfile + - name: Generate private key + run: openssl ecparam -name prime256v1 -genkey -noout -out ecdsa-p256-private.pem + - name: Run tests run: yarn test diff --git a/.gitignore b/.gitignore index a40dfe0..f4e004c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ output node_modules database.sqlite + +*.pem diff --git a/package.json b/package.json index 9530b79..30f9fc9 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "@types/bcryptjs": "^2.4.2", "@types/chai": "^4.3.5", "@types/co-body": "^6.1.0", + "@types/fs-extra": "^11.0.4", + "@types/jsonwebtoken": "^9.0.5", "@types/koa": "^2.13.7", "@types/koa-static": "^4.0.3", "@types/koa__cors": "^4.0.1", @@ -37,6 +39,7 @@ "eslint-plugin-prettier": "^5.0.0", "maildev": "^2.1.0", "mocha": "^10.2.0", + "msw": "^2.0.14", "prettier": "^3.0.0", "rdf-namespaces": "^1.11.0", "sinon": "^15.2.0", @@ -57,6 +60,8 @@ "cross-fetch": "^4.0.0", "css-authn": "^0.0.14", "dotenv": "^16.0.0", + "fs-extra": "^11.2.0", + "jsonwebtoken": "^9.0.2", "koa": "^2.14.2", "koa-helmet": "^7.0.2", "koa-static": "^5.0.0", diff --git a/src/app.ts b/src/app.ts index 059dc4b..40ac088 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,7 +4,7 @@ import Router from '@koa/router' import Koa from 'koa' import helmet from 'koa-helmet' import serve from 'koa-static' -import { isBehindProxy } from './config' +import { allowedGroups, isBehindProxy } from './config' import { checkVerificationLink, finishIntegration, @@ -12,6 +12,7 @@ import { } from './controllers/integration' import { getStatus } from './controllers/status' import { webhookReceiver } from './controllers/webhookReceiver' +import { authorizeGroups } from './middlewares/authorizeGroup' import { solidAuth } from './middlewares/solidAuth' import { validateBody } from './middlewares/validate' @@ -20,42 +21,69 @@ app.proxy = isBehindProxy const router = new Router() router + // .post( + // '/inbox', + // solidAuth, + // /* + // #swagger.requestBody = { + // required: true, + // content: { + // 'application/json': { + // schema: { + // type: 'object', + // properties: { + // '@context': { const: 'https://www.w3.org/ns/activitystreams' }, + // '@id': { type: 'string' }, + // '@type': { const: 'Add' }, + // actor: { type: 'string', format: 'uri' }, + // object: { type: 'string', format: 'uri' }, + // target: { type: 'string', format: 'email' }, + // }, + // required: ['@context', '@type', 'actor', 'object', 'target'], + // additionalProperties: false, + // }, + // }, + // }, + // } + // */ + // validateBody({ + // type: 'object', + // properties: { + // '@context': { const: 'https://www.w3.org/ns/activitystreams' }, + // '@id': { type: 'string' }, + // '@type': { const: 'Add' }, + // actor: { type: 'string', format: 'uri' }, + // object: { type: 'string', format: 'uri' }, + // target: { type: 'string', format: 'email' }, + // }, + // required: ['@context', '@type', 'actor', 'object', 'target'], + // additionalProperties: false, + // }), + // initializeIntegration, + // ) .post( - '/inbox', + '/init', solidAuth, - /* - #swagger.requestBody = { - required: true, - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - '@context': { const: 'https://www.w3.org/ns/activitystreams' }, - '@id': { type: 'string' }, - '@type': { const: 'Add' }, - actor: { type: 'string', format: 'uri' }, - object: { type: 'string', format: 'uri' }, - target: { type: 'string', format: 'email' }, - }, - required: ['@context', '@type', 'actor', 'object', 'target'], - additionalProperties: false, - }, - }, - }, - } - */ + authorizeGroups(allowedGroups), + // #swagger.requestBody = { + // required: true, + // content: { + // 'application/json': { + // schema: { + // type: 'object', + // properties: { + // email: { type: 'string', format: 'email' }, + // }, + // required: ['email'], + // additionalProperties: false, + // }, + // }, + // }, + // } validateBody({ type: 'object', - properties: { - '@context': { const: 'https://www.w3.org/ns/activitystreams' }, - '@id': { type: 'string' }, - '@type': { const: 'Add' }, - actor: { type: 'string', format: 'uri' }, - object: { type: 'string', format: 'uri' }, - target: { type: 'string', format: 'email' }, - }, - required: ['@context', '@type', 'actor', 'object', 'target'], + properties: { email: { type: 'string', format: 'email' } }, + required: ['email'], additionalProperties: false, }), initializeIntegration, @@ -71,7 +99,7 @@ app bodyParser({ enableTypes: ['text', 'json'], extendTypes: { - json: ['application/ld+json'], + json: ['application/ld+json', 'application/json'], text: ['text/turtle'], }, }), diff --git a/src/config/index.ts b/src/config/index.ts index a050702..de9bf52 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -13,6 +13,9 @@ export const mailerCredentials = { email: process.env.MAILER_IDENTITY_EMAIL ?? 'bot@example', password: process.env.MAILER_IDENTITY_PASSWORD ?? 'password', provider: process.env.MAILER_IDENTITY_PROVIDER ?? 'http://localhost:3456', + webId: + process.env.MAILER_IDENTITY_WEBID ?? + 'http://localhost:3456/bot/profile/card#me', } const stringToBoolean = (value: string | undefined): boolean => { @@ -39,8 +42,8 @@ export const emailSender = process.env.EMAIL_SENDER export const port: number = +(process.env.PORT ?? 3005) -// email verification expiration in milliseconds (1 hour) -export const emailVerificationExpiration = 3600 * 1000 +// email verification expiration in seconds (1 hour) +export const emailVerificationExpiration = 3600 // configuration of database in form of sequelize options export const database: Options = { @@ -54,3 +57,17 @@ export const database: Options = { } export const isBehindProxy = stringToBoolean(process.env.BEHIND_PROXY) + +const stringToArray = (value: string | undefined) => { + if (!value) return [] + return value.split(/\s*,\s*/) +} + +export const allowedGroups = stringToArray( + process.env.ALLOWED_GROUPS ?? 'https://example.com#us', +) + +export const jwt = { + key: process.env.JWT_KEY ?? './ecdsa-p256-private.pem', + alg: process.env.JWT_ALG ?? 'ES256', +} diff --git a/src/controllers/integration.ts b/src/controllers/integration.ts index eceb6e8..262c0b3 100644 --- a/src/controllers/integration.ts +++ b/src/controllers/integration.ts @@ -1,48 +1,35 @@ -import { compare, hash } from 'bcryptjs' -import fetch from 'cross-fetch' -import crypto from 'crypto' -import { getAuthenticatedFetch } from 'css-authn/dist/7.x' +import { readFile } from 'fs-extra' +import jsonwebtoken, { + JsonWebTokenError, + TokenExpiredError, +} from 'jsonwebtoken' import { Middleware } from 'koa' import { pick } from 'lodash' -import n3 from 'n3' -import parseLinkHeader from 'parse-link-header' import * as config from '../config' -import { EmailVerification, Integration } from '../config/sequelize' import { sendMail } from '../services/mailerService' export const initializeIntegration: Middleware = async ctx => { // we should receive info about webId and email address - const email: string = ctx.request.body.target - const webId: string = ctx.request.body.actor - const inbox: string = ctx.request.body.object - - // webId should belong to the authenticated user - - if (webId !== ctx.state.user) ctx.throw(403, {}) - - // search for webId's inbox - - // try to subscribe to the inbox - - // send verification email to provided email address - const token = crypto.randomBytes(64).toString('base64url').slice(0, 72) // there is no sense in having more than 72, since bcrypt cuts remaining stuff away - // or we would need to use different hashing - const tokenHash = await hash(token, 12) - // tokens should expire (in 1 hour perhaps?) - const tokenExpiration = Date.now() + config.emailVerificationExpiration - - // save the webId, email, and hashedToken to database - await EmailVerification.create({ - webId, - email, - inbox, - tokenHash, - tokenExpiration, - }) + const email: string = ctx.request.body.email + const user: string = ctx.state.user + + // make a jwt token that verifies email address + + const tokenExpiration = config.emailVerificationExpiration + + const pem = await readFile(config.jwt.key, { encoding: 'utf-8' }) + const jwt = jsonwebtoken.sign( + { + webId: user, + email, + emailVerified: false, + iss: config.mailerCredentials.webId, + }, + pem, + { algorithm: 'ES256', expiresIn: tokenExpiration }, + ) - const emailVerificationLink = `${ - config.baseUrl - }/verify-email?id=${encodeURIComponent(webId)}&token=${token}` + const emailVerificationLink = `${config.baseUrl}/verify-email?token=${jwt}` await sendMail({ from: config.emailSender, @@ -55,143 +42,51 @@ export const initializeIntegration: Middleware = async ctx => { } export const checkVerificationLink: Middleware = async (ctx, next) => { - const webId = ctx.request.query.id - const token = ctx.request.query.token + const jwt = ctx.request.query.token - if ( - typeof webId !== 'string' || - typeof token !== 'string' || - !webId || - !token - ) + if (typeof jwt !== 'string' || !jwt) return ctx.throw( 400, 'This is not a valid verification link. Have you received the link in your email?', ) - const verification = await EmailVerification.findOne({ where: { webId } }) - - if (!verification) return ctx.throw(400, 'Verification link is invalid') - - const isCorrectToken = await compare(token, verification.tokenHash) - - if (!isCorrectToken) return ctx.throw(400, 'Verification link is invalid') - - ctx.state.integration = pick( - verification.dataValues, - 'inbox', - 'email', - 'webId', - ) - - await verification.destroy() - - if (verification.tokenExpiration < Date.now()) { - return ctx.throw(400, 'Verification link is expired') + const pem = await readFile(config.jwt.key, { encoding: 'utf-8' }) + try { + const payload = jsonwebtoken.verify(jwt, pem) as { + webId: string + email: string + emailVerified: boolean + iss: string + iat: number + exp: number + } + + ctx.state.integration = pick(payload, 'email', 'webId') + await next() + } catch (error) { + if (error instanceof TokenExpiredError) + return ctx.throw(400, 'Verification link is expired') + + if (error instanceof JsonWebTokenError) + return ctx.throw(400, 'Verification link is invalid') + + throw error } - - await next() } export const finishIntegration: Middleware = async ctx => { - const integrationData = ctx.state.integration as { + const { webId, email } = ctx.state.integration as { webId: string - inbox: string email: string } - // save the integration to database - await Integration.create(integrationData) - - try { - // subscribe to the inbox' webhook notifications - await subscribeForNotifications(integrationData.inbox) - ctx.response.body = - 'Email notifications have been successfully integrated to your inbox' - } catch (e) { - ctx.response.body = - "Email was successfully verified, but notifications won't work, yet. It looks like your Solid Pod doesn't support notifications. We'll implement a workaround in the future.\nError: " + - (e as Error).message - } finally { - ctx.response.status = 200 - } -} - -type Fetch = typeof fetch - -/** - * Discover a webhook channel to subscribe to a resource - */ -const getWebhookChannel = async ( - url: string, - fetch: Fetch, -): Promise => { - const resourceInfo = await fetch(url, { - method: 'HEAD', - }) - - if (!resourceInfo.ok) throw new Error('Fetch of resource not successful') - - const linkHeader = parseLinkHeader(resourceInfo.headers.get('link')) - - if (!linkHeader) throw new Error('Link header not found') - - const storageDescriptionUri = - linkHeader['http://www.w3.org/ns/solid/terms#storageDescription']?.url - - const descriptionResourceUri = linkHeader.describedby?.url - - if (!storageDescriptionUri) throw new Error('storage description not found') - - const storageDescriptionResponse = await fetch(storageDescriptionUri) - const storageDescription = await storageDescriptionResponse.text() - - // parse the RDF that we received - const parser = new n3.Parser({ - format: storageDescriptionResponse.headers.get('content-type') ?? undefined, - baseIRI: storageDescriptionUri, - }) - const triples = parser.parse(storageDescription) - - // find the webhook channel to subscribe to - // in storage description - const webhookChannelTriple = triples.find( - triple => - triple.predicate.id === - 'http://www.w3.org/ns/solid/notifications#channelType' && - triple.object.id === - 'http://www.w3.org/ns/solid/notifications#WebhookChannel2023', + const pem = await readFile(config.jwt.key, { encoding: 'utf-8' }) + const jwt = jsonwebtoken.sign( + { webId, email, emailVerified: true, iss: config.mailerCredentials.webId }, + pem, + { algorithm: 'ES256' }, ) - - if (!webhookChannelTriple) throw new Error('webhook channel not found') - - const dru = new URL(descriptionResourceUri as string, url).toString() - // we want to include looking into this resource, too - // TODO use this variable below - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const descriptionResource = await (await fetch(dru)).text() - - return webhookChannelTriple.subject.id -} - -const subscribeForNotifications = async (resourceUrl: string) => { - const authenticatedMailerFetch = await getAuthenticatedFetch( - config.mailerCredentials, - ) - - const webhookChannel = await getWebhookChannel( - resourceUrl, - authenticatedMailerFetch, - ) - - return await authenticatedMailerFetch(webhookChannel, { - method: 'POST', - body: JSON.stringify({ - '@context': ['https://www.w3.org/ns/solid/notification/v1'], - type: 'http://www.w3.org/ns/solid/notifications#WebhookChannel2023', - topic: resourceUrl, - sendTo: `${config.baseUrl}/webhook-receiver`, - }), - headers: { 'content-type': 'application/ld+json' }, - }) + ctx.response.body = jwt + ctx.set('content-type', 'text/plain') + ctx.response.status = 200 } diff --git a/src/middlewares/authorizeGroup.ts b/src/middlewares/authorizeGroup.ts new file mode 100644 index 0000000..267740d --- /dev/null +++ b/src/middlewares/authorizeGroup.ts @@ -0,0 +1,55 @@ +import { Middleware } from 'koa' +import { Parser } from 'n3' +import { vcard } from 'rdf-namespaces' + +export const authorizeGroups = + (groups: string[]): Middleware => + async (ctx, next) => { + // if array of groups are empty, we allow everybody (default) + if (groups.length === 0) return await next() + + const user = ctx.state.user + + const memberships = await Promise.allSettled( + groups.map(group => isGroupMember(user, group)), + ) + + const isAllowed = memberships.some( + membership => + membership.status === 'fulfilled' && membership.value === true, + ) + + if (!isAllowed) { + return ctx.throw( + 403, + 'Authenticated user is not a member of any allowed group', + ) + } + + await next() + } + +const isGroupMember = async (user: string, group: string) => { + const groupDocumentResponse = await fetch(group) + if (!groupDocumentResponse.ok) return false + const groupDocument = await groupDocumentResponse.text() + + const parser = new Parser({ baseIRI: group }) + + return await new Promise((resolve, reject) => { + parser.parse(groupDocument, (error, quad) => { + if (error) return reject(error) + + // finished without finding the membership + if (!quad) return resolve(false) + + // is the quad expressing the searched membership? + if ( + quad.subject.value === group && + quad.predicate.value === vcard.hasMember && + quad.object.value === user + ) + return resolve(true) + }) + }) +} diff --git a/src/middlewares/solidAuth.ts b/src/middlewares/solidAuth.ts index 4f1d99c..223408a 100644 --- a/src/middlewares/solidAuth.ts +++ b/src/middlewares/solidAuth.ts @@ -27,6 +27,7 @@ export const solidAuth: Middleware = async (ctx, next) => { }` ctx.throw(401, message) + return } // on success continue diff --git a/src/test/integration-finish.spec.ts b/src/test/integration-finish.spec.ts index 8bdb6d9..77fd865 100644 --- a/src/test/integration-finish.spec.ts +++ b/src/test/integration-finish.spec.ts @@ -1,19 +1,15 @@ import { expect } from 'chai' import * as cheerio from 'cheerio' import fetch from 'cross-fetch' +import * as jsonwebtoken from 'jsonwebtoken' import { describe } from 'mocha' import Mail from 'nodemailer/lib/mailer' import { SinonSandbox, SinonSpy, createSandbox } from 'sinon' -import { baseUrl } from '../config' +import * as config from '../config' import * as mailerService from '../services/mailerService' -import { - authenticatedFetch, - authenticatedFetchNoNotifications, - person, - personNoNotifications, -} from './testSetup.spec' +import { authenticatedFetch, person } from './testSetup.spec' -describe('email verification via /verify-email?id=webId&token=base64Token', () => { +describe('email verification via /verify-email?token=jwt', () => { let sendMailSpy: SinonSpy<[options: Mail.Options], Promise> let verificationLink: string let sandbox: SinonSandbox @@ -30,20 +26,10 @@ describe('email verification via /verify-email?id=webId&token=base64Token', () = beforeEach(async () => { // initialize the integration - const initResponse = await authenticatedFetch(`${baseUrl}/inbox`, { + const initResponse = await authenticatedFetch(`${config.baseUrl}/init`, { method: 'post', - headers: { - 'content-type': - 'application/ld+json;profile="https://www.w3.org/ns/activitystreams"', - }, - body: JSON.stringify({ - '@context': 'https://www.w3.org/ns/activitystreams', - '@id': '', - '@type': 'Add', - actor: person.webId, - object: person.podUrl + 'profile/card', - target: 'email@example.com', - }), + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ email: 'email@example.com' }), }) expect(initResponse.status).to.equal(200) @@ -54,27 +40,38 @@ describe('email verification via /verify-email?id=webId&token=base64Token', () = expect(verificationLink).to.not.be.null }) - it('[correct token] should finish the integration of webId + inbox + email and respond 200', async () => { + it('[correct token] should respond with 200', async () => { const response = await fetch(verificationLink) expect(response.status).to.equal(200) }) - it('[incorrect token] should respond with 400', async () => { - const response = await fetch(verificationLink.slice(0, -2)) - expect(response.status).to.equal(400) - expect(await response.text()).to.equal('Verification link is invalid') + it('[correct token] should return proof of verification for the user to save on their pod', async () => { + // the proof is a JWT token, it keeps email and webId of the person as payload + const response = await fetch(verificationLink) + expect(response.ok).to.be.true + const jwt = await response.text() + const payload = jsonwebtoken.decode(jwt) as jsonwebtoken.JwtPayload + + expect(payload).to.include({ + iss: config.mailerCredentials.webId, + webId: person.webId, + email: 'email@example.com', + emailVerified: true, + }) }) - it('[integration for webId not started] should respond with 400', async () => { + it("[correct token] (maybe) should save the verification proof to user's pod") + + it('[incorrect token] should respond with 400', async () => { const response = await fetch( - `${baseUrl}/verify-email?id=asdf&token=12345678`, + verificationLink.slice(0, -32) + '0'.repeat(32), ) expect(response.status).to.equal(400) expect(await response.text()).to.equal('Verification link is invalid') }) - it('[missing id or token] should respond with 400', async () => { - const response = await fetch(`${baseUrl}/verify-email`) + it('[missing token] should respond with 400', async () => { + const response = await fetch(`${config.baseUrl}/verify-email`) expect(response.status).to.equal(400) expect(await response.text()).to.equal( 'This is not a valid verification link. Have you received the link in your email?', @@ -88,41 +85,4 @@ describe('email verification via /verify-email?id=webId&token=base64Token', () = expect(response.status).to.equal(400) expect(await response.text()).to.equal('Verification link is expired') }) - - it('when we send out multiple verification emails, the last link should work') - - context("server doesn't support webhook notifications", () => { - beforeEach(async () => { - // initialize the integration - const initResponse = await authenticatedFetchNoNotifications( - `${baseUrl}/inbox`, - { - method: 'post', - headers: { - 'content-type': - 'application/ld+json;profile="https://www.w3.org/ns/activitystreams"', - }, - body: JSON.stringify({ - '@context': 'https://www.w3.org/ns/activitystreams', - '@id': '', - '@type': 'Add', - actor: personNoNotifications.webId, - object: personNoNotifications.podUrl + 'profile/card', - target: 'email@example.com', - }), - }, - ) - - expect(initResponse.status).to.equal(200) - // email was sent - const emailMessage = sendMailSpy.secondCall.firstArg.html - const $ = cheerio.load(emailMessage) - verificationLink = $('a').first().attr('href') as string - expect(verificationLink).to.not.be.null - }) - it("should verify email, but inform user that notifications aren't supported", async () => { - const response = await fetch(verificationLink) - expect(response.status).to.equal(200) - }) - }) }) diff --git a/src/test/integration-start.spec.ts b/src/test/integration-start.spec.ts index 9b25b0e..cac72a6 100644 --- a/src/test/integration-start.spec.ts +++ b/src/test/integration-start.spec.ts @@ -5,9 +5,9 @@ import Mail from 'nodemailer/lib/mailer' import { SinonSandbox, SinonSpy, createSandbox } from 'sinon' import { baseUrl } from '../config' import * as mailerService from '../services/mailerService' -import { authenticatedFetch, person } from './testSetup.spec' +import { authenticatedFetch, otherAuthenticatedFetch } from './testSetup.spec' -describe('Mailer integration via /inbox', () => { +describe('Initialize email integration via /init', () => { let sendMailSpy: SinonSpy<[options: Mail.Options], Promise> let sandbox: SinonSandbox @@ -20,52 +20,41 @@ describe('Mailer integration via /inbox', () => { sandbox.restore() }) - it('should be able to receive integration request to inbox', async () => { - const response = await authenticatedFetch(`${baseUrl}/inbox`, { - method: 'post', - headers: { - 'content-type': - 'application/ld+json;profile="https://www.w3.org/ns/activitystreams"', - }, - body: JSON.stringify({ - '@context': 'https://www.w3.org/ns/activitystreams', - '@id': '', - '@type': 'Add', - actor: person.webId, - object: person.podUrl + 'inbox/', - target: 'email@example.com', - }), + context('everything ok', () => { + let response: Response + beforeEach(async () => { + response = await authenticatedFetch(`${baseUrl}/init`, { + method: 'post', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ email: 'email@example.com' }), + }) + }) + + it('should be able to receive integration request to /init', async () => { + expect(response.status).to.equal(200) }) - expect(sendMailSpy.calledOnce).to.be.true - expect(sendMailSpy.firstCall.firstArg).to.haveOwnProperty( - 'to', - 'email@example.com', - ) - expect(sendMailSpy.firstCall.firstArg) - .to.haveOwnProperty('text') - .include(`verify-email?id=${encodeURIComponent(person.webId)}&token=`) - expect(sendMailSpy.firstCall.firstArg) - .to.haveOwnProperty('html') - .include(`verify-email?id=${encodeURIComponent(person.webId)}&token=`) - expect(response.status).to.equal(200) + it('should send email with verification link', async () => { + expect(sendMailSpy.calledOnce).to.be.true + expect(sendMailSpy.firstCall.firstArg).to.haveOwnProperty( + 'to', + 'email@example.com', + ) + expect(sendMailSpy.firstCall.firstArg) + .to.haveOwnProperty('text') + .include(`verify-email?token=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.`) + expect(sendMailSpy.firstCall.firstArg) + .to.haveOwnProperty('html') + .include(`verify-email?token=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.`) + // maybe TODO we can perhaps also check the payload of the token + }) }) it('[invalid request body] should respond with 400', async () => { - const response = await authenticatedFetch(`${baseUrl}/inbox`, { + const response = await authenticatedFetch(`${baseUrl}/init`, { method: 'post', - headers: { - 'content-type': - 'application/ld+json;profile="https://www.w3.org/ns/activitystreams"', - }, - body: JSON.stringify({ - '@context': 'https://www.w3.org/ns/activitystrams', - '@id': '', - '@type': 'ads', - actor: 'asdf', - object: '/inbox', - target: 'yay', - }), + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ email: 'yay' }), }) expect(response.status).to.equal(400) @@ -73,47 +62,25 @@ describe('Mailer integration via /inbox', () => { context('person not signed in', () => { it('should respond with 401', async () => { - const response = await fetch(`${baseUrl}/inbox`, { + const response = await fetch(`${baseUrl}/init`, { method: 'post', - headers: { 'content-type': 'application/ld+json' }, - body: JSON.stringify({ - '@context': 'https://www.w3.org/ns/activitystreams', - '@id': '', - '@type': 'Add', - actor: person.webId, - object: person.podUrl + 'inbox/', - target: 'email@example.com', - }), + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ email: 'email@example.com' }), }) expect(response.status).to.equal(401) }) }) - context("authenticated person and actor don't match", () => { + context('person is not in the allowed group(s)', () => { it('should respond with 403', async () => { - const response = await authenticatedFetch(`${baseUrl}/inbox`, { + const response = await otherAuthenticatedFetch(`${baseUrl}/init`, { method: 'post', - headers: { 'content-type': 'application/ld+json' }, - body: JSON.stringify({ - '@context': 'https://www.w3.org/ns/activitystreams', - '@id': '', - '@type': 'Add', - actor: 'http://localhost:3456/person2/profile/card#me', - object: person.podUrl + 'inbox/', - target: 'email2@example.com', - }), + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ email: 'email@example.com' }), }) expect(response.status).to.equal(403) }) }) - - it( - 'should check that the inbox belongs to the person requesting subscription', - ) - - it('should check that it can read the inbox') - - it('should send email with verification link') }) diff --git a/src/test/notification.spec.ts b/src/test/notification.spec.ts index 2b07328..8aec890 100644 --- a/src/test/notification.spec.ts +++ b/src/test/notification.spec.ts @@ -11,7 +11,7 @@ import * as mailerService from '../services/mailerService' import { addRead, setupInbox } from '../setup' import { authenticatedFetch, person } from './testSetup.spec' -describe('received notification via /webhook-receiver', () => { +describe.skip('received notification via /inbox', () => { let sendMailSpy: SinonSpy<[options: Mail.Options], Promise> let verificationLink: string let sandbox: SinonSandbox @@ -70,7 +70,7 @@ describe('received notification via /webhook-receiver', () => { expect(finishResponse.status).to.equal(200) }) - it('[everything ok] should send email to email address when notifiation arrives', async function () { + it('[everything ok] should send email to email address when requested', async function () { this.timeout(10000) // create notification in inbox of person2 const authenticatedPerson2Fetch = await getAuthenticatedFetch({ @@ -104,5 +104,8 @@ describe('received notification via /webhook-receiver', () => { // TODO }) - it('[irrelevant update] should do nothing') + it('[not authenticated] should fail with 401') + it('[authenticated person not from group] should fail with 403') + + it('[requesting person not from group] should fail with 400') }) diff --git a/src/test/status.spec.ts b/src/test/status.spec.ts index c1f22e4..b7553b2 100644 --- a/src/test/status.spec.ts +++ b/src/test/status.spec.ts @@ -9,7 +9,7 @@ import * as mailerService from '../services/mailerService' import { addRead, setupInbox } from '../setup' import { authenticatedFetch, person } from './testSetup.spec' -describe('get info about integrations of current person with GET /status', () => { +describe.skip('get info about integrations of a person with GET /status', () => { let sendMailSpy: SinonSpy<[options: Mail.Options], Promise> let verificationLink: string let sandbox: SinonSandbox @@ -69,43 +69,6 @@ describe('get info about integrations of current person with GET /status', () => expect(response.status).to.equal(401) }) - it('should show list of resources (inboxes) current user is observing', async () => { - // finish the integration - const finishResponse = await fetch(verificationLink) - expect(finishResponse.status).to.equal(200) - - const response = await authenticatedFetch(`${baseUrl}/status`) - expect(response.status).to.equal(200) - - const body = await response.json() - - expect(body).to.deep.equal({ - actor: person.webId, - integrations: [ - { - object: `${person.podUrl}inbox/`, - target: 'email@example.com', - verified: true, - }, - ], - }) - }) - - it('should show unverified integrations', async () => { - const response = await authenticatedFetch(`${baseUrl}/status`) - expect(response.status).to.equal(200) - - const body = await response.json() - - expect(body).to.deep.equal({ - actor: person.webId, - integrations: [ - { - object: `${person.podUrl}inbox/`, - target: 'email@example.com', - verified: false, - }, - ], - }) - }) + it('[authenticated person not from group] should fail with 403') + it('[requested person not from group] should fail with 400') }) diff --git a/src/test/testSetup.spec.ts b/src/test/testSetup.spec.ts index a31aea4..b99cea2 100644 --- a/src/test/testSetup.spec.ts +++ b/src/test/testSetup.spec.ts @@ -1,7 +1,10 @@ import * as css from '@solid/community-server' +import fetch from 'cross-fetch' import { getAuthenticatedFetch } from 'css-authn/dist/7.x' import { IncomingMessage, Server, ServerResponse } from 'http' import MailDev from 'maildev' +import { HttpResponse, http } from 'msw' +import { SetupServer, setupServer } from 'msw/node' import app from '../app' import { port } from '../config' import { EmailVerification, Integration } from '../config/sequelize' @@ -9,7 +12,7 @@ import { createRandomAccount } from '../helpers' let server: Server let authenticatedFetch: typeof fetch -let authenticatedFetchNoNotifications: typeof fetch +let otherAuthenticatedFetch: typeof fetch let person: { idp: string podUrl: string @@ -18,7 +21,7 @@ let person: { password: string email: string } -let personNoNotifications: { +let otherPerson: { idp: string podUrl: string webId: string @@ -27,7 +30,7 @@ let personNoNotifications: { email: string } let cssServer: css.App -let cssServerNoNotifications: css.App +let mockServer: SetupServer before(async function () { this.timeout(60000) @@ -62,39 +65,6 @@ after(async () => { await cssServer.stop() }) -before(async function () { - this.timeout(60000) - const start = Date.now() - - // eslint-disable-next-line no-console - console.log('Starting CSS server without notifications') - // Community Solid Server (CSS) set up following example in https://github.com/CommunitySolidServer/hello-world-component/blob/main/test/integration/Server.test.ts - cssServerNoNotifications = await new css.AppRunner().create({ - loaderProperties: { - mainModulePath: css.joinFilePath(__dirname, '../../'), // ? - typeChecking: false, // ? - dumpErrorState: false, // disable CSS error dump - }, - config: css.joinFilePath(__dirname, './css-config-no-notifications.json'), // CSS config - variableBindings: {}, - // CSS cli options - // https://github.com/CommunitySolidServer/CommunitySolidServer/tree/main#-parameters - shorthand: { - port: 3457, - loggingLevel: 'off', - // seededPodConfigJson: css.joinFilePath(__dirname, './css-pod-seed.json'), // set up some Solid accounts - }, - }) - await cssServerNoNotifications.start() - - // eslint-disable-next-line no-console - console.log('CSS server started in', (Date.now() - start) / 1000, 'seconds') -}) - -after(async () => { - await cssServerNoNotifications.stop() -}) - before(done => { server = app.listen(port, done) }) @@ -131,25 +101,45 @@ beforeEach(async () => { password: person.password, provider: 'http://localhost:3456', }) -}) -beforeEach(async () => { - personNoNotifications = await createRandomAccount({ - solidServer: 'http://localhost:3457', + otherPerson = await createRandomAccount({ + solidServer: 'http://localhost:3456', }) - authenticatedFetchNoNotifications = await getAuthenticatedFetch({ - email: personNoNotifications.email, - password: personNoNotifications.password, - provider: 'http://localhost:3457', + otherAuthenticatedFetch = await getAuthenticatedFetch({ + email: otherPerson.email, + password: otherPerson.password, + provider: 'http://localhost:3456', }) }) +// Enable request interception. +beforeEach(async () => { + mockServer = setupServer( + // Describe network behavior with request handlers. + // Tip: move the handlers into their own module and + // import it across your browser and Node.js setups! + http.get('https://example.com/', (/*{ request, params, cookies }*/) => { + return HttpResponse.text(` + @prefix vcard: . + <#us> vcard:hasMember <${person.webId}> . + `) + }), + ) + mockServer.listen({ onUnhandledRequest() {} }) // quiet the unhandled request warnings +}) + +// Reset handlers so that each test could alter them +// without affecting other, unrelated tests. +afterEach(() => { + mockServer.resetHandlers() + mockServer.close() +}) + export { authenticatedFetch, - authenticatedFetchNoNotifications, cssServer, - cssServerNoNotifications, + otherAuthenticatedFetch, + otherPerson, person, - personNoNotifications, server, } diff --git a/yarn.lock b/yarn.lock index 4668861..a86218e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14,6 +14,27 @@ dependencies: buffer "^6.0.3" +"@bundled-es-modules/cookie@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz#c3b82703969a61cf6a46e959a012b2c257f6b164" + integrity sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw== + dependencies: + cookie "^0.5.0" + +"@bundled-es-modules/js-levenshtein@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/js-levenshtein/-/js-levenshtein-2.0.1.tgz#b02bbbd546358ab77080a430f0911cfc2b3779c4" + integrity sha512-DERMS3yfbAljKsQc0U2wcqGKUWpdFjwqWuoMugEJlqBnKO180/n+4SR/J8MRDt1AN48X1ovgoD9KrdVXcaa3Rg== + dependencies: + js-levenshtein "^1.1.6" + +"@bundled-es-modules/statuses@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz#761d10f44e51a94902c4da48675b71a76cc98872" + integrity sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg== + dependencies: + statuses "^2.0.1" + "@colors/colors@1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" @@ -2535,6 +2556,23 @@ semver "^7.3.5" tar "^6.1.11" +"@mswjs/cookies@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-1.1.0.tgz#1528eb43630caf83a1d75d5332b30e75e9bb1b5b" + integrity sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw== + +"@mswjs/interceptors@^0.25.14": + version "0.25.14" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.25.14.tgz#70c1c9736b5c327337c0eb3da6d4895d7928049b" + integrity sha512-2dnIxl+obqIqjoPXTFldhe6pcdOrqiz+GcLaQQ6hmL02OldAF7nIC+rUgTWm+iF6lvmyCVhFFqbgbapNhR8eag== + dependencies: + "@open-draft/deferred-promise" "^2.2.0" + "@open-draft/logger" "^0.3.0" + "@open-draft/until" "^2.0.0" + is-node-process "^1.2.0" + outvariant "^1.2.1" + strict-event-emitter "^0.5.1" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2572,6 +2610,24 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@open-draft/deferred-promise@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd" + integrity sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA== + +"@open-draft/logger@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@open-draft/logger/-/logger-0.3.0.tgz#2b3ab1242b360aa0adb28b85f5d7da1c133a0954" + integrity sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ== + dependencies: + is-node-process "^1.2.0" + outvariant "^1.4.0" + +"@open-draft/until@^2.0.0", "@open-draft/until@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.1.0.tgz#0acf32f470af2ceaf47f095cdecd40d68666efda" + integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== + "@pkgr/utils@^2.3.1": version "2.4.2" resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" @@ -2946,7 +3002,7 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/fs-extra@^11.0.2": +"@types/fs-extra@^11.0.2", "@types/fs-extra@^11.0.4": version "11.0.4" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45" integrity sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ== @@ -2976,6 +3032,11 @@ dependencies: "@types/node" "*" +"@types/js-levenshtein@^1.1.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.3.tgz#a6fd0bdc8255b274e5438e0bfb25f154492d1106" + integrity sha512-jd+Q+sD20Qfu9e2aEXogiO3vpOC1PYJOUdyN9gvs4Qrvkg4wF43L5OhqrPeokdv8TL0/mXoYfpkcoGZMNN2pkQ== + "@types/json-schema@^7.0.11": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" @@ -2988,6 +3049,13 @@ dependencies: "@types/node" "*" +"@types/jsonwebtoken@^9.0.5": + version "9.0.5" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz#0bd9b841c9e6c5a937c17656e2368f65da025588" + integrity sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA== + dependencies: + "@types/node" "*" + "@types/keygrip@*": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72" @@ -3261,6 +3329,11 @@ dependencies: "@rdfjs/types" ">=1.0.0" +"@types/statuses@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.4.tgz#041143ba4a918e8f080f8b0ffbe3d4cb514e2315" + integrity sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw== + "@types/tough-cookie@^4.0.5": version "4.0.5" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" @@ -3524,6 +3597,13 @@ ansi-colors@4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -3662,6 +3742,15 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + body-parser@1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" @@ -3724,6 +3813,19 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== +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@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + 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" @@ -3847,7 +3949,7 @@ chai@^4.3.7: pathval "^1.1.1" type-detect "^4.0.5" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3855,6 +3957,11 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" @@ -3885,7 +3992,7 @@ cheerio@^1.0.0-rc.12: parse5 "^7.0.0" parse5-htmlparser2-tree-adapter "^7.0.0" -chokidar@3.5.3: +chokidar@3.5.3, chokidar@^3.4.2: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -3910,6 +4017,23 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.5.0: + version "2.9.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -3928,6 +4052,11 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + clownface@^1.4.0: version "1.5.1" resolved "https://registry.yarnpkg.com/clownface/-/clownface-1.5.1.tgz#5471f462aa8a5945ad878305b832361214424759" @@ -4290,6 +4419,13 @@ default-browser@^4.0.0: execa "^7.1.1" titleize "^3.0.0" +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + defer-to-connect@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" @@ -4416,6 +4552,13 @@ dottie@^2.0.4: resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.6.tgz#34564ebfc6ec5e5772272d466424ad5b696484d4" integrity sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA== +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" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -4508,7 +4651,7 @@ escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escape-string-regexp@~1.0.5: +escape-string-regexp@^1.0.5, escape-string-regexp@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== @@ -4738,6 +4881,15 @@ extend@~3.0.0: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -4801,6 +4953,13 @@ fetch-sparql-endpoint@^4.0.0, fetch-sparql-endpoint@^4.1.0: sparqlxml-parse "^2.1.1" stream-to-string "^1.1.0" +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -4899,6 +5058,15 @@ fs-extra@^11.1.1: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -5081,6 +5249,11 @@ graphql@^15.5.2: resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== +graphql@^16.8.1: + version "16.8.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" + integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== + handlebars@^4.7.8: version "4.7.8" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" @@ -5140,6 +5313,11 @@ he@1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +headers-polyfill@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.2.tgz#9115a76eee3ce8fbf95b6e3c6bf82d936785b44a" + integrity sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw== + helmet@^6.0.1: version "6.2.0" resolved "https://registry.yarnpkg.com/helmet/-/helmet-6.2.0.tgz#c29d62014be4c70b8ef092c9c5e54c8c26b8e16e" @@ -5273,7 +5451,7 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -iconv-lite@0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -5294,7 +5472,7 @@ iconv-lite@0.6.3, iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.2.1: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -5350,7 +5528,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -5360,6 +5538,27 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== +inquirer@^8.2.0: + version "8.2.6" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.6.tgz#733b74888195d8d400a67ac332011b5fae5ea562" + integrity sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^6.0.1" + ioredis@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7" @@ -5443,11 +5642,21 @@ is-inside-container@^1.0.0: dependencies: is-docker "^3.0.0" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== +is-node-process@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.2.0.tgz#ea02a1b90ddb3934a19aea414e88edef7e11d134" + integrity sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw== + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -5530,6 +5739,11 @@ jose@^5.1.3: resolved "https://registry.yarnpkg.com/jose/-/jose-5.2.0.tgz#d0ffd7f7e31253f633eefb190a930cd14a916995" integrity sha512-oW3PCnvyrcm1HMvGTzqjxxfnEs9EoFOFWi2HsEGhlFVOXxTE3K9GKWVMFoFw06yPUqwpvEWic1BmtUZBI/tIjw== +js-levenshtein@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== + js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -5659,11 +5873,44 @@ jsonld-streaming-serializer@^2.1.0: jsonld-context-parser "^2.0.0" readable-stream "^4.0.0" +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" + just-extend@^4.0.2: version "4.2.1" resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + 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" + keygrip@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" @@ -5774,16 +6021,51 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== +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.isarguments@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== +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.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.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.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +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" @@ -5794,7 +6076,7 @@ lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@4.1.0: +log-symbols@4.1.0, log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -6133,6 +6415,38 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +msw@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/msw/-/msw-2.0.14.tgz#1fdfcad215e3c2d2fe1647bce14420c53203abbc" + integrity sha512-RcKx/JM/IOvjBjOA+cfNzLkSoMv408Tf+5TdgvzitM8/cPeN68004wBLXvbc2Jvwm1Sj4h8azR/WdO1wJg9iSw== + dependencies: + "@bundled-es-modules/cookie" "^2.0.0" + "@bundled-es-modules/js-levenshtein" "^2.0.1" + "@bundled-es-modules/statuses" "^1.0.1" + "@mswjs/cookies" "^1.1.0" + "@mswjs/interceptors" "^0.25.14" + "@open-draft/until" "^2.1.0" + "@types/cookie" "^0.4.1" + "@types/js-levenshtein" "^1.1.1" + "@types/statuses" "^2.0.1" + chalk "^4.1.2" + chokidar "^3.4.2" + graphql "^16.8.1" + headers-polyfill "^4.0.1" + inquirer "^8.2.0" + is-node-process "^1.2.0" + js-levenshtein "^1.1.6" + outvariant "^1.4.0" + path-to-regexp "^6.2.0" + strict-event-emitter "^0.5.0" + type-fest "^2.19.0" + yargs "^17.3.1" + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + n3@^1.16.2, n3@^1.16.3, n3@^1.17.0, n3@^1.6.3: version "1.17.0" resolved "https://registry.yarnpkg.com/n3/-/n3-1.17.0.tgz#224d87e235e91e2d3e3fea04043ed34b6524a8ac" @@ -6371,7 +6685,7 @@ one-time@^1.0.0: dependencies: fn.name "1.x.x" -onetime@^5.1.2: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -6422,6 +6736,31 @@ optionator@^0.9.3: prelude-ls "^1.2.1" type-check "^0.4.0" +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +outvariant@^1.2.1, outvariant@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.4.2.tgz#f54f19240eeb7f15b28263d5147405752d8e2066" + integrity sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ== + p-cancelable@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" @@ -6519,7 +6858,7 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" -path-to-regexp@^6.2.1: +path-to-regexp@^6.2.0, path-to-regexp@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== @@ -7054,6 +7393,14 @@ responselike@^3.0.0: dependencies: lowercase-keys "^3.0.0" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + retry-as-promised@^7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-7.0.4.tgz#9df73adaeea08cb2948b9d34990549dc13d800a2" @@ -7083,6 +7430,11 @@ run-applescript@^5.0.0: dependencies: execa "^5.0.0" +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -7090,12 +7442,19 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rxjs@^7.5.5: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-buffer@5.1.2, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -7122,7 +7481,7 @@ semver@^6.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.5, semver@^7.5.0, semver@^7.5.1: +semver@^7.3.2, semver@^7.3.5, semver@^7.5.0, semver@^7.5.1, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -7438,7 +7797,7 @@ standard-as-callback@^2.1.0: resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== -statuses@2.0.1: +statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== @@ -7470,6 +7829,11 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== +strict-event-emitter@^0.5.0, strict-event-emitter@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" + integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== + "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -7567,6 +7931,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + tiny-case@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" @@ -7577,6 +7946,13 @@ titleize@^3.0.0: resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -7655,6 +8031,11 @@ ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +tslib@^2.1.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tslib@^2.5.0, tslib@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" @@ -7682,6 +8063,11 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-fest@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" @@ -7843,6 +8229,13 @@ w3c-xmlserializer@^3.0.0: dependencies: xml-name-validator "^4.0.0" +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + web-streams-ponyfill@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/web-streams-ponyfill/-/web-streams-ponyfill-1.4.2.tgz#0ae863cc5f7493903679f16b08cbf14d432b62f4" @@ -7968,6 +8361,15 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +wrap-ansi@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -8060,7 +8462,7 @@ yargs@16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.7.2: +yargs@^17.3.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==