From 456940f77c9690eadac05c93de22b6611565eb35 Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Wed, 18 Sep 2024 16:17:55 +0300 Subject: [PATCH 1/5] feat: add method to fetch subname records --- apps/vc-api/src/api/auth/auth.controller.ts | 26 +++------- .../environment/isdk-initializer.getter.ts | 7 +++ .../isubname-records.fetcher.ts | 7 +++ .../verify-records/iverify-records.service.ts | 5 ++ .../verify-records/verify-records.service.ts | 20 ++++++++ .../sdk-initializer/sdk-initializer.getter.ts | 25 ++++++++++ .../subname-records.fetcher.ts | 50 +++++++++++++++++++ apps/vc-api/src/vc-management.module.ts | 18 +++++++ 8 files changed, 140 insertions(+), 18 deletions(-) create mode 100644 apps/vc-api/src/core/applications/environment/isdk-initializer.getter.ts create mode 100644 apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts create mode 100644 apps/vc-api/src/core/applications/verify-records/iverify-records.service.ts create mode 100644 apps/vc-api/src/core/applications/verify-records/verify-records.service.ts create mode 100644 apps/vc-api/src/external/sdk-initializer/sdk-initializer.getter.ts create mode 100644 apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts diff --git a/apps/vc-api/src/api/auth/auth.controller.ts b/apps/vc-api/src/api/auth/auth.controller.ts index ea8e3b8..8dc9909 100644 --- a/apps/vc-api/src/api/auth/auth.controller.ts +++ b/apps/vc-api/src/api/auth/auth.controller.ts @@ -1,37 +1,27 @@ import { Body, Controller, Get, Inject, Post, Req, Res, UseGuards } from '@nestjs/common'; -import { JustaName } from '@justaname.id/sdk'; -import { ENVIRONMENT_GETTER, IEnvironmentGetter } from '../../core/applications/environment/ienvironment.getter'; import { AuthSigninApiRequest } from './requests/auth.signin.api.request'; import { Response, Request} from 'express'; import { JwtService } from '@nestjs/jwt'; import moment from 'moment'; import { JwtGuard } from '../../guards/jwt.guard'; +import { + ISdkInitializerGetter, + SDK_INITIALIZER_GETTER +} from '../../core/applications/environment/isdk-initializer.getter'; type Siwj = { address: string, subname: string }; @Controller('auth') export class AuthController { - justaname: JustaName constructor( - @Inject(ENVIRONMENT_GETTER) private readonly environmentGetter: IEnvironmentGetter, - + @Inject(SDK_INITIALIZER_GETTER) private readonly sdkInitializerGetter: ISdkInitializerGetter, private readonly jwtService: JwtService - ) { - this.justaname = JustaName.init({ - config: { - chainId: this.environmentGetter.getChainId(), - domain: this.environmentGetter.getSiweDomain(), - origin:this.environmentGetter.getSiweOrigin(), - }, - ensDomain: this.environmentGetter.getEnsDomain(), - providerUrl: (this.environmentGetter.getChainId() === 1 ? 'https://mainnet.infura.io/v3/' :'https://sepolia.infura.io/v3/') +this.environmentGetter.getInfuraProjectId() - }) - } + ) {} @Get('nonce') async getNonce() { - return this.justaname.signIn.generateNonce() + return this.sdkInitializerGetter.getInitializedSdk().signIn.generateNonce() } @Post('signin') @@ -40,7 +30,7 @@ export class AuthController { @Res() res: Response, @Req() req: Request ) { - const { data: message, subname } = await this.justaname.signIn.signIn(body.message, body.signature) + const { data: message, subname } = await this.sdkInitializerGetter.getInitializedSdk().signIn.signIn(body.message, body.signature) if (!message) { res.status(500).json({ message: 'No message returned.' }); diff --git a/apps/vc-api/src/core/applications/environment/isdk-initializer.getter.ts b/apps/vc-api/src/core/applications/environment/isdk-initializer.getter.ts new file mode 100644 index 0000000..95f01e6 --- /dev/null +++ b/apps/vc-api/src/core/applications/environment/isdk-initializer.getter.ts @@ -0,0 +1,7 @@ +import { JustaName } from '@justaname.id/sdk'; + +export const SDK_INITIALIZER_GETTER = 'SDK_INITIALIZER_GETTER' + +export interface ISdkInitializerGetter { + getInitializedSdk(): JustaName; +} diff --git a/apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts b/apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts new file mode 100644 index 0000000..4be374f --- /dev/null +++ b/apps/vc-api/src/core/applications/verify-records/isubname-records.fetcher.ts @@ -0,0 +1,7 @@ +import { Subname } from '../../domain/entities/subname'; + +export const SUBNAME_RECORDS_FETCHER = 'SUBNAME_RECORDS_FETCHER'; + +export interface ISubnameRecordsFetcher { + fetchRecords(subname: string, chainId: number): Promise; +} diff --git a/apps/vc-api/src/core/applications/verify-records/iverify-records.service.ts b/apps/vc-api/src/core/applications/verify-records/iverify-records.service.ts new file mode 100644 index 0000000..7b8f5b0 --- /dev/null +++ b/apps/vc-api/src/core/applications/verify-records/iverify-records.service.ts @@ -0,0 +1,5 @@ +export const VERIFY_RECORDS_SERVICE = 'VERIFY_RECORDS_SERVICE'; + +export interface IVerifyRecordsService { + verifyRecords(subname: string, recordsToVerify: string[], chainId: number): Promise; +} diff --git a/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts new file mode 100644 index 0000000..d231bed --- /dev/null +++ b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts @@ -0,0 +1,20 @@ +import { IVerifyRecordsService } from './iverify-records.service'; +import { Inject, Injectable } from '@nestjs/common'; +import { ISubnameRecordsFetcher } from './isubname-records.fetcher'; + +@Injectable() +export class VerifyRecordsService implements IVerifyRecordsService { + constructor( + @Inject('SUBNAME_RECORDS_FETCHER') + private readonly subnameRecordsFetcher: ISubnameRecordsFetcher + ) {} + + async verifyRecords(subname: string, recordsToVerify: string[], chainId: number): Promise { + if (chainId !== 1 && chainId !== 11155111) { + throw new Error('Invalid chainId'); + } + + const subnameRecords = await this.subnameRecordsFetcher.fetchRecords(subname, chainId); + + } +} diff --git a/apps/vc-api/src/external/sdk-initializer/sdk-initializer.getter.ts b/apps/vc-api/src/external/sdk-initializer/sdk-initializer.getter.ts new file mode 100644 index 0000000..a6aa02a --- /dev/null +++ b/apps/vc-api/src/external/sdk-initializer/sdk-initializer.getter.ts @@ -0,0 +1,25 @@ +import { ISdkInitializerGetter } from '../../core/applications/environment/isdk-initializer.getter'; +import { Inject, Injectable } from '@nestjs/common'; +import { JustaName } from '@justaname.id/sdk'; +import { ENVIRONMENT_GETTER, IEnvironmentGetter } from '../../core/applications/environment/ienvironment.getter'; + +@Injectable() +export class SdkInitializerGetter implements ISdkInitializerGetter { + justaname: JustaName + constructor( + @Inject(ENVIRONMENT_GETTER) private readonly environmentGetter: IEnvironmentGetter, + ) {} + + getInitializedSdk(): JustaName { + return this.justaname = JustaName.init({ + config: { + chainId: this.environmentGetter.getChainId(), + domain: this.environmentGetter.getSiweDomain(), + origin:this.environmentGetter.getSiweOrigin(), + }, + ensDomain: this.environmentGetter.getEnsDomain(), + providerUrl: (this.environmentGetter.getChainId() === 1 ? 'https://mainnet.infura.io/v3/' :'https://sepolia.infura.io/v3/') + this.environmentGetter.getInfuraProjectId() + }) + } + +} diff --git a/apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts b/apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts new file mode 100644 index 0000000..e8872e3 --- /dev/null +++ b/apps/vc-api/src/external/subname-records-fetcher/subname-records.fetcher.ts @@ -0,0 +1,50 @@ +import { ISubnameRecordsFetcher } from '../../core/applications/verify-records/isubname-records.fetcher'; +import { Inject, Injectable } from '@nestjs/common'; +import { Subname } from '../../core/domain/entities/subname'; +import { ChainId, SubnameRecordsResponse } from '@justaname.id/sdk'; +import { ENVIRONMENT_GETTER, IEnvironmentGetter } from '../../core/applications/environment/ienvironment.getter'; +import { + ISdkInitializerGetter, + SDK_INITIALIZER_GETTER +} from '../../core/applications/environment/isdk-initializer.getter'; + + +@Injectable() +export class SubnameRecordsFetcher implements ISubnameRecordsFetcher { + constructor( + @Inject(ENVIRONMENT_GETTER) private readonly environmentGetter: IEnvironmentGetter, + @Inject(SDK_INITIALIZER_GETTER) private readonly sdkInitializerGetter: ISdkInitializerGetter, + + ) {} + + async fetchRecords(subname: string, chainId: number): Promise { + const providerUrl = (chainId === 1 ? 'https://mainnet.infura.io/v3/' :'https://sepolia.infura.io/v3/') + this.environmentGetter.getInfuraProjectId() + + const records: SubnameRecordsResponse = await this.sdkInitializerGetter.getInitializedSdk().subnames.getRecordsByFullName({ + fullName: subname, + providerUrl: providerUrl, + chainId: chainId as ChainId + }) + + return this.mapSubnameRecordsResponseToSubname(subname, records); + } + + mapSubnameRecordsResponseToSubname(subname: string, records: SubnameRecordsResponse): Subname { + return { + subname: subname, + metadata: { + contentHash: records.contentHash?.decoded ?? '', + addresses: records.coins.map(coin => ({ + id: String(coin.id), + coinType: coin.id, + address: coin.value, + metadataId: '' + })), + textRecords: records.texts.map(text => ({ + key: text.key, + value: text.value + })) + } + }; + } +} diff --git a/apps/vc-api/src/vc-management.module.ts b/apps/vc-api/src/vc-management.module.ts index b3d85da..2907be6 100644 --- a/apps/vc-api/src/vc-management.module.ts +++ b/apps/vc-api/src/vc-management.module.ts @@ -41,6 +41,12 @@ import { AuthController } from './api/auth/auth.controller'; import { DID_RESOLVER } from './core/applications/did/resolver/idid.resolver'; import { CryptoService } from './external/crypto/crypto.service'; import { CRYPTO_SERVICE } from './core/applications/crypto/icrypto.service'; +import { VerifyRecordsService } from './core/applications/verify-records/verify-records.service'; +import { VERIFY_RECORDS_SERVICE } from './core/applications/verify-records/iverify-records.service'; +import { SubnameRecordsFetcher } from './external/subname-records-fetcher/subname-records.fetcher'; +import { SUBNAME_RECORDS_FETCHER } from './core/applications/verify-records/isubname-records.fetcher'; +import { SdkInitializerGetter } from './external/sdk-initializer/sdk-initializer.getter'; +import { SDK_INITIALIZER_GETTER } from './core/applications/environment/isdk-initializer.getter'; const dynamicImport = async (packageName: string) => new Function(`return import('${packageName}')`)(); @@ -105,6 +111,18 @@ const dynamicImport = async (packageName: string) => useClass: CryptoService, provide: CRYPTO_SERVICE }, + { + useClass: VerifyRecordsService, + provide: VERIFY_RECORDS_SERVICE + }, + { + useClass: SubnameRecordsFetcher, + provide: SUBNAME_RECORDS_FETCHER + }, + { + useClass: SdkInitializerGetter, + provide: SDK_INITIALIZER_GETTER + }, GithubSubjectResolver, DiscordSubjectResolver, TelegramSubjectResolver, From 75985648a8fb72c104b98063af55344edd18364c Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Thu, 19 Sep 2024 11:06:20 +0300 Subject: [PATCH 2/5] feat: untested verify-records flow --- .../iverify-records.controller.mapper.ts | 17 ++++ .../verify-records.controller.mapper.ts | 32 +++++++ .../requests/verify-records.api.request.ts | 18 ++++ .../responses/verify-records.api.response.ts | 15 ++++ .../verify-records.controller.ts | 30 +++++++ .../verify-records/iverify-records.service.ts | 5 +- .../requests/verify-records.request.ts | 6 ++ .../response/verify-records.response.ts | 3 + .../verify-records/verify-records.service.ts | 84 ++++++++++++++++++- apps/vc-api/src/vc-management.module.ts | 10 ++- 10 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 apps/vc-api/src/api/verify-records/mapper/iverify-records.controller.mapper.ts create mode 100644 apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts create mode 100644 apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts create mode 100644 apps/vc-api/src/api/verify-records/responses/verify-records.api.response.ts create mode 100644 apps/vc-api/src/api/verify-records/verify-records.controller.ts create mode 100644 apps/vc-api/src/core/applications/verify-records/requests/verify-records.request.ts create mode 100644 apps/vc-api/src/core/applications/verify-records/response/verify-records.response.ts diff --git a/apps/vc-api/src/api/verify-records/mapper/iverify-records.controller.mapper.ts b/apps/vc-api/src/api/verify-records/mapper/iverify-records.controller.mapper.ts new file mode 100644 index 0000000..a639921 --- /dev/null +++ b/apps/vc-api/src/api/verify-records/mapper/iverify-records.controller.mapper.ts @@ -0,0 +1,17 @@ +import { VerifyRecordsApiRequest } from '../requests/verify-records.api.request'; +import { VerifyRecordsRequest } from '../../../core/applications/verify-records/requests/verify-records.request'; +import { VeirfyRecordsResponse } from '../../../core/applications/verify-records/response/verify-records.response'; +import { VerifyRecordsApiResponse } from '../responses/verify-records.api.response'; + +export const VERIFY_RECORDS_CONTROLLER_MAPPER = "VERIFY_RECORDS_CONTROLLER_MAPPER"; + +export interface IVerifyRecordsControllerMapper { + mapVerifyRecordsApiRequestToVerifyRecordsRequest( + verifyRecordsApiRequest: VerifyRecordsApiRequest, + issuer: string + ): VerifyRecordsRequest; + + mapVerifyRecordsResponseToVerifyRecordsApiResponse( + verifyRecordsResponse: VeirfyRecordsResponse[] + ): VerifyRecordsApiResponse[]; +} diff --git a/apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts b/apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts new file mode 100644 index 0000000..efdd0dd --- /dev/null +++ b/apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { IVerifyRecordsControllerMapper } from './iverify-records.controller.mapper'; +import { VerifyRecordsApiRequest } from '../requests/verify-records.api.request'; +import { VerifyRecordsRequest } from '../../../core/applications/verify-records/requests/verify-records.request'; +import { VeirfyRecordsResponse } from '../../../core/applications/verify-records/response/verify-records.response'; +import { VerifyRecordsApiResponse } from '../responses/verify-records.api.response'; + +@Injectable() +export class VerifyRecordsControllerMapper implements IVerifyRecordsControllerMapper { + constructor() {} + + mapVerifyRecordsApiRequestToVerifyRecordsRequest( + verifyRecordsApiRequest: VerifyRecordsApiRequest, + issuer: string // Add issuerName as an additional argument + ): VerifyRecordsRequest { + return { + subname: verifyRecordsApiRequest.subname, + chainId: verifyRecordsApiRequest.chainId, + recordsToVerify: verifyRecordsApiRequest.recordsToVerify, + issuer: issuer + }; + } + + mapVerifyRecordsResponseToVerifyRecordsApiResponse(verifyRecordsResponses: VeirfyRecordsResponse[]): VerifyRecordsApiResponse[] { + return verifyRecordsResponses.map(verifyRecordsResponse => ({ + records: { + ...verifyRecordsResponse + } + })); + } + +} diff --git a/apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts b/apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts new file mode 100644 index 0000000..9b85755 --- /dev/null +++ b/apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts @@ -0,0 +1,18 @@ +import { IsArray, IsInt, IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; + +export class VerifyRecordsApiRequest { + @ApiProperty() + @IsString() + subname: string; + + @ApiProperty() + @Type(() => Number) + @IsInt() + chainId: number; + + @ApiProperty() + @IsArray() + recordsToVerify: string[]; +} diff --git a/apps/vc-api/src/api/verify-records/responses/verify-records.api.response.ts b/apps/vc-api/src/api/verify-records/responses/verify-records.api.response.ts new file mode 100644 index 0000000..06eb15a --- /dev/null +++ b/apps/vc-api/src/api/verify-records/responses/verify-records.api.response.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class VerifyRecordsApiResponse { + @ApiProperty({ + type: 'object', + additionalProperties: { + type: 'boolean' + }, + example: { + record1: true, + record2: false + } + }) + records: { [key: string]: boolean }; +} diff --git a/apps/vc-api/src/api/verify-records/verify-records.controller.ts b/apps/vc-api/src/api/verify-records/verify-records.controller.ts new file mode 100644 index 0000000..f6b48c8 --- /dev/null +++ b/apps/vc-api/src/api/verify-records/verify-records.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get, Inject, Param, Query } from '@nestjs/common'; +import { + IVerifyRecordsService, + VERIFY_RECORDS_SERVICE +} from '../../core/applications/verify-records/iverify-records.service'; +import { VerifyRecordsApiRequest } from './requests/verify-records.api.request'; +import { IVerifyRecordsControllerMapper } from './mapper/iverify-records.controller.mapper'; + +@Controller('verify-records') +export class VerifyRecordsController { + + constructor( + @Inject(VERIFY_RECORDS_SERVICE) private readonly verifyRecordsService: IVerifyRecordsService, + @Inject('VERIFY_RECORDS_CONTROLLER_MAPPER') private readonly verifyRecordsControllerMapper: IVerifyRecordsControllerMapper + ) {} + @Get(':issuer') + async verifyRecords( + @Param('issuer') issuer: string, + @Query() query: VerifyRecordsApiRequest + ) { + const response = await this.verifyRecordsService.verifyRecords( + this.verifyRecordsControllerMapper.mapVerifyRecordsApiRequestToVerifyRecordsRequest( + query, + issuer + ) + ); + + return this.verifyRecordsControllerMapper.mapVerifyRecordsResponseToVerifyRecordsApiResponse(response); + } +} diff --git a/apps/vc-api/src/core/applications/verify-records/iverify-records.service.ts b/apps/vc-api/src/core/applications/verify-records/iverify-records.service.ts index 7b8f5b0..e69ad78 100644 --- a/apps/vc-api/src/core/applications/verify-records/iverify-records.service.ts +++ b/apps/vc-api/src/core/applications/verify-records/iverify-records.service.ts @@ -1,5 +1,8 @@ +import { VerifyRecordsRequest } from './requests/verify-records.request'; +import { VeirfyRecordsResponse } from './response/verify-records.response'; + export const VERIFY_RECORDS_SERVICE = 'VERIFY_RECORDS_SERVICE'; export interface IVerifyRecordsService { - verifyRecords(subname: string, recordsToVerify: string[], chainId: number): Promise; + verifyRecords(verifyRecordsRequest: VerifyRecordsRequest): Promise; } diff --git a/apps/vc-api/src/core/applications/verify-records/requests/verify-records.request.ts b/apps/vc-api/src/core/applications/verify-records/requests/verify-records.request.ts new file mode 100644 index 0000000..d00226a --- /dev/null +++ b/apps/vc-api/src/core/applications/verify-records/requests/verify-records.request.ts @@ -0,0 +1,6 @@ +export class VerifyRecordsRequest { + subname: string; + recordsToVerify: string[]; + chainId: number; + issuer: string; +} diff --git a/apps/vc-api/src/core/applications/verify-records/response/verify-records.response.ts b/apps/vc-api/src/core/applications/verify-records/response/verify-records.response.ts new file mode 100644 index 0000000..d6a67eb --- /dev/null +++ b/apps/vc-api/src/core/applications/verify-records/response/verify-records.response.ts @@ -0,0 +1,3 @@ +export class VeirfyRecordsResponse { + [key: string]: boolean; +} diff --git a/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts index d231bed..221697b 100644 --- a/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts +++ b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts @@ -1,20 +1,96 @@ import { IVerifyRecordsService } from './iverify-records.service'; import { Inject, Injectable } from '@nestjs/common'; -import { ISubnameRecordsFetcher } from './isubname-records.fetcher'; +import { ISubnameRecordsFetcher, SUBNAME_RECORDS_FETCHER } from './isubname-records.fetcher'; +import { VerifyRecordsRequest } from './requests/verify-records.request'; +import { VeirfyRecordsResponse } from './response/verify-records.response'; +import { Subname } from '../../domain/entities/subname'; @Injectable() export class VerifyRecordsService implements IVerifyRecordsService { + private chainIdMapping = { + "mainnet": 1, + "sepolia": 11155111 + }; + constructor( - @Inject('SUBNAME_RECORDS_FETCHER') - private readonly subnameRecordsFetcher: ISubnameRecordsFetcher + @Inject(SUBNAME_RECORDS_FETCHER) + private readonly subnameRecordsFetcher: ISubnameRecordsFetcher, ) {} - async verifyRecords(subname: string, recordsToVerify: string[], chainId: number): Promise { + async verifyRecords(verifyRecordsRequest: VerifyRecordsRequest): Promise { + const { subname, chainId, recordsToVerify, issuer } = verifyRecordsRequest; + if (chainId !== 1 && chainId !== 11155111) { throw new Error('Invalid chainId'); } const subnameRecords = await this.subnameRecordsFetcher.fetchRecords(subname, chainId); + const responses: VeirfyRecordsResponse[] = []; + + for (const record of recordsToVerify) { + const response = this._recordVerifier(subname, record, subnameRecords, chainId, issuer); + responses.push(response); + } + + return responses; + } + + private _recordVerifier(subname: string, record: string, subnameRecords: Subname, chainId: number, issuer: string): VeirfyRecordsResponse { + // 1) check if record exists in subnameRecords, if not return false + if (!subnameRecords.metadata.textRecords[record]) { + return { + [record]: false + } + } + // 2) check if record_verifier.id exists in subnameRecords, if not return false + if (!subnameRecords.metadata.textRecords[`${record}_${issuer}`]) { + return { + [record]: false + } + } + // 3) parse the value of record_verifier.id + const vc = JSON.parse(subnameRecords.metadata.textRecords[`${record}_${issuer}`].value); + + // 4) check the expirationDate, if expired return false + const currentDate = new Date(); + const expirationDate = new Date(vc.expirationDate); + if (expirationDate < currentDate) { + return { + [record]: false + }; + } + // 5) check if it belongs to the subname, if not return false + const didSubname = vc.credentialSubject.did.split(':')[3]; + if (didSubname !== subname) { + return { + [record]: false + }; + } + // 6) check the issuer did, if not return false + const issuerDid = vc.issuer.id.split(':'); + const issuerChain = issuerDid[2]; // Extract chain from DID + const issuerName = issuerDid[3]; // Extract subname + if (issuerName !== issuer || this.chainIdMapping[issuerChain] !== chainId) { + return { + [record]: false + }; + } + // 7) check if it's on the correct chain, if not return false (for both the issuer did and credential subject did, and the chainId in the proof) + const subjectDid = vc.credentialSubject.did.split(':'); + const subjectChain = subjectDid[2]; // Extract chain from DID + if (this.chainIdMapping[subjectChain] !== chainId || Number(vc.proof.eip712.domain.chainId) !== chainId) { + return { + [record]: false + }; + } + // 8) check that the value of the username of the credentialSubject matches the value of the record inside the subnameRecords, if not return false + if (vc.credentialSubject.username !== subnameRecords.metadata.textRecords[record]) { + return { + [record]: false + }; + } + + return { [record]: true }; } } diff --git a/apps/vc-api/src/vc-management.module.ts b/apps/vc-api/src/vc-management.module.ts index 2907be6..84a78c1 100644 --- a/apps/vc-api/src/vc-management.module.ts +++ b/apps/vc-api/src/vc-management.module.ts @@ -47,6 +47,9 @@ import { SubnameRecordsFetcher } from './external/subname-records-fetcher/subnam import { SUBNAME_RECORDS_FETCHER } from './core/applications/verify-records/isubname-records.fetcher'; import { SdkInitializerGetter } from './external/sdk-initializer/sdk-initializer.getter'; import { SDK_INITIALIZER_GETTER } from './core/applications/environment/isdk-initializer.getter'; +import { VerifyRecordsController } from './api/verify-records/verify-records.controller'; +import { VerifyRecordsControllerMapper } from './api/verify-records/mapper/verify-records.controller.mapper'; +import { VERIFY_RECORDS_CONTROLLER_MAPPER } from './api/verify-records/mapper/iverify-records.controller.mapper'; const dynamicImport = async (packageName: string) => new Function(`return import('${packageName}')`)(); @@ -64,7 +67,8 @@ const dynamicImport = async (packageName: string) => ], controllers: [ CredentialsController, - AuthController + AuthController, + VerifyRecordsController ], providers: [ { @@ -123,6 +127,10 @@ const dynamicImport = async (packageName: string) => useClass: SdkInitializerGetter, provide: SDK_INITIALIZER_GETTER }, + { + useClass: VerifyRecordsControllerMapper, + provide: VERIFY_RECORDS_CONTROLLER_MAPPER + }, GithubSubjectResolver, DiscordSubjectResolver, TelegramSubjectResolver, From 10176506a17397993295448e1d0eb1cb5ccef45d Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Thu, 19 Sep 2024 12:21:09 +0300 Subject: [PATCH 3/5] feat: add default issuer --- .../mapper/iverify-records.controller.mapper.ts | 1 - .../mapper/verify-records.controller.mapper.ts | 3 +-- .../requests/verify-records.api.request.ts | 6 +++++- .../api/verify-records/verify-records.controller.ts | 6 ++---- .../verify-records/verify-records.service.ts | 11 +++++++++-- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/apps/vc-api/src/api/verify-records/mapper/iverify-records.controller.mapper.ts b/apps/vc-api/src/api/verify-records/mapper/iverify-records.controller.mapper.ts index a639921..7c7cbb9 100644 --- a/apps/vc-api/src/api/verify-records/mapper/iverify-records.controller.mapper.ts +++ b/apps/vc-api/src/api/verify-records/mapper/iverify-records.controller.mapper.ts @@ -8,7 +8,6 @@ export const VERIFY_RECORDS_CONTROLLER_MAPPER = "VERIFY_RECORDS_CONTROLLER_MAPPE export interface IVerifyRecordsControllerMapper { mapVerifyRecordsApiRequestToVerifyRecordsRequest( verifyRecordsApiRequest: VerifyRecordsApiRequest, - issuer: string ): VerifyRecordsRequest; mapVerifyRecordsResponseToVerifyRecordsApiResponse( diff --git a/apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts b/apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts index efdd0dd..4884222 100644 --- a/apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts +++ b/apps/vc-api/src/api/verify-records/mapper/verify-records.controller.mapper.ts @@ -11,13 +11,12 @@ export class VerifyRecordsControllerMapper implements IVerifyRecordsControllerMa mapVerifyRecordsApiRequestToVerifyRecordsRequest( verifyRecordsApiRequest: VerifyRecordsApiRequest, - issuer: string // Add issuerName as an additional argument ): VerifyRecordsRequest { return { subname: verifyRecordsApiRequest.subname, chainId: verifyRecordsApiRequest.chainId, recordsToVerify: verifyRecordsApiRequest.recordsToVerify, - issuer: issuer + issuer: verifyRecordsApiRequest.issuer, }; } diff --git a/apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts b/apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts index 9b85755..1ec57e6 100644 --- a/apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts +++ b/apps/vc-api/src/api/verify-records/requests/verify-records.api.request.ts @@ -1,4 +1,4 @@ -import { IsArray, IsInt, IsString } from 'class-validator'; +import { IsArray, IsInt, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; @@ -15,4 +15,8 @@ export class VerifyRecordsApiRequest { @ApiProperty() @IsArray() recordsToVerify: string[]; + + @ApiProperty() + @IsOptional() + issuer?: string; } diff --git a/apps/vc-api/src/api/verify-records/verify-records.controller.ts b/apps/vc-api/src/api/verify-records/verify-records.controller.ts index f6b48c8..42ca02c 100644 --- a/apps/vc-api/src/api/verify-records/verify-records.controller.ts +++ b/apps/vc-api/src/api/verify-records/verify-records.controller.ts @@ -13,15 +13,13 @@ export class VerifyRecordsController { @Inject(VERIFY_RECORDS_SERVICE) private readonly verifyRecordsService: IVerifyRecordsService, @Inject('VERIFY_RECORDS_CONTROLLER_MAPPER') private readonly verifyRecordsControllerMapper: IVerifyRecordsControllerMapper ) {} - @Get(':issuer') + @Get('') async verifyRecords( - @Param('issuer') issuer: string, @Query() query: VerifyRecordsApiRequest ) { const response = await this.verifyRecordsService.verifyRecords( this.verifyRecordsControllerMapper.mapVerifyRecordsApiRequestToVerifyRecordsRequest( - query, - issuer + query ) ); diff --git a/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts index 221697b..3cd6b65 100644 --- a/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts +++ b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts @@ -4,6 +4,7 @@ import { ISubnameRecordsFetcher, SUBNAME_RECORDS_FETCHER } from './isubname-reco import { VerifyRecordsRequest } from './requests/verify-records.request'; import { VeirfyRecordsResponse } from './response/verify-records.response'; import { Subname } from '../../domain/entities/subname'; +import { ENVIRONMENT_GETTER, IEnvironmentGetter } from '../environment/ienvironment.getter'; @Injectable() export class VerifyRecordsService implements IVerifyRecordsService { @@ -12,14 +13,20 @@ export class VerifyRecordsService implements IVerifyRecordsService { "sepolia": 11155111 }; + domain: string; constructor( @Inject(SUBNAME_RECORDS_FETCHER) private readonly subnameRecordsFetcher: ISubnameRecordsFetcher, - ) {} + @Inject(ENVIRONMENT_GETTER) private readonly environmentGetter: IEnvironmentGetter, + ) { + this.domain = this.environmentGetter.getEnsDomain(); + } async verifyRecords(verifyRecordsRequest: VerifyRecordsRequest): Promise { const { subname, chainId, recordsToVerify, issuer } = verifyRecordsRequest; + const validIssuer = issuer ? issuer : this.domain; + if (chainId !== 1 && chainId !== 11155111) { throw new Error('Invalid chainId'); } @@ -29,7 +36,7 @@ export class VerifyRecordsService implements IVerifyRecordsService { const responses: VeirfyRecordsResponse[] = []; for (const record of recordsToVerify) { - const response = this._recordVerifier(subname, record, subnameRecords, chainId, issuer); + const response = this._recordVerifier(subname, record, subnameRecords, chainId, validIssuer); responses.push(response); } From b5e757b3c8711d99576cc6ebc765efe42fb4bef4 Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Thu, 19 Sep 2024 12:36:55 +0300 Subject: [PATCH 4/5] fix: small fixes --- .../verify-records/verify-records.service.ts | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts index 3cd6b65..f549dc8 100644 --- a/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts +++ b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts @@ -36,28 +36,34 @@ export class VerifyRecordsService implements IVerifyRecordsService { const responses: VeirfyRecordsResponse[] = []; for (const record of recordsToVerify) { - const response = this._recordVerifier(subname, record, subnameRecords, chainId, validIssuer); + const response = this._recordVerifier(record, subnameRecords, chainId, validIssuer); responses.push(response); } return responses; } - private _recordVerifier(subname: string, record: string, subnameRecords: Subname, chainId: number, issuer: string): VeirfyRecordsResponse { - // 1) check if record exists in subnameRecords, if not return false - if (!subnameRecords.metadata.textRecords[record]) { + private _recordVerifier(record: string, subnameRecords: Subname, chainId: number, issuer: string): VeirfyRecordsResponse { + // 1) check if record exists in subnameRecords, if not return false + const foundRecord = subnameRecords.metadata.textRecords.find((item) => item.key === record); + + if (!foundRecord) { return { [record]: false } } - // 2) check if record_verifier.id exists in subnameRecords, if not return false - if (!subnameRecords.metadata.textRecords[`${record}_${issuer}`]) { + + // 2) check if record_issuer exists in subnameRecords, if not return false + const foundRecordIssuer = subnameRecords.metadata.textRecords.find((item) => item.key === `${record}_${issuer}`); + + if (!foundRecordIssuer) { return { [record]: false } } - // 3) parse the value of record_verifier.id - const vc = JSON.parse(subnameRecords.metadata.textRecords[`${record}_${issuer}`].value); + + // 3) parse the value of record_issuer + const vc = JSON.parse(foundRecordIssuer.value); // 4) check the expirationDate, if expired return false const currentDate = new Date(); @@ -69,7 +75,7 @@ export class VerifyRecordsService implements IVerifyRecordsService { } // 5) check if it belongs to the subname, if not return false const didSubname = vc.credentialSubject.did.split(':')[3]; - if (didSubname !== subname) { + if (didSubname !== subnameRecords.subname) { return { [record]: false }; @@ -92,7 +98,7 @@ export class VerifyRecordsService implements IVerifyRecordsService { }; } // 8) check that the value of the username of the credentialSubject matches the value of the record inside the subnameRecords, if not return false - if (vc.credentialSubject.username !== subnameRecords.metadata.textRecords[record]) { + if (vc.credentialSubject.username !== foundRecord.value) { return { [record]: false }; From 4e618f34364ded10810279ab3634e17613eb6871 Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Thu, 19 Sep 2024 16:18:53 +0300 Subject: [PATCH 5/5] fix: did separation fix --- .../verify-records/verify-records.service.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts index f549dc8..df18d99 100644 --- a/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts +++ b/apps/vc-api/src/core/applications/verify-records/verify-records.service.ts @@ -63,7 +63,7 @@ export class VerifyRecordsService implements IVerifyRecordsService { } // 3) parse the value of record_issuer - const vc = JSON.parse(foundRecordIssuer.value); + const vc = JSON.parse(foundRecordIssuer.value); // 4) check the expirationDate, if expired return false const currentDate = new Date(); @@ -73,22 +73,29 @@ export class VerifyRecordsService implements IVerifyRecordsService { [record]: false }; } + // 5) check if it belongs to the subname, if not return false - const didSubname = vc.credentialSubject.did.split(':')[3]; + const didSubnameWithFragment = vc.credentialSubject.did.split(':')[3]; + const didSubname = didSubnameWithFragment.split('#')[0]; + if (didSubname !== subnameRecords.subname) { return { [record]: false }; } + // 6) check the issuer did, if not return false const issuerDid = vc.issuer.id.split(':'); - const issuerChain = issuerDid[2]; // Extract chain from DID - const issuerName = issuerDid[3]; // Extract subname + const issuerChain = issuerDid[2]; + const issuerNameFragment = issuerDid[3]; + const issuerName = issuerNameFragment.split('#')[0]; + if (issuerName !== issuer || this.chainIdMapping[issuerChain] !== chainId) { return { [record]: false }; } + // 7) check if it's on the correct chain, if not return false (for both the issuer did and credential subject did, and the chainId in the proof) const subjectDid = vc.credentialSubject.did.split(':'); const subjectChain = subjectDid[2]; // Extract chain from DID @@ -97,6 +104,7 @@ export class VerifyRecordsService implements IVerifyRecordsService { [record]: false }; } + // 8) check that the value of the username of the credentialSubject matches the value of the record inside the subnameRecords, if not return false if (vc.credentialSubject.username !== foundRecord.value) { return {