From 6bc7913bac493edbbc3071d68bc030fd89ea750a Mon Sep 17 00:00:00 2001 From: georgesMouawad Date: Tue, 8 Oct 2024 15:37:36 +0300 Subject: [PATCH 01/11] feat(vc-api): api apiFilters implementation --- apps/vc-api/src/api/auth/auth.controller.ts | 33 +++++++++++------- .../api/credentials/credentials.controller.ts | 34 ++++++++----------- .../api/filters/auth/authentication.filter.ts | 16 +++++++++ .../credentials/credentials.invalid.filter.ts | 16 +++++++++ .../credentials/email-sender.filter.ts | 16 +++++++++ .../src/api/filters/credentials/otp.filter.ts | 16 +++++++++ .../social-resolver.not-found.filter.ts | 16 +++++++++ apps/vc-api/src/api/filters/vc.api.filters.ts | 15 ++++++++ .../verify-records/chainId.invalid.filter.ts | 16 +++++++++ apps/vc-api/src/vc-management.module.ts | 6 ++++ 10 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 apps/vc-api/src/api/filters/auth/authentication.filter.ts create mode 100644 apps/vc-api/src/api/filters/credentials/credentials.invalid.filter.ts create mode 100644 apps/vc-api/src/api/filters/credentials/email-sender.filter.ts create mode 100644 apps/vc-api/src/api/filters/credentials/otp.filter.ts create mode 100644 apps/vc-api/src/api/filters/credentials/social-resolver.not-found.filter.ts create mode 100644 apps/vc-api/src/api/filters/vc.api.filters.ts create mode 100644 apps/vc-api/src/api/filters/verify-records/chainId.invalid.filter.ts diff --git a/apps/vc-api/src/api/auth/auth.controller.ts b/apps/vc-api/src/api/auth/auth.controller.ts index 3ef2379..6d52e71 100644 --- a/apps/vc-api/src/api/auth/auth.controller.ts +++ b/apps/vc-api/src/api/auth/auth.controller.ts @@ -5,6 +5,7 @@ import { JwtService } from '@nestjs/jwt'; import moment from 'moment'; import { JwtGuard } from '../../guards/jwt.guard'; import { ENS_MANAGER_SERVICE, IEnsManagerService } from '../../core/applications/ens-manager/iens-manager.service'; +import { AuthenticationException } from '../../core/domain/exceptions/Authentication.exception'; type Siwens = { address: string, ens: string }; @@ -28,21 +29,29 @@ export class AuthController { @Res() res: Response, @Req() req: Request ) { - const { address, ens } = await this.ensManagerService.signIn({ - message: body.message, - signature: body.signature - }) + try { + const { address, ens } = await this.ensManagerService.signIn({ + message: body.message, + signature: body.signature, + }); + const token = this.jwtService.sign( + { ens, address }, + { + expiresIn: moment().add(1, 'hour').unix(), + } + ); - const token = this.jwtService.sign({ ens, address }, { - expiresIn: moment().add(1, 'hour').unix() - }); - - - res.cookie('justverifiedtoken', token, { httpOnly: true, secure: true, sameSite: 'none' }); - - return res.status(200).send({ ens, address }); + res.cookie('justverifiedtoken', token, { + httpOnly: true, + secure: true, + sameSite: 'none', + }); + return res.status(200).send({ ens, address }); + } catch (error) { + throw AuthenticationException.withError(error); + } } @UseGuards(JwtGuard) diff --git a/apps/vc-api/src/api/credentials/credentials.controller.ts b/apps/vc-api/src/api/credentials/credentials.controller.ts index 4235abd..e38c23a 100644 --- a/apps/vc-api/src/api/credentials/credentials.controller.ts +++ b/apps/vc-api/src/api/credentials/credentials.controller.ts @@ -82,27 +82,23 @@ export class CredentialsController { @Query() authGetAuthUrlRequestApiQuery: any, @Res() res: Response ): Promise { - try { - const verifiedEthereumEip712Signature2021 = await this.credentialCreatorFacade.socialCallback( - this.authControllerMapper.mapAuthCallbackApiRequestToCredentialCallbackRequest( - authGetAuthUrlRequestApiQuery, - authGetAuthUrlRequestApiParam) - ) - - const { authId, dataKey, verifiableCredential } = verifiedEthereumEip712Signature2021 - - const subject = this.authSubjects.get(authId); - subject?.next({ - authId, - result: { - verifiableCredential, - dataKey - } - }); - }catch(e){ + + const verifiedEthereumEip712Signature2021 = await this.credentialCreatorFacade.socialCallback( + this.authControllerMapper.mapAuthCallbackApiRequestToCredentialCallbackRequest( + authGetAuthUrlRequestApiQuery, + authGetAuthUrlRequestApiParam) + ) - } + const { authId, dataKey, verifiableCredential } = verifiedEthereumEip712Signature2021 + const subject = this.authSubjects.get(authId); + subject?.next({ + authId, + result: { + verifiableCredential, + dataKey + } + }); res.send(` diff --git a/apps/vc-api/src/api/filters/auth/authentication.filter.ts b/apps/vc-api/src/api/filters/auth/authentication.filter.ts new file mode 100644 index 0000000..f4f2bd1 --- /dev/null +++ b/apps/vc-api/src/api/filters/auth/authentication.filter.ts @@ -0,0 +1,16 @@ +import { BaseExceptionFilter } from '@nestjs/core'; +import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; +import { AuthenticationException } from '../../../core/domain/exceptions/Authentication.exception'; + +@Catch(AuthenticationException) +export class AuthenticationExceptionFilter extends BaseExceptionFilter { + catch(exception: AuthenticationException, host: ArgumentsHost) { + const context = host.switchToHttp(); + const response = context.getResponse(); + const httpStatus = HttpStatus.UNAUTHORIZED; + + response.status(httpStatus).json({ + message: exception.message, + }); + } +} diff --git a/apps/vc-api/src/api/filters/credentials/credentials.invalid.filter.ts b/apps/vc-api/src/api/filters/credentials/credentials.invalid.filter.ts new file mode 100644 index 0000000..95954e1 --- /dev/null +++ b/apps/vc-api/src/api/filters/credentials/credentials.invalid.filter.ts @@ -0,0 +1,16 @@ +import { BaseExceptionFilter } from '@nestjs/core'; +import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; +import { CredentialsInvalidException } from '../../../core/domain/exceptions/CredentialsInvalid.exception'; + +@Catch(CredentialsInvalidException) +export class CredentialsInvalidExceptionFilter extends BaseExceptionFilter { + catch(exception: CredentialsInvalidException, host: ArgumentsHost) { + const context = host.switchToHttp(); + const response = context.getResponse(); + const httpStatus = HttpStatus.BAD_REQUEST; + + response.status(httpStatus).json({ + message: exception.message, + }); + } +} diff --git a/apps/vc-api/src/api/filters/credentials/email-sender.filter.ts b/apps/vc-api/src/api/filters/credentials/email-sender.filter.ts new file mode 100644 index 0000000..0010ca3 --- /dev/null +++ b/apps/vc-api/src/api/filters/credentials/email-sender.filter.ts @@ -0,0 +1,16 @@ +import { BaseExceptionFilter } from '@nestjs/core'; +import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; +import { EmailSenderException } from '../../../core/domain/exceptions/EmailSender.exception'; + +@Catch(EmailSenderException) +export class EmailSenderExceptionFilter extends BaseExceptionFilter { + catch(exception: EmailSenderException, host: ArgumentsHost) { + const context = host.switchToHttp(); + const response = context.getResponse(); + const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + + response.status(httpStatus).json({ + message: exception.message, + }); + } +} diff --git a/apps/vc-api/src/api/filters/credentials/otp.filter.ts b/apps/vc-api/src/api/filters/credentials/otp.filter.ts new file mode 100644 index 0000000..6eabf0e --- /dev/null +++ b/apps/vc-api/src/api/filters/credentials/otp.filter.ts @@ -0,0 +1,16 @@ +import { BaseExceptionFilter } from '@nestjs/core'; +import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; +import { OTPException } from '../../../core/domain/exceptions/OTP.exception'; + +@Catch(OTPException) +export class OTPExceptionFilter extends BaseExceptionFilter { + catch(exception: OTPException, host: ArgumentsHost) { + const context = host.switchToHttp(); + const response = context.getResponse(); + const httpStatus = HttpStatus.BAD_REQUEST; + + response.status(httpStatus).json({ + message: exception.message, + }); + } +} diff --git a/apps/vc-api/src/api/filters/credentials/social-resolver.not-found.filter.ts b/apps/vc-api/src/api/filters/credentials/social-resolver.not-found.filter.ts new file mode 100644 index 0000000..c597bd4 --- /dev/null +++ b/apps/vc-api/src/api/filters/credentials/social-resolver.not-found.filter.ts @@ -0,0 +1,16 @@ +import { BaseExceptionFilter } from '@nestjs/core'; +import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; +import { SocialResolverNotFoundException } from '../../../core/domain/exceptions/SocialResolverNotFound.exception'; + +@Catch(SocialResolverNotFoundException) +export class SocialResolverNotFoundExceptionFilter extends BaseExceptionFilter { + catch(exception: SocialResolverNotFoundException, host: ArgumentsHost) { + const context = host.switchToHttp(); + const response = context.getResponse(); + const httpStatus = HttpStatus.NOT_FOUND; + + response.status(httpStatus).json({ + message: exception.message, + }); + } +} diff --git a/apps/vc-api/src/api/filters/vc.api.filters.ts b/apps/vc-api/src/api/filters/vc.api.filters.ts new file mode 100644 index 0000000..efdae76 --- /dev/null +++ b/apps/vc-api/src/api/filters/vc.api.filters.ts @@ -0,0 +1,15 @@ +import { OTPExceptionFilter } from './credentials/otp.filter'; +import { EmailSenderExceptionFilter } from './credentials/email-sender.filter'; +import { ChainIdInvalidExceptionFilter } from './verify-records/chainId.invalid.filter'; +import { SocialResolverNotFoundExceptionFilter } from './credentials/social-resolver.not-found.filter'; +import { CredentialsInvalidExceptionFilter } from './credentials/credentials.invalid.filter'; +import { AuthenticationExceptionFilter } from './auth/authentication.filter'; + +export const VCManagementApiFilters = [ + OTPExceptionFilter, + EmailSenderExceptionFilter, + AuthenticationExceptionFilter, + ChainIdInvalidExceptionFilter, + CredentialsInvalidExceptionFilter, + SocialResolverNotFoundExceptionFilter, +]; diff --git a/apps/vc-api/src/api/filters/verify-records/chainId.invalid.filter.ts b/apps/vc-api/src/api/filters/verify-records/chainId.invalid.filter.ts new file mode 100644 index 0000000..137a651 --- /dev/null +++ b/apps/vc-api/src/api/filters/verify-records/chainId.invalid.filter.ts @@ -0,0 +1,16 @@ +import { BaseExceptionFilter } from '@nestjs/core'; +import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; +import { ChainIdInvalidException } from '../../../core/domain/exceptions/ChainIdInvalid.exception'; + +@Catch(ChainIdInvalidException) +export class ChainIdInvalidExceptionFilter extends BaseExceptionFilter { + catch(exception: ChainIdInvalidException, host: ArgumentsHost) { + const context = host.switchToHttp(); + const response = context.getResponse(); + const httpStatus = HttpStatus.BAD_REQUEST; + + response.status(httpStatus).json({ + message: exception.message, + }); + } +} diff --git a/apps/vc-api/src/vc-management.module.ts b/apps/vc-api/src/vc-management.module.ts index cff602d..8052cf6 100644 --- a/apps/vc-api/src/vc-management.module.ts +++ b/apps/vc-api/src/vc-management.module.ts @@ -54,6 +54,8 @@ import { EMAIL_SENDER } from './core/applications/email-sender/iemail-sender.ser import { EmailSender } from './external/email-sender/email-sender.service'; import {EmailResolver} from "./core/applications/credentials/facade/email-resolver/email.resolver"; import {EMAIL_RESOLVER} from "./core/applications/credentials/facade/email-resolver/iemail.resolver"; +import { VCManagementApiFilters } from './api/filters/vc.api.filters'; +import { APP_FILTER } from '@nestjs/core'; const dynamicImport = async (packageName: string) => new Function(`return import('${packageName}')`)(); @@ -75,6 +77,10 @@ const dynamicImport = async (packageName: string) => VerifyRecordsController ], providers: [ + ...VCManagementApiFilters.map((filter) => ({ + provide: APP_FILTER, + useClass: filter, + })), { useClass: CredentialsControllerMapper, provide: AUTH_CONTROLLER_MAPPER From bad78c98a10d8c1b2031dc90b78da84cfd7a81fc Mon Sep 17 00:00:00 2001 From: georgesMouawad Date: Tue, 8 Oct 2024 15:38:08 +0300 Subject: [PATCH 02/11] feat(vc-api): core domain exceptions implementation --- .../credentials/facade/credential.facade.ts | 6 +++--- .../facade/email-resolver/email.resolver.ts | 10 +++++----- .../telegram.social.resolver.ts | 3 ++- .../verify-records/verify-records.service.ts | 3 ++- .../exceptions/Authentication.exception.ts | 12 ++++++++++++ .../exceptions/ChainIdInvalid.exception.ts | 10 ++++++++++ .../exceptions/CredentialsInvalid.exception.ts | 9 +++++++++ .../domain/exceptions/EmailSender.exception.ts | 10 ++++++++++ .../core/domain/exceptions/OTP.exception.ts | 18 ++++++++++++++++++ .../SocialResolverNotFound.exception.ts | 10 ++++++++++ .../email-sender/email-sender.service.ts | 5 ++--- 11 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 apps/vc-api/src/core/domain/exceptions/Authentication.exception.ts create mode 100644 apps/vc-api/src/core/domain/exceptions/ChainIdInvalid.exception.ts create mode 100644 apps/vc-api/src/core/domain/exceptions/CredentialsInvalid.exception.ts create mode 100644 apps/vc-api/src/core/domain/exceptions/EmailSender.exception.ts create mode 100644 apps/vc-api/src/core/domain/exceptions/OTP.exception.ts create mode 100644 apps/vc-api/src/core/domain/exceptions/SocialResolverNotFound.exception.ts diff --git a/apps/vc-api/src/core/applications/credentials/facade/credential.facade.ts b/apps/vc-api/src/core/applications/credentials/facade/credential.facade.ts index a946641..91bca21 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/credential.facade.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/credential.facade.ts @@ -7,6 +7,7 @@ import { CredentialCallbackResponse } from './credential.callback.response'; import {EmailResolver} from "./email-resolver/email.resolver"; import {EMAIL_RESOLVER, IEmailResolver} from "./email-resolver/iemail.resolver"; import {EmailCallback} from "./email-resolver/email.callback"; +import { SocialResolverNotFoundException } from '../../../domain/exceptions/SocialResolverNotFound.exception'; @Injectable() export class CredentialCreatorFacade implements ICredentialCreatorFacade { @@ -18,8 +19,7 @@ export class CredentialCreatorFacade implements ICredentialCreatorFacade { @Inject(EMAIL_RESOLVER) private readonly emailResolver: IEmailResolver, - ) { - } + ) {} getSocialResolver(credentialName: string) { for(const resolver of this.subjectResolver.getSocialResolvers()){ @@ -27,7 +27,7 @@ export class CredentialCreatorFacade implements ICredentialCreatorFacade { return resolver; } } - throw new Error(`No resolver found for ${credentialName}`); + throw SocialResolverNotFoundException.forCredentialName(credentialName); } async getSocialAuthUrl(credentialName: string, ens: string, authId: string): Promise { diff --git a/apps/vc-api/src/core/applications/credentials/facade/email-resolver/email.resolver.ts b/apps/vc-api/src/core/applications/credentials/facade/email-resolver/email.resolver.ts index 2c1348c..a13ce4f 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/email-resolver/email.resolver.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/email-resolver/email.resolver.ts @@ -1,4 +1,3 @@ - import {AbstractResolver} from "../abstract.resolver"; import {Inject, Injectable} from "@nestjs/common"; import {EmailCallback} from "./email.callback"; @@ -7,6 +6,7 @@ import {EMAIL_SENDER, IEmailSender} from "../../../email-sender/iemail-sender.se import {EmailOtpGenerateRequest} from "./requests/email.otp.generate.request"; import {IEmailResolver} from "./iemail.resolver"; import {EmailOtpGenerateResponse} from "./responses/email.otp.generate.response"; +import { OTPException } from "../../../../domain/exceptions/OTP.exception"; @Injectable() @@ -39,19 +39,19 @@ EmailCredential const { authId } = this.decryptState(params.state) const otpData = this.otpStore.get(authId); if (!otpData) { - throw new Error('OTP not found or expired.'); + throw OTPException.notFound(); } const currentTime = Date.now(); if (otpData.expiresAt < currentTime) { this.otpStore.delete(authId); - throw new Error('OTP has expired.'); + throw OTPException.expired(); } const hashedOtp = this.cryptoEncryption.createHash(params.otp); if (!this.cryptoEncryption.timingSafeEqual(hashedOtp, otpData.otpHash)) { - throw new Error('Invalid OTP.'); + throw OTPException.invalid(); } const email = this.otpStore.get(authId).email @@ -108,7 +108,7 @@ EmailCredential const { authId } = this.decryptState(state); const otpData = this.otpStore.get(authId); if (!otpData) { - throw new Error('OTP not found or expired.'); + throw OTPException.notFound(); } const { email } = otpData; diff --git a/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/telegram.social.resolver.ts b/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/telegram.social.resolver.ts index 4a87ff1..49aef15 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/telegram.social.resolver.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/telegram.social.resolver.ts @@ -2,6 +2,7 @@ import { AbstractSocialResolver } from './abstract.social.resolver'; import { TelegramCredential } from '../../../../../domain/credentials/telegram.credential'; import { TelegramCallback } from './callback/telegram.callback'; import {GetAuthUrlRequest} from "./requests/get-auth-url.request"; +import { CredentialsInvalidException } from '../../../../../domain/exceptions/CredentialsInvalid.exception'; export class TelegramSocialResolver extends AbstractSocialResolver< TelegramCallback, @@ -48,7 +49,7 @@ export class TelegramSocialResolver extends AbstractSocialResolver< const calculatedHash = this.cryptoEncryption.createTelegramHash(dataCheckString); if (calculatedHash !== hash) { - throw new Error('Invalid data: Authentication failed.'); + throw CredentialsInvalidException.withMessage('Invalid data: Authentication failed.'); } return { 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 a604e7e..0ff10f2 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 @@ -11,6 +11,7 @@ import {TwitterCredential} from "../../domain/credentials/twitter.credential"; import {TelegramCredential} from "../../domain/credentials/telegram.credential"; import {EmailCredential} from "../../domain/credentials/email.credential"; import {DiscordCredential} from "../../domain/credentials/discord.credential"; +import { ChainIdInvalidException } from '../../domain/exceptions/ChainIdInvalid.exception'; @Injectable() export class VerifyRecordsService implements IVerifyRecordsService { @@ -34,7 +35,7 @@ export class VerifyRecordsService implements IVerifyRecordsService { const validIssuer = issuer ? issuer : this.domain; if (chainId !== 1 && chainId !== 11155111) { - throw new Error('Invalid chainId'); + throw ChainIdInvalidException.withId(chainId); } const subnameRecords = await this.subnameRecordsFetcher.fetchRecords(ens, chainId); diff --git a/apps/vc-api/src/core/domain/exceptions/Authentication.exception.ts b/apps/vc-api/src/core/domain/exceptions/Authentication.exception.ts new file mode 100644 index 0000000..9afcbb9 --- /dev/null +++ b/apps/vc-api/src/core/domain/exceptions/Authentication.exception.ts @@ -0,0 +1,12 @@ +export class AuthenticationException extends Error { + constructor(message: string) { + super(message); + } + + static withError(error: unknown) { + if (error instanceof Error) { + return new AuthenticationException(error.message); + } + return new AuthenticationException('Authentication failed.'); + } +} diff --git a/apps/vc-api/src/core/domain/exceptions/ChainIdInvalid.exception.ts b/apps/vc-api/src/core/domain/exceptions/ChainIdInvalid.exception.ts new file mode 100644 index 0000000..8cc654e --- /dev/null +++ b/apps/vc-api/src/core/domain/exceptions/ChainIdInvalid.exception.ts @@ -0,0 +1,10 @@ +export class ChainIdInvalidException extends Error { + constructor(message: string) { + super(message); + } + + static withId(chainId: number) { + const message = `Chain ID ${chainId} is invalid.`; + return new ChainIdInvalidException(message); + } +} \ No newline at end of file diff --git a/apps/vc-api/src/core/domain/exceptions/CredentialsInvalid.exception.ts b/apps/vc-api/src/core/domain/exceptions/CredentialsInvalid.exception.ts new file mode 100644 index 0000000..f1271f9 --- /dev/null +++ b/apps/vc-api/src/core/domain/exceptions/CredentialsInvalid.exception.ts @@ -0,0 +1,9 @@ +export class CredentialsInvalidException extends Error { + constructor(message: string) { + super(message); + } + + static withMessage(message: string) { + return new CredentialsInvalidException(message); + } +} \ No newline at end of file diff --git a/apps/vc-api/src/core/domain/exceptions/EmailSender.exception.ts b/apps/vc-api/src/core/domain/exceptions/EmailSender.exception.ts new file mode 100644 index 0000000..90d4ab3 --- /dev/null +++ b/apps/vc-api/src/core/domain/exceptions/EmailSender.exception.ts @@ -0,0 +1,10 @@ +export class EmailSenderException extends Error { + constructor(message: string) { + super(message); + } + + static withEmail(email: string, message: string) { + const errorMessage = `Error while sending email for Email Verification ${email}. ${message}`; + return new EmailSenderException(errorMessage); + } +} diff --git a/apps/vc-api/src/core/domain/exceptions/OTP.exception.ts b/apps/vc-api/src/core/domain/exceptions/OTP.exception.ts new file mode 100644 index 0000000..0379f18 --- /dev/null +++ b/apps/vc-api/src/core/domain/exceptions/OTP.exception.ts @@ -0,0 +1,18 @@ +export class OTPException extends Error { + constructor(message: string) { + super(message); + this.name = 'OTPException'; + } + + static invalid() { + return new OTPException('Invalid OTP.'); + } + + static notFound() { + return new OTPException('OTP not found or expired.'); + } + + static expired() { + return new OTPException('OTP expired.'); + } +} \ No newline at end of file diff --git a/apps/vc-api/src/core/domain/exceptions/SocialResolverNotFound.exception.ts b/apps/vc-api/src/core/domain/exceptions/SocialResolverNotFound.exception.ts new file mode 100644 index 0000000..a0e74e9 --- /dev/null +++ b/apps/vc-api/src/core/domain/exceptions/SocialResolverNotFound.exception.ts @@ -0,0 +1,10 @@ +export class SocialResolverNotFoundException extends Error { + constructor(message: string) { + super(message); + } + + static forCredentialName(credentialName: string) { + const message = `No resolver found for ${credentialName}`; + return new SocialResolverNotFoundException(message); + } +} diff --git a/apps/vc-api/src/external/email-sender/email-sender.service.ts b/apps/vc-api/src/external/email-sender/email-sender.service.ts index aeaa928..509fc57 100644 --- a/apps/vc-api/src/external/email-sender/email-sender.service.ts +++ b/apps/vc-api/src/external/email-sender/email-sender.service.ts @@ -7,6 +7,7 @@ import {createElement} from "react"; import VerificationEmail from './templates/verification-email.template'; import 'react'; import 'react-dom/server'; +import { EmailSenderException } from '../../core/domain/exceptions/EmailSender.exception'; @Injectable() export class EmailSender implements IEmailSender { @@ -32,9 +33,7 @@ export class EmailSender implements IEmailSender { }); if (error) { - throw new Error( - `Error while sending email for Email Verification: ${error.message}` - ); + throw EmailSenderException.withEmail(emailNotification.to, error.message); } } } From 7ea738cec4d0530a45c6b712fb61f27513e7090d Mon Sep 17 00:00:00 2001 From: georgesMouawad Date: Tue, 8 Oct 2024 15:39:13 +0300 Subject: [PATCH 03/11] test(vc-api): controllers emailResolver credentialFacade --- .../test/src/api/auth/auth.controller.spec.ts | 101 ++++++++++ .../credentials.controller.spec.ts | 118 +++++++++++ .../verity-records.controller.spec.ts | 184 ++++++++++++++++++ .../facade/credential.facade.spec.ts | 73 +++++++ .../credentials/facade/email-resolver.spec.ts | 172 ++++++++++++++++ 5 files changed, 648 insertions(+) create mode 100644 apps/vc-api/test/src/api/auth/auth.controller.spec.ts create mode 100644 apps/vc-api/test/src/api/credentials/credentials.controller.spec.ts create mode 100644 apps/vc-api/test/src/api/verify-records/verity-records.controller.spec.ts create mode 100644 apps/vc-api/test/src/core/applications/credentials/facade/credential.facade.spec.ts create mode 100644 apps/vc-api/test/src/core/applications/credentials/facade/email-resolver.spec.ts diff --git a/apps/vc-api/test/src/api/auth/auth.controller.spec.ts b/apps/vc-api/test/src/api/auth/auth.controller.spec.ts new file mode 100644 index 0000000..7e5d0e7 --- /dev/null +++ b/apps/vc-api/test/src/api/auth/auth.controller.spec.ts @@ -0,0 +1,101 @@ +import request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { JwtService } from '@nestjs/jwt'; +import { APP_FILTER } from '@nestjs/core'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { INestApplication } from '@nestjs/common'; +import { AuthController } from '../../../../src/api/auth/auth.controller'; +import { VCManagementApiFilters } from '../../../../src/api/filters/vc.api.filters'; +import { + ENS_MANAGER_SERVICE, + IEnsManagerService, +} from '../../../../src/core/applications/ens-manager/iens-manager.service'; + +const ENS = 'ENS'; +const ADDRESS = 'ADDRESS'; +const MESSAGE = 'MESSAGE'; +const SIGNATURE = 'SIGNATURE'; +const SIGNATURE_2 = 'SIGNATURE_2'; +const ERROR_MESSAGE = 'ERROR_MESSAGE'; +const TOKEN = 'TOKEN'; + +describe('Auth controller integration tests', () => { + let app: INestApplication; + let ensManagerService: DeepMocked; + let jwtService: DeepMocked; + + beforeAll(async () => { + const module = await Test.createTestingModule({ + controllers: [AuthController], + providers: [ + ...VCManagementApiFilters.map((filter) => ({ + provide: APP_FILTER, + useClass: filter, + })), + { + provide: ENS_MANAGER_SERVICE, + useValue: createMock(), + }, + { + provide: JwtService, + useValue: createMock(), + }, + ], + }).compile(); + + ensManagerService = + module.get>(ENS_MANAGER_SERVICE); + jwtService = module.get>(JwtService); + + ensManagerService.signIn.mockImplementation(async (param) => { + if (param.message === MESSAGE && param.signature === SIGNATURE) { + return { ens: ENS, address: ADDRESS }; + } + throw new Error(ERROR_MESSAGE); + }); + + jwtService.sign.mockImplementation((payload) => { + if ( + JSON.stringify(payload) === + JSON.stringify({ ens: ENS, address: ADDRESS }) + ) { + return TOKEN; + } + throw new Error(ERROR_MESSAGE); + }); + + app = module.createNestApplication(); + await app.init(); + }); + + afterAll(async () => { + await app.close(); + }); + + describe('Sign in endpoint tests', () => { + it('should return 200 and a token when the sign in is successful', async () => { + await request(app.getHttpServer()) + .post('/auth/signin') + .send({ message: MESSAGE, signature: SIGNATURE }) + .expect((res) => { + expect(res.status).toBe(200); + expect(res.body).toEqual({ ens: ENS, address: ADDRESS }); + expect(res.headers['set-cookie']).toBeDefined(); + expect(res.headers['set-cookie'][0]).toContain('justverifiedtoken=' + TOKEN); + expect(res.headers['set-cookie'][0]).toContain('HttpOnly'); + expect(res.headers['set-cookie'][0]).toContain('Secure'); + expect(res.headers['set-cookie'][0]).toContain('SameSite=None'); + }); + }); + + it('should return 401 when sign in is unsuccessful', async () => { + await request(app.getHttpServer()) + .post('/auth/signin') + .send({ message: MESSAGE, signature: SIGNATURE_2 }) + .expect((res) => { + expect(res.status).toBe(401); + expect(res.body).toEqual({ message: ERROR_MESSAGE }); + }); + }); + }); +}); diff --git a/apps/vc-api/test/src/api/credentials/credentials.controller.spec.ts b/apps/vc-api/test/src/api/credentials/credentials.controller.spec.ts new file mode 100644 index 0000000..e7bffc4 --- /dev/null +++ b/apps/vc-api/test/src/api/credentials/credentials.controller.spec.ts @@ -0,0 +1,118 @@ +import request from 'supertest'; +import { Test } from '@nestjs/testing'; +import { APP_FILTER } from '@nestjs/core'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { INestApplication } from '@nestjs/common'; +import { VCManagementApiFilters } from '../../../../src/api/filters/vc.api.filters'; +import { + CREDENTIAL_CREATOR_FACADE, + ICredentialCreatorFacade, +} from '../../../../src/core/applications/credentials/facade/icredential.facade'; +import { + AUTH_CONTROLLER_MAPPER, + IcredentialsControllerMapper, +} from '../../../../src/api/credentials/mapper/icredentials.controller.mapper'; +import { CredentialsController } from '../../../../src/api/credentials/credentials.controller'; +import { JwtGuard } from '../../../../src/guards/jwt.guard'; +import { EmailSenderException } from '../../../../src/core/domain/exceptions/EmailSender.exception'; +import { JwtService } from '@nestjs/jwt'; +import { SocialResolverNotFoundException } from '../../../../src/core/domain/exceptions/SocialResolverNotFound.exception'; + +const ENS = 'ENS'; +const ADDRESS = 'ADDRESS'; +const ERROR_MESSAGE = 'ERROR_MESSAGE'; +const EMAIL = 'EMAIL'; +const STATE = 'STATE'; +const SOCIAL = 'SOCIAL'; + +const setupJwtGuard = () => { + jest + .spyOn(JwtGuard.prototype, 'canActivate') + .mockImplementation((context) => { + const request = context.switchToHttp().getRequest(); + request.user = { ens: ENS, address: ADDRESS }; + return Promise.resolve(true); + }); +}; + +describe('Credentials controller integration tests', () => { + let app: INestApplication; + let credentialCreatorFacade: DeepMocked; + + beforeAll(async () => { + const module = await Test.createTestingModule({ + controllers: [CredentialsController], + providers: [ + ...VCManagementApiFilters.map((filter) => ({ + provide: APP_FILTER, + useClass: filter, + })), + { + provide: CREDENTIAL_CREATOR_FACADE, + useValue: createMock(), + }, + { + provide: AUTH_CONTROLLER_MAPPER, + useValue: createMock(), + }, + { + provide: JwtService, + useValue: createMock(), + }, + ], + }).compile(); + + credentialCreatorFacade = module.get>( + CREDENTIAL_CREATOR_FACADE + ); + + setupJwtGuard(); + + app = module.createNestApplication(); + await app.init(); + }); + + afterAll(async () => { + await app.close(); + }); + + describe('get social auth url endpoint tests', () => { + it('should return 404 status code if social resolver is not found', async () => { + credentialCreatorFacade.getSocialAuthUrl.mockImplementationOnce( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (authName, _ens, __authId) => { + if (authName === SOCIAL) { + throw new SocialResolverNotFoundException(SOCIAL); + } + return Promise.resolve(STATE); + } + ); + + await request(app.getHttpServer()) + .get(`/credentials/socials/${SOCIAL}`) + .expect(404); + }); + }); + + describe('generate email otp endpoint tests', () => { + it('should return 500 status code if email sender exception was thrown', async () => { + credentialCreatorFacade.getEmailOTP.mockImplementationOnce( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (email, ens, _authId) => { + if (email === EMAIL && ens === ENS) { + throw new EmailSenderException(ERROR_MESSAGE); + } + return Promise.resolve(STATE); + } + ); + + await request(app.getHttpServer()) + .get('/credentials/email') + .query({ email: EMAIL }) + .expect((res) => { + expect(res.status).toBe(500); + expect(res.body.message).toBe(ERROR_MESSAGE); + }); + }); + }); +}); diff --git a/apps/vc-api/test/src/api/verify-records/verity-records.controller.spec.ts b/apps/vc-api/test/src/api/verify-records/verity-records.controller.spec.ts new file mode 100644 index 0000000..4160578 --- /dev/null +++ b/apps/vc-api/test/src/api/verify-records/verity-records.controller.spec.ts @@ -0,0 +1,184 @@ +import request from 'supertest'; +import { APP_FILTER } from '@nestjs/core'; +import { INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { VCManagementApiFilters } from '../../../../src/api/filters/vc.api.filters'; +import { IVerifyRecordsControllerMapper } from '../../../../src/api/verify-records/mapper/iverify-records.controller.mapper'; +import { VerifyRecordsController } from '../../../../src/api/verify-records/verify-records.controller'; +import { VerifyRecordsApiRequest } from '../../../../src/api/verify-records/requests/verify-records.api.request'; +import { SocialCredentials } from '../../../../src/core/domain/entities/credentials'; +import { VerifyRecordsRequest } from '../../../../src/core/applications/verify-records/requests/verify-records.request'; +import { VerifyRecordsApiResponse } from '../../../../src/api/verify-records/responses/verify-records.api.response'; +import { VerifyRecordsResponse } from '../../../../src/core/applications/verify-records/response/verify-records.response'; +import { ChainIdInvalidException } from '../../../../src/core/domain/exceptions/ChainIdInvalid.exception'; +import { + IVerifyRecordsService, + VERIFY_RECORDS_SERVICE, +} from '../../../../src/core/applications/verify-records/iverify-records.service'; + +const ENS = 'ENS'; +const ERROR_MESSAGE = 'ERROR_MESSAGE'; +const SOCIAL_CREDENTIAL: SocialCredentials = 'github'; +const CHAIN_ID = 11155111; +const CHAIN_ID_2 = 31337; +const ISSUER = 'ISSUER'; + +const RECORD_KEY = 'RECORD_KEY'; +const RECORD_VALUE = true; + +const getVerifyRecordsApiRequest = (): VerifyRecordsApiRequest => ({ + ens: ENS, + chainId: CHAIN_ID, + credentials: [SOCIAL_CREDENTIAL], + issuer: ISSUER, +}); + +const getVerifyRecordsRequest = ( + chainId: number = CHAIN_ID +): VerifyRecordsRequest => ({ + ens: ENS, + chainId, + credentials: [SOCIAL_CREDENTIAL], + issuer: ISSUER, +}); + +const getVerifyRecordsResponse = (): VerifyRecordsResponse => ({ + [RECORD_KEY]: RECORD_VALUE, +}); + +const getVerifyRecordsApiResponse = (): VerifyRecordsApiResponse => ({ + records: { + [RECORD_KEY]: RECORD_VALUE, + }, +}); + +describe('Verify records controller integration tests', () => { + let app: INestApplication; + let verifyRecordsService: DeepMocked; + let verifyRecordsControllerMapper: DeepMocked; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [VerifyRecordsController], + providers: [ + ...VCManagementApiFilters.map((filter) => ({ + provide: APP_FILTER, + useClass: filter, + })), + { + provide: VERIFY_RECORDS_SERVICE, + useValue: createMock(), + }, + { + provide: 'VERIFY_RECORDS_CONTROLLER_MAPPER', + useValue: createMock(), + }, + ], + }).compile(); + + verifyRecordsService = module.get>( + VERIFY_RECORDS_SERVICE + ); + + verifyRecordsControllerMapper = module.get< + DeepMocked + >('VERIFY_RECORDS_CONTROLLER_MAPPER'); + + app = module.createNestApplication(); + await app.init(); + }); + + afterAll(async () => { + await app.close(); + }); + + describe('Verify records endpoint tests', () => { + it('should return a response with the correct data', async () => { + verifyRecordsControllerMapper.mapVerifyRecordsApiRequestToVerifyRecordsRequest.mockImplementationOnce( + (param) => { + if ( + JSON.stringify(param) === + JSON.stringify({ + ...getVerifyRecordsApiRequest(), + chainId: CHAIN_ID.toString(), + credentials: SOCIAL_CREDENTIAL, + }) + ) { + return getVerifyRecordsRequest(); + } + throw new Error(ERROR_MESSAGE); + } + ); + + verifyRecordsService.verifyRecords.mockImplementationOnce((param) => { + if ( + JSON.stringify(param) === JSON.stringify(getVerifyRecordsRequest()) + ) { + return Promise.resolve(getVerifyRecordsResponse()); + } + + throw new Error(ERROR_MESSAGE); + }); + + verifyRecordsControllerMapper.mapVerifyRecordsResponseToVerifyRecordsApiResponse.mockImplementationOnce( + (param) => { + if ( + JSON.stringify(param) === JSON.stringify(getVerifyRecordsResponse()) + ) { + return getVerifyRecordsApiResponse(); + } + + throw new Error(ERROR_MESSAGE); + } + ); + + await request(app.getHttpServer()) + .get('/verify-records') + .query(getVerifyRecordsApiRequest()) + .expect((res) => { + expect(res.status).toBe(200); + expect(res.body).toEqual(getVerifyRecordsApiResponse()); + }); + }); + }); + + it('should return 400 status code if provided chain id is not found', async () => { + verifyRecordsControllerMapper.mapVerifyRecordsApiRequestToVerifyRecordsRequest.mockImplementationOnce( + (param) => { + if ( + JSON.stringify(param) === + JSON.stringify({ + ...getVerifyRecordsApiRequest(), + chainId: CHAIN_ID_2.toString(), + credentials: SOCIAL_CREDENTIAL, + }) + ) { + return getVerifyRecordsRequest(CHAIN_ID_2); + } + throw new Error(ERROR_MESSAGE); + } + ); + + verifyRecordsService.verifyRecords.mockImplementationOnce((param) => { + if ( + JSON.stringify(param) === + JSON.stringify(getVerifyRecordsRequest(CHAIN_ID_2)) + ) { + throw ChainIdInvalidException.withId(CHAIN_ID_2); + } + + return Promise.resolve(getVerifyRecordsResponse()); + }); + + await request(app.getHttpServer()) + .get('/verify-records') + .query({ ...getVerifyRecordsApiRequest(), chainId: CHAIN_ID_2 }) + .expect((res) => { + expect(res.status).toBe(400); + expect(res.body).toEqual({ + message: ChainIdInvalidException.withId(CHAIN_ID_2).message, + }); + }); + }); +}); diff --git a/apps/vc-api/test/src/core/applications/credentials/facade/credential.facade.spec.ts b/apps/vc-api/test/src/core/applications/credentials/facade/credential.facade.spec.ts new file mode 100644 index 0000000..36ec8b9 --- /dev/null +++ b/apps/vc-api/test/src/core/applications/credentials/facade/credential.facade.spec.ts @@ -0,0 +1,73 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { AbstractSocialResolver } from '../../../../../../src/core/applications/credentials/facade/social-credential-resolver/social-resolver/abstract.social.resolver'; +import { CredentialCreatorFacade } from '../../../../../../src/core/applications/credentials/facade/credential.facade'; +import { SocialResolverNotFoundException } from '../../../../../../src/core/domain/exceptions/SocialResolverNotFound.exception'; +import { + ISocialCredentialResolver, + SOCIAL_CREDENTIAL_RESOLVER, +} from '../../../../../../src/core/applications/credentials/facade/social-credential-resolver/isocial.credential.resolver'; +import { + EMAIL_RESOLVER, + IEmailResolver, +} from '../../../../../../src/core/applications/credentials/facade/email-resolver/iemail.resolver'; + +const CREDENTIAL_NAME = 'CREDENTIAL_NAME'; +const CREDENTIAL_NAME_2 = 'CREDENTIAL_NAME_2'; + +const AUTH_URL = 'AUTH_URL'; +const CALLBACK_URL = 'CALLBACK_URL'; + +const RESOLVER: Partial> = { + getCredentialName: () => CREDENTIAL_NAME, + getAuthUrl: () => AUTH_URL, + getCallbackUrl: () => CALLBACK_URL, +} + +describe('CredentialCreatorFacade', () => { + let facade: CredentialCreatorFacade; + let socialResolver: DeepMocked; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + CredentialCreatorFacade, + { + provide: SOCIAL_CREDENTIAL_RESOLVER, + useValue: createMock(), + }, + { + provide: EMAIL_RESOLVER, + useValue: createMock(), + }, + ], + }).compile(); + + facade = module.get(CredentialCreatorFacade); + socialResolver = module.get>( + SOCIAL_CREDENTIAL_RESOLVER + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('getSocialResolver', () => { + it('should return the correct social resolver for a given credential name', () => { + socialResolver.getSocialResolvers.mockReturnValue([RESOLVER as AbstractSocialResolver]); + + expect( + facade.getSocialResolver(CREDENTIAL_NAME).getCredentialName() + ).toBe(CREDENTIAL_NAME); + }); + + it('should throw SocialResolverNotFoundException if resolver is not found', () => { + socialResolver.getSocialResolvers.mockReturnValue([RESOLVER as AbstractSocialResolver]); + + expect(() => { + facade.getSocialResolver(CREDENTIAL_NAME_2); + }).toThrow(SocialResolverNotFoundException); + }); + }); +}); diff --git a/apps/vc-api/test/src/core/applications/credentials/facade/email-resolver.spec.ts b/apps/vc-api/test/src/core/applications/credentials/facade/email-resolver.spec.ts new file mode 100644 index 0000000..eb7f4df --- /dev/null +++ b/apps/vc-api/test/src/core/applications/credentials/facade/email-resolver.spec.ts @@ -0,0 +1,172 @@ +import { HttpService } from '@nestjs/axios'; +import { Test, TestingModule } from '@nestjs/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { EmailResolver } from '../../../../../../src/core/applications/credentials/facade/email-resolver/email.resolver'; +import { EmailCallback } from '../../../../../../src/core/applications/credentials/facade/email-resolver/email.callback'; +import { OTPException } from '../../../../../../src/core/domain/exceptions/OTP.exception'; +import { IEmailSender } from '../../../../../../src/core/applications/email-sender/iemail-sender.service'; +import { EMAIL_SENDER } from '../../../../../../src/core/applications/email-sender/iemail-sender.service'; +import { + CRYPTO_SERVICE, + ICryptoService, +} from '../../../../../../src/core/applications/crypto/icrypto.service'; +import { + ENVIRONMENT_GETTER, + IEnvironmentGetter, +} from '../../../../../../src/core/applications/environment/ienvironment.getter'; +import { + CREDENTIAL_CREATOR, + ICredentialCreator, +} from '../../../../../../src/core/applications/credentials/creator/icredential.creator'; +import { + DID_RESOLVER, + IDIDResolver, +} from '../../../../../../src/core/applications/did/resolver/idid.resolver'; +import { + TIME_GENERATOR, + TimeGenerator, +} from '../../../../../../src/core/applications/time.generator'; +import { + ENS_MANAGER_SERVICE, + IEnsManagerService, +} from '../../../../../../src/core/applications/ens-manager/iens-manager.service'; + +const GENERATED_OTP = '123456'; +const GENERATED_OTP_2 = '654321'; +const HASHED_OTP = Buffer.from(GENERATED_OTP); +const HASHED_OTP_2 = Buffer.from(GENERATED_OTP_2); + +const ENCRYPTED_STATE = 'ENCRYPTED_STATE'; +const EMAIL = 'EMAIL'; +const AUTH_ID = 'AUTH_ID'; +const AUTH_ID_2 = 'AUTH_ID_2'; +const ENS = 'ENS'; +const CURRENT_TIME = Date.now(); +const EXPIRES_AT = CURRENT_TIME + 6 * 60 * 1000; + +const getEmailCallback = (): EmailCallback => ({ + state: ENCRYPTED_STATE, + otp: GENERATED_OTP, +}); + +const getOTPStore = ( + authId: string +): Map => { + const otpStore = new Map< + string, + { email: string; otpHash: Buffer; expiresAt: number } + >(); + otpStore.set(authId, { + email: EMAIL, + otpHash: HASHED_OTP, + expiresAt: EXPIRES_AT, + }); + return otpStore; +}; + +describe('EmailResolver', () => { + let emailResolver: EmailResolver; + let cryptoEncryption: DeepMocked; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + EmailResolver, + { + provide: HttpService, + useValue: createMock(), + }, + { + provide: DID_RESOLVER, + useValue: createMock(), + }, + { + provide: TIME_GENERATOR, + useValue: createMock(), + }, + { + provide: EMAIL_SENDER, + useValue: createMock(), + }, + { + provide: CRYPTO_SERVICE, + useValue: createMock(), + }, + { + provide: CRYPTO_SERVICE, + useValue: createMock(), + }, + { + provide: ENS_MANAGER_SERVICE, + useValue: createMock(), + }, + { + provide: ENVIRONMENT_GETTER, + useValue: createMock(), + }, + { + provide: CREDENTIAL_CREATOR, + useValue: createMock(), + }, + ], + }).compile(); + + emailResolver = module.get(EmailResolver); + cryptoEncryption = module.get>(CRYPTO_SERVICE); + + cryptoEncryption.decrypt.mockReturnValue( + JSON.stringify({ ens: ENS, authId: AUTH_ID }) + ); + + emailResolver.otpStore = getOTPStore(AUTH_ID); + + cryptoEncryption.timingSafeEqual.mockImplementationOnce( + (firstHash, secondHash) => { + if (firstHash.equals(HASHED_OTP_2) && secondHash.equals(HASHED_OTP)) { + return false; + } + return true; + } + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('extractCredentialSubject method tests', () => { + it('should throw an exception when the otp data is not found', async () => { + emailResolver.otpStore = getOTPStore(AUTH_ID_2); + + await expect( + emailResolver.extractCredentialSubject(getEmailCallback()) + ).rejects.toThrow(OTPException.notFound()); + }); + + it('should throw an exception when the otp data is expired', async () => { + jest.spyOn(Date, 'now').mockReturnValueOnce(EXPIRES_AT + 1); + + await expect( + emailResolver.extractCredentialSubject(getEmailCallback()) + ).rejects.toThrow(OTPException.expired()); + }); + + it('should throw an exception when the otp is invalid', async () => { + cryptoEncryption.createHash.mockReturnValue(HASHED_OTP_2); + + await expect( + emailResolver.extractCredentialSubject(getEmailCallback()) + ).rejects.toThrow(OTPException.invalid()); + }); + }); + + describe('resendOtp method test', () => { + it('should throw an exception when the otp data is not found', async () => { + emailResolver.otpStore = getOTPStore(AUTH_ID_2); + + await expect(emailResolver.resendOtp(ENCRYPTED_STATE)).rejects.toThrow( + OTPException.notFound() + ); + }); + }); +}); From 793e03f54e15a8bf6d87597fafc56356a5e20d25 Mon Sep 17 00:00:00 2001 From: georgesMouawad Date: Wed, 9 Oct 2024 15:58:35 +0300 Subject: [PATCH 04/11] feat(vc-api): external justanameInitializerService error handling --- apps/vc-api/src/api/auth/auth.controller.ts | 33 +++---- .../auth/justaName-intializer.filter.ts | 16 ++++ ...nvalid.filter.ts => credentials.filter.ts} | 8 +- .../credentials/email-sender.filter.ts | 2 +- apps/vc-api/src/api/filters/vc.api.filters.ts | 6 +- .../telegram.social.resolver.ts | 20 ++--- .../exceptions/Credentials.exception.ts | 10 +++ .../CredentialsInvalid.exception.ts | 9 -- .../JustaNameInitializer.exception.ts | 14 +++ .../justaname-initializer.service.ts | 85 +++++++++++-------- 10 files changed, 119 insertions(+), 84 deletions(-) create mode 100644 apps/vc-api/src/api/filters/auth/justaName-intializer.filter.ts rename apps/vc-api/src/api/filters/credentials/{credentials.invalid.filter.ts => credentials.filter.ts} (53%) create mode 100644 apps/vc-api/src/core/domain/exceptions/Credentials.exception.ts delete mode 100644 apps/vc-api/src/core/domain/exceptions/CredentialsInvalid.exception.ts create mode 100644 apps/vc-api/src/core/domain/exceptions/JustaNameInitializer.exception.ts diff --git a/apps/vc-api/src/api/auth/auth.controller.ts b/apps/vc-api/src/api/auth/auth.controller.ts index 6d52e71..3ef2379 100644 --- a/apps/vc-api/src/api/auth/auth.controller.ts +++ b/apps/vc-api/src/api/auth/auth.controller.ts @@ -5,7 +5,6 @@ import { JwtService } from '@nestjs/jwt'; import moment from 'moment'; import { JwtGuard } from '../../guards/jwt.guard'; import { ENS_MANAGER_SERVICE, IEnsManagerService } from '../../core/applications/ens-manager/iens-manager.service'; -import { AuthenticationException } from '../../core/domain/exceptions/Authentication.exception'; type Siwens = { address: string, ens: string }; @@ -29,29 +28,21 @@ export class AuthController { @Res() res: Response, @Req() req: Request ) { - try { - const { address, ens } = await this.ensManagerService.signIn({ - message: body.message, - signature: body.signature, - }); + const { address, ens } = await this.ensManagerService.signIn({ + message: body.message, + signature: body.signature + }) - const token = this.jwtService.sign( - { ens, address }, - { - expiresIn: moment().add(1, 'hour').unix(), - } - ); - res.cookie('justverifiedtoken', token, { - httpOnly: true, - secure: true, - sameSite: 'none', - }); + const token = this.jwtService.sign({ ens, address }, { + expiresIn: moment().add(1, 'hour').unix() + }); + + + res.cookie('justverifiedtoken', token, { httpOnly: true, secure: true, sameSite: 'none' }); + + return res.status(200).send({ ens, address }); - return res.status(200).send({ ens, address }); - } catch (error) { - throw AuthenticationException.withError(error); - } } @UseGuards(JwtGuard) diff --git a/apps/vc-api/src/api/filters/auth/justaName-intializer.filter.ts b/apps/vc-api/src/api/filters/auth/justaName-intializer.filter.ts new file mode 100644 index 0000000..518ae53 --- /dev/null +++ b/apps/vc-api/src/api/filters/auth/justaName-intializer.filter.ts @@ -0,0 +1,16 @@ +import { BaseExceptionFilter } from '@nestjs/core'; +import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; +import { JustaNameInitializerException } from '../../../core/domain/exceptions/JustaNameInitializer.exception'; + +@Catch(JustaNameInitializerException) +export class JustaNameInitializerExceptionFilter extends BaseExceptionFilter { + catch(exception: JustaNameInitializerException, host: ArgumentsHost) { + const context = host.switchToHttp(); + const response = context.getResponse(); + const httpStatus = HttpStatus.BAD_GATEWAY; + + response.status(httpStatus).json({ + message: exception.message, + }); + } +} diff --git a/apps/vc-api/src/api/filters/credentials/credentials.invalid.filter.ts b/apps/vc-api/src/api/filters/credentials/credentials.filter.ts similarity index 53% rename from apps/vc-api/src/api/filters/credentials/credentials.invalid.filter.ts rename to apps/vc-api/src/api/filters/credentials/credentials.filter.ts index 95954e1..16937a9 100644 --- a/apps/vc-api/src/api/filters/credentials/credentials.invalid.filter.ts +++ b/apps/vc-api/src/api/filters/credentials/credentials.filter.ts @@ -1,10 +1,10 @@ import { BaseExceptionFilter } from '@nestjs/core'; import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; -import { CredentialsInvalidException } from '../../../core/domain/exceptions/CredentialsInvalid.exception'; +import { CredentialsException } from '../../../core/domain/exceptions/Credentials.exception'; -@Catch(CredentialsInvalidException) -export class CredentialsInvalidExceptionFilter extends BaseExceptionFilter { - catch(exception: CredentialsInvalidException, host: ArgumentsHost) { +@Catch(CredentialsException) +export class CredentialsExceptionFilter extends BaseExceptionFilter { + catch(exception: CredentialsException, host: ArgumentsHost) { const context = host.switchToHttp(); const response = context.getResponse(); const httpStatus = HttpStatus.BAD_REQUEST; diff --git a/apps/vc-api/src/api/filters/credentials/email-sender.filter.ts b/apps/vc-api/src/api/filters/credentials/email-sender.filter.ts index 0010ca3..a42929f 100644 --- a/apps/vc-api/src/api/filters/credentials/email-sender.filter.ts +++ b/apps/vc-api/src/api/filters/credentials/email-sender.filter.ts @@ -7,7 +7,7 @@ export class EmailSenderExceptionFilter extends BaseExceptionFilter { catch(exception: EmailSenderException, host: ArgumentsHost) { const context = host.switchToHttp(); const response = context.getResponse(); - const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + const httpStatus = HttpStatus.BAD_GATEWAY; response.status(httpStatus).json({ message: exception.message, diff --git a/apps/vc-api/src/api/filters/vc.api.filters.ts b/apps/vc-api/src/api/filters/vc.api.filters.ts index efdae76..901c5ed 100644 --- a/apps/vc-api/src/api/filters/vc.api.filters.ts +++ b/apps/vc-api/src/api/filters/vc.api.filters.ts @@ -2,14 +2,16 @@ import { OTPExceptionFilter } from './credentials/otp.filter'; import { EmailSenderExceptionFilter } from './credentials/email-sender.filter'; import { ChainIdInvalidExceptionFilter } from './verify-records/chainId.invalid.filter'; import { SocialResolverNotFoundExceptionFilter } from './credentials/social-resolver.not-found.filter'; -import { CredentialsInvalidExceptionFilter } from './credentials/credentials.invalid.filter'; +import { CredentialsExceptionFilter } from './credentials/credentials.filter'; import { AuthenticationExceptionFilter } from './auth/authentication.filter'; +import { JustaNameInitializerExceptionFilter } from './auth/justaName-intializer.filter'; export const VCManagementApiFilters = [ OTPExceptionFilter, EmailSenderExceptionFilter, AuthenticationExceptionFilter, + JustaNameInitializerExceptionFilter, ChainIdInvalidExceptionFilter, - CredentialsInvalidExceptionFilter, + CredentialsExceptionFilter, SocialResolverNotFoundExceptionFilter, ]; diff --git a/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/telegram.social.resolver.ts b/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/telegram.social.resolver.ts index 49aef15..9d04148 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/telegram.social.resolver.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/telegram.social.resolver.ts @@ -1,14 +1,13 @@ import { AbstractSocialResolver } from './abstract.social.resolver'; import { TelegramCredential } from '../../../../../domain/credentials/telegram.credential'; import { TelegramCallback } from './callback/telegram.callback'; -import {GetAuthUrlRequest} from "./requests/get-auth-url.request"; -import { CredentialsInvalidException } from '../../../../../domain/exceptions/CredentialsInvalid.exception'; +import { GetAuthUrlRequest } from './requests/get-auth-url.request'; +import { CredentialsException } from '../../../../../domain/exceptions/Credentials.exception'; export class TelegramSocialResolver extends AbstractSocialResolver< TelegramCallback, TelegramCredential > { - getCredentialName(): string { return 'telegram'; } @@ -20,10 +19,9 @@ export class TelegramSocialResolver extends AbstractSocialResolver< getType(): string[] { return ['VerifiableTelegramAccount']; } - getAuthUrl(authUrlRequest:GetAuthUrlRequest): string { + getAuthUrl(authUrlRequest: GetAuthUrlRequest): string { const state = this.encryptState(authUrlRequest); - return ( - ` + return ` - ` - ); + `; } async extractCredentialSubject( @@ -46,14 +43,15 @@ export class TelegramSocialResolver extends AbstractSocialResolver< .map((key) => `${key}=${telegramData[key]}`) .join('\n'); - const calculatedHash = this.cryptoEncryption.createTelegramHash(dataCheckString); + const calculatedHash = + this.cryptoEncryption.createTelegramHash(dataCheckString); if (calculatedHash !== hash) { - throw CredentialsInvalidException.withMessage('Invalid data: Authentication failed.'); + throw CredentialsException.invalid(); } return { username: telegramData.username, - } + }; } } diff --git a/apps/vc-api/src/core/domain/exceptions/Credentials.exception.ts b/apps/vc-api/src/core/domain/exceptions/Credentials.exception.ts new file mode 100644 index 0000000..52c7f20 --- /dev/null +++ b/apps/vc-api/src/core/domain/exceptions/Credentials.exception.ts @@ -0,0 +1,10 @@ +export class CredentialsException extends Error { + constructor(message: string) { + super(message); + this.name = 'CredentialsException'; + } + + static invalid() { + return new CredentialsException('Invalid data: Authentication failed'); + } +} \ No newline at end of file diff --git a/apps/vc-api/src/core/domain/exceptions/CredentialsInvalid.exception.ts b/apps/vc-api/src/core/domain/exceptions/CredentialsInvalid.exception.ts deleted file mode 100644 index f1271f9..0000000 --- a/apps/vc-api/src/core/domain/exceptions/CredentialsInvalid.exception.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class CredentialsInvalidException extends Error { - constructor(message: string) { - super(message); - } - - static withMessage(message: string) { - return new CredentialsInvalidException(message); - } -} \ No newline at end of file diff --git a/apps/vc-api/src/core/domain/exceptions/JustaNameInitializer.exception.ts b/apps/vc-api/src/core/domain/exceptions/JustaNameInitializer.exception.ts new file mode 100644 index 0000000..9ed3333 --- /dev/null +++ b/apps/vc-api/src/core/domain/exceptions/JustaNameInitializer.exception.ts @@ -0,0 +1,14 @@ +export class JustaNameInitializerException extends Error { + constructor(message: string) { + super(message); + } + + static withError(error: unknown) { + if (error instanceof Error) { + return new JustaNameInitializerException(error.message); + } + return new JustaNameInitializerException( + 'An error has occurred.' + ); + } +} diff --git a/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts b/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts index feee079..998a2e0 100644 --- a/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts +++ b/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts @@ -1,4 +1,3 @@ - import { IEnsManagerService } from '../../core/applications/ens-manager/iens-manager.service'; import { SignInRequest } from '../../core/applications/ens-manager/requests/sign-in.request'; import { SignInResponse } from '../../core/applications/ens-manager/responses/sign-in.response'; @@ -12,6 +11,8 @@ import { IKeyManagementFetcher, KEY_MANAGEMENT_FETCHER } from '../../core/applications/key-management/ikey-management.fetcher'; +import { JustaNameInitializerException } from '../../core/domain/exceptions/JustaNameInitializer.exception'; +import { AuthenticationException } from '../../core/domain/exceptions/Authentication.exception'; export class JustaNameInitializerService implements IEnsManagerService { @@ -32,11 +33,18 @@ export class JustaNameInitializerService implements IEnsManagerService { } async signIn(params: SignInRequest): Promise { - const sign = await this.justaname.signIn.signIn(params.message,params.signature) - - return { - address: sign.data.address, - ens: sign.ens + try { + const sign = await this.justaname.signIn.signIn( + params.message, + params.signature + ); + + return { + address: sign.data.address, + ens: sign.ens, + }; + } catch (error) { + throw AuthenticationException.withError(error); } } @@ -45,15 +53,19 @@ export class JustaNameInitializerService implements IEnsManagerService { } async getRecords(params: GetRecordsRequest): Promise { - const providerUrl = (params.chainId === 1 ? 'https://mainnet.infura.io/v3/' :'https://sepolia.infura.io/v3/') + this.environmentGetter.getInfuraProjectId() - - const records: SubnameRecordsResponse = await this.justaname.subnames.getRecordsByFullName({ - fullName: params.ens, - providerUrl: providerUrl, - chainId: params.chainId, - }) - - return this.mapSubnameRecordsResponseToGetRecordsResponse(records) + try { + const providerUrl = (params.chainId === 1 ? 'https://mainnet.infura.io/v3/' :'https://sepolia.infura.io/v3/') + this.environmentGetter.getInfuraProjectId() + + const records: SubnameRecordsResponse = await this.justaname.subnames.getRecordsByFullName({ + fullName: params.ens, + providerUrl: providerUrl, + chainId: params.chainId, + }) + + return this.mapSubnameRecordsResponseToGetRecordsResponse(records) + } catch (error) { + throw JustaNameInitializerException.withError(error); + } } checkIfMAppEnabled(ens: string): Promise { @@ -65,29 +77,30 @@ export class JustaNameInitializerService implements IEnsManagerService { } async appendVcInMAppEnabledEns(ens: string, vc: VerifiableEthereumEip712Signature2021, field: string): Promise { - const message = await this.justaname.mApps.requestAppendMAppFieldChallenge({ - mApp: this.environmentGetter.getEnsDomain(), - subname: ens, + try { + const message = await this.justaname.mApps.requestAppendMAppFieldChallenge({ + mApp: this.environmentGetter.getEnsDomain(), + subname: ens, address: this.keyManagementFetcher.fetchKey().publicKey, }) - - - const signature = await this.keyManagementFetcher.signMessage(message.challenge) - - - const appended = await this.justaname.mApps.appendMAppField({ - subname: ens, - fields: [{ - key: field, - value: JSON.stringify(vc), - }] - }, { - xAddress: this.keyManagementFetcher.fetchKey().publicKey, - xMessage: message.challenge, - xSignature: signature - }) - - return !!appended + const signature = await this.keyManagementFetcher.signMessage(message.challenge) + + const appended = await this.justaname.mApps.appendMAppField({ + subname: ens, + fields: [{ + key: field, + value: JSON.stringify(vc), + }] + }, { + xAddress: this.keyManagementFetcher.fetchKey().publicKey, + xMessage: message.challenge, + xSignature: signature + }) + + return !!appended + } catch (error) { + throw JustaNameInitializerException.withError(error); + } } private mapSubnameRecordsResponseToGetRecordsResponse(records: SubnameRecordsResponse): GetRecordsResponse { From 8cff513fbf7ff1a8475b58d61ad599b15556aa4f Mon Sep 17 00:00:00 2001 From: georgesMouawad Date: Wed, 9 Oct 2024 16:07:04 +0300 Subject: [PATCH 05/11] test(vc-api): authController justaNameServiceInitializer --- .../test/src/api/auth/auth.controller.spec.ts | 3 +- .../justaname-initializer.service.spec.ts | 151 ++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts diff --git a/apps/vc-api/test/src/api/auth/auth.controller.spec.ts b/apps/vc-api/test/src/api/auth/auth.controller.spec.ts index 7e5d0e7..e169bf3 100644 --- a/apps/vc-api/test/src/api/auth/auth.controller.spec.ts +++ b/apps/vc-api/test/src/api/auth/auth.controller.spec.ts @@ -10,6 +10,7 @@ import { ENS_MANAGER_SERVICE, IEnsManagerService, } from '../../../../src/core/applications/ens-manager/iens-manager.service'; +import { AuthenticationException } from '../../../../src/core/domain/exceptions/Authentication.exception'; const ENS = 'ENS'; const ADDRESS = 'ADDRESS'; @@ -51,7 +52,7 @@ describe('Auth controller integration tests', () => { if (param.message === MESSAGE && param.signature === SIGNATURE) { return { ens: ENS, address: ADDRESS }; } - throw new Error(ERROR_MESSAGE); + throw new AuthenticationException(ERROR_MESSAGE); }); jwtService.sign.mockImplementation((payload) => { diff --git a/apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts b/apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts new file mode 100644 index 0000000..9b7f2ff --- /dev/null +++ b/apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts @@ -0,0 +1,151 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ENVIRONMENT_GETTER } from '../../../../src/core/applications/environment/ienvironment.getter'; +import { IEnvironmentGetter } from '../../../../src/core/applications/environment/ienvironment.getter'; +import { IKeyManagementFetcher } from '../../../../src/core/applications/key-management/ikey-management.fetcher'; +import { KEY_MANAGEMENT_FETCHER } from '../../../../src/core/applications/key-management/ikey-management.fetcher'; +import { JustaNameInitializerService } from '../../../../src/external/justaname-initializer/justaname-initializer.service'; +import { SignInResponse } from '../../../../src/core/applications/ens-manager/responses/sign-in.response'; +import { SignInRequest } from '../../../../src/core/applications/ens-manager/requests/sign-in.request'; +import { AuthenticationException } from '../../../../src/core/domain/exceptions/Authentication.exception'; +import { JustaNameInitializerException } from '../../../../src/core/domain/exceptions/JustaNameInitializer.exception'; +import { + CredentialSubject, + EthereumEip712Signature2021, + VerifiableEthereumEip712Signature2021, +} from '../../../../src/core/domain/entities/ethereumEip712Signature'; + +const MESSAGE = 'MESSAGE'; +const SIGNATURE = 'SIGNATURE'; +const ADDRESS = 'ADDRESS'; +const ENS = 'ENS'; + +const ERROR_MESSAGE = 'ERROR_MESSAGE'; + +const CHAIN_ID = 11155111; +const SIWE_DOMAIN = 'SIWE_DOMAIN'; +const SIWE_ORIGIN = 'SIWE_ORIGIN'; +const ENS_DOMAIN = 'ENS_DOMAIN'; +const INFURA_PROJECT_ID = 'INFURA_PROJECT_ID'; + +const CREDENTIAL_SUBJUECT = new CredentialSubject(); +const ISSUANCE_DATE = new Date(); +const EXPIRATION_DATE = new Date( + ISSUANCE_DATE.setFullYear(ISSUANCE_DATE.getFullYear() + 1) +); +const CONTEXT = 'CONTEXT'; +const TYPE = 'TYPE'; +const FIELD = 'FIELD'; + +const getSignInRequest = (): SignInRequest => ({ + message: MESSAGE, + signature: SIGNATURE, +}); + +const getSignInResponse = (): SignInResponse => ({ + address: ADDRESS, + ens: ENS, +}); + +const getEthereumEip712Signature2021 = (): EthereumEip712Signature2021 => { + return new EthereumEip712Signature2021({ + credentialSubject: CREDENTIAL_SUBJUECT, + issuanceDate: ISSUANCE_DATE, + expirationDate: EXPIRATION_DATE, + context: [CONTEXT], + type: [TYPE], + }); +}; + +describe('JustaName initializer service', () => { + let justaNameInitializerService: JustaNameInitializerService; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + JustaNameInitializerService, + { + provide: ENVIRONMENT_GETTER, + useValue: createMock({ + getChainId: jest.fn().mockReturnValue(CHAIN_ID), + getSiweDomain: jest.fn().mockReturnValue(SIWE_DOMAIN), + getSiweOrigin: jest.fn().mockReturnValue(SIWE_ORIGIN), + getEnsDomain: jest.fn().mockReturnValue(ENS_DOMAIN), + getInfuraProjectId: jest.fn().mockReturnValue(INFURA_PROJECT_ID), + }), + }, + { + provide: KEY_MANAGEMENT_FETCHER, + useValue: createMock(), + }, + ], + }).compile(); + + justaNameInitializerService = module.get( + JustaNameInitializerService + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined', () => { + expect(justaNameInitializerService).toBeDefined(); + }); + + describe('signIn method tests', () => { + it('should throw an Authentication exception when the sign in fails', async () => { + jest + .spyOn(justaNameInitializerService.justaname.signIn, 'signIn') + .mockRejectedValue(new Error(ERROR_MESSAGE)); + + await expect( + justaNameInitializerService.signIn(getSignInRequest()) + ).rejects.toThrow( + AuthenticationException.withError(new Error(ERROR_MESSAGE)) + ); + }); + }); + + describe('getRecords method tests', () => { + it('should throw a JustaNameInitializer exception when getRecords fails', async () => { + jest + .spyOn( + justaNameInitializerService.justaname.subnames, + 'getRecordsByFullName' + ) + .mockRejectedValue(new Error(ERROR_MESSAGE)); + + await expect( + justaNameInitializerService.getRecords({ + chainId: CHAIN_ID, + ens: ENS, + }) + ).rejects.toThrow( + JustaNameInitializerException.withError(new Error(ERROR_MESSAGE)) + ); + }); + }); + + describe('appendVcInMAppEnabledEns', () => { + it('should throw a JustaNameInitializer exception when an error occurs', async () => { + jest + .spyOn( + justaNameInitializerService.justaname.mApps, + 'requestAppendMAppFieldChallenge' + ) + .mockRejectedValue(new Error(ERROR_MESSAGE)); + + await expect( + justaNameInitializerService.appendVcInMAppEnabledEns( + ENS, + {} as VerifiableEthereumEip712Signature2021, + FIELD + ) + ).rejects.toThrow( + JustaNameInitializerException.withError(new Error(ERROR_MESSAGE)) + ); + }); + }); +}); From 1ceeb366d9a928bf3c1de6a8cd0b0009dc8fcb55 Mon Sep 17 00:00:00 2001 From: georgesMouawad Date: Thu, 10 Oct 2024 09:58:39 +0300 Subject: [PATCH 06/11] feat(vc-api): core exeptions adjustments --- .../core/domain/exceptions/Authentication.exception.ts | 5 +---- .../src/core/domain/exceptions/Credentials.exception.ts | 1 - .../domain/exceptions/JustaNameInitializer.exception.ts | 9 ++------- apps/vc-api/src/core/domain/exceptions/OTP.exception.ts | 1 - 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/apps/vc-api/src/core/domain/exceptions/Authentication.exception.ts b/apps/vc-api/src/core/domain/exceptions/Authentication.exception.ts index 9afcbb9..8a028a0 100644 --- a/apps/vc-api/src/core/domain/exceptions/Authentication.exception.ts +++ b/apps/vc-api/src/core/domain/exceptions/Authentication.exception.ts @@ -3,10 +3,7 @@ export class AuthenticationException extends Error { super(message); } - static withError(error: unknown) { - if (error instanceof Error) { + static withError(error: Error) { return new AuthenticationException(error.message); - } - return new AuthenticationException('Authentication failed.'); } } diff --git a/apps/vc-api/src/core/domain/exceptions/Credentials.exception.ts b/apps/vc-api/src/core/domain/exceptions/Credentials.exception.ts index 52c7f20..38969d6 100644 --- a/apps/vc-api/src/core/domain/exceptions/Credentials.exception.ts +++ b/apps/vc-api/src/core/domain/exceptions/Credentials.exception.ts @@ -1,7 +1,6 @@ export class CredentialsException extends Error { constructor(message: string) { super(message); - this.name = 'CredentialsException'; } static invalid() { diff --git a/apps/vc-api/src/core/domain/exceptions/JustaNameInitializer.exception.ts b/apps/vc-api/src/core/domain/exceptions/JustaNameInitializer.exception.ts index 9ed3333..84ff0b0 100644 --- a/apps/vc-api/src/core/domain/exceptions/JustaNameInitializer.exception.ts +++ b/apps/vc-api/src/core/domain/exceptions/JustaNameInitializer.exception.ts @@ -3,12 +3,7 @@ export class JustaNameInitializerException extends Error { super(message); } - static withError(error: unknown) { - if (error instanceof Error) { - return new JustaNameInitializerException(error.message); - } - return new JustaNameInitializerException( - 'An error has occurred.' - ); + static withError(error: Error) { + return new JustaNameInitializerException(error.message); } } diff --git a/apps/vc-api/src/core/domain/exceptions/OTP.exception.ts b/apps/vc-api/src/core/domain/exceptions/OTP.exception.ts index 0379f18..070739a 100644 --- a/apps/vc-api/src/core/domain/exceptions/OTP.exception.ts +++ b/apps/vc-api/src/core/domain/exceptions/OTP.exception.ts @@ -1,7 +1,6 @@ export class OTPException extends Error { constructor(message: string) { super(message); - this.name = 'OTPException'; } static invalid() { From 987092b5bb42735d82ad76965471958aedda16cd Mon Sep 17 00:00:00 2001 From: georgesMouawad Date: Mon, 14 Oct 2024 10:21:13 +0300 Subject: [PATCH 07/11] fix(vc-api): package.json credentrialControllerSpec --- apps/vc-api/package.json | 4 +- .../credentials.controller.spec.ts | 2 +- package.json | 6 +- yarn.lock | 84 +++++++++++++++++-- 4 files changed, 85 insertions(+), 11 deletions(-) diff --git a/apps/vc-api/package.json b/apps/vc-api/package.json index 2685543..cded1cb 100644 --- a/apps/vc-api/package.json +++ b/apps/vc-api/package.json @@ -34,6 +34,8 @@ "@react-email/components": "^0.0.25", "react": "18.3.1", "resend": "^4.0.0", - "react-dom": "18.3.1" + "react-dom": "18.3.1", + "@nestjs/testing": "^10.0.2", + "@golevelup/ts-jest": "^0.5.6" } } diff --git a/apps/vc-api/test/src/api/credentials/credentials.controller.spec.ts b/apps/vc-api/test/src/api/credentials/credentials.controller.spec.ts index e7bffc4..189834d 100644 --- a/apps/vc-api/test/src/api/credentials/credentials.controller.spec.ts +++ b/apps/vc-api/test/src/api/credentials/credentials.controller.spec.ts @@ -110,7 +110,7 @@ describe('Credentials controller integration tests', () => { .get('/credentials/email') .query({ email: EMAIL }) .expect((res) => { - expect(res.status).toBe(500); + expect(res.status).toBe(502); expect(res.body.message).toBe(ERROR_MESSAGE); }); }); diff --git a/package.json b/package.json index 7d761cd..691246c 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "@nestjs/swagger": "^7.4.0", "@nx-tools/container-metadata": "^6.0.2", "@rainbow-me/rainbowkit": "^2.1.6", - "@tanstack/react-query": "^5.56.2", "@react-email/components": "^0.0.25", + "@tanstack/react-query": "^5.56.2", "@tanstack/react-query-devtools": "^5.56.2", "@veramo/core": "^6.0.0", "@veramo/credential-eip712": "^6.0.0", @@ -62,6 +62,7 @@ "devDependencies": { "@babel/core": "^7.25.2", "@babel/preset-react": "^7.14.5", + "@golevelup/ts-jest": "^0.5.6", "@nestjs/axios": "^3.0.3", "@nestjs/schematics": "^10.0.1", "@nestjs/testing": "^10.0.2", @@ -111,7 +112,7 @@ "@testing-library/react": "15.0.6", "@types/crypto-js": "^4.2.2", "@types/express-session": "^1.18.0", - "@types/jest": "29.5.12", + "@types/jest": "^29.5.13", "@types/node": "~18.16.9", "@types/react": "18.3.1", "@types/react-dom": "18.3.0", @@ -138,6 +139,7 @@ "rollup": "^4.14.0", "rollup-plugin-terser": "^7.0.2", "storybook": "^8.2.8", + "supertest": "^7.0.0", "ts-jest": "^29.1.0", "ts-node": "10.9.1", "typescript": "5.5.4", diff --git a/yarn.lock b/yarn.lock index 5e24645..8b2da96 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2055,6 +2055,11 @@ resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== +"@golevelup/ts-jest@^0.5.6": + version "0.5.6" + resolved "https://registry.yarnpkg.com/@golevelup/ts-jest/-/ts-jest-0.5.6.tgz#e63e3d746417de07cbd5d45208de380cd185346a" + integrity sha512-QnxP42Qu9M2UogdrF2kxpZcgWeW9R3WoUr+LdpcsbkvX3LjEoDYgrJ/PnV/QUCbxB1gaKbhR0ZxlDxE1cPkpDg== + "@graphql-typed-document-node/core@^3.2.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" @@ -6108,10 +6113,10 @@ jest-matcher-utils "^28.0.0" pretty-format "^28.0.0" -"@types/jest@29.5.12": - version "29.5.12" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" - integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== +"@types/jest@^29.5.13": + version "29.5.13" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.13.tgz#8bc571659f401e6a719a7bf0dbcb8b78c71a8adc" + integrity sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -7694,6 +7699,11 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + asmcrypto.js@^0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/asmcrypto.js/-/asmcrypto.js-0.22.0.tgz#38fc1440884d802c7bd37d1d23c2b26a5cd5d2d2" @@ -8872,6 +8882,11 @@ compare-versions@^3.4.0: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== +component-emitter@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" + integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== + compressible@~2.0.16: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -8999,6 +9014,11 @@ cookie@0.6.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookiejar@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== + cookies@~0.9.0: version "0.9.1" resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.9.1.tgz#3ffed6f60bb4fb5f146feeedba50acc418af67e3" @@ -9843,6 +9863,14 @@ detect-port@^1.5.1: address "^1.0.1" debug "4" +dezalgo@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + dependencies: + asap "^2.0.0" + wrappy "1" + did-jwt-vc@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/did-jwt-vc/-/did-jwt-vc-4.0.4.tgz#05f6e29778dd5655cb11cb94a3d1ee8e3cd2f255" @@ -11129,7 +11157,7 @@ fast-redact@^3.0.0: resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.5.0.tgz#e9ea02f7e57d0cd8438180083e93077e496285e4" integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== -fast-safe-stringify@2.1.1, fast-safe-stringify@^2.0.6: +fast-safe-stringify@2.1.1, fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== @@ -11454,6 +11482,15 @@ formdata-polyfill@^4.0.10: dependencies: fetch-blob "^3.1.2" +formidable@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.1.tgz#9360a23a656f261207868b1484624c4c8d06ee1a" + integrity sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og== + dependencies: + dezalgo "^1.0.4" + hexoid "^1.0.0" + once "^1.4.0" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -12023,6 +12060,11 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hexoid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" + integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== + hey-listen@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68" @@ -14716,7 +14758,7 @@ merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@~1.1.2: +methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== @@ -14764,6 +14806,11 @@ mime@1.6.0, mime@^1.4.1, mime@^1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + mime@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" @@ -16735,7 +16782,7 @@ qrcode@1.5.4: pngjs "^5.0.0" yargs "^15.3.1" -qs@6.13.0, qs@^6.10.0, qs@^6.12.0, qs@^6.12.3, qs@^6.4.0: +qs@6.13.0, qs@^6.10.0, qs@^6.11.0, qs@^6.12.0, qs@^6.12.3, qs@^6.4.0: version "6.13.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== @@ -18441,11 +18488,34 @@ stylus@^0.59.0: sax "~1.2.4" source-map "^0.7.3" +superagent@^9.0.1: + version "9.0.2" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-9.0.2.tgz#a18799473fc57557289d6b63960610e358bdebc1" + integrity sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w== + dependencies: + component-emitter "^1.3.0" + cookiejar "^2.1.4" + debug "^4.3.4" + fast-safe-stringify "^2.1.1" + form-data "^4.0.0" + formidable "^3.5.1" + methods "^1.1.2" + mime "2.6.0" + qs "^6.11.0" + superstruct@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== +supertest@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-7.0.0.tgz#cac53b3d6872a0b317980b2b0cfa820f09cd7634" + integrity sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA== + dependencies: + methods "^1.1.2" + superagent "^9.0.1" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" From d1758e103c92ad1d433203136d97e0058195e2d8 Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Mon, 14 Oct 2024 12:55:09 +0300 Subject: [PATCH 08/11] feat: support for sepolia and mainnet --- apps/vc-api/.env.example | 13 +++++--- apps/vc-api/src/api/auth/auth.controller.ts | 11 ++++--- .../api/credentials/credentials.controller.ts | 7 ++-- apps/vc-api/src/config/env.validation.ts | 33 +++++-------------- .../credentials/facade/abstract.resolver.ts | 23 +++++++------ .../credentials/facade/credential.facade.ts | 9 ++--- .../facade/email-resolver/email.resolver.ts | 3 +- .../requests/email.otp.generate.request.ts | 3 ++ .../credentials/facade/icredential.facade.ts | 5 +-- .../social-resolver/github.social.resolver.ts | 5 +-- .../requests/get-auth-url.request.ts | 3 ++ .../ens-manager/iens-manager.service.ts | 5 +-- .../ens-manager/responses/sign-in.response.ts | 3 ++ .../environment/environment.getter.ts | 22 +++++-------- .../environment/ienvironment.getter.ts | 7 ++-- .../src/core/domain/entities/environment.ts | 8 ++--- .../justaname-initializer.service.ts | 26 ++++++--------- .../test/src/api/auth/auth.controller.spec.ts | 3 +- .../justaname-initializer.service.spec.ts | 4 +-- 19 files changed, 94 insertions(+), 99 deletions(-) diff --git a/apps/vc-api/.env.example b/apps/vc-api/.env.example index e6b5b67..61a8aec 100644 --- a/apps/vc-api/.env.example +++ b/apps/vc-api/.env.example @@ -1,8 +1,12 @@ INFURA_PROJECT_ID= -SIGNING_PRIVATE_KEY= ENVIRONMENT=# (development, production, staging, test) + +SIGNING_PRIVATE_KEY= +SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN= + ENS_DOMAIN= -CHAIN_ID= # 1 or 11155111 +ENS_DOMAIN_SEPOLIA= + API_DOMAIN= GITHUB_CLIENT_ID= @@ -18,8 +22,7 @@ DISCORD_CLIENT_ID= DISCORD_CLIENT_SECRET= JWT_SECRET= -SIWE_DOMAIN= -SIWE_ORIGIN= - ENCRYPT_KEY`= ENCRYPT_SALT= + +RESEND_API_KEY= diff --git a/apps/vc-api/src/api/auth/auth.controller.ts b/apps/vc-api/src/api/auth/auth.controller.ts index 3ef2379..5f4deba 100644 --- a/apps/vc-api/src/api/auth/auth.controller.ts +++ b/apps/vc-api/src/api/auth/auth.controller.ts @@ -5,8 +5,9 @@ import { JwtService } from '@nestjs/jwt'; import moment from 'moment'; import { JwtGuard } from '../../guards/jwt.guard'; import { ENS_MANAGER_SERVICE, IEnsManagerService } from '../../core/applications/ens-manager/iens-manager.service'; +import { ChainId } from '../../core/domain/entities/environment'; -type Siwens = { address: string, ens: string }; +type Siwens = { address: string, ens: string, chainId: ChainId }; @Controller('auth') export class AuthController { @@ -28,20 +29,20 @@ export class AuthController { @Res() res: Response, @Req() req: Request ) { - const { address, ens } = await this.ensManagerService.signIn({ + const { address, ens, chainId } = await this.ensManagerService.signIn({ message: body.message, signature: body.signature }) - const token = this.jwtService.sign({ ens, address }, { + const token = this.jwtService.sign({ ens, address, chainId }, { expiresIn: moment().add(1, 'hour').unix() }); res.cookie('justverifiedtoken', token, { httpOnly: true, secure: true, sameSite: 'none' }); - return res.status(200).send({ ens, address }); + return res.status(200).send({ ens, address, chainId }); } @@ -56,7 +57,7 @@ export class AuthController { return; } res.setHeader('Content-Type', 'text/plain'); - res.status(200).send({ ens: req.user?.ens, address: req.user?.address }); + res.status(200).send({ ens: req.user?.ens, address: req.user?.address, chainId: req.user?.chainId }); } @UseGuards(JwtGuard) diff --git a/apps/vc-api/src/api/credentials/credentials.controller.ts b/apps/vc-api/src/api/credentials/credentials.controller.ts index e38c23a..9cdf610 100644 --- a/apps/vc-api/src/api/credentials/credentials.controller.ts +++ b/apps/vc-api/src/api/credentials/credentials.controller.ts @@ -16,8 +16,9 @@ import {CredentialsResendOtpRequestApi} from "./requests/credentials.resend-otp. import {CredentialsVerifyOtpRequestApi} from "./requests/credentials.verify-otp.request.api"; import {CredentialsClearEmailRequestApi} from "./requests/credentials.clear-email.request.api"; import {AuthCallbackApiResponse} from "./responses/credentials.callback.response.api"; +import { ChainId } from '../../core/domain/entities/environment'; -type Siwens = { address: string, ens: string }; +type Siwens = { address: string, ens: string, chainId: ChainId }; @Controller('credentials') export class CredentialsController { @@ -47,6 +48,7 @@ export class CredentialsController { const redirectUrl = await this.credentialCreatorFacade.getSocialAuthUrl( authGetAuthUrlRequestApi.authName, req.user?.ens, + req.user?.chainId, authId ) @@ -82,7 +84,7 @@ export class CredentialsController { @Query() authGetAuthUrlRequestApiQuery: any, @Res() res: Response ): Promise { - + const verifiedEthereumEip712Signature2021 = await this.credentialCreatorFacade.socialCallback( this.authControllerMapper.mapAuthCallbackApiRequestToCredentialCallbackRequest( authGetAuthUrlRequestApiQuery, @@ -127,6 +129,7 @@ export class CredentialsController { const state = await this.credentialCreatorFacade.getEmailOTP( query.email, req.user?.ens, + req.user?.chainId, authId ) diff --git a/apps/vc-api/src/config/env.validation.ts b/apps/vc-api/src/config/env.validation.ts index 56d2776..da076ae 100644 --- a/apps/vc-api/src/config/env.validation.ts +++ b/apps/vc-api/src/config/env.validation.ts @@ -1,42 +1,33 @@ -import { plainToClass, Transform } from 'class-transformer'; +import { plainToClass } from 'class-transformer'; import { IsEnum, - IsNumber, IsNumberString, IsString, registerDecorator, validateSync, ValidationOptions } from 'class-validator'; import { ethers } from 'ethers'; -import { ChainId, Environment, EnvironmentType, SupportedChainIds } from '../core/domain/entities/environment'; -import { x } from 'vitest/dist/reporters-yx5ZTtEV'; +import { Environment, EnvironmentType } from '../core/domain/entities/environment'; class EnvironmentVariables implements Environment{ @IsString({ message: 'SIGNING_PRIVATE_KEY must be a string' }) @IsEthereumPrivateKey({ message: 'SIGNING_PRIVATE_KEY must be a valid private key' }) SIGNING_PRIVATE_KEY!: string; + @IsString({ message: 'SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN must be a string' }) + @IsEthereumPrivateKey({ message: 'SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN must be a valid private key' }) + SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN!: string; + @IsString({ message: 'ENVIROMENT must be a string' }) @IsEnum(EnvironmentType, { message: 'ENVIRONMENT must be a valid environment' }) ENVIRONMENT!: EnvironmentType; - @Transform(({ value }) => { - const intValue = parseInt(value) - if (isNaN(intValue)) { - throw new Error('CHAIN_ID must be a number') - } - - const isValidChainId = (x: any): x is ChainId => SupportedChainIds.includes(x); - - if (!isValidChainId(intValue)) { - throw new Error('CHAIN_ID must be a valid chain id (1, 11155111)') - } - }) - CHAIN_ID!: ChainId; - @IsString({ message: 'ENS_DOMAIN must be a string' }) ENS_DOMAIN!: string; + @IsString({ message: 'ENS_DOMAIN_SEPOLIA must be a string' }) + ENS_DOMAIN_SEPOLIA!: string; + @IsString({message: 'INFURA_PROJECT_ID must be a string'}) INFURA_PROJECT_ID!: string; @@ -70,12 +61,6 @@ class EnvironmentVariables implements Environment{ @IsString({message: 'JWT_SECRET must be a string'}) JWT_SECRET: string; - @IsString({message: 'SIWE_DOMAIN must be a string'}) - SIWE_DOMAIN: string; - - @IsString({message: 'SIWE_ORIGIN must be a string'}) - SIWE_ORIGIN: string; - @IsString({message: 'ENCRYPT_KEY must be a string'}) ENCRYPT_KEY: string; diff --git a/apps/vc-api/src/core/applications/credentials/facade/abstract.resolver.ts b/apps/vc-api/src/core/applications/credentials/facade/abstract.resolver.ts index 8d128bc..16e8415 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/abstract.resolver.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/abstract.resolver.ts @@ -12,6 +12,7 @@ import {ENS_MANAGER_SERVICE, IEnsManagerService} from "../../ens-manager/iens-ma import {HttpService} from "@nestjs/axios"; import {CredentialCallbackResponse} from "./credential.callback.response"; import {BaseCallback} from "./base.callback"; +import { ChainId } from '../../../domain/entities/environment'; export abstract class AbstractResolver< @@ -60,37 +61,39 @@ export abstract class AbstractResolver< async successfulCredentialGeneration( vc: VerifiableEthereumEip712Signature2021, - subname: string + subname: string, + chainId: ChainId ): Promise { - const checkIfMAppEnabled = await this.ensManagerService.checkIfMAppEnabled(subname); + const checkIfMAppEnabled = await this.ensManagerService.checkIfMAppEnabled(subname, chainId); if (checkIfMAppEnabled) { await this.ensManagerService.appendVcInMAppEnabledEns( subname, + chainId, vc, this.getKey() ); } } - encryptState({ens, authId}: { ens: string; authId: string }): string { - const stateObject = { ens, authId }; + encryptState({ens, chainId, authId}: { ens: string; chainId: ChainId; authId: string }): string { + const stateObject = { ens, chainId, authId }; return this.cryptoEncryption.encrypt(JSON.stringify(stateObject)); } - decryptState(state: string): { ens: string; authId: string } { + decryptState(state: string): { ens: string; chainId: ChainId; authId: string } { return JSON.parse(this.cryptoEncryption.decrypt(state)); } - getEnsAndAuthId(data: T): { ens: string; authId: string } { - const { ens, authId } = this.decryptState(data.state); - return { ens, authId }; + getEnsAndAuthId(data: T): { ens: string; chainId: ChainId; authId: string } { + const { ens, chainId, authId } = this.decryptState(data.state); + return { ens, chainId, authId }; } async generateCredential( data: T, ): Promise { const credentialSubject = await this.extractCredentialSubject(data); - const { ens , authId} = this.getEnsAndAuthId(data); + const { ens , chainId, authId} = this.getEnsAndAuthId(data); const did = await this.didResolver.getEnsDid(ens) const ethereumEip712Signature2021 = new EthereumEip712Signature2021({ @@ -113,7 +116,7 @@ export abstract class AbstractResolver< ethereumEip712Signature2021 )) as VerifiableEthereumEip712Signature2021; - await this.successfulCredentialGeneration(verifiableCredential, ens); + await this.successfulCredentialGeneration(verifiableCredential, ens, chainId); return { dataKey: this.getDataKey(), diff --git a/apps/vc-api/src/core/applications/credentials/facade/credential.facade.ts b/apps/vc-api/src/core/applications/credentials/facade/credential.facade.ts index 91bca21..95c6ba0 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/credential.facade.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/credential.facade.ts @@ -8,6 +8,7 @@ import {EmailResolver} from "./email-resolver/email.resolver"; import {EMAIL_RESOLVER, IEmailResolver} from "./email-resolver/iemail.resolver"; import {EmailCallback} from "./email-resolver/email.callback"; import { SocialResolverNotFoundException } from '../../../domain/exceptions/SocialResolverNotFound.exception'; +import { ChainId } from '../../../domain/entities/environment'; @Injectable() export class CredentialCreatorFacade implements ICredentialCreatorFacade { @@ -30,16 +31,16 @@ export class CredentialCreatorFacade implements ICredentialCreatorFacade { throw SocialResolverNotFoundException.forCredentialName(credentialName); } - async getSocialAuthUrl(credentialName: string, ens: string, authId: string): Promise { - return this.getSocialResolver(credentialName).getAuthUrl({ens, authId}); + async getSocialAuthUrl(credentialName: string, ens: string, chainId: ChainId, authId: string): Promise { + return this.getSocialResolver(credentialName).getAuthUrl({ens, chainId, authId}); } async socialCallback(credentialCallbackRequest: CredentialCallbackRequest): Promise { return await this.getSocialResolver(credentialCallbackRequest.credentialName).generateCredential(credentialCallbackRequest.callbackData); } - async getEmailOTP(email: string, ens: string, authId:string): Promise { - const state = await this.emailResolver.generateEmailOtp({email, authId, ens}) + async getEmailOTP(email: string, ens: string, chainId: ChainId, authId:string): Promise { + const state = await this.emailResolver.generateEmailOtp({email, authId, ens, chainId}) return state.state } diff --git a/apps/vc-api/src/core/applications/credentials/facade/email-resolver/email.resolver.ts b/apps/vc-api/src/core/applications/credentials/facade/email-resolver/email.resolver.ts index a13ce4f..60749c7 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/email-resolver/email.resolver.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/email-resolver/email.resolver.ts @@ -70,7 +70,7 @@ EmailCredential async generateEmailOtp(emailOtpGenerateRequest: EmailOtpGenerateRequest): Promise { - const { email, ens, authId } = emailOtpGenerateRequest; + const { email, ens, chainId, authId } = emailOtpGenerateRequest; const { otp, expiresAt } = this.generateOtp(email); if (this.otpStore.has(authId)) { @@ -85,6 +85,7 @@ EmailCredential const encryptedState = this.encryptState({ ens, + chainId, authId, }); diff --git a/apps/vc-api/src/core/applications/credentials/facade/email-resolver/requests/email.otp.generate.request.ts b/apps/vc-api/src/core/applications/credentials/facade/email-resolver/requests/email.otp.generate.request.ts index f7827a5..f069536 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/email-resolver/requests/email.otp.generate.request.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/email-resolver/requests/email.otp.generate.request.ts @@ -1,5 +1,8 @@ +import { ChainId } from '../../../../../domain/entities/environment'; + export interface EmailOtpGenerateRequest { email: string; authId: string; ens: string; + chainId: ChainId } diff --git a/apps/vc-api/src/core/applications/credentials/facade/icredential.facade.ts b/apps/vc-api/src/core/applications/credentials/facade/icredential.facade.ts index e9fcb5f..e6d341a 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/icredential.facade.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/icredential.facade.ts @@ -2,14 +2,15 @@ import {AbstractSocialResolver} from "./social-credential-resolver/social-resolv import { CredentialCallbackRequest } from './credential.callback.request'; import { CredentialCallbackResponse } from './credential.callback.response'; import {EmailCallback} from "./email-resolver/email.callback"; +import { ChainId } from '../../../domain/entities/environment'; export const CREDENTIAL_CREATOR_FACADE = 'CREDENTIAL_CREATOR_FACADE'; export interface ICredentialCreatorFacade { getSocialResolver(credentialName: string): AbstractSocialResolver ; - getSocialAuthUrl(credentialName: string, ens: string, authId: string): Promise; + getSocialAuthUrl(credentialName: string, ens: string, chainId: ChainId, authId: string): Promise; socialCallback(credentialCallbackRequest: CredentialCallbackRequest): Promise; - getEmailOTP(email: string, ens: string, authId:string): Promise; + getEmailOTP(email: string, ens: string, chainId: ChainId, authId:string): Promise; resendOtp(state: string): Promise; callbackEmailOTP(callbackData: EmailCallback ): Promise; clearState(state: string): string; diff --git a/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/github.social.resolver.ts b/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/github.social.resolver.ts index 69cec7c..96a988e 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/github.social.resolver.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/github.social.resolver.ts @@ -6,6 +6,7 @@ import { GithubCallback } from './callback/github.callback'; import { GithubToken } from './token/github.token'; import { GithubAuth } from './auth/github.auth'; import { VerifiableEthereumEip712Signature2021 } from '../../../../../domain/entities/ethereumEip712Signature'; +import { ChainId } from '../../../../../domain/entities/environment'; export class GithubSocialResolver extends AbstractSocialResolver< GithubCallback, @@ -27,8 +28,8 @@ export class GithubSocialResolver extends AbstractSocialResolver< return ['VerifiableGithubAccount']; } - getAuthUrl({ens, authId}): string { - const stateObject = { ens, authId }; + getAuthUrl({ens, chainId, authId}: {ens: string; chainId: ChainId; authId: string}): string { + const stateObject = { ens, chainId, authId }; const encryptedState = this.cryptoEncryption.encrypt(JSON.stringify(stateObject)); const clientId = this.environmentGetter.getGithubClientId(); const scope = 'read:user'; diff --git a/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/requests/get-auth-url.request.ts b/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/requests/get-auth-url.request.ts index cb04e6c..d4ecb3e 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/requests/get-auth-url.request.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/social-credential-resolver/social-resolver/requests/get-auth-url.request.ts @@ -1,4 +1,7 @@ +import { ChainId } from '../../../../../../domain/entities/environment'; + export class GetAuthUrlRequest { ens: string; + chainId: ChainId; authId: string; } diff --git a/apps/vc-api/src/core/applications/ens-manager/iens-manager.service.ts b/apps/vc-api/src/core/applications/ens-manager/iens-manager.service.ts index c96b097..dd24cee 100644 --- a/apps/vc-api/src/core/applications/ens-manager/iens-manager.service.ts +++ b/apps/vc-api/src/core/applications/ens-manager/iens-manager.service.ts @@ -3,6 +3,7 @@ import { SignInResponse } from './responses/sign-in.response'; import { GetRecordsRequest } from './requests/get-records.request'; import { GetRecordsResponse } from './responses/get-records.response'; import { VerifiableEthereumEip712Signature2021 } from '../../domain/entities/ethereumEip712Signature'; +import { ChainId } from '../../domain/entities/environment'; export const ENS_MANAGER_SERVICE = 'ENS_MANAGER_SERVICE' @@ -10,6 +11,6 @@ export interface IEnsManagerService { signIn(params: SignInRequest): Promise generateNonce(): string; getRecords(params: GetRecordsRequest): Promise - checkIfMAppEnabled(ens: string): Promise - appendVcInMAppEnabledEns(ens: string, vc: VerifiableEthereumEip712Signature2021, field: string): Promise + checkIfMAppEnabled(ens: string, chainId: ChainId): Promise + appendVcInMAppEnabledEns(ens: string, chainId: ChainId, vc: VerifiableEthereumEip712Signature2021, field: string): Promise } diff --git a/apps/vc-api/src/core/applications/ens-manager/responses/sign-in.response.ts b/apps/vc-api/src/core/applications/ens-manager/responses/sign-in.response.ts index 4ce93ab..3b4b9ea 100644 --- a/apps/vc-api/src/core/applications/ens-manager/responses/sign-in.response.ts +++ b/apps/vc-api/src/core/applications/ens-manager/responses/sign-in.response.ts @@ -1,4 +1,7 @@ +import { ChainId } from '../../../domain/entities/environment'; + export class SignInResponse { address: string ens: string + chainId: ChainId } diff --git a/apps/vc-api/src/core/applications/environment/environment.getter.ts b/apps/vc-api/src/core/applications/environment/environment.getter.ts index acc27de..15f72e9 100644 --- a/apps/vc-api/src/core/applications/environment/environment.getter.ts +++ b/apps/vc-api/src/core/applications/environment/environment.getter.ts @@ -1,7 +1,7 @@ import {Injectable} from "@nestjs/common"; import {ConfigService} from "@nestjs/config"; import {IEnvironmentGetter} from "./ienvironment.getter"; -import { ChainId, Environment, EnvironmentType } from '../../domain/entities/environment'; +import { Environment, EnvironmentType } from '../../domain/entities/environment'; @Injectable() export class EnvironmentGetter implements IEnvironmentGetter { @@ -11,18 +11,22 @@ export class EnvironmentGetter implements IEnvironmentGetter { return this.configService.get('SIGNING_PRIVATE_KEY'); } - getEnv(): EnvironmentType { - return this.configService.get('ENVIRONMENT'); + getPkSepolia(): string { + return this.configService.get('SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN'); } - getChainId(): ChainId { - return parseInt(this.configService.get('CHAIN_ID')) as ChainId; + getEnv(): EnvironmentType { + return this.configService.get('ENVIRONMENT'); } getEnsDomain(): string { return this.configService.get('ENS_DOMAIN'); } + getEnsDomainSepolia(): string { + return this.configService.get('ENS_DOMAIN_SEPOLIA'); + } + getInfuraProjectId(): string { return this.configService.get('INFURA_PROJECT_ID'); } @@ -63,14 +67,6 @@ export class EnvironmentGetter implements IEnvironmentGetter { return this.configService.get('TELEGRAM_BOT_USERNAME'); } - getSiweDomain(): string { - return this.configService.get('SIWE_DOMAIN'); - } - - getSiweOrigin(): string { - return this.configService.get('SIWE_ORIGIN'); - } - getEncryptKey(): string { return this.configService.get('ENCRYPT_KEY'); } diff --git a/apps/vc-api/src/core/applications/environment/ienvironment.getter.ts b/apps/vc-api/src/core/applications/environment/ienvironment.getter.ts index 9e59be1..1e2cf8e 100644 --- a/apps/vc-api/src/core/applications/environment/ienvironment.getter.ts +++ b/apps/vc-api/src/core/applications/environment/ienvironment.getter.ts @@ -1,14 +1,13 @@ -import { ChainId, EnvironmentType } from '../../domain/entities/environment'; +import { EnvironmentType } from '../../domain/entities/environment'; export const ENVIRONMENT_GETTER = 'ENVIRONMENT_GETTER' export interface IEnvironmentGetter { getPk(): string; + getPkSepolia(): string; getEnv(): EnvironmentType; getEnsDomain(): string; - getSiweDomain(): string; - getSiweOrigin(): string; - getChainId(): ChainId + getEnsDomainSepolia(): string; getApiDomain(): string; getInfuraProjectId(): string; getGithubClientId(): string; diff --git a/apps/vc-api/src/core/domain/entities/environment.ts b/apps/vc-api/src/core/domain/entities/environment.ts index 735ac04..c28d283 100644 --- a/apps/vc-api/src/core/domain/entities/environment.ts +++ b/apps/vc-api/src/core/domain/entities/environment.ts @@ -13,11 +13,13 @@ export type ChainId = typeof SupportedChainIds[number]; export class Environment { SIGNING_PRIVATE_KEY!: string; + SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN!: string; + ENVIRONMENT!: EnvironmentType; ENS_DOMAIN!: string; - CHAIN_ID!: ChainId; + ENS_DOMAIN_SEPOLIA!: string; INFURA_PROJECT_ID!: string; @@ -41,10 +43,6 @@ export class Environment { JWT_SECRET!: string; - SIWE_DOMAIN!: string; - - SIWE_ORIGIN!: string; - ENCRYPT_KEY!: string; ENCRYPT_SALT!: string; diff --git a/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts b/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts index 998a2e0..d63b1d5 100644 --- a/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts +++ b/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts @@ -1,7 +1,7 @@ import { IEnsManagerService } from '../../core/applications/ens-manager/iens-manager.service'; import { SignInRequest } from '../../core/applications/ens-manager/requests/sign-in.request'; import { SignInResponse } from '../../core/applications/ens-manager/responses/sign-in.response'; -import { JustaName, SubnameRecordsResponse } from '@justaname.id/sdk'; +import { ChainId, JustaName, SubnameRecordsResponse } from '@justaname.id/sdk'; import { Inject } from '@nestjs/common'; import { ENVIRONMENT_GETTER, IEnvironmentGetter } from '../../core/applications/environment/ienvironment.getter'; import { GetRecordsRequest } from '../../core/applications/ens-manager/requests/get-records.request'; @@ -21,15 +21,7 @@ export class JustaNameInitializerService implements IEnsManagerService { @Inject(ENVIRONMENT_GETTER) private readonly environmentGetter: IEnvironmentGetter, @Inject(KEY_MANAGEMENT_FETCHER) private readonly keyManagementFetcher: IKeyManagementFetcher ) { - 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() - }) + this.justaname = JustaName.init({}) } async signIn(params: SignInRequest): Promise { @@ -42,6 +34,7 @@ export class JustaNameInitializerService implements IEnsManagerService { return { address: sign.data.address, ens: sign.ens, + chainId: sign.data.chainId as ChainId, }; } catch (error) { throw AuthenticationException.withError(error); @@ -55,32 +48,33 @@ export class JustaNameInitializerService implements IEnsManagerService { async getRecords(params: GetRecordsRequest): Promise { try { const providerUrl = (params.chainId === 1 ? 'https://mainnet.infura.io/v3/' :'https://sepolia.infura.io/v3/') + this.environmentGetter.getInfuraProjectId() - + const records: SubnameRecordsResponse = await this.justaname.subnames.getRecordsByFullName({ fullName: params.ens, providerUrl: providerUrl, chainId: params.chainId, }) - + return this.mapSubnameRecordsResponseToGetRecordsResponse(records) } catch (error) { throw JustaNameInitializerException.withError(error); } } - checkIfMAppEnabled(ens: string): Promise { + checkIfMAppEnabled(ens: string, chainId: ChainId): Promise { return this.justaname.mApps.checkIfMAppIsEnabled({ mApp: this.environmentGetter.getEnsDomain(), ens: ens, - chainId: this.environmentGetter.getChainId() + chainId: chainId }) } - async appendVcInMAppEnabledEns(ens: string, vc: VerifiableEthereumEip712Signature2021, field: string): Promise { + async appendVcInMAppEnabledEns(ens: string, chainId: ChainId, vc: VerifiableEthereumEip712Signature2021, field: string): Promise { try { const message = await this.justaname.mApps.requestAppendMAppFieldChallenge({ mApp: this.environmentGetter.getEnsDomain(), subname: ens, + chainId: chainId, address: this.keyManagementFetcher.fetchKey().publicKey, }) const signature = await this.keyManagementFetcher.signMessage(message.challenge) @@ -96,7 +90,7 @@ export class JustaNameInitializerService implements IEnsManagerService { xMessage: message.challenge, xSignature: signature }) - + return !!appended } catch (error) { throw JustaNameInitializerException.withError(error); diff --git a/apps/vc-api/test/src/api/auth/auth.controller.spec.ts b/apps/vc-api/test/src/api/auth/auth.controller.spec.ts index e169bf3..c3849c7 100644 --- a/apps/vc-api/test/src/api/auth/auth.controller.spec.ts +++ b/apps/vc-api/test/src/api/auth/auth.controller.spec.ts @@ -14,6 +14,7 @@ import { AuthenticationException } from '../../../../src/core/domain/exceptions/ const ENS = 'ENS'; const ADDRESS = 'ADDRESS'; +const CHAINID = 11155111; const MESSAGE = 'MESSAGE'; const SIGNATURE = 'SIGNATURE'; const SIGNATURE_2 = 'SIGNATURE_2'; @@ -50,7 +51,7 @@ describe('Auth controller integration tests', () => { ensManagerService.signIn.mockImplementation(async (param) => { if (param.message === MESSAGE && param.signature === SIGNATURE) { - return { ens: ENS, address: ADDRESS }; + return { ens: ENS, chainId: CHAINID, address: ADDRESS }; } throw new AuthenticationException(ERROR_MESSAGE); }); diff --git a/apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts b/apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts index 9b7f2ff..203a827 100644 --- a/apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts +++ b/apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts @@ -45,6 +45,7 @@ const getSignInRequest = (): SignInRequest => ({ const getSignInResponse = (): SignInResponse => ({ address: ADDRESS, ens: ENS, + chainId: CHAIN_ID, }); const getEthereumEip712Signature2021 = (): EthereumEip712Signature2021 => { @@ -67,9 +68,6 @@ describe('JustaName initializer service', () => { { provide: ENVIRONMENT_GETTER, useValue: createMock({ - getChainId: jest.fn().mockReturnValue(CHAIN_ID), - getSiweDomain: jest.fn().mockReturnValue(SIWE_DOMAIN), - getSiweOrigin: jest.fn().mockReturnValue(SIWE_ORIGIN), getEnsDomain: jest.fn().mockReturnValue(ENS_DOMAIN), getInfuraProjectId: jest.fn().mockReturnValue(INFURA_PROJECT_ID), }), From db78ed8464804e5b96b6a2ce6ced7f4a10d63a82 Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Mon, 14 Oct 2024 14:34:40 +0300 Subject: [PATCH 09/11] fix: fix k8s files according to new env config --- k8s-staging/staging-secrets.yaml | 20 ++++++++------------ k8s-staging/vc-api-deployment.yaml | 19 +++++++------------ k8s/secrets.yaml | 20 ++++++++------------ k8s/vc-api-deployment.yaml | 19 +++++++------------ 4 files changed, 30 insertions(+), 48 deletions(-) diff --git a/k8s-staging/staging-secrets.yaml b/k8s-staging/staging-secrets.yaml index 15cf58b..30b0644 100644 --- a/k8s-staging/staging-secrets.yaml +++ b/k8s-staging/staging-secrets.yaml @@ -23,6 +23,8 @@ spec: key: INFURA_PROJECT_ID - objectName: signing_private_key key: SIGNING_PRIVATE_KEY + - objectName: signing_private_key_sepolia_domain + key: SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN - objectName: environment key: ENVIRONMENT - objectName: api_domain @@ -45,14 +47,10 @@ spec: key: DISCORD_CLIENT_SECRET - objectName: ens_domain key: ENS_DOMAIN - - objectName: chain_id - key: CHAIN_ID + - objectName: ens_domain_sepolia + key: ENS_DOMAIN_SEPOLIA - objectName: jwt_secret key: JWT_SECRET - - objectName: siwe_domain - key: SIWE_DOMAIN - - objectName: siwe_origin - key: SIWE_ORIGIN - objectName: encrypt_key key: ENCRYPT_KEY - objectName: encrypt_salt @@ -73,6 +71,8 @@ spec: objectAlias: infura_project_id - path: SIGNING_PRIVATE_KEY objectAlias: signing_private_key + - path: SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN + objectAlias: signing_private_key_sepolia_domain - path: ENVIRONMENT objectAlias: environment - path: API_DOMAIN @@ -95,14 +95,10 @@ spec: objectAlias: discord_client_secret - path: ENS_DOMAIN objectAlias: ens_domain - - path: CHAIN_ID - objectAlias: chain_id + - path: ENS_DOMAIN_SEPOLIA + objectAlias: ens_domain_sepolia - path: JWT_SECRET objectAlias: jwt_secret - - path: SIWE_DOMAIN - objectAlias: siwe_domain - - path: SIWE_ORIGIN - objectAlias: siwe_origin - path: ENCRYPT_KEY objectAlias: encrypt_key - path: ENCRYPT_SALT diff --git a/k8s-staging/vc-api-deployment.yaml b/k8s-staging/vc-api-deployment.yaml index e5f39a7..f4b0e6f 100644 --- a/k8s-staging/vc-api-deployment.yaml +++ b/k8s-staging/vc-api-deployment.yaml @@ -43,6 +43,11 @@ spec: secretKeyRef: name: foosecret-verifications key: SIGNING_PRIVATE_KEY + - name: SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN + valueFrom: + secretKeyRef: + name: foosecret-verifications + key: SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN - name: ENVIRONMENT valueFrom: secretKeyRef: @@ -98,26 +103,16 @@ spec: secretKeyRef: name: foosecret-verifications key: ENS_DOMAIN - - name: CHAIN_ID + - name: ENS_DOMAIN_SEPOLIA valueFrom: secretKeyRef: name: foosecret-verifications - key: CHAIN_ID + key: ENS_DOMAIN_SEPOLIA - name: JWT_SECRET valueFrom: secretKeyRef: name: foosecret-verifications key: JWT_SECRET - - name: SIWE_DOMAIN - valueFrom: - secretKeyRef: - name: foosecret-verifications - key: SIWE_DOMAIN - - name: SIWE_ORIGIN - valueFrom: - secretKeyRef: - name: foosecret-verifications - key: SIWE_ORIGIN - name: ENCRYPT_KEY valueFrom: secretKeyRef: diff --git a/k8s/secrets.yaml b/k8s/secrets.yaml index 1028c08..2d3315c 100644 --- a/k8s/secrets.yaml +++ b/k8s/secrets.yaml @@ -23,6 +23,8 @@ spec: key: INFURA_PROJECT_ID - objectName: signing_private_key key: SIGNING_PRIVATE_KEY + - objectName: signing_private_key_sepolia_domain + key: SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN - objectName: environment key: ENVIRONMENT - objectName: api_domain @@ -45,14 +47,10 @@ spec: key: DISCORD_CLIENT_SECRET - objectName: ens_domain key: ENS_DOMAIN - - objectName: chain_id - key: CHAIN_ID + - objectName: ens_domain_sepolia + key: ENS_DOMAIN_SEPOLIA - objectName: jwt_secret key: JWT_SECRET - - objectName: siwe_domain - key: SIWE_DOMAIN - - objectName: siwe_origin - key: SIWE_ORIGIN - objectName: encrypt_key key: ENCRYPT_KEY - objectName: encrypt_salt @@ -71,6 +69,8 @@ spec: objectAlias: infura_project_id - path: SIGNING_PRIVATE_KEY objectAlias: signing_private_key + - path: SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN + objectAlias: signing_private_key_sepolia_domain - path: ENVIRONMENT objectAlias: environment - path: API_DOMAIN @@ -93,14 +93,10 @@ spec: objectAlias: discord_client_secret - path: ENS_DOMAIN objectAlias: ens_domain - - path: CHAIN_ID - objectAlias: chain_id + - path: ENS_DOMAIN_SEPOLIA + objectAlias: ens_domain_sepolia - path: JWT_SECRET objectAlias: jwt_secret - - path: SIWE_DOMAIN - objectAlias: siwe_domain - - path: SIWE_ORIGIN - objectAlias: siwe_origin - path: ENCRYPT_KEY objectAlias: encrypt_key - path: ENCRYPT_SALT diff --git a/k8s/vc-api-deployment.yaml b/k8s/vc-api-deployment.yaml index 590dc9f..b7c2d41 100644 --- a/k8s/vc-api-deployment.yaml +++ b/k8s/vc-api-deployment.yaml @@ -43,6 +43,11 @@ spec: secretKeyRef: name: foosecret-verifications key: SIGNING_PRIVATE_KEY + - name: SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN + valueFrom: + secretKeyRef: + name: foosecret-verifications + key: SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN - name: ENVIRONMENT valueFrom: secretKeyRef: @@ -98,26 +103,16 @@ spec: secretKeyRef: name: foosecret-verifications key: ENS_DOMAIN - - name: CHAIN_ID + - name: ENS_DOMAIN_SEPOLIA valueFrom: secretKeyRef: name: foosecret-verifications - key: CHAIN_ID + key: ENS_DOMAIN_SEPOLIA - name: JWT_SECRET valueFrom: secretKeyRef: name: foosecret-verifications key: JWT_SECRET - - name: SIWE_DOMAIN - valueFrom: - secretKeyRef: - name: foosecret-verifications - key: SIWE_DOMAIN - - name: SIWE_ORIGIN - valueFrom: - secretKeyRef: - name: foosecret-verifications - key: SIWE_ORIGIN - name: ENCRYPT_KEY valueFrom: secretKeyRef: From 00c883ace5d2baa21c8a0c9a6249990993af2e4d Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Mon, 14 Oct 2024 16:37:20 +0300 Subject: [PATCH 10/11] fix: additional fixes for multi chainId support --- apps/vc-api/package.json | 6 +-- .../creator/icredential.creator.ts | 3 +- .../credentials/facade/abstract.resolver.ts | 5 +- .../verifier/icredential.verifier.ts | 3 +- .../did/resolver/idid.resolver.ts | 3 +- .../key-management/ikey-management.fetcher.ts | 5 +- .../external/credentials/credential.agent.ts | 53 +++++++++++++------ .../justaname-initializer.service.ts | 37 +++++++------ .../key-management/key-management.fetcher.ts | 17 +++--- .../test/src/api/auth/auth.controller.spec.ts | 2 +- .../justaname-initializer.service.spec.ts | 5 +- package.json | 6 +-- yarn.lock | 19 ++++++- 13 files changed, 105 insertions(+), 59 deletions(-) diff --git a/apps/vc-api/package.json b/apps/vc-api/package.json index cded1cb..d806778 100644 --- a/apps/vc-api/package.json +++ b/apps/vc-api/package.json @@ -26,16 +26,16 @@ "@veramo/did-resolver": "^6.0.0", "@veramo/credential-w3c": "^6.0.0", "@veramo/credential-ld": "^6.0.0", - "@justaname.id/sdk": "^0.2.76", "@nestjs/jwt": "^10.2.0", "uuid": "^10.0.0", "rxjs": "^7.8.0", - "vitest": "^1.3.1", "@react-email/components": "^0.0.25", "react": "18.3.1", "resend": "^4.0.0", "react-dom": "18.3.1", "@nestjs/testing": "^10.0.2", - "@golevelup/ts-jest": "^0.5.6" + "@golevelup/ts-jest": "^0.5.6", + "supertest": "^7.0.0", + "@justaname.id/sdk": "0.2.98" } } diff --git a/apps/vc-api/src/core/applications/credentials/creator/icredential.creator.ts b/apps/vc-api/src/core/applications/credentials/creator/icredential.creator.ts index dde6006..c900862 100644 --- a/apps/vc-api/src/core/applications/credentials/creator/icredential.creator.ts +++ b/apps/vc-api/src/core/applications/credentials/creator/icredential.creator.ts @@ -3,9 +3,10 @@ import { EthereumEip712Signature2021, VerifiableEthereumEip712Signature2021 } from '../../../domain/entities/ethereumEip712Signature'; +import { ChainId } from '../../../domain/entities/environment'; export const CREDENTIAL_CREATOR = 'CREDENTIAL_CREATOR'; export interface ICredentialCreator { - createCredential(credential: EthereumEip712Signature2021): Promise + createCredential(credential: EthereumEip712Signature2021, chainId: ChainId): Promise } diff --git a/apps/vc-api/src/core/applications/credentials/facade/abstract.resolver.ts b/apps/vc-api/src/core/applications/credentials/facade/abstract.resolver.ts index 16e8415..4157c01 100644 --- a/apps/vc-api/src/core/applications/credentials/facade/abstract.resolver.ts +++ b/apps/vc-api/src/core/applications/credentials/facade/abstract.resolver.ts @@ -95,7 +95,7 @@ export abstract class AbstractResolver< const credentialSubject = await this.extractCredentialSubject(data); const { ens , chainId, authId} = this.getEnsAndAuthId(data); - const did = await this.didResolver.getEnsDid(ens) + const did = await this.didResolver.getEnsDid(ens, chainId) const ethereumEip712Signature2021 = new EthereumEip712Signature2021({ type: this.getType(), context: this.getContext(), @@ -113,7 +113,8 @@ export abstract class AbstractResolver< }); const verifiableCredential = (await this.credentialCreator.createCredential( - ethereumEip712Signature2021 + ethereumEip712Signature2021, + chainId )) as VerifiableEthereumEip712Signature2021; await this.successfulCredentialGeneration(verifiableCredential, ens, chainId); diff --git a/apps/vc-api/src/core/applications/credentials/verifier/icredential.verifier.ts b/apps/vc-api/src/core/applications/credentials/verifier/icredential.verifier.ts index decc7dd..b2037d8 100644 --- a/apps/vc-api/src/core/applications/credentials/verifier/icredential.verifier.ts +++ b/apps/vc-api/src/core/applications/credentials/verifier/icredential.verifier.ts @@ -1,7 +1,8 @@ import {VerifiableEthereumEip712Signature2021} from "../../../domain/entities/ethereumEip712Signature"; +import { ChainId } from '../../../domain/entities/environment'; export const CREDENTIAL_VERIFIER = 'CREDENTIAL_VERIFIER'; export interface ICredentialVerifier { - verifyCredential(verifiedEthereumEip712Signature2021: VerifiableEthereumEip712Signature2021): Promise; + verifyCredential(verifiedEthereumEip712Signature2021: VerifiableEthereumEip712Signature2021, chainId: ChainId): Promise; } diff --git a/apps/vc-api/src/core/applications/did/resolver/idid.resolver.ts b/apps/vc-api/src/core/applications/did/resolver/idid.resolver.ts index 74f975d..275e543 100644 --- a/apps/vc-api/src/core/applications/did/resolver/idid.resolver.ts +++ b/apps/vc-api/src/core/applications/did/resolver/idid.resolver.ts @@ -1,6 +1,7 @@ +import { ChainId } from '../../../domain/entities/environment'; export const DID_RESOLVER = 'DID_RESOLVER' export interface IDIDResolver { - getEnsDid(ens: string): Promise + getEnsDid(ens: string, chainId: ChainId): Promise } diff --git a/apps/vc-api/src/core/applications/key-management/ikey-management.fetcher.ts b/apps/vc-api/src/core/applications/key-management/ikey-management.fetcher.ts index 69c1e36..2975a86 100644 --- a/apps/vc-api/src/core/applications/key-management/ikey-management.fetcher.ts +++ b/apps/vc-api/src/core/applications/key-management/ikey-management.fetcher.ts @@ -1,8 +1,9 @@ import {SigningWallet} from "../../domain/entities/signingWallet"; +import { ChainId } from '../../domain/entities/environment'; export const KEY_MANAGEMENT_FETCHER = 'KEY_MANAGEMENT_FETCHER'; export interface IKeyManagementFetcher { - fetchKey(): SigningWallet; - signMessage(message: string): Promise + fetchKey(chainId: ChainId): SigningWallet; + signMessage(message: string, chainId: ChainId): Promise } diff --git a/apps/vc-api/src/external/credentials/credential.agent.ts b/apps/vc-api/src/external/credentials/credential.agent.ts index 0ece289..e144461 100644 --- a/apps/vc-api/src/external/credentials/credential.agent.ts +++ b/apps/vc-api/src/external/credentials/credential.agent.ts @@ -7,12 +7,15 @@ import {IKeyManagementFetcher, KEY_MANAGEMENT_FETCHER} from "../../core/applicat import {CREDENTIAL_AGENT_MAPPER, ICredentialAgentMapper} from "./mapper/icredential-agent.mapper"; import { Agent, CredentialAgentInitiator, Identifier } from './credential.agent.initiator'; import { IDIDResolver } from '../../core/applications/did/resolver/idid.resolver'; +import { ChainId } from '../../core/domain/entities/environment'; @Injectable() export class CredentialAgent implements ICredentialCreator, ICredentialVerifier,IDIDResolver, OnModuleInit { - private agent: Agent - private identifier: Identifier + private mainnetAgent: Agent + private mainnetIdentifier: Identifier + private sepoliaAgent: Agent + private sepoliaIdentifier: Identifier constructor( @Inject(ENVIRONMENT_GETTER) @@ -29,36 +32,56 @@ export class CredentialAgent implements ICredentialCreator, ICredentialVerifier, } async onModuleInit(){ + const mainnetChainId = 1; + const sepoliaChainId = 11155111; const { agent, identifier } = await this.agentInitiator.createAgentWithIdentifier( this.environmentGetter.getEnsDomain(), - this.keyManagementFetcher.fetchKey().publicKey, - this.keyManagementFetcher.fetchKey().privateKey, - this.environmentGetter.getChainId() + this.keyManagementFetcher.fetchKey(mainnetChainId).publicKey, + this.keyManagementFetcher.fetchKey(mainnetChainId).privateKey, + mainnetChainId ) - this.agent = agent - this.identifier = identifier + this.mainnetAgent = agent + this.mainnetIdentifier = identifier + + const {agent: sepoliaAgent, identifier: sepoliaIdentifier} = await this.agentInitiator.createAgentWithIdentifier( + this.environmentGetter.getEnsDomain(), + this.keyManagementFetcher.fetchKey(sepoliaChainId).publicKey, + this.keyManagementFetcher.fetchKey(sepoliaChainId).privateKey, + sepoliaChainId + ) + + this.sepoliaAgent = sepoliaAgent + this.sepoliaIdentifier = sepoliaIdentifier } - async createCredential(credential: EthereumEip712Signature2021): Promise { - const verifiedCredential = await this.agent.createVerifiableCredentialEIP712( - this.credentialAgentMapper.mapEthereumEip712Signature2021ToVeramoICreateVerifiableCredentialEIP712Args(this.identifier.did,credential) + async createCredential(credential: EthereumEip712Signature2021, chainId: ChainId): Promise { + const verifiedCredential = await this.getAgent(chainId).createVerifiableCredentialEIP712( + this.credentialAgentMapper.mapEthereumEip712Signature2021ToVeramoICreateVerifiableCredentialEIP712Args(this.getIdentifier(chainId).did, credential) ) return this.credentialAgentMapper.mapVeramoVerifiedCredentialToVerifiedEthereumEip721Signature2021(verifiedCredential) } - async getEnsDid(ens: string): Promise { - const didUrl = 'did:ens:' + (this.environmentGetter.getChainId() === 1 ? '' : 'sepolia:') + ens - const did = await this.agent.resolveDid({ + async getEnsDid(ens: string, chainId: ChainId): Promise { + const didUrl = 'did:ens:' + (chainId === 1 ? '' : 'sepolia:') + ens + const did = await this.getAgent(chainId).resolveDid({ didUrl }) return typeof did.didDocument.authentication[0] === 'string' ? did.didDocument.authentication[0] : did.didDocument.authentication[0].id } - verifyCredential(verifiedEthereumEip712Signature2021: VerifiableEthereumEip712Signature2021): Promise { - return this.agent.verifyVerifiableCredentialEIP712({ + verifyCredential(verifiedEthereumEip712Signature2021: VerifiableEthereumEip712Signature2021, chainId: ChainId): Promise { + return this.getAgent(chainId).verifyVerifiableCredentialEIP712({ credential: this.credentialAgentMapper.mapVerifiedEthereumEip721Signature2021ToVeramoVerifiedCredential(verifiedEthereumEip712Signature2021) }) } + + private getAgent(chainId: ChainId): Agent { + return chainId ? this.mainnetAgent : this.sepoliaAgent + } + + private getIdentifier(chainId: ChainId): Identifier { + return chainId ? this.mainnetIdentifier : this.sepoliaIdentifier + } } diff --git a/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts b/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts index d63b1d5..654fed7 100644 --- a/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts +++ b/apps/vc-api/src/external/justaname-initializer/justaname-initializer.service.ts @@ -1,7 +1,7 @@ import { IEnsManagerService } from '../../core/applications/ens-manager/iens-manager.service'; import { SignInRequest } from '../../core/applications/ens-manager/requests/sign-in.request'; import { SignInResponse } from '../../core/applications/ens-manager/responses/sign-in.response'; -import { ChainId, JustaName, SubnameRecordsResponse } from '@justaname.id/sdk'; +import { JustaName, SubnameResponse } from '@justaname.id/sdk'; import { Inject } from '@nestjs/common'; import { ENVIRONMENT_GETTER, IEnvironmentGetter } from '../../core/applications/environment/ienvironment.getter'; import { GetRecordsRequest } from '../../core/applications/ens-manager/requests/get-records.request'; @@ -13,6 +13,7 @@ import { } from '../../core/applications/key-management/ikey-management.fetcher'; import { JustaNameInitializerException } from '../../core/domain/exceptions/JustaNameInitializer.exception'; import { AuthenticationException } from '../../core/domain/exceptions/Authentication.exception'; +import { ChainId } from '../../core/domain/entities/environment'; export class JustaNameInitializerService implements IEnsManagerService { @@ -21,14 +22,17 @@ export class JustaNameInitializerService implements IEnsManagerService { @Inject(ENVIRONMENT_GETTER) private readonly environmentGetter: IEnvironmentGetter, @Inject(KEY_MANAGEMENT_FETCHER) private readonly keyManagementFetcher: IKeyManagementFetcher ) { - this.justaname = JustaName.init({}) + this.justaname = JustaName.init({ + dev: this.environmentGetter.getEnv() === 'staging' || this.environmentGetter.getEnv() === 'development' + }) } async signIn(params: SignInRequest): Promise { try { - const sign = await this.justaname.signIn.signIn( - params.message, - params.signature + const sign = await this.justaname.signIn.signIn({ + message: params.message, + signature: params.signature + } ); return { @@ -49,10 +53,9 @@ export class JustaNameInitializerService implements IEnsManagerService { try { const providerUrl = (params.chainId === 1 ? 'https://mainnet.infura.io/v3/' :'https://sepolia.infura.io/v3/') + this.environmentGetter.getInfuraProjectId() - const records: SubnameRecordsResponse = await this.justaname.subnames.getRecordsByFullName({ - fullName: params.ens, + const records: SubnameResponse = await this.justaname.subnames.getRecords({ + ens: params.ens, providerUrl: providerUrl, - chainId: params.chainId, }) return this.mapSubnameRecordsResponseToGetRecordsResponse(records) @@ -75,9 +78,9 @@ export class JustaNameInitializerService implements IEnsManagerService { mApp: this.environmentGetter.getEnsDomain(), subname: ens, chainId: chainId, - address: this.keyManagementFetcher.fetchKey().publicKey, + address: this.keyManagementFetcher.fetchKey(chainId).publicKey, }) - const signature = await this.keyManagementFetcher.signMessage(message.challenge) + const signature = await this.keyManagementFetcher.signMessage(message.challenge, chainId) const appended = await this.justaname.mApps.appendMAppField({ subname: ens, @@ -86,7 +89,7 @@ export class JustaNameInitializerService implements IEnsManagerService { value: JSON.stringify(vc), }] }, { - xAddress: this.keyManagementFetcher.fetchKey().publicKey, + xAddress: this.keyManagementFetcher.fetchKey(chainId).publicKey, xMessage: message.challenge, xSignature: signature }) @@ -97,13 +100,13 @@ export class JustaNameInitializerService implements IEnsManagerService { } } - private mapSubnameRecordsResponseToGetRecordsResponse(records: SubnameRecordsResponse): GetRecordsResponse { + private mapSubnameRecordsResponseToGetRecordsResponse(response: SubnameResponse): GetRecordsResponse { return { - texts: records.texts, - resolverAddress: records.resolverAddress, - contentHash: records.contentHash, - coins: records.coins, - isJAN: records.isJAN, + texts: response?.records?.texts, + resolverAddress: response?.records?.resolverAddress, + contentHash: response?.records?.contentHash, + coins: response?.records?.coins, + isJAN: response.isJAN, } } } diff --git a/apps/vc-api/src/external/key-management/key-management.fetcher.ts b/apps/vc-api/src/external/key-management/key-management.fetcher.ts index 7b5ba8f..e9dc3d6 100644 --- a/apps/vc-api/src/external/key-management/key-management.fetcher.ts +++ b/apps/vc-api/src/external/key-management/key-management.fetcher.ts @@ -4,28 +4,31 @@ import {Inject, Injectable} from "@nestjs/common"; import { ENVIRONMENT_GETTER, IEnvironmentGetter } from '../../core/applications/environment/ienvironment.getter'; import { IKeyManagementFetcher } from '../../core/applications/key-management/ikey-management.fetcher'; import { SigningWallet } from '../../core/domain/entities/signingWallet'; +import { ChainId } from '../../core/domain/entities/environment'; @Injectable() export class KeyManagementFetcher implements IKeyManagementFetcher { - private readonly wallet: Wallet + private readonly mainnetWallet: Wallet + private readonly sepoliaWallet: Wallet constructor( @Inject(ENVIRONMENT_GETTER) private readonly environmentGetter: IEnvironmentGetter, ) { - this.wallet = new Wallet(this.environmentGetter.getPk()) + this.mainnetWallet = new Wallet(this.environmentGetter.getPk()) + this.sepoliaWallet = new Wallet(this.environmentGetter.getPkSepolia()) } - fetchKey(): SigningWallet { + fetchKey(chainId: ChainId): SigningWallet { return { - publicKey: this.wallet.address, - privateKey: this.wallet.privateKey + publicKey: chainId === 1 ? this.mainnetWallet.address : this.sepoliaWallet.address, + privateKey: chainId === 1 ? this.mainnetWallet.privateKey : this.sepoliaWallet.privateKey } } - signMessage(message: string): Promise { - return this.wallet.signMessage(message); + signMessage(message: string, chainId: ChainId): Promise { + return chainId === 1 ? this.mainnetWallet.signMessage(message) : this.sepoliaWallet.signMessage(message) } } diff --git a/apps/vc-api/test/src/api/auth/auth.controller.spec.ts b/apps/vc-api/test/src/api/auth/auth.controller.spec.ts index c3849c7..64512c6 100644 --- a/apps/vc-api/test/src/api/auth/auth.controller.spec.ts +++ b/apps/vc-api/test/src/api/auth/auth.controller.spec.ts @@ -81,7 +81,7 @@ describe('Auth controller integration tests', () => { .send({ message: MESSAGE, signature: SIGNATURE }) .expect((res) => { expect(res.status).toBe(200); - expect(res.body).toEqual({ ens: ENS, address: ADDRESS }); + expect(res.body).toEqual({ ens: ENS, chainId: CHAINID, address: ADDRESS }); expect(res.headers['set-cookie']).toBeDefined(); expect(res.headers['set-cookie'][0]).toContain('justverifiedtoken=' + TOKEN); expect(res.headers['set-cookie'][0]).toContain('HttpOnly'); diff --git a/apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts b/apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts index 203a827..33c8752 100644 --- a/apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts +++ b/apps/vc-api/test/src/external/justaname-initializer/justaname-initializer.service.spec.ts @@ -23,8 +23,6 @@ const ENS = 'ENS'; const ERROR_MESSAGE = 'ERROR_MESSAGE'; const CHAIN_ID = 11155111; -const SIWE_DOMAIN = 'SIWE_DOMAIN'; -const SIWE_ORIGIN = 'SIWE_ORIGIN'; const ENS_DOMAIN = 'ENS_DOMAIN'; const INFURA_PROJECT_ID = 'INFURA_PROJECT_ID'; @@ -111,7 +109,7 @@ describe('JustaName initializer service', () => { jest .spyOn( justaNameInitializerService.justaname.subnames, - 'getRecordsByFullName' + 'getRecords' ) .mockRejectedValue(new Error(ERROR_MESSAGE)); @@ -138,6 +136,7 @@ describe('JustaName initializer service', () => { await expect( justaNameInitializerService.appendVcInMAppEnabledEns( ENS, + CHAIN_ID, {} as VerifiableEthereumEip712Signature2021, FIELD ) diff --git a/package.json b/package.json index 691246c..490e746 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,7 @@ "license": "MIT", "private": true, "dependencies": { - "@justaname.id/react": "^0.3.94", - "@justaname.id/react-signin": "^0.0.27", - "@justaname.id/react-ui": "^0.0.15", - "@justaname.id/sdk": "^0.2.91", - "@justaname.id/siwens": "^0.0.24", + "@justaname.id/sdk": "0.2.98", "@nestjs/common": "^10.0.2", "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.0.2", diff --git a/yarn.lock b/yarn.lock index 8b2da96..df7e648 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2635,7 +2635,7 @@ "@tanstack/react-query" "^5.28.4" ethers "^6.11.1" -"@justaname.id/sdk@0.2.91", "@justaname.id/sdk@^0.2.91": +"@justaname.id/sdk@0.2.91": version "0.2.91" resolved "https://registry.yarnpkg.com/@justaname.id/sdk/-/sdk-0.2.91.tgz#971b5da654e81b354db45565dc1c7e1adfcc7b3a" integrity sha512-jJ7DEq1ZsNSatupn6Yv50qdfhdhfe045xC8yMqpeaz01TJXDPopgH8n0tj1Ei0BLO/59qHHb4CJOETUNrIU8DA== @@ -2644,6 +2644,15 @@ axios "^1.6.0" qs "^6.12.0" +"@justaname.id/sdk@0.2.98": + version "0.2.98" + resolved "https://registry.yarnpkg.com/@justaname.id/sdk/-/sdk-0.2.98.tgz#2bb1d1495e9bf01deb8332b3fee4746d839ba13f" + integrity sha512-nOKiP+SH29ycFkspRVRcZXcHDbh97pbs3mvHG0hutnt+weY+VMc1or25WumFChl8oJypAg41NpbiIYmbh5dO8A== + dependencies: + "@justaname.id/siwens" "0.0.31" + axios "^1.6.0" + qs "^6.12.0" + "@justaname.id/siwens@0.0.24", "@justaname.id/siwens@^0.0.24": version "0.0.24" resolved "https://registry.yarnpkg.com/@justaname.id/siwens/-/siwens-0.0.24.tgz#af8172f6c7034394c82311ea213ec4075458faf2" @@ -2652,6 +2661,14 @@ ethers "^6.11.1" siwe "^2.3.2" +"@justaname.id/siwens@0.0.31": + version "0.0.31" + resolved "https://registry.yarnpkg.com/@justaname.id/siwens/-/siwens-0.0.31.tgz#8919e17d8bb09d1e39cf720e1c2e5a1a115c0dac" + integrity sha512-uGUlHVmkP8IYkdA/TqUwdqefTB/apOUcJSTG0GTLqV/ljIAy95zsJa2313U/j0PfpGGcZX5J8PwAZ8E8m7wl0Q== + dependencies: + ethers "^6.11.1" + siwe "^2.3.2" + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.5" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" From 51989a26b6646eb0ee418ad4d078dcbccc56e03b Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Mon, 14 Oct 2024 17:15:53 +0300 Subject: [PATCH 11/11] fix: additional fixes --- apps/vc-api/src/api/auth/auth.controller.ts | 2 -- .../src/external/credentials/credential.agent.initiator.ts | 1 - apps/vc-api/src/external/credentials/credential.agent.ts | 4 ++-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/vc-api/src/api/auth/auth.controller.ts b/apps/vc-api/src/api/auth/auth.controller.ts index 5f4deba..93bfb0c 100644 --- a/apps/vc-api/src/api/auth/auth.controller.ts +++ b/apps/vc-api/src/api/auth/auth.controller.ts @@ -34,12 +34,10 @@ export class AuthController { signature: body.signature }) - const token = this.jwtService.sign({ ens, address, chainId }, { expiresIn: moment().add(1, 'hour').unix() }); - res.cookie('justverifiedtoken', token, { httpOnly: true, secure: true, sameSite: 'none' }); return res.status(200).send({ ens, address, chainId }); diff --git a/apps/vc-api/src/external/credentials/credential.agent.initiator.ts b/apps/vc-api/src/external/credentials/credential.agent.initiator.ts index 92717da..3c4d4ad 100644 --- a/apps/vc-api/src/external/credentials/credential.agent.initiator.ts +++ b/apps/vc-api/src/external/credentials/credential.agent.initiator.ts @@ -42,7 +42,6 @@ export class CredentialAgentInitiator { agent: Agent; identifier: Identifier; }> { - const identifier = await this.agent.didManagerImport({ did: 'did:ens:' + (chainId === 1 ? '' : 'sepolia:') + ensDomain + '#' + publicKey, provider: 'did:ens', diff --git a/apps/vc-api/src/external/credentials/credential.agent.ts b/apps/vc-api/src/external/credentials/credential.agent.ts index e144461..3237e6a 100644 --- a/apps/vc-api/src/external/credentials/credential.agent.ts +++ b/apps/vc-api/src/external/credentials/credential.agent.ts @@ -78,10 +78,10 @@ export class CredentialAgent implements ICredentialCreator, ICredentialVerifier, } private getAgent(chainId: ChainId): Agent { - return chainId ? this.mainnetAgent : this.sepoliaAgent + return chainId === 1 ? this.mainnetAgent : this.sepoliaAgent } private getIdentifier(chainId: ChainId): Identifier { - return chainId ? this.mainnetIdentifier : this.sepoliaIdentifier + return chainId === 1 ? this.mainnetIdentifier : this.sepoliaIdentifier } }