From b9dbe34fe8bac8545761b5dcb3b1c637fc334767 Mon Sep 17 00:00:00 2001 From: Holash Chand Date: Thu, 30 May 2024 13:07:56 +0530 Subject: [PATCH] added support for generating web did with multiple hosts --- .../identity-service/docker-compose-test.yml | 2 +- services/identity-service/docker-compose.yml | 2 +- .../src/did/did.controller.ts | 8 ++-- .../src/did/did.service.spec.ts | 11 ++--- .../identity-service/src/did/did.service.ts | 46 +++++++++++-------- ...teDid.dto.ts => GenerateDidRequest.dto.ts} | 21 +++++++++ 6 files changed, 60 insertions(+), 30 deletions(-) rename services/identity-service/src/did/dtos/{GenerateDid.dto.ts => GenerateDidRequest.dto.ts} (52%) diff --git a/services/identity-service/docker-compose-test.yml b/services/identity-service/docker-compose-test.yml index d35d1586d..19c605ae7 100644 --- a/services/identity-service/docker-compose-test.yml +++ b/services/identity-service/docker-compose-test.yml @@ -1,4 +1,4 @@ -version: '3' +version: '2.4' services: vault-test: image: vault:1.13.3 diff --git a/services/identity-service/docker-compose.yml b/services/identity-service/docker-compose.yml index dd47cf716..32dd8dfd0 100644 --- a/services/identity-service/docker-compose.yml +++ b/services/identity-service/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3' +version: '2.4' services: vault: image: vault:1.13.3 diff --git a/services/identity-service/src/did/did.controller.ts b/services/identity-service/src/did/did.controller.ts index 4b3e53dd6..1a5bb8e20 100644 --- a/services/identity-service/src/did/did.controller.ts +++ b/services/identity-service/src/did/did.controller.ts @@ -21,7 +21,7 @@ import { const { DIDDocument } = require('did-resolver'); type DIDDocument = typeof DIDDocument; import { DidService } from './did.service'; -import { GenerateDidDTO } from './dtos/GenerateDid.dto'; +import { GenerateDidRequestDTO } from './dtos/GenerateDidRequest.dto'; const pLimit = require('p-limit'); const limit = pLimit(100); @@ -33,10 +33,10 @@ export class DidController { @ApiOperation({ summary: 'Generate a new DID' }) @ApiOkResponse({ description: 'DID Generated', isArray: true }) @ApiBadRequestResponse({ description: 'Bad request' }) - @ApiBody({ type: GenerateDidDTO, isArray: true }) + @ApiBody({ type: GenerateDidRequestDTO, isArray: false }) @Post('/did/generate') async generateDID( - @Body() generateRequest: { content: GenerateDidDTO[] }, + @Body() generateRequest: GenerateDidRequestDTO, ): Promise { const promises = generateRequest.content.map((generateDidDTO) => { return limit(() => this.didService.generateDID(generateDidDTO)); @@ -45,7 +45,7 @@ export class DidController { return await Promise.all(promises); } catch (err) { Logger.error(err); - throw new InternalServerErrorException(`Error generating one or more DIDs`); + throw new InternalServerErrorException(err?.message); } } diff --git a/services/identity-service/src/did/did.service.spec.ts b/services/identity-service/src/did/did.service.spec.ts index 1d297b8aa..c154129cb 100644 --- a/services/identity-service/src/did/did.service.spec.ts +++ b/services/identity-service/src/did/did.service.spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { DidService } from './did.service'; import { PrismaService } from '../utils/prisma.service'; import { VaultService } from '../utils/vault.service'; -import { GenerateDidDTO } from './dtos/GenerateDid.dto'; +import { GenerateDidDTO } from './dtos/GenerateDidRequest.dto'; import { ConfigService } from '@nestjs/config'; describe('DidService', () => { @@ -74,20 +74,20 @@ describe('DidService', () => { }); it("generate web did id test", () => { - service.webDidBaseUrl = "did:web:example.com:identity:"; + service.webDidPrefix = "did:web:example.com:identity:"; const didId = service.generateDidUri("web"); expect(didId).toBeDefined(); expect(didId).toContain("did:web:example.com:identity"); }); it("get web did id for id test", () => { - service.webDidBaseUrl = "did:web:example.com:identity:"; + service.webDidPrefix = "did:web:example.com:identity:"; const didId = service.getWebDidIdForId("abc"); expect(didId).toBeDefined(); expect(didId).toEqual("did:web:example.com:identity:abc"); }); it('should generate a DID with a web method', async () => { - service.webDidBaseUrl = "did:web:example.com:identity:"; + service.webDidPrefix = "did:web:example.com:identity:"; const result = await service.generateDID({ alsoKnownAs: [], services: [], @@ -95,13 +95,12 @@ describe('DidService', () => { }); expect(result).toBeDefined(); expect(result.verificationMethod).toBeDefined(); - expect(result.verificationMethod[0].publicKeyJwk).toBeDefined(); expect(result.id.split(':')[1]).toEqual('web'); expect(result.id).toContain("did:web:example.com:identity"); }); it("throw exception when web did base url is not set", () => { - service.webDidBaseUrl = undefined; + service.webDidPrefix = undefined; expect(() => service.getWebDidIdForId("abc")) .toThrow("Web did base url not found"); }); diff --git a/services/identity-service/src/did/did.service.ts b/services/identity-service/src/did/did.service.ts index 3f7ea9636..d8e43f857 100644 --- a/services/identity-service/src/did/did.service.ts +++ b/services/identity-service/src/did/did.service.ts @@ -1,26 +1,23 @@ import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../utils/prisma.service'; -const { DIDDocument } = require('did-resolver'); -type DIDDocument = typeof DIDDocument; import { v4 as uuid } from 'uuid'; -import { GenerateDidDTO } from './dtos/GenerateDid.dto'; import { VaultService } from '../utils/vault.service'; import { Identity } from '@prisma/client'; -import { RSAKeyPair } from "crypto-ld"; +import { RSAKeyPair } from 'crypto-ld'; +import { GenerateDidDTO } from './dtos/GenerateDidRequest.dto'; + +const { DIDDocument } = require('did-resolver'); +type DIDDocument = typeof DIDDocument; + @Injectable() export class DidService { keys = {} - webDidBaseUrl: string; + webDidPrefix: string; signingAlgorithm: string; didResolver: any; constructor(private prisma: PrismaService, private vault: VaultService) { let baseUrl: string = process.env.WEB_DID_BASE_URL; - if(baseUrl && typeof baseUrl === "string") { - baseUrl = baseUrl.replace("https://", "") - .replace(/:/g, "%3A") - .replace(/\//g, ":"); - this.webDidBaseUrl = `did:web:${baseUrl}:`; - } + this.webDidPrefix = this.getDidPrefixForBaseUrl(baseUrl); this.signingAlgorithm = process.env.SIGNING_ALGORITHM; this.init(); } @@ -40,22 +37,35 @@ export class DidService { }); } - generateDidUri(method: string, id: string): string { + getDidPrefixForBaseUrl(baseUrl: string): string { + if(!baseUrl || typeof baseUrl !== "string") return ''; + baseUrl = baseUrl.split("?")[0] + .replace("https://", "") + .replace(/:/g, "%3A") + .replace(/\//g, ":"); + return `did:web:${baseUrl}:`; + } + + generateDidUri(method: string, id?: string, webDidBaseUrl?: string): string { if(id) return id; if (method === 'web') { - return this.getWebDidIdForId(uuid()); + return this.getWebDidIdForId(uuid(), webDidBaseUrl); } return `did:${(method && method.trim() !== '') ? method.trim() : 'rcw'}:${uuid()}`; } - getWebDidIdForId(id: string): string { - if(!this.webDidBaseUrl) throw new NotFoundException("Web did base url not found"); - return `${this.webDidBaseUrl}${id}`; + getWebDidIdForId(id: string, webDidBaseUrl?: string): string { + if(!this.webDidPrefix && !webDidBaseUrl) throw new NotFoundException("Web did base url not found"); + if(webDidBaseUrl) { + const webDidBasePrefix = this.getDidPrefixForBaseUrl(webDidBaseUrl); + return `${webDidBasePrefix}${id}`; + } + return `${this.webDidPrefix}${id}`; } async generateDID(doc: GenerateDidDTO): Promise { // Create a UUID for the DID using uuidv4 - const didUri: string = this.generateDidUri(doc?.method, doc?.id); + const didUri: string = this.generateDidUri(doc?.method, doc?.id, doc?.webDidBaseUrl); // Create private/public key pair let authnKeys; @@ -149,7 +159,7 @@ export class DidService { throw new InternalServerErrorException(`Error fetching DID: ${id} from db`); } - if(!artifact && id?.startsWith("did:web") && !id?.startsWith(this.webDidBaseUrl)) { + if(!artifact && id?.startsWith("did:web") && !id?.startsWith(this.webDidPrefix)) { try { return (await this.didResolver.resolve(id)).didDocument; } catch (err) { diff --git a/services/identity-service/src/did/dtos/GenerateDid.dto.ts b/services/identity-service/src/did/dtos/GenerateDidRequest.dto.ts similarity index 52% rename from services/identity-service/src/did/dtos/GenerateDid.dto.ts rename to services/identity-service/src/did/dtos/GenerateDidRequest.dto.ts index b58a24df7..f64537f35 100644 --- a/services/identity-service/src/did/dtos/GenerateDid.dto.ts +++ b/services/identity-service/src/did/dtos/GenerateDidRequest.dto.ts @@ -8,6 +8,7 @@ export class GenerateDidDTO { 'AlsoKnownAs property is a unique combination aadhaar and username.', type: String, isArray: true, + required: false, }) alsoKnownAs?: string[]; @@ -15,11 +16,31 @@ export class GenerateDidDTO { description: 'An array of services that are used, for example a user registration service.', isArray: true, + required: false, + type: Service }) services?: Service[]; @ApiProperty({ description: 'The method of DID.', }) method: string; + @ApiProperty({ + required: false, + description: 'Specific ID to generate DID document with.', + }) id?: string; + @ApiProperty({ + required: false, + description: 'In case of method "web" the web url path to access the did document. It would be appended by generated uuid', + }) + webDidBaseUrl?: string; +} + +export class GenerateDidRequestDTO { + @ApiProperty({ + type: GenerateDidDTO, + description: 'List of generate did requests', + isArray: true + }) + content: GenerateDidDTO[] }