diff --git a/package.json b/package.json index fd8f021..cd91d29 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@nestjs/swagger": "^6.1.4", "@nestjs/terminus": "^9.1.4", "@prisma/client": "4.8.1", + "@techsavvyash/bitstring": "^3.2.0", "@types/uuid": "^9.0.1", "ajv": "^8.12.0", "did-jwt-vc": "^3.1.0", diff --git a/prisma/migrations/20230908124838_add_revocation_list/migration.sql b/prisma/migrations/20230908124838_add_revocation_list/migration.sql new file mode 100644 index 0000000..7a90524 --- /dev/null +++ b/prisma/migrations/20230908124838_add_revocation_list/migration.sql @@ -0,0 +1,9 @@ +-- CreateTable +CREATE TABLE "RevocationLists" ( + "issuer" TEXT NOT NULL, + "latestRevocationListId" TEXT NOT NULL, + "lastCredentialIdx" INTEGER NOT NULL, + "allRevocationLists" TEXT[], + + CONSTRAINT "RevocationLists_pkey" PRIMARY KEY ("issuer") +); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5f12f91..1e0cf65 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -37,3 +37,10 @@ model VerifiableCredentials { updatedBy String? tags String[] } + +model RevocationLists { + issuer String @id + latestRevocationListId String + lastCredentialIdx Int + allRevocationLists String[] +} diff --git a/src/app.module.ts b/src/app.module.ts index 407df07..10f3747 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,15 +7,21 @@ import { CredentialsModule } from './credentials/credentials.module'; import { TerminusModule } from '@nestjs/terminus'; import { HealthCheckUtilsService } from './credentials/utils/healthcheck.utils.service'; import { PrismaClient } from '@prisma/client'; +import { RevocationListService } from './revocation-list/revocation-list.service'; +import { RevocationListModule } from './revocation-list/revocation-list.module'; +import { RevocationList } from './revocation-list/revocation-list.helper'; +import { RevocationListImpl } from './revocation-list/revocation-list.impl'; +import { IdentityUtilsService } from './credentials/utils/identity.utils.service'; @Module({ imports: [ HttpModule, ConfigModule.forRoot({ isGlobal: true }), CredentialsModule, - TerminusModule + TerminusModule, + RevocationListModule, ], controllers: [AppController], - providers: [AppService, ConfigService, PrismaClient, HealthCheckUtilsService], + providers: [AppService, ConfigService, PrismaClient, HealthCheckUtilsService, RevocationListService, RevocationList, RevocationListImpl, IdentityUtilsService], }) export class AppModule {} diff --git a/src/credentials/credentials.module.ts b/src/credentials/credentials.module.ts index ef365f8..f1d6208 100644 --- a/src/credentials/credentials.module.ts +++ b/src/credentials/credentials.module.ts @@ -6,10 +6,12 @@ import { IdentityUtilsService } from './utils/identity.utils.service'; import { RenderingUtilsService } from './utils/rendering.utils.service'; import { SchemaUtilsSerivce } from './utils/schema.utils.service'; import { PrismaClient } from '@prisma/client'; +import { HealthCheckUtilsService } from './utils/healthcheck.utils.service'; @Module({ imports: [HttpModule], - providers: [CredentialsService, PrismaClient, IdentityUtilsService, RenderingUtilsService, SchemaUtilsSerivce], + providers: [CredentialsService, PrismaClient, IdentityUtilsService, RenderingUtilsService, SchemaUtilsSerivce, HealthCheckUtilsService], controllers: [CredentialsController], + exports: [HealthCheckUtilsService] }) export class CredentialsModule {} diff --git a/src/credentials/credentials.service.ts b/src/credentials/credentials.service.ts index 3f3f4ee..63f803e 100644 --- a/src/credentials/credentials.service.ts +++ b/src/credentials/credentials.service.ts @@ -59,6 +59,7 @@ export class CredentialsService { } return credentials.map((cred: VerifiableCredentials) => { const res = cred.signed; + res['credentialSchemaId'] = cred.credential_schema; delete res['options']; res['id'] = cred.id; return res as W3CCredential; @@ -73,6 +74,7 @@ export class CredentialsService { const credential = await this.prisma.verifiableCredentials.findUnique({ where: { id: id }, select: { + credential_schema: true, signed: true, }, }); @@ -82,6 +84,7 @@ export class CredentialsService { } // formatting the response as per the spec const res = credential.signed; + res['credentialSchemaId'] = credential.credential_schema; delete res['options']; res['id'] = id; let template = null; @@ -288,16 +291,17 @@ export class CredentialsService { issuer: getCreds.issuer?.id, AND: filteringSubject ? Object.keys(filteringSubject).map((key: string) => ({ - subject: { - path: [key.toString()], - equals: filteringSubject[key], - }, - })) + subject: { + path: [key.toString()], + equals: filteringSubject[key], + }, + })) : [], }, select: { id: true, signed: true, + credential_schema: true }, skip: (page - 1) * limit, take: limit, @@ -316,7 +320,7 @@ export class CredentialsService { // formatting the output as per the spec delete signed['id']; delete signed['options']; - return { id: cred.id, ...signed }; + return { id: cred.id, ...signed, credentialSchemaId: cred.credential_schema }; }); } } diff --git a/src/revocation-list/revocation-list.helper.ts b/src/revocation-list/revocation-list.helper.ts new file mode 100644 index 0000000..d6b5cbf --- /dev/null +++ b/src/revocation-list/revocation-list.helper.ts @@ -0,0 +1,36 @@ +// @ts-ignore +import { Bitstring } from '@techsavvyash/bitstring'; + +type BitstringConstructorParam = { + length?: number, + buffer?: Uint8Array +}; + +export class RevocationList { + + private bitstring: any; + constructor({ length, buffer }: BitstringConstructorParam = { length: 100000 }) { + this.bitstring = new Bitstring({ length, buffer }); + } + + setRevoked(index, revoked) { + if (typeof revoked !== 'boolean') { + throw new TypeError('revoked must be a boolean.'); + } + + return this.bitstring.set(index, revoked); + } + + isRevoked(index) { + return this.bitstring.get(index); + } + + async encode() { + return this.bitstring.encodeBits(); + } + + static async decode({ encodedList }) { + const buffer = await Bitstring.decodeBits({ encoded: encodedList }); + return new RevocationList({ buffer }); + } +} diff --git a/src/revocation-list/revocation-list.impl.ts b/src/revocation-list/revocation-list.impl.ts new file mode 100644 index 0000000..d157ecc --- /dev/null +++ b/src/revocation-list/revocation-list.impl.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@nestjs/common'; +import { RevocationList } from './revocation-list.helper'; + +@Injectable() +export class RevocationListImpl { + constructor() {} + + public createList({ length }) { + return new RevocationList({ length }); + } + + public async decodeList({ encodedList }) { + return await RevocationList.decode({ encodedList }); + } +} diff --git a/src/revocation-list/revocation-list.module.ts b/src/revocation-list/revocation-list.module.ts new file mode 100644 index 0000000..0548910 --- /dev/null +++ b/src/revocation-list/revocation-list.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { RevocationListService } from './revocation-list.service'; +import { RevocationList } from './revocation-list.helper'; +import { PrismaClient } from '@prisma/client'; +import { RevocationListImpl } from './revocation-list.impl'; +import { CredentialsModule } from 'src/credentials/credentials.module'; +import { IdentityUtilsService } from 'src/credentials/utils/identity.utils.service'; +import { HttpModule, HttpService } from '@nestjs/axios'; + +@Module({ + imports: [HttpModule], + providers: [RevocationList, RevocationListService, PrismaClient, RevocationListImpl, IdentityUtilsService] +}) +export class RevocationListModule {} diff --git a/src/revocation-list/revocation-list.service.spec.ts b/src/revocation-list/revocation-list.service.spec.ts new file mode 100644 index 0000000..8ae93a8 --- /dev/null +++ b/src/revocation-list/revocation-list.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { RevocationListService } from './revocation-list.service'; + +describe('RevocationListService', () => { + let service: RevocationListService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [RevocationListService], + }).compile(); + + service = module.get(RevocationListService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/revocation-list/revocation-list.service.ts b/src/revocation-list/revocation-list.service.ts new file mode 100644 index 0000000..2c7507a --- /dev/null +++ b/src/revocation-list/revocation-list.service.ts @@ -0,0 +1,259 @@ +import { HttpService } from '@nestjs/axios'; +import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; +import { CredentialPayload, transformCredentialInput } from 'did-jwt-vc'; +import { DIDDocument } from 'did-resolver'; +import { IdentityUtilsService } from '../credentials/utils/identity.utils.service'; +// import { PrismaService } from 'src/prisma.service'; +import { PrismaClient } from '@prisma/client'; +import { IssuerType, Proof } from 'did-jwt-vc/lib/types'; +import { JwtCredentialSubject } from 'src/app.interface'; +import { RevocationLists, VerifiableCredentials } from '@prisma/client'; +import { RevocationListImpl } from './revocation-list.impl'; +import { RevocationList } from './revocation-list.helper'; + +@Injectable() +export class RevocationListService { + constructor( + private readonly prismaService: PrismaClient, + private readonly rl: RevocationListImpl, + private readonly identityService: IdentityUtilsService) {} + + private async signRevocationListCredential(revocationListCredential, issuer) { + try { + revocationListCredential['proof'] = { + proofValue: await this.identityService.signVC( + transformCredentialInput(revocationListCredential as CredentialPayload), + issuer, + ), + type: 'Ed25519Signature2020', + created: new Date().toISOString(), + verificationMethod: issuer, + }; + } catch (err) { + Logger.error('Error signing revocation list', err); + throw new InternalServerErrorException( + 'Error signing revocation list', + ); + } + + return revocationListCredential.proof; + } + + private generateRevocationListCredentialSkeleton(id: string, issuer: string) { + return { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc-revocation-list-2020/v1" + ], + id, + type: ["VerifiableCredential", "RevocationList2020Credential"], + issuer, + issuanceDate: new Date().toISOString(), + credentialSubject: { + id, + type: "RevocationList2020", + }, + proof: {} + }; + } + async createNewRevocationList(issuer: string) { + // generate did for the revocation list + let credDID: ReadonlyArray; + try { + credDID = await this.identityService.generateDID(['verifiable credential']); + } catch (err) { + Logger.error('Error generating DID for revocation list', err); + throw new InternalServerErrorException( + 'Error generating DID for revocation list', + ) + } + + const revocationList = await this.rl.createList({ length: 100000 }); + const encodedList = await revocationList.encode(); + + const revocationListCredential = this.generateRevocationListCredentialSkeleton(credDID[0]?.id, issuer); + revocationListCredential.credentialSubject['encodedList'] = encodedList; + // sign the revocation list + revocationListCredential.proof = await this.signRevocationListCredential(revocationListCredential, issuer); + + + // save this in the db + try { + const revocationListsOfIssuer = await this.prismaService.revocationLists.findUnique({ + where: { + issuer + } + }); + if (!revocationListsOfIssuer) { + await this.prismaService.$transaction([ + // save the revocation list as a verifiable credential + this.prismaService.verifiableCredentials.create({ + data: { + id: revocationListCredential.id, + type: revocationListCredential.type, + issuer: revocationListCredential.issuer as IssuerType as string, + issuanceDate: revocationListCredential.issuanceDate, + expirationDate: '', + subject: revocationListCredential.credentialSubject as JwtCredentialSubject, + subjectId: (revocationListCredential.credentialSubject as JwtCredentialSubject).id, + proof: revocationListCredential.proof as Proof, + credential_schema: '', // HOST A JSONLD for this in the github repo and link to that + signed: revocationListCredential as object, + tags: ['RevocationList2020Credential', 'RevocationList2020'], + } + }), + // update the revocation list data + + this.prismaService.revocationLists.create({ + data: { + issuer, + latestRevocationListId: revocationListCredential.id, + lastCredentialIdx: 0, + allRevocationLists: [revocationListCredential.id] + } + }) + ]) + } else { + await this.prismaService.$transaction([ + this.prismaService.verifiableCredentials.create({ + data: { + id: revocationListCredential.id, + type: revocationListCredential.type, + issuer: revocationListCredential.issuer as IssuerType as string, + issuanceDate: revocationListCredential.issuanceDate, + expirationDate: '', + subject: revocationListCredential.credentialSubject as JwtCredentialSubject, + subjectId: (revocationListCredential.credentialSubject as JwtCredentialSubject).id, + proof: revocationListCredential.proof as Proof, + credential_schema: '', // HOST A JSONLD for this in the github repo and link to that + signed: revocationListCredential as object, + tags: ['RevocationList2020Credential', 'RevocationList2020'], + } + }), + // update the revocation list data + this.prismaService.revocationLists.update({ + where: { + issuer + }, + data: { + latestRevocationListId: revocationListCredential.id, + lastCredentialIdx: 0, + allRevocationLists: [revocationListCredential.id, ...revocationListsOfIssuer.allRevocationLists] + } + }) + ]) + } + } catch (err) { + Logger.error('Error saving the revocation list credential into db: ', err); + throw new InternalServerErrorException( + 'Error saving the revocation list credential into db', + ); + } + + return revocationListCredential; + } + + async updateRevocationList(issuer: string, idx: number) { + // fetch the revocation list from the db + let revocationList: VerifiableCredentials; + let revocationListInfo: RevocationLists; + try { + revocationListInfo = await this.prismaService.revocationLists.findUnique({ + where: { + issuer + } + }); + + revocationList = await this.prismaService.verifiableCredentials.findUnique({ + where: { + id: revocationListInfo.latestRevocationListId + } + }); + } catch (err) { + Logger.error('Error fetching revocation list from db', err); + throw new InternalServerErrorException( + 'Error fetching revocation list from db', + ); + } + + let revocationListCredential; + try { + const encodedList = (revocationList.subject as any).encodedList; + const decodedList: RevocationList = await this.rl.decodeList({ encodedList }); + decodedList.setRevoked(idx, true); + const updatedEncodedList = await decodedList.encode(); + // update the RevocationListCredential by resigning it + revocationListCredential = this.generateRevocationListCredentialSkeleton(revocationList.id, issuer); + revocationListCredential.credentialSubject['encodedList'] = updatedEncodedList; + } catch (err) { + Logger.error('Error updating the revocation list bits: ', err); + throw new InternalServerErrorException( + 'Error updating the revocation list bits', + ); + } + // sign this again + try { + revocationListCredential.proof = await this.signRevocationListCredential(revocationListCredential, issuer); + } catch (err) { + Logger.error('Error signing the revocation list credential', err); + throw new InternalServerErrorException( + 'Error signing the revocation list credential', + ); + } + + // update the db + let revocationListCredentialId = revocationListInfo.latestRevocationListId; + try { + // update the proof of revocation list credential + await this.prismaService.verifiableCredentials.update({ + where: { + id: revocationList.id + }, + data: { + proof: revocationListCredential.proof as Proof, + } + }); + if ((revocationListInfo.lastCredentialIdx) < 100000) { + // update the counter of index in the revocation list + this.prismaService.revocationLists.update({ + where: { + issuer + }, + data: { + lastCredentialIdx: idx + 1, + } + }) + } else { + // create a new revocation list + const newRevocationList = await this.createNewRevocationList(issuer); + revocationListCredentialId = newRevocationList.id; + } + } catch (err) { + Logger.error('Error updating the revocation list credential into db: ', err); + throw new InternalServerErrorException( + 'Error updating the revocation list credential into db', + ); + } + + return revocationListCredentialId; + } + + async getDecodedRevocationString(revocationCredentialId: string) { + // fetch the credential from db + try { + const revocationListCredential = await this.prismaService.verifiableCredentials.findUnique({ + where: { + id: revocationCredentialId + } + }); + const encodedList = (revocationListCredential.subject as any).encodedList; + const decodedList = await this.rl.decodeList({ encodedList }); + return decodedList; + } catch (err) { + Logger.error('Error fetching the RevocationListCredential from db', err); + throw new InternalServerErrorException( + 'Error fetching the RevocationListCredential from db', + ); + } + } +} diff --git a/yarn.lock b/yarn.lock index e70622e..9c6d331 100644 --- a/yarn.lock +++ b/yarn.lock @@ -68,6 +68,22 @@ ora "5.4.1" rxjs "6.6.7" +"@babel/cli@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.22.15.tgz#22ed82d76745a43caa60a89917bedb7c9b5bd145" + integrity sha512-prtg5f6zCERIaECeTZzd2fMtVjlfjhUcO+fBLQ6DXXdq5FljN+excVitJ2nogsusdf31LeqkjAfXZ7Xq+HmN8g== + dependencies: + "@jridgewell/trace-mapping" "^0.3.17" + commander "^4.0.1" + convert-source-map "^1.1.0" + fs-readdir-recursive "^1.1.0" + glob "^7.2.0" + make-dir "^2.1.0" + slash "^2.0.0" + optionalDependencies: + "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" + chokidar "^3.4.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" @@ -1373,6 +1389,11 @@ dependencies: tslib "2.4.0" +"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": + version "2.1.8-no-fsevents.3" + resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" + integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ== + "@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" @@ -1627,6 +1648,15 @@ "@stablelib/wipe" "^1.0.1" "@stablelib/xchacha20" "^1.0.1" +"@techsavvyash/bitstring@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@techsavvyash/bitstring/-/bitstring-3.2.0.tgz#d0e4afdbf5420414148a210fc20ade4d9726e65a" + integrity sha512-+DsLQKkU3Cq3IHlO3uCJxd6Uk798emXNVbpNozaq+vPyheNe5l2C4iGn9GuecOhoOaXxDuITAqGjk8B4UkHQdQ== + dependencies: + "@babel/cli" "^7.22.15" + base64url-universal "^2.0.0" + pako "^2.0.4" + "@transmute/did-key-bls12381@^0.2.1-unstable.42": version "0.2.1-unstable.42" resolved "https://registry.yarnpkg.com/@transmute/did-key-bls12381/-/did-key-bls12381-0.2.1-unstable.42.tgz#d2f2252043cf9c1e874acbc7f51fd9f4bcd7707a" @@ -2595,6 +2625,13 @@ 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-universal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64url-universal/-/base64url-universal-2.0.0.tgz#6023785c0e349a90de1cf396e8a4519750a4e67b" + integrity sha512-6Hpg7EBf3t148C3+fMzjf+CHnADVDafWzlJUXAqqqbm4MKNXbsoPdOkWeRTjNlkYG7TpyjIpRO1Gk0SnsFD1rw== + dependencies: + base64url "^3.0.1" + base64url@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" @@ -2890,7 +2927,7 @@ check-disk-space@3.3.1: resolved "https://registry.yarnpkg.com/check-disk-space/-/check-disk-space-3.3.1.tgz#10c4c8706fdd16d3e5c3572a16aa95efd0b4d40b" integrity sha512-iOrT8yCZjSnyNZ43476FE2rnssvgw5hnuwOM0hm8Nj1qa0v4ieUUEbCyxxsEliaoDUb/75yCOL71zkDiDBLbMQ== -chokidar@3.5.3, chokidar@^3.5.3: +chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -3063,7 +3100,7 @@ command-line-usage@^4.0.0: table-layout "^0.4.2" typical "^2.6.1" -commander@4.1.1: +commander@4.1.1, commander@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== @@ -3125,7 +3162,7 @@ content-type@^1.0.4, content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.1.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== @@ -4089,6 +4126,11 @@ fs-monkey@^1.0.3: resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== +fs-readdir-recursive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" + integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -4180,7 +4222,7 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^7.0.0, glob@^7.1.3, glob@^7.1.4: +glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -5295,6 +5337,14 @@ magic-string@0.25.7: dependencies: sourcemap-codec "^1.4.4" +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -5828,6 +5878,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +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" @@ -5900,6 +5955,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + pino-abstract-transport@v1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3" @@ -6526,6 +6586,11 @@ slang@>=0.2: resolved "https://registry.yarnpkg.com/slang/-/slang-0.3.0.tgz#13af75b4f0c018c6a8193d704f65b23be4fbabdc" integrity sha512-kGj3TvxSDR1Enhig/aan5ucfWNDULTiWg3NrExUzO2ShXcYsm6k2oPxnav8GtslYvozxRAqL+E2O3P3QKtQYSg== +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"