diff --git a/.env.demo b/.env.demo index f9130b987..369af85d3 100644 --- a/.env.demo +++ b/.env.demo @@ -29,7 +29,7 @@ NATS_URL=nats://your-ip:4222 REDIS_HOST=your-ip REDIS_PORT=6379 -SENDGRID_API_KEY= +SENDGRID_API_KEY= WALLET_STORAGE_HOST=your-ip WALLET_STORAGE_PORT=5432 @@ -76,7 +76,7 @@ AWS_ORG_LOGO_BUCKET_NAME= # Required (As connecting to org requires Shortened url) AWS_S3_STOREOBJECT_ACCESS_KEY= AWS_S3_STOREOBJECT_SECRET_KEY= -AWS_S3_STOREOBJECT_REGION= +AWS_S3_STOREOBJECT_REGION= AWS_S3_STOREOBJECT_BUCKET= # Please refere AWS to determine your bucket url @@ -84,7 +84,7 @@ AWS_S3_STOREOBJECT_BUCKET= SHORTENED_URL_DOMAIN='https://AWS_S3_STOREOBJECT_REGION.amazonaws.com/AWS_S3_STOREOBJECT_BUCKET' DEEPLINK_DOMAIN='https://link.credebl.id?url=' -ENABLE_CORS_IP_LIST=http://localhost:3000, http://localhost:3001, http://localhost:5000,http://localhost:8085,https://verify.credebl.id,https://verifyed.credebl.id,https://verify-api.credebl.id,https://qa.credebl.id,https://dev.credebl.id,https://credebl.id +ENABLE_CORS_IP_LIST=http://localhost:3000,http://localhost:3001,http://localhost:5000,http://localhost:8085 USER_NKEY_SEED= API_GATEWAY_NKEY_SEED= diff --git a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md deleted file mode 100644 index bb511c8f2..000000000 --- a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md +++ /dev/null @@ -1,44 +0,0 @@ -## โœ… Preliminary Checks - -- [ ] I have searched [existing issues](https://github.com/credebl/platform/issues) and [pull requests](https://github.com/credebl/platform/pulls) to avoid duplicates. -- [ ] I'm willing to create a PR for this feature. (if applicable). - ---- - -## ๐Ÿงฉ Problem Statement - -_Is your feature request related to a problem? Please describe it clearly._ - -> Ex: I'm always frustrated when [...] - ---- - -## ๐Ÿ’ก Proposed Solution - -_A clear and concise description of what you want to happen._ - -> Ex: It would be great if [...] - ---- - -## ๐Ÿ”„ Alternatives Considered - -_Have you considered any alternative solutions or features?_ - -> Ex: I also thought about [...], but [...] - ---- - -## ๐Ÿ“Ž Additional Context - -_Add any other context, references, mockups, or screenshots here._ - ---- - -## โœ… Acceptance Criteria - -_List specific tasks or outcomes that define when this request is complete._ - -- A new endpoint `/v1/...` is added -- Docs updated -- Tests written and passing diff --git a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml new file mode 100644 index 000000000..4a95540b9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml @@ -0,0 +1,73 @@ +name: "\U0001F680 Feature Request - new" +title: "feat: " +description: Suggest an idea or enhancement for CREDEBL platform +labels: [enhancement, triage] +body: + - type: markdown + attributes: + value: | + ## Reporting a feature request/enhancement to CREDEBL + + Thank you for taking time to create a feature request for CREDEBL, your contribution will help + make the product better for everyone. + + Make sure your issue has a generous description that will help others understand and fix it at earliest. + + - type: checkboxes + id: preliminary-checks + attributes: + label: "โœ… Preliminary Checks" + description: "Please confirm the following before submitting." + options: + - label: "I've read the [contibution guide](https://docs.credebl.id/docs/contribute/how-to-contribute) and agree to it" + required: true + - label: "I have searched [existing issues](https://github.com/credebl/platform/issues) and [pull requests](https://github.com/credebl/platform/pulls) to avoid duplicates." + required: true + - label: "I'm willing to create a PR for this feature. (if applicable)." + + - type: textarea + id: problem-statement + attributes: + label: "๐Ÿงฉ Problem Statement" + description: "Is your feature request related to a problem? Please describe it clearly." + placeholder: "Ex: I'm always frustrated when [...]" + validations: + required: true + + - type: textarea + id: proposed-solution + attributes: + label: "๐Ÿ’ก Proposed Solution" + description: "A clear and concise description of what you want to happen." + placeholder: "Ex: It would be great if [...]" + validations: + required: true + + - type: textarea + id: alternatives-considered + attributes: + label: "๐Ÿ”„ Alternatives Considered" + description: "Have you considered any alternative solutions or features?" + placeholder: "Ex: I also thought about [...], but [...]" + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: "๐Ÿ“Ž Additional Context" + description: "Add any other context, references, mockups, or screenshots here." + validations: + required: false + + - type: textarea + id: acceptance-criteria + attributes: + label: "โœ… Acceptance Criteria" + description: "List specific tasks or outcomes that define when this request is complete." + placeholder: | + - A new endpoint `/v1/...` is added + - Docs updated + - Tests written and passing + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 726cdd0da..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,58 +0,0 @@ -## ๐Ÿงพ Preliminary Checks - -- [ ] I have searched [existing issues](https://github.com/credebl/platform/issues) and [pull requests](https://github.com/credebl/platform/pulls) for duplicates. -- [ ] I'm willing to create a PR fixing this issue. (if applicable). - ---- - -## ๐Ÿž Bug Description - -_A clear and concise description of what the bug is._ - -When I try to [...], I get this unexpected behavior [...] - ---- - -## ๐Ÿงช Steps to Reproduce - -_Provide clear steps to reproduce the bug._ - -1. Go to '...' -2. Click on '...' -3. Scroll down to '...' -4. See error - ---- - -## โœ… Expected Behavior - -_What did you expect to happen?_ - ---- - -## โŒ Actual Behavior - -_What actually happened instead?_ - ---- - -## ๐Ÿ“Œ Affected Version/Commit - -_Version number, branch name, or commit hash where the bug occurs._ - ---- - -## ๐Ÿ’ป Environment - -_Where did the issue occur?_ - -- [ ] Local development -- [ ] Production -- [ ] CI/CD -- [ ] Other - ---- - -## ๐Ÿงพ Relevant Logs, Screenshots, or Stack Traces - -_Paste any error messages or screenshots that can help diagnose the issue._ diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..a9f326c77 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,90 @@ +name: "๐Ÿ› Bug Report" +description: Report a bug or unexpected behavior in the project +labels: [bug] +title: "fix: " +body: + - type: markdown + attributes: + value: | + ## Reporting a bug on CREDEBL + + Thank you for taking time to report the bug on CREDEBL, your contribution will help + make the product better for everyone. + + Make sure your issue has a generous description that will help others understand and fix it at earliest. + - type: checkboxes + id: agreement + attributes: + label: Preliminary Checks + options: + - label: I have read the contributions guide [contibution guide](https://docs.credebl.id/docs/contribute/how-to-contribute) and agree to it + required: true + - label: I have searched [existing issues](https://github.com/credebl/platform/issues) and [pull requests](https://github.com/credebl/platform/pulls) to avoid duplicates. + required: true + - label: "I'm willing to create a PR for this feature. (if applicable)." + - type: textarea + id: steps-to-reproduce + attributes: + label: "๐Ÿงช Steps to Reproduce" + description: "Provide clear steps to reproduce the bug." + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error + validations: + required: true + + - type: textarea + id: expected-behavior + attributes: + label: "โœ… Expected Behavior" + description: "What did you expect to happen?" + placeholder: | + Ex: After clicking 'Submit', I expected a confirmation modal to appear. + validations: + required: true + + - type: textarea + id: current-behavior + attributes: + label: "โŒ Current Behavior" + description: "What is currently happening instead?" + placeholder: | + Ex: The page crashed with a 500 error when clicking 'Submit'. + validations: + required: true + + - type: input + id: affected-version + attributes: + label: "๐Ÿ“Œ Affected Version/Commit" + description: "Version number, branch name, or commit hash where the bug occurs." + placeholder: "e.g., v1.2.3, main, 4f3e2d1" + validations: + required: false + + - type: checkboxes + id: environment + attributes: + label: "๐Ÿ’ป Environment" + description: "Where did the issue occur?" + options: + - label: "Local development" + - label: "Production" + - label: "CI/CD" + - label: "Other" + + - type: textarea + id: logs-and-screenshots + attributes: + label: "๐Ÿงพ Relevant Logs, Screenshots, or Stack Traces" + description: "Paste any error messages or screenshots that can help diagnose the issue." + placeholder: | + Please include: + - Error logs + - Screenshots + - Console output + - Stack traces + validations: + required: false diff --git a/Dockerfiles/Dockerfile.cloud-wallet b/Dockerfiles/Dockerfile.cloud-wallet index a9d6d95af..db7e31f1e 100644 --- a/Dockerfiles/Dockerfile.cloud-wallet +++ b/Dockerfiles/Dockerfile.cloud-wallet @@ -1,10 +1,9 @@ # Stage 1: Build the application -FROM node:18-slim as build +FROM node:18-alpine AS build # Install OpenSSL +RUN apk add --no-cache openssl RUN npm install -g pnpm -RUN apt-get update -y -RUN apt-get --no-install-recommends install -y openssl # Set the working directory WORKDIR /app @@ -25,10 +24,10 @@ RUN cd libs/prisma-service && npx prisma generate RUN pnpm run build cloud-wallet # Stage 2: Create the final image -FROM node:18-slim +FROM node:18-alpine + +RUN apk add --no-cache openssl -RUN apt-get update -y -RUN apt-get --no-install-recommends install -y openssl # Set the working directory WORKDIR /app # RUN npm install -g pnpm diff --git a/Dockerfiles/Dockerfile.seed b/Dockerfiles/Dockerfile.seed index f4c51da87..b7c6611b7 100644 --- a/Dockerfiles/Dockerfile.seed +++ b/Dockerfiles/Dockerfile.seed @@ -1,16 +1,18 @@ -FROM node:18 as build +FROM node:18-alpine # Install pnpm RUN npm install -g pnpm -# Install PostgreSQL client (use apt for Debian-based images) -RUN apt-get update && apt-get install -y postgresql-client +RUN apk add --no-cache postgresql-client openssl # Set working directory WORKDIR /app COPY . . +RUN chmod +x /app/libs/prisma-service/prisma/scripts/geo_location_data_import.sh +RUN chmod +x /app/libs/prisma-service/prisma/scripts/update_client_credential_data.sh +ENV PUPPETEER_SKIP_DOWNLOAD=true RUN pnpm i --ignore-scripts # Run Prisma commands diff --git a/Dockerfiles/Dockerfile.user b/Dockerfiles/Dockerfile.user index 41bc20ddc..8969f6e77 100644 --- a/Dockerfiles/Dockerfile.user +++ b/Dockerfiles/Dockerfile.user @@ -1,32 +1,20 @@ # Stage 1: Build the application -FROM node:18-slim as build - - +FROM node:18-alpine AS build +# Install OpenSSL +RUN apk add --no-cache openssl RUN npm install -g pnpm -# We don't need the standalone Chromium -ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true -ENV PUPPETEER_SKIP_DOWNLOAD true - -# Install Google Chrome Stable and fonts -# Note: this installs the necessary libs to make the browser work with Puppeteer. -RUN apt-get update && apt-get install openssl gnupg wget -y && \ - wget --quiet --output-document=- https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor > /etc/apt/trusted.gpg.d/google-archive.gpg && \ - sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' && \ - apt-get update && \ - apt-get install google-chrome-stable -y --no-install-recommends && \ - rm -rf /var/lib/apt/lists/* - -# RUN apk update && apk list --all-versions chromium # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ +ENV PUPPETEER_SKIP_DOWNLOAD=true + # Install dependencies -RUN pnpm install +RUN pnpm i --ignore-scripts # Copy the rest of the application code COPY . . @@ -37,24 +25,11 @@ RUN cd libs/prisma-service && npx prisma generate RUN pnpm run build user # Stage 2: Create the final image -FROM node:18-slim - -# We don't need the standalone Chromium -ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true -ENV PUPPETEER_SKIP_DOWNLOAD true - -# Install Google Chrome Stable and fonts -# Note: this installs the necessary libs to make the browser work with Puppeteer. -RUN apt-get update && apt-get install openssl gnupg wget -y && \ - wget --quiet --output-document=- https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor > /etc/apt/trusted.gpg.d/google-archive.gpg && \ - sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' && \ - apt-get update && \ - apt-get install google-chrome-stable -y --no-install-recommends && \ - rm -rf /var/lib/apt/lists/* +FROM node:18-alpine +RUN apk add --no-cache openssl # Set the working directory WORKDIR /app -RUN npm install -g pnpm # Copy the compiled code from the build stage COPY --from=build /app/dist/apps/user/ ./dist/apps/user/ diff --git a/Dockerfiles/Dockerfile.utility b/Dockerfiles/Dockerfile.utility index ad96df022..a39aef3b8 100644 --- a/Dockerfiles/Dockerfile.utility +++ b/Dockerfiles/Dockerfile.utility @@ -1,10 +1,9 @@ # Stage 1: Build the application -FROM node:18-slim as build +FROM node:18-alpine AS build +# Install OpenSSL +RUN apk add --no-cache openssl RUN npm install -g pnpm - -RUN apt-get update -y -RUN apt-get --no-install-recommends install -y openssl # Set the working directory WORKDIR /app @@ -25,10 +24,9 @@ RUN cd libs/prisma-service && npx prisma generate RUN pnpm run build utility # Stage 2: Create the final image -FROM node:18-slim +FROM node:18-alpine -RUN apt-get update -y -RUN apt-get --no-install-recommends install -y openssl +RUN apk add --no-cache openssl # Set the working directory WORKDIR /app # RUN npm install -g pnpm diff --git a/agent.env b/agent.env index 1398f6239..41bf0c008 100644 --- a/agent.env +++ b/agent.env @@ -20,7 +20,7 @@ RPC_URL=https://polygon-rpc.com # RPC_URL=https://rpc-amoy.polygon.technology # Add url and token from your file server -SERVER_URL=https://schema.credebl.id +SERVER_URL= FILE_SERVER_TOKEN= BCOVRIN_TEST_GENESIS='{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node1","blskey":"4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba","blskey_pop":"RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1","client_ip":"138.197.138.255","client_port":9702,"node_ip":"138.197.138.255","node_port":9701,"services":["VALIDATOR"]},"dest":"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv"},"metadata":{"from":"Th7MpTaRZVRYnPiabds81Y"},"type":"0"},"txnMetadata":{"seqNo":1,"txnId":"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62"},"ver":"1"} diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index 64ea5a711..d368781cd 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -26,6 +26,7 @@ import { import { user } from '@prisma/client'; import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface'; import { AgentSpinUpStatus } from '@credebl/enum/enum'; +import { SignDataDto } from '../../api-gateway/src/agent-service/dto/agent-service.dto'; @Controller() export class AgentServiceController { @@ -37,7 +38,7 @@ export class AgentServiceController { * @returns Get agent status */ @MessagePattern({ cmd: 'agent-spinup' }) - async walletProvision(payload: { agentSpinupDto: IAgentSpinupDto, user: IUserRequestInterface }): Promise<{ + async walletProvision(payload: { agentSpinupDto: IAgentSpinupDto; user: IUserRequestInterface }): Promise<{ agentSpinupStatus: AgentSpinUpStatus; }> { return this.agentServiceService.walletProvision(payload.agentSpinupDto, payload.user); @@ -46,8 +47,8 @@ export class AgentServiceController { //DONE @MessagePattern({ cmd: 'create-tenant' }) async createTenant(payload: { - createTenantDto: ITenantDto, - user: IUserRequestInterface, + createTenantDto: ITenantDto; + user: IUserRequestInterface; }): Promise { return this.agentServiceService.createTenant(payload.createTenantDto, payload.user); } @@ -56,12 +57,12 @@ export class AgentServiceController { * @returns did */ @MessagePattern({ cmd: 'create-did' }) - async createDid(payload: { createDidDto: IDidCreate, orgId: string, user: IUserRequestInterface }): Promise { + async createDid(payload: { createDidDto: IDidCreate; orgId: string; user: IUserRequestInterface }): Promise { return this.agentServiceService.createDid(payload.createDidDto, payload.orgId, payload.user); } @MessagePattern({ cmd: 'create-wallet' }) - async createWallet(payload: { createWalletDto: IWallet, user: IUserRequestInterface }): Promise { + async createWallet(payload: { createWalletDto: IWallet; user: IUserRequestInterface }): Promise { return this.agentServiceService.createWallet(payload.createWalletDto); } @@ -73,7 +74,7 @@ export class AgentServiceController { //DONE @MessagePattern({ cmd: 'agent-create-w3c-schema' }) - async createW3CSchema(payload: { url, orgId, schemaRequestPayload }): Promise { + async createW3CSchema(payload: { url; orgId; schemaRequestPayload }): Promise { return this.agentServiceService.createW3CSchema(payload.url, payload.orgId, payload.schemaRequestPayload); } @@ -99,8 +100,8 @@ export class AgentServiceController { @MessagePattern({ cmd: 'agent-create-connection-legacy-invitation' }) async createLegacyConnectionInvitation(payload: { connectionPayload: IConnectionDetails; - url: string, - orgId: string, + url: string; + orgId: string; }): Promise { return this.agentServiceService.createLegacyConnectionInvitation( payload.connectionPayload, @@ -112,58 +113,58 @@ export class AgentServiceController { @MessagePattern({ cmd: 'agent-send-credential-create-offer' }) async sendCredentialCreateOffer(payload: { issueData: IIssuanceCreateOffer; - url: string, - orgId: string, + url: string; + orgId: string; }): Promise { return this.agentServiceService.sendCredentialCreateOffer(payload.issueData, payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'agent-get-all-issued-credentials' }) - async getIssueCredentials(payload: { url: string, apiKey: string }): Promise { + async getIssueCredentials(payload: { url: string; apiKey: string }): Promise { return this.agentServiceService.getIssueCredentials(payload.url, payload.apiKey); } //DONE @MessagePattern({ cmd: 'agent-get-issued-credentials-by-credentialDefinitionId' }) - async getIssueCredentialsbyCredentialRecordId(payload: { url: string, orgId: string }): Promise { + async getIssueCredentialsbyCredentialRecordId(payload: { url: string; orgId: string }): Promise { return this.agentServiceService.getIssueCredentialsbyCredentialRecordId(payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'agent-get-proof-presentations' }) - async getProofPresentations(payload: { url: string, apiKey: string }): Promise { + async getProofPresentations(payload: { url: string; apiKey: string }): Promise { return this.agentServiceService.getProofPresentations(payload.url, payload.apiKey); } //DONE @MessagePattern({ cmd: 'agent-get-proof-presentation-by-id' }) - async getProofPresentationById(payload: { url: string, orgId: string }): Promise { + async getProofPresentationById(payload: { url: string; orgId: string }): Promise { return this.agentServiceService.getProofPresentationById(payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'agent-send-proof-request' }) async sendProofRequest(payload: { - proofRequestPayload: ISendProofRequestPayload, - url: string, - orgId: string, + proofRequestPayload: ISendProofRequestPayload; + url: string; + orgId: string; }): Promise { return this.agentServiceService.sendProofRequest(payload.proofRequestPayload, payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'agent-verify-presentation' }) - async verifyPresentation(payload: { url: string, orgId: string }): Promise { + async verifyPresentation(payload: { url: string; orgId: string }): Promise { return this.agentServiceService.verifyPresentation(payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'agent-get-all-connections' }) - async getConnections(payload: { url: string, orgId: string }): Promise { + async getConnections(payload: { url: string; orgId: string }): Promise { return this.agentServiceService.getConnections(payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-get-connection-details-by-connectionId' }) - async getConnectionsByconnectionId(payload: { url: string, orgId: string }): Promise { + async getConnectionsByconnectionId(payload: { url: string; orgId: string }): Promise { return this.agentServiceService.getConnectionsByconnectionId(payload.url, payload.orgId); } @@ -173,10 +174,30 @@ export class AgentServiceController { * @returns Get agent health */ @MessagePattern({ cmd: 'agent-health' }) - async getAgentHealth(payload: { user: user, orgId: string }): Promise { + async getAgentHealth(payload: { user: user; orgId: string }): Promise { return this.agentServiceService.getAgentHealthDetails(payload.orgId); } + /** + * Sign data from agent + * @param payload + * @returns Signed data by agent + */ + @MessagePattern({ cmd: 'sign-data-from-agent' }) + async signData(payload: { data: SignDataDto; orgId: string }): Promise { + return this.agentServiceService.signDataFromAgent(payload.data, payload.orgId); + } + + /** + * Get agent health + * @param payload + * @returns Get agent health + */ + @MessagePattern({ cmd: 'verify-signature-from-agent' }) + async verifysignature(payload: { data: unknown; orgId: string }): Promise { + return this.agentServiceService.verifysignature(payload.data, payload.orgId); + } + @MessagePattern({ cmd: 'get-ledger-config' }) async getLedgerConfig(payload: { user: IUserRequestInterface }): Promise { return this.agentServiceService.getLedgerConfigDetails(payload.user); @@ -185,24 +206,24 @@ export class AgentServiceController { //DONE @MessagePattern({ cmd: 'agent-send-out-of-band-proof-request' }) async sendOutOfBandProofRequest(payload: { - proofRequestPayload: ISendProofRequestPayload, - url: string, - orgId: string, + proofRequestPayload: ISendProofRequestPayload; + url: string; + orgId: string; }): Promise { return this.agentServiceService.sendOutOfBandProofRequest(payload.proofRequestPayload, payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'get-agent-verified-proof-details' }) - async getVerifiedProofDetails(payload: { url: string, orgId: string }): Promise { + async getVerifiedProofDetails(payload: { url: string; orgId: string }): Promise { return this.agentServiceService.getVerifiedProofDetails(payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-schema-endorsement-request' }) async schemaEndorsementRequest(payload: { - url: string, - orgId: string, - requestSchemaPayload: object, + url: string; + orgId: string; + requestSchemaPayload: object; }): Promise { return this.agentServiceService.schemaEndorsementRequest(payload.url, payload.orgId, payload.requestSchemaPayload); } @@ -217,22 +238,22 @@ export class AgentServiceController { //DONE @MessagePattern({ cmd: 'agent-sign-transaction' }) - async signTransaction(payload: { url: string, orgId: string, signEndorsementPayload: object }): Promise { + async signTransaction(payload: { url: string; orgId: string; signEndorsementPayload: object }): Promise { return this.agentServiceService.signTransaction(payload.url, payload.orgId, payload.signEndorsementPayload); } //DONE @MessagePattern({ cmd: 'agent-submit-transaction' }) - async submitTransaction(payload: { url: string; orgId: string, submitEndorsementPayload: object }): Promise { + async submitTransaction(payload: { url: string; orgId: string; submitEndorsementPayload: object }): Promise { return this.agentServiceService.sumbitTransaction(payload.url, payload.orgId, payload.submitEndorsementPayload); } //DONE @MessagePattern({ cmd: 'agent-out-of-band-credential-offer' }) async outOfBandCredentialOffer(payload: { - outOfBandIssuancePayload: IOutOfBandCredentialOffer, - url: string, - orgId: string, + outOfBandIssuancePayload: IOutOfBandCredentialOffer; + url: string; + orgId: string; }): Promise { return this.agentServiceService.outOfBandCredentialOffer( payload.outOfBandIssuancePayload, @@ -242,32 +263,32 @@ export class AgentServiceController { } @MessagePattern({ cmd: 'delete-wallet' }) - async deleteWallet(payload: { orgId, user }): Promise { + async deleteWallet(payload: { orgId; user }): Promise { return this.agentServiceService.deleteWallet(payload.orgId, payload.user); } @MessagePattern({ cmd: 'agent-receive-invitation-url' }) - async receiveInvitationUrl(payload: { url, orgId, receiveInvitationUrl }): Promise { + async receiveInvitationUrl(payload: { url; orgId; receiveInvitationUrl }): Promise { return this.agentServiceService.receiveInvitationUrl(payload.receiveInvitationUrl, payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-receive-invitation' }) - async receiveInvitation(payload: { url, orgId, receiveInvitation }): Promise { + async receiveInvitation(payload: { url; orgId; receiveInvitation }): Promise { return this.agentServiceService.receiveInvitation(payload.receiveInvitation, payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-send-question' }) - async sendQuestion(payload: { url, orgId, questionPayload }): Promise { + async sendQuestion(payload: { url; orgId; questionPayload }): Promise { return this.agentServiceService.sendQuestion(payload.questionPayload, payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-send-basic-message' }) - async sendBasicMessage(payload: { url, orgId, content }): Promise { + async sendBasicMessage(payload: { url; orgId; content }): Promise { return this.agentServiceService.sendBasicMessage(payload.content, payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-get-question-answer-record' }) - async getQuestionAnswersRecord(payload: { url: string, orgId: string }): Promise { + async getQuestionAnswersRecord(payload: { url: string; orgId: string }): Promise { return this.agentServiceService.getQuestionAnswersRecord(payload.url, payload.orgId); } @@ -278,9 +299,9 @@ export class AgentServiceController { @MessagePattern({ cmd: 'agent-create-connection-invitation' }) async createConnectionInvitation(payload: { - url: string, - orgId: string, - connectionPayload: ICreateConnectionInvitation, + url: string; + orgId: string; + connectionPayload: ICreateConnectionInvitation; }): Promise { return this.agentServiceService.createConnectionInvitation(payload.url, payload.orgId, payload.connectionPayload); } @@ -292,16 +313,14 @@ export class AgentServiceController { */ @MessagePattern({ cmd: 'agent-configure' }) async agentConfigure(payload: { - agentConfigureDto: IAgentConfigure, - user: IUserRequestInterface, + agentConfigureDto: IAgentConfigure; + user: IUserRequestInterface; }): Promise { return this.agentServiceService.agentConfigure(payload.agentConfigureDto, payload.user); } @MessagePattern({ cmd: 'get-agent-details-by-org-id' }) - async agentdetailsByOrgId(payload: { - orgId: string, - }): Promise { + async agentdetailsByOrgId(payload: { orgId: string }): Promise { return this.agentServiceService.getAgentDetails(payload.orgId); } -} \ No newline at end of file +} diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index acac5a637..4a61dba6f 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -69,8 +69,6 @@ import { WebSocketGateway } from '@nestjs/websockets'; import * as retry from 'async-retry'; import { Cache } from 'cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; -import { IProofPresentationDetails } from '@credebl/common/interfaces/verification.interface'; -import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; import { ledgerName } from '@credebl/common/cast.helper'; import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface'; import * as CryptoJS from 'crypto-js'; @@ -78,7 +76,8 @@ import { UserActivityRepository } from 'libs/user-activity/repositories'; import { PrismaService } from '@credebl/prisma-service'; import { from } from 'rxjs'; import { NATSClient } from '@credebl/common/NATSClient'; - +import { SignDataDto } from '../../api-gateway/src/agent-service/dto/agent-service.dto'; +import { IVerificationMethod } from 'apps/organization/interfaces/organization.interface'; @Injectable() @WebSocketGateway() export class AgentServiceService { @@ -92,16 +91,14 @@ export class AgentServiceService { @Inject('NATS_CLIENT') private readonly agentServiceProxy: ClientProxy, @Inject(CACHE_MANAGER) private cacheService: Cache, private readonly userActivityRepository: UserActivityRepository, - private readonly natsClient : NATSClient + private readonly natsClient: NATSClient ) {} async ReplaceAt(input, search, replace, start, end): Promise { return input.slice(0, start) + input.slice(start, end).replace(search, replace) + input.slice(end); } - async getAgentDetails( - orgId: string - ): Promise { + async getAgentDetails(orgId: string): Promise { try { const agentDetails = await this.agentServiceRepository.getAgentDetailsByOrgId(orgId); return agentDetails; @@ -819,23 +816,23 @@ export class AgentServiceService { let ledgerIdData = []; try { let ledger; - const { network } = payload; - if (network) { - ledger = await ledgerName(network); - } else { - ledger = Ledgers.Not_Applicable; - } + const { network } = payload; + if (network) { + ledger = await ledgerName(network); + } else { + ledger = Ledgers.Not_Applicable; + } + + const ledgerList = (await this._getALlLedgerDetails()) as unknown as LedgerListResponse; + const isLedgerExist = ledgerList.response.find((existingLedgers) => existingLedgers.name === ledger); + if (!isLedgerExist) { + throw new BadRequestException(ResponseMessages.agent.error.invalidLedger, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + ledgerIdData = await this.agentServiceRepository.getLedgerDetails(ledger); - const ledgerList = (await this._getALlLedgerDetails()) as unknown as LedgerListResponse; - const isLedgerExist = ledgerList.response.find((existingLedgers) => existingLedgers.name === ledger); - if (!isLedgerExist) { - throw new BadRequestException(ResponseMessages.agent.error.invalidLedger, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); - } - ledgerIdData = await this.agentServiceRepository.getLedgerDetails(ledger); - const agentSpinUpStatus = AgentSpinUpStatus.PROCESSED; // Create and stored agent details @@ -1560,6 +1557,123 @@ export class AgentServiceService { } } + /** + * Get agent health + * @param orgId + * @returns agent status + */ + async signDataFromAgent(data: SignDataDto, orgId: string): Promise { + try { + // Get organization agent details + const orgAgentDetails: org_agents = await this.agentServiceRepository.getOrgAgentDetails(orgId); + let agentApiKey; + if (orgAgentDetails) { + agentApiKey = await this.getOrgAgentApiKey(orgId); + } + + if (!orgAgentDetails) { + throw new NotFoundException(ResponseMessages.agent.error.agentNotExists, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + + if (!orgAgentDetails?.agentEndPoint) { + throw new NotFoundException(ResponseMessages.agent.error.agentUrl, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + const orgAgentType = await this.agentServiceRepository.getOrgAgentType(orgAgentDetails?.orgAgentTypeId); + + const url = this.getAgentUrl( + 'sign-data-from-agent', + orgAgentType.agent, + orgAgentDetails.agentEndPoint, + orgAgentDetails.tenantId + ); + + const { dataTypeToSign, credentialPayload, rawPayload, storeCredential } = data; + + if (dataTypeToSign === 'jsonLd' && credentialPayload) { + // Currently, get only primary did for issuance + const diddoc = await this.agentServiceRepository.getOrgDid(orgId, true); + const verificationMethod = diddoc[0].didDocument['verificationMethod'] as IVerificationMethod[]; + // For now, we are strictly restricting dids and verification method associated with the primary did + // We can optionally modify it to be taken from the payload itself + credentialPayload.verificationMethod = verificationMethod[0].id; + } + + const dataToSign = dataTypeToSign === 'jsonLd' ? credentialPayload : rawPayload; + + // Invoke an API request from the agent to assess its current status + const signedDataFromAgent = await this.commonService + .httpPost( + `${url}?dataTypeToSign=${dataTypeToSign}&storeCredential=${storeCredential}`, + { ...dataToSign }, + { + headers: { authorization: agentApiKey } + } + ) + .then(async (response) => response); + + return signedDataFromAgent; + } catch (error) { + this.logger.error(`Agent signature request details : ${JSON.stringify(error)}`); + throw new RpcException(error.response ?? error); + } + } + + /** + * Get agent health + * @param orgId + * @returns agent status + */ + async verifysignature(data: unknown, orgId: string): Promise { + try { + // Get organization agent details + const orgAgentDetails: org_agents = await this.agentServiceRepository.getOrgAgentDetails(orgId); + let agentApiKey; + if (orgAgentDetails) { + agentApiKey = await this.getOrgAgentApiKey(orgId); + } + + if (!orgAgentDetails) { + throw new NotFoundException(ResponseMessages.agent.error.agentNotExists, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + + if (!orgAgentDetails?.agentEndPoint) { + throw new NotFoundException(ResponseMessages.agent.error.agentUrl, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + const orgAgentType = await this.agentServiceRepository.getOrgAgentType(orgAgentDetails?.orgAgentTypeId); + + const url = this.getAgentUrl( + 'verify-signed-data-from-agent', + orgAgentType.agent, + orgAgentDetails.agentEndPoint, + orgAgentDetails.tenantId + ); + + // Invoke an API request from the agent to assess its current status + const signedDataFromAgent = await this.commonService + .httpPost(`${url}`, data, { + headers: { authorization: agentApiKey } + }) + .then(async (response) => response); + + return signedDataFromAgent; + } catch (error) { + this.logger.error(`Agent signature request details : ${JSON.stringify(error)}`); + throw new RpcException(error.response ?? error); + } + } + async getLedgerConfigDetails(user: IUserRequestInterface): Promise { try { const getLedgerConfigData = await this.agentServiceRepository.getLedgerConfigByOrgId(); @@ -1672,110 +1786,112 @@ export class AgentServiceService { async deleteWallet(orgId: string, user: user): Promise { try { - // Retrieve the API key and agent information - const [getApiKeyResult, orgAgentResult] = await Promise.allSettled([ - this.getOrgAgentApiKey(orgId), - this.agentServiceRepository.getAgentApiKey(orgId) - ]); - - if (orgAgentResult.status === PromiseResult.FULFILLED && !orgAgentResult.value) { - throw new NotFoundException(ResponseMessages.agent.error.walletDoesNotExists); + // Retrieve the API key and agent information + const [getApiKeyResult, orgAgentResult] = await Promise.allSettled([ + this.getOrgAgentApiKey(orgId), + this.agentServiceRepository.getAgentApiKey(orgId) + ]); + + if (orgAgentResult.status === PromiseResult.FULFILLED && !orgAgentResult.value) { + throw new NotFoundException(ResponseMessages.agent.error.walletDoesNotExists); } - if (getApiKeyResult.status === PromiseResult.REJECTED) { - throw new InternalServerErrorException(`Failed to get API key: ${getApiKeyResult.reason}`); - } + if (getApiKeyResult.status === PromiseResult.REJECTED) { + throw new InternalServerErrorException(`Failed to get API key: ${getApiKeyResult.reason}`); + } - if (orgAgentResult.status === PromiseResult.REJECTED) { - throw new InternalServerErrorException(`Failed to get agent information: ${orgAgentResult.reason}`); - } + if (orgAgentResult.status === PromiseResult.REJECTED) { + throw new InternalServerErrorException(`Failed to get agent information: ${orgAgentResult.reason}`); + } - const getApiKey = getApiKeyResult?.value; - const orgAgent = orgAgentResult?.value; + const getApiKey = getApiKeyResult?.value; + const orgAgent = orgAgentResult?.value; - const orgAgentTypeResult = await this.agentServiceRepository.getOrgAgentType(orgAgent.orgAgentTypeId); + const orgAgentTypeResult = await this.agentServiceRepository.getOrgAgentType(orgAgent.orgAgentTypeId); - if (!orgAgentTypeResult) { - throw new NotFoundException(ResponseMessages.agent.error.orgAgentNotFound); - } + if (!orgAgentTypeResult) { + throw new NotFoundException(ResponseMessages.agent.error.orgAgentNotFound); + } - // Determine the URL based on the agent type - const url = - orgAgentTypeResult.agent === OrgAgentType.SHARED - ? `${orgAgent.agentEndPoint}${CommonConstants.URL_SHAGENT_DELETE_SUB_WALLET}`.replace('#', orgAgent?.tenantId) - : `${orgAgent.agentEndPoint}${CommonConstants.URL_DELETE_WALLET}`; + // Determine the URL based on the agent type + const url = + orgAgentTypeResult.agent === OrgAgentType.SHARED + ? `${orgAgent.agentEndPoint}${CommonConstants.URL_SHAGENT_DELETE_SUB_WALLET}`.replace('#', orgAgent?.tenantId) + : `${orgAgent.agentEndPoint}${CommonConstants.URL_DELETE_WALLET}`; + + // Perform the deletion in a transaction + return await this.prisma.$transaction(async (prisma) => { + // Delete org agent and related records + const { orgDid, agentInvitation, deleteOrgAgent } = await this.agentServiceRepository.deleteOrgAgentByOrg( + orgId + ); - // Perform the deletion in a transaction - return await this.prisma.$transaction(async (prisma) => { - // Delete org agent and related records - const { orgDid, agentInvitation, deleteOrgAgent } = await this.agentServiceRepository.deleteOrgAgentByOrg(orgId); + // Make the HTTP DELETE request + const deleteWallet = await this.commonService.httpDelete(url, { + headers: { authorization: getApiKey } + }); - // Make the HTTP DELETE request - const deleteWallet = await this.commonService.httpDelete(url, { - headers: { authorization: getApiKey } - }); + if (deleteWallet.status !== HttpStatus.NO_CONTENT) { + throw new InternalServerErrorException(ResponseMessages.agent.error.walletNotDeleted); + } - if (deleteWallet.status !== HttpStatus.NO_CONTENT) { - throw new InternalServerErrorException(ResponseMessages.agent.error.walletNotDeleted); - } + const deletions = [ + { records: orgDid.count, tableName: 'org_dids' }, + { records: agentInvitation.count, tableName: 'agent_invitations' }, + { records: deleteOrgAgent ? 1 : 0, tableName: 'org_agents' } + ]; + + const did = orgAgent?.orgDid; - const deletions = [ - { records: orgDid.count, tableName: 'org_dids' }, - { records: agentInvitation.count, tableName: 'agent_invitations' }, - { records: deleteOrgAgent ? 1 : 0, tableName: 'org_agents' } - ]; - - const did = orgAgent?.orgDid; - - //archive schemas - await this._updateIsSchemaArchivedFlag(did); - - const logDeletionActivity = async (records, tableName): Promise => { - if (records) { - const txnMetadata = { - deletedRecordsCount: records, - deletedRecordInTable: tableName - }; - const recordType = RecordType.WALLET; - await this.userActivityRepository._orgDeletedActivity(orgId, user, txnMetadata, recordType); - } + //archive schemas + await this._updateIsSchemaArchivedFlag(did); + + const logDeletionActivity = async (records, tableName): Promise => { + if (records) { + const txnMetadata = { + deletedRecordsCount: records, + deletedRecordInTable: tableName }; + const recordType = RecordType.WALLET; + await this.userActivityRepository._orgDeletedActivity(orgId, user, txnMetadata, recordType); + } + }; - for (const { records, tableName } of deletions) { - await logDeletionActivity(records, tableName); - } + for (const { records, tableName } of deletions) { + await logDeletionActivity(records, tableName); + } - return deleteOrgAgent; - }); + return deleteOrgAgent; + }); } catch (error) { - this.logger.error(`Error in delete wallet in agent service: ${JSON.stringify(error.message)}`); - throw new RpcException(error.response ? error.response : error); + this.logger.error(`Error in delete wallet in agent service: ${JSON.stringify(error.message)}`); + throw new RpcException(error.response ? error.response : error); } -} + } -async _updateIsSchemaArchivedFlag(did: string): Promise<{ - count: number -}> { - const pattern = { cmd: 'archive-schemas' }; - - const payload = { - did - }; - const updatedSchemaInfo = await this.agentServiceProxy - .send(pattern, payload) - .toPromise() - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); - }); - return updatedSchemaInfo; -} + async _updateIsSchemaArchivedFlag(did: string): Promise<{ + count: number; + }> { + const pattern = { cmd: 'archive-schemas' }; + + const payload = { + did + }; + const updatedSchemaInfo = await this.agentServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + return updatedSchemaInfo; + } async receiveInvitationUrl(receiveInvitationUrl: IReceiveInvitationUrl, url: string, orgId: string): Promise { try { @@ -1928,8 +2044,7 @@ async _updateIsSchemaArchivedFlag(did: string): Promise<{ response: string; }> { try { - return from(this.natsClient - .send(this.agentServiceProxy, pattern, payload)) + return from(this.natsClient.send(this.agentServiceProxy, pattern, payload)) .pipe(map((response) => ({ response }))) .toPromise() .catch((error) => { @@ -1957,4 +2072,45 @@ async _updateIsSchemaArchivedFlag(did: string): Promise<{ throw error; } } -} \ No newline at end of file + + /** + * Description: Fetch agent url + * @param referenceId + * @returns agent URL + */ + getAgentUrl(agentMethodLabel: string, orgAgentType: string, agentEndPoint: string, tenantId: string): string { + try { + let url; + switch (agentMethodLabel) { + case 'sign-data-from-agent': { + url = + orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_AGENT_SIGN_DATA}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHARED_AGENT_SIGN_DATA}`.replace('#', tenantId) + : null; + break; + } + case 'verify-signed-data-from-agent': { + url = + orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_AGENT_VERIFY_SIGNED_DATA}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHARED_AGENT_VERIFY_SIGNED_DATA}`.replace('#', tenantId) + : null; + break; + } + default: { + break; + } + } + if (!url) { + throw new NotFoundException(ResponseMessages.issuance.error.agentUrlNotFound); + } + return url; + } catch (error) { + this.logger.error(`Error in get agent url: ${JSON.stringify(error)}`); + throw error; + } + } +} diff --git a/apps/agent-service/src/repositories/agent-service.repository.ts b/apps/agent-service/src/repositories/agent-service.repository.ts index 23537b343..4eb239932 100644 --- a/apps/agent-service/src/repositories/agent-service.repository.ts +++ b/apps/agent-service/src/repositories/agent-service.repository.ts @@ -1,442 +1,450 @@ import { PrismaService } from '@credebl/prisma-service'; import { ConflictException, Injectable, Logger } from '@nestjs/common'; // eslint-disable-next-line camelcase -import { Prisma, ledgerConfig, ledgers, org_agents, org_agents_type, org_dids, organisation, platform_config, user } from '@prisma/client'; -import { ICreateOrgAgent, ILedgers, IOrgAgent, IOrgAgentsResponse, IOrgLedgers, IStoreAgent, IStoreDidDetails, IStoreOrgAgentDetails, LedgerNameSpace, OrgDid } from '../interface/agent-service.interface'; +import { + Prisma, + ledgerConfig, + ledgers, + org_agents, + org_agents_type, + org_dids, + organisation, + platform_config, + user +} from '@prisma/client'; +import { + ICreateOrgAgent, + ILedgers, + IOrgAgent, + IOrgAgentsResponse, + IOrgLedgers, + IStoreAgent, + IStoreDidDetails, + IStoreOrgAgentDetails, + LedgerNameSpace, + OrgDid +} from '../interface/agent-service.interface'; import { AgentType, PrismaTables } from '@credebl/enum/enum'; @Injectable() export class AgentServiceRepository { - constructor( - private readonly prisma: PrismaService, - private readonly logger: Logger - ) { } - - /** - * Get platform config details - * @returns - */ - // eslint-disable-next-line camelcase - async getPlatformConfigDetails(): Promise { - try { - return this.prisma.platform_config.findFirst(); - } catch (error) { - this.logger.error(`[getPlatformConfigDetails] - error: ${JSON.stringify(error)}`); - throw error; - } + constructor( + private readonly prisma: PrismaService, + private readonly logger: Logger + ) {} + + /** + * Get platform config details + * @returns + */ + // eslint-disable-next-line camelcase + async getPlatformConfigDetails(): Promise { + try { + return this.prisma.platform_config.findFirst(); + } catch (error) { + this.logger.error(`[getPlatformConfigDetails] - error: ${JSON.stringify(error)}`); + throw error; } + } - async getLedgerConfigByOrgId(): Promise { - try { - const ledgerConfigData = await this.prisma.ledgerConfig.findMany(); - return ledgerConfigData; - } catch (error) { - this.logger.error(`[getGenesisUrl] - get genesis URL: ${JSON.stringify(error)}`); - throw error; - } + async getLedgerConfigByOrgId(): Promise { + try { + const ledgerConfigData = await this.prisma.ledgerConfig.findMany(); + return ledgerConfigData; + } catch (error) { + this.logger.error(`[getGenesisUrl] - get genesis URL: ${JSON.stringify(error)}`); + throw error; } - /** - * Get genesis url - * @param id - * @returns - */ - async getGenesisUrl(ledgerId: string[]): Promise { - try { - const genesisData = await this.prisma.ledgers.findMany({ - where: { - id: { - in: ledgerId - } - } - }); - - return genesisData; - } catch (error) { - this.logger.error(`[getGenesisUrl] - get genesis URL: ${JSON.stringify(error)}`); - throw error; + } + /** + * Get genesis url + * @param id + * @returns + */ + async getGenesisUrl(ledgerId: string[]): Promise { + try { + const genesisData = await this.prisma.ledgers.findMany({ + where: { + id: { + in: ledgerId + } } - } + }); - /** - * Get organization details - * @param id - * @returns - */ - async getOrgDetails(id: string): Promise { - try { - if (id) { - const oranizationDetails = await this.prisma.organisation.findUnique({ - where: { - id - } - }); - return oranizationDetails; - } - } catch (error) { - this.logger.error(`[getOrgDetails] - get organization details: ${JSON.stringify(error)}`); - throw error; - } + return genesisData; + } catch (error) { + this.logger.error(`[getGenesisUrl] - get genesis URL: ${JSON.stringify(error)}`); + throw error; } + } - // eslint-disable-next-line camelcase - async createOrgAgent(agentSpinUpStatus: number, userId: string): Promise { - try { - - return this.prisma.org_agents.create({ - data: { - agentSpinUpStatus, - createdBy: userId, - lastChangedBy: userId - }, - select: { - id: true - } - }); - } catch (error) { - this.logger.error(`[createOrgAgent] - create agent details: ${JSON.stringify(error)}`); - throw error; - } + /** + * Get organization details + * @param id + * @returns + */ + async getOrgDetails(id: string): Promise { + try { + if (id) { + const oranizationDetails = await this.prisma.organisation.findUnique({ + where: { + id + } + }); + return oranizationDetails; + } + } catch (error) { + this.logger.error(`[getOrgDetails] - get organization details: ${JSON.stringify(error)}`); + throw error; } + } - // eslint-disable-next-line camelcase - async removeOrgAgent(id: string): Promise { - try { - if (id) { - - await this.prisma.org_agents.delete({ - where: { - id - } - }); - } - } catch (error) { - this.logger.error(`[removeOrgAgent] - remove org agent details: ${JSON.stringify(error)}`); - throw error; + // eslint-disable-next-line camelcase + async createOrgAgent(agentSpinUpStatus: number, userId: string): Promise { + try { + return this.prisma.org_agents.create({ + data: { + agentSpinUpStatus, + createdBy: userId, + lastChangedBy: userId + }, + select: { + id: true } - + }); + } catch (error) { + this.logger.error(`[createOrgAgent] - create agent details: ${JSON.stringify(error)}`); + throw error; } + } - // eslint-disable-next-line camelcase - async getAgentDetailsByOrgId(orgId: string): Promise { - try { - - const agentDetails = - await this.prisma.org_agents.findFirst({ - where: { - orgId - } - }); - return agentDetails; - } catch (error) { - this.logger.error(`[getAgentDetailsByOrgId] - get agent details by orgId: ${JSON.stringify(error)}`); - throw error; - } + // eslint-disable-next-line camelcase + async removeOrgAgent(id: string): Promise { + try { + if (id) { + await this.prisma.org_agents.delete({ + where: { + id + } + }); + } + } catch (error) { + this.logger.error(`[removeOrgAgent] - remove org agent details: ${JSON.stringify(error)}`); + throw error; } + } - /** - * Store agent details - * @param storeAgentDetails - * @returns - */ - // eslint-disable-next-line camelcase - async storeOrgAgentDetails(storeOrgAgentDetails: IStoreOrgAgentDetails): Promise { - try { - const { id, userId, ledgerId, did, didDoc, ...commonFields } = storeOrgAgentDetails; - const firstLedgerId = Array.isArray(ledgerId) ? ledgerId[0] : null; - const data = { - ...commonFields, - ledgerId: firstLedgerId, - createdBy: userId, - lastChangedBy: userId, - didDocument: didDoc, - orgDid: did - }; - - // eslint-disable-next-line camelcase - const query: Promise = id ? - this.prisma.org_agents.update({ - where: { id }, - data - }) : - this.prisma.org_agents.create({ data }); - - return { id: (await query).id }; - } catch (error) { - this.logger.error(`[storeAgentDetails] - store agent details: ${JSON.stringify(error)}`); - throw error; + // eslint-disable-next-line camelcase + async getAgentDetailsByOrgId(orgId: string): Promise { + try { + const agentDetails = await this.prisma.org_agents.findFirst({ + where: { + orgId } + }); + return agentDetails; + } catch (error) { + this.logger.error(`[getAgentDetailsByOrgId] - get agent details by orgId: ${JSON.stringify(error)}`); + throw error; } - - /** - * Store DID details - * @param storeDidDetails - * @returns did details - */ - // eslint-disable-next-line camelcase - async storeDidDetails(storeDidDetails: IStoreDidDetails): Promise { - try { - const {orgId, did, didDocument, isPrimaryDid, userId, orgAgentId} = storeDidDetails; - - return this.prisma.org_dids.create({ - data: { - orgId, - did, - didDocument, - isPrimaryDid, - createdBy: userId, - lastChangedBy: userId, - orgAgentId - } - }); - } catch (error) { - this.logger.error(`[storeDidDetails] - Store DID details: ${JSON.stringify(error)}`); - throw error; - } + } + + /** + * Store agent details + * @param storeAgentDetails + * @returns + */ + // eslint-disable-next-line camelcase + async storeOrgAgentDetails(storeOrgAgentDetails: IStoreOrgAgentDetails): Promise { + try { + const { id, userId, ledgerId, did, didDoc, ...commonFields } = storeOrgAgentDetails; + const firstLedgerId = Array.isArray(ledgerId) ? ledgerId[0] : null; + const data = { + ...commonFields, + ledgerId: firstLedgerId, + createdBy: userId, + lastChangedBy: userId, + didDocument: didDoc, + orgDid: did + }; + + // eslint-disable-next-line camelcase + const query: Promise = id + ? this.prisma.org_agents.update({ + where: { id }, + data + }) + : this.prisma.org_agents.create({ data }); + + return { id: (await query).id }; + } catch (error) { + this.logger.error(`[storeAgentDetails] - store agent details: ${JSON.stringify(error)}`); + throw error; } + } + /** + * Store DID details + * @param storeDidDetails + * @returns did details + */ + // eslint-disable-next-line camelcase + async storeDidDetails(storeDidDetails: IStoreDidDetails): Promise { + try { + const { orgId, did, didDocument, isPrimaryDid, userId, orgAgentId } = storeDidDetails; - /** - * Set primary DID - * @param did - * @returns did details - */ - // eslint-disable-next-line camelcase - async setPrimaryDid(orgDid: string, orgId: string, didDocument: Prisma.JsonValue): Promise { - try { - return await this.prisma.org_agents.update({ - where: { - orgId - }, - data: { - orgDid, - didDocument - } - }); - - } catch (error) { - this.logger.error(`[setprimaryDid] - Update DID details: ${JSON.stringify(error)}`); - throw error; + return this.prisma.org_dids.create({ + data: { + orgId, + did, + didDocument, + isPrimaryDid, + createdBy: userId, + lastChangedBy: userId, + orgAgentId } + }); + } catch (error) { + this.logger.error(`[storeDidDetails] - Store DID details: ${JSON.stringify(error)}`); + throw error; } + } - // eslint-disable-next-line camelcase - async updateLedgerId(orgId: string, ledgerId: string): Promise { - try { - return await this.prisma.org_agents.update({ - where: { - orgId - }, - data: { - ledgerId - } - }); - - } catch (error) { - this.logger.error(`[updateLedgerId] - Update ledgerId: ${JSON.stringify(error)}`); - throw error; + /** + * Set primary DID + * @param did + * @returns did details + */ + // eslint-disable-next-line camelcase + async setPrimaryDid(orgDid: string, orgId: string, didDocument: Prisma.JsonValue): Promise { + try { + return await this.prisma.org_agents.update({ + where: { + orgId + }, + data: { + orgDid, + didDocument } + }); + } catch (error) { + this.logger.error(`[setprimaryDid] - Update DID details: ${JSON.stringify(error)}`); + throw error; } + } - /** - * Get agent details - * @param orgId - * @returns - */ - // eslint-disable-next-line camelcase - async getAgentDetails(orgId: string): Promise { - try { - - if (orgId) { - - return this.prisma.org_agents.findUnique({ - where: { - orgId - }, - select: { - agentSpinUpStatus: true - } - }); - } - - } catch (error) { - this.logger.error(`[getAgentDetails] - get agent details: ${JSON.stringify(error)}`); - throw error; + // eslint-disable-next-line camelcase + async updateLedgerId(orgId: string, ledgerId: string): Promise { + try { + return await this.prisma.org_agents.update({ + where: { + orgId + }, + data: { + ledgerId } + }); + } catch (error) { + this.logger.error(`[updateLedgerId] - Update ledgerId: ${JSON.stringify(error)}`); + throw error; } + } - // eslint-disable-next-line camelcase - async platformAdminAgent(platformOrg: string): Promise { - return this.prisma.organisation.findFirstOrThrow({ - where: { - name: platformOrg - }, - select: { - // eslint-disable-next-line camelcase - org_agents: { - select: { - agentSpinUpStatus: true, - agentEndPoint: true, - apiKey: true - } - } - } + /** + * Get agent details + * @param orgId + * @returns + */ + // eslint-disable-next-line camelcase + async getAgentDetails(orgId: string): Promise { + try { + if (orgId) { + return this.prisma.org_agents.findUnique({ + where: { + orgId + }, + select: { + agentSpinUpStatus: true + } }); + } + } catch (error) { + this.logger.error(`[getAgentDetails] - get agent details: ${JSON.stringify(error)}`); + throw error; } + } - async getAgentTypeDetails(): Promise { - try { - const { id } = await this.prisma.agents_type.findFirstOrThrow({ - where: { - agent: AgentType.AFJ - } - }); - return id; - } catch (error) { - this.logger.error(`[getAgentTypeDetails] - get org agent health details: ${JSON.stringify(error)}`); - throw error; + // eslint-disable-next-line camelcase + async platformAdminAgent(platformOrg: string): Promise { + return this.prisma.organisation.findFirstOrThrow({ + where: { + name: platformOrg + }, + select: { + // eslint-disable-next-line camelcase + org_agents: { + select: { + agentSpinUpStatus: true, + agentEndPoint: true, + apiKey: true + } } + } + }); + } + + async getAgentTypeDetails(): Promise { + try { + const { id } = await this.prisma.agents_type.findFirstOrThrow({ + where: { + agent: AgentType.AFJ + } + }); + return id; + } catch (error) { + this.logger.error(`[getAgentTypeDetails] - get org agent health details: ${JSON.stringify(error)}`); + throw error; } + } + + async getLedgerDetails(name: string[] | string): Promise { + try { + let whereClause; + + if (Array.isArray(name)) { + whereClause = { + name: { + in: name + } + }; + } else { + whereClause = { + name + }; + } - async getLedgerDetails(name: string[] | string): Promise { - try { - let whereClause; - - if (Array.isArray(name)) { - whereClause = { - name: { - in: name - } - }; - } else { - whereClause = { - name - }; - } - - const ledgersDetails = await this.prisma.ledgers.findMany({ - where: whereClause, - select: { - id: true - } - }); - return ledgersDetails; - } catch (error) { - this.logger.error(`[getLedgerDetails] - get ledger details: ${JSON.stringify(error)}`); - throw error; + const ledgersDetails = await this.prisma.ledgers.findMany({ + where: whereClause, + select: { + id: true } + }); + return ledgersDetails; + } catch (error) { + this.logger.error(`[getLedgerDetails] - get ledger details: ${JSON.stringify(error)}`); + throw error; } + } - async getOrgAgentTypeDetails(agentType: string): Promise { - try { - const { id } = await this.prisma.org_agents_type.findFirstOrThrow({ - where: { - agent: agentType - } - }); - return id; - } catch (error) { - this.logger.error(`[getOrgAgentTypeDetails] - get org agent type details: ${JSON.stringify(error)}`); - throw error; + async getOrgAgentTypeDetails(agentType: string): Promise { + try { + const { id } = await this.prisma.org_agents_type.findFirstOrThrow({ + where: { + agent: agentType } + }); + return id; + } catch (error) { + this.logger.error(`[getOrgAgentTypeDetails] - get org agent type details: ${JSON.stringify(error)}`); + throw error; } + } - async getPlatfomOrg(orgName: string): Promise { - try { - const { id } = await this.prisma.organisation.findFirstOrThrow({ - where: { - name: orgName - } - }); - return id; - } catch (error) { - this.logger.error(`[getPlatfomOrg] - get platform org details: ${JSON.stringify(error)}`); - throw error; + async getPlatfomOrg(orgName: string): Promise { + try { + const { id } = await this.prisma.organisation.findFirstOrThrow({ + where: { + name: orgName } + }); + return id; + } catch (error) { + this.logger.error(`[getPlatfomOrg] - get platform org details: ${JSON.stringify(error)}`); + throw error; } + } - async getAgentType(id: string): Promise { - try { - const { agent } = await this.prisma.agents_type.findUnique({ - where: { - id - } - }); - return agent; - } catch (error) { - this.logger.error(`[getAgentType] - get agent type details: ${JSON.stringify(error)}`); - throw error; + async getAgentType(id: string): Promise { + try { + const { agent } = await this.prisma.agents_type.findUnique({ + where: { + id } + }); + return agent; + } catch (error) { + this.logger.error(`[getAgentType] - get agent type details: ${JSON.stringify(error)}`); + throw error; } + } - async getAgentTypeId(agentType: string): Promise { - try { - const { id } = await this.prisma.agents_type.findFirstOrThrow({ - where: { - agent: agentType - } - }); - return id; - } catch (error) { - this.logger.error(`[getAgentType] - get agent type details: ${JSON.stringify(error)}`); - throw error; + async getAgentTypeId(agentType: string): Promise { + try { + const { id } = await this.prisma.agents_type.findFirstOrThrow({ + where: { + agent: agentType } + }); + return id; + } catch (error) { + this.logger.error(`[getAgentType] - get agent type details: ${JSON.stringify(error)}`); + throw error; } + } - /** + /** * Get agent details * @param orgId * @returns Agent health details */ - // eslint-disable-next-line camelcase - async getOrgAgentDetails(orgId: string): Promise { - try { - if (orgId) { - - const oranizationAgentDetails = await this.prisma.org_agents.findUnique({ - where: { - orgId - } - }); - return oranizationAgentDetails; - } - } catch (error) { - this.logger.error(`[getOrgAgentDetails] - get org agent health details: ${JSON.stringify(error)}`); - throw error; - } + // eslint-disable-next-line camelcase + async getOrgAgentDetails(orgId: string): Promise { + try { + if (orgId) { + const oranizationAgentDetails = await this.prisma.org_agents.findUnique({ + where: { + orgId + } + }); + return oranizationAgentDetails; + } + } catch (error) { + this.logger.error(`[getOrgAgentDetails] - get org agent health details: ${JSON.stringify(error)}`); + throw error; } + } - // eslint-disable-next-line camelcase - async getOrgAgentType(orgAgentId: string): Promise { - try { - const orgAgent = await this.prisma.org_agents_type.findUnique({ - where: { - id: orgAgentId - } - }); - - return orgAgent; - - } catch (error) { - this.logger.error(`[getOrgAgentType] - error: ${JSON.stringify(error)}`); - throw error; + // eslint-disable-next-line camelcase + async getOrgAgentType(orgAgentId: string): Promise { + try { + const orgAgent = await this.prisma.org_agents_type.findUnique({ + where: { + id: orgAgentId } - } - - async getPlatfomAdminUser(platformAdminUserEmail: string): Promise { - try { - const platformAdminUser = await this.prisma.user.findUnique({ - where: { - email: platformAdminUserEmail - } - }); - return platformAdminUser; - } catch (error) { - this.logger.error(`[getPlatfomAdminUser] - get platform admin user: ${JSON.stringify(error)}`); - throw error; + }); + + return orgAgent; + } catch (error) { + this.logger.error(`[getOrgAgentType] - error: ${JSON.stringify(error)}`); + throw error; + } + } + + async getPlatfomAdminUser(platformAdminUserEmail: string): Promise { + try { + const platformAdminUser = await this.prisma.user.findUnique({ + where: { + email: platformAdminUserEmail } + }); + return platformAdminUser; + } catch (error) { + this.logger.error(`[getPlatfomAdminUser] - get platform admin user: ${JSON.stringify(error)}`); + throw error; } + } - // eslint-disable-next-line camelcase + // eslint-disable-next-line camelcase async getAgentApiKey(orgId: string): Promise { try { if (orgId) { @@ -447,7 +455,6 @@ export class AgentServiceRepository { }); return agent; } - } catch (error) { this.logger.error(`[getAgentApiKey] - get api key: ${JSON.stringify(error)}`); throw error; @@ -464,20 +471,23 @@ export class AgentServiceRepository { }); return ledgerDetails; } - } catch (error) { this.logger.error(`[getLedgerByNameSpace] - get indy ledger: ${JSON.stringify(error)}`); throw error; } } - async getOrgDid(orgId: string): Promise { + async getOrgDid(orgId: string, isPrimaryDid?: boolean): Promise { try { + const whereClause: { orgId: string; isPrimaryDid?: boolean } = { orgId }; + if (isPrimaryDid) { + whereClause.isPrimaryDid = isPrimaryDid; + } + const orgDids = await this.prisma.org_dids.findMany({ - where: { - orgId - } + where: whereClause }); + return orgDids; } catch (error) { this.logger.error(`[getOrgDid] - get org DID: ${JSON.stringify(error)}`); @@ -502,58 +512,58 @@ export class AgentServiceRepository { } } - async deleteOrgAgentByOrg(orgId: string): Promise<{orgDid: Prisma.BatchPayload; + async deleteOrgAgentByOrg(orgId: string): Promise<{ + orgDid: Prisma.BatchPayload; agentInvitation: Prisma.BatchPayload; // eslint-disable-next-line camelcase deleteOrgAgent: org_agents; - }> { - const tablesToCheck = [ - `${PrismaTables.CONNECTIONS}`, - `${PrismaTables.CREDENTIALS}`, - `${PrismaTables.PRESENTATIONS}` - ]; + }> { + const tablesToCheck = [ + `${PrismaTables.CONNECTIONS}`, + `${PrismaTables.CREDENTIALS}`, + `${PrismaTables.PRESENTATIONS}` + ]; try { - return await this.prisma.$transaction(async (prisma) => { - const referenceCounts = await Promise.all( - tablesToCheck.map(table => prisma[table].count({ where: { orgId } })) - ); - - referenceCounts.forEach((count, index) => { - if (0 < count) { - throw new ConflictException(`Organization ID ${orgId} is referenced in the table ${tablesToCheck[index]}`); - } - }); - - // Concurrently delete related records - const [orgDid, agentInvitation] = await Promise.all([ - prisma.org_dids.deleteMany({ where: { orgId } }), - prisma.agent_invitations.deleteMany({ where: { orgId } }) - ]); - - // Delete the organization agent - const deleteOrgAgent = await prisma.org_agents.delete({ where: { orgId } }); - - return {orgDid, agentInvitation, deleteOrgAgent}; + return await this.prisma.$transaction(async (prisma) => { + const referenceCounts = await Promise.all( + tablesToCheck.map((table) => prisma[table].count({ where: { orgId } })) + ); + + referenceCounts.forEach((count, index) => { + if (0 < count) { + throw new ConflictException(`Organization ID ${orgId} is referenced in the table ${tablesToCheck[index]}`); + } }); + + // Concurrently delete related records + const [orgDid, agentInvitation] = await Promise.all([ + prisma.org_dids.deleteMany({ where: { orgId } }), + prisma.agent_invitations.deleteMany({ where: { orgId } }) + ]); + + // Delete the organization agent + const deleteOrgAgent = await prisma.org_agents.delete({ where: { orgId } }); + + return { orgDid, agentInvitation, deleteOrgAgent }; + }); } catch (error) { - this.logger.error(`[deleteOrgAgentByOrg] - Error deleting org agent record: ${error.message}`); - throw error; + this.logger.error(`[deleteOrgAgentByOrg] - Error deleting org agent record: ${error.message}`); + throw error; } -} + } - async getLedger(name: string): Promise { - try { - const ledgerData = await this.prisma.ledgers.findFirstOrThrow({ - where: { - name - } - }); - return ledgerData; - } catch (error) { - this.logger.error(`[getLedger] - get org ledger: ${JSON.stringify(error)}`); - throw error; + async getLedger(name: string): Promise { + try { + const ledgerData = await this.prisma.ledgers.findFirstOrThrow({ + where: { + name } - } - -} \ No newline at end of file + }); + return ledgerData; + } catch (error) { + this.logger.error(`[getLedger] - get org ledger: ${JSON.stringify(error)}`); + throw error; + } + } +} diff --git a/apps/api-gateway/src/agent-service/agent-service.controller.ts b/apps/api-gateway/src/agent-service/agent-service.controller.ts index d7b4fcd34..2bd780ecc 100644 --- a/apps/api-gateway/src/agent-service/agent-service.controller.ts +++ b/apps/api-gateway/src/agent-service/agent-service.controller.ts @@ -20,18 +20,18 @@ import { ApiTags, ApiResponse, ApiOperation, - ApiUnauthorizedResponse, ApiForbiddenResponse, - ApiBearerAuth + ApiBody, + ApiBearerAuth, + ApiUnauthorizedResponse } from '@nestjs/swagger'; import { AuthGuard } from '@nestjs/passport'; -import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { ResponseMessages } from '@credebl/common/response-messages'; import { AgentService } from './agent-service.service'; import IResponseType, { IResponse } from '@credebl/common/interfaces/response.interface'; -import { AgentSpinupDto } from './dto/agent-service.dto'; +import { AgentSpinupDto, SignDataDto, VerifySignatureDto } from './dto/agent-service.dto'; import { Response } from 'express'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; @@ -46,6 +46,8 @@ import { CreateWalletDto } from './dto/create-wallet.dto'; import { CreateNewDidDto } from './dto/create-new-did.dto'; import { AgentSpinupValidator, TrimStringParamPipe } from '@credebl/common/cast.helper'; import { AgentConfigureDto } from './dto/agent-configure.dto'; +import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; +import { IVerifySignature } from './interface/agent-service.interface'; const seedLength = 32; @@ -72,9 +74,16 @@ export class AgentController { description: 'Get the agent health details for the organization' }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER, OrgRoles.VERIFIER) - - async getAgentHealth(@Param('orgId') orgId: string, @User() reqUser: user, @Res() res: Response): Promise { + @Roles( + OrgRoles.OWNER, + OrgRoles.ADMIN, + OrgRoles.HOLDER, + OrgRoles.ISSUER, + OrgRoles.SUPER_ADMIN, + OrgRoles.MEMBER, + OrgRoles.VERIFIER + ) + async getAgentHealth(@Param('orgId') orgId: string, @User() reqUser: user, @Res() res: Response): Promise { const agentData = await this.agentService.getAgentHealth(reqUser, orgId); const finalResponse: IResponse = { @@ -86,6 +95,87 @@ export class AgentController { return res.status(HttpStatus.OK).json(finalResponse); } + /** + * Get Organization agent health + * @param orgId The ID of the organization + * @param reqUser The user making the request + * @param res The response object + * @returns Get agent details + */ + @ApiBody({ + description: + 'Enter the data you would like to sign. It can be of type w3c jsonld credential or any type that needs to be signed', + type: SignDataDto, + required: true + }) + @Post('/orgs/:orgId/agents/sign') + @ApiOperation({ + summary: 'Signs data from agent', + description: 'Signs data from agent' + }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles( + OrgRoles.OWNER, + OrgRoles.ADMIN, + OrgRoles.HOLDER, + OrgRoles.ISSUER, + OrgRoles.SUPER_ADMIN, + OrgRoles.MEMBER, + OrgRoles.VERIFIER + ) + async signData(@Param('orgId') orgId: string, @Body() data: SignDataDto, @Res() res: Response): Promise { + const agentData = await this.agentService.signData(data, orgId); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.agent.success.sign, + data: agentData + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Get Organization agent health + * @param orgId The ID of the organization + * @param reqUser The user making the request + * @param res The response object + * @returns Get agent details + */ + @ApiBody({ + description: + 'Enter the data you would like to verify the signature for. It can be of type w3c jsonld credential or any type that needs to be verified', + type: VerifySignatureDto + }) + @Post('/orgs/:orgId/agents/verify-signature') + @ApiOperation({ + summary: 'Validates signed data from agent, including credentials', + description: 'Credentials or any other data signed by the organisation is validated' + }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles( + OrgRoles.OWNER, + OrgRoles.ADMIN, + OrgRoles.HOLDER, + OrgRoles.ISSUER, + OrgRoles.SUPER_ADMIN, + OrgRoles.MEMBER, + OrgRoles.VERIFIER + ) + async verifysignature( + @Param('orgId') orgId: string, + @Body() data: IVerifySignature, + @Res() res: Response + ): Promise { + const agentData = await this.agentService.verifysignature(data, orgId); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.agent.success.verify, + data: agentData + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * Get the ledger config details * @param reqUser The user making the request @@ -335,7 +425,16 @@ export class AgentController { @Roles(OrgRoles.OWNER) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async deleteWallet( - @Param('orgId', TrimStringParamPipe, new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, + @Param( + 'orgId', + TrimStringParamPipe, + new ParseUUIDPipe({ + exceptionFactory: (): Error => { + throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); + } + }) + ) + orgId: string, @User() user: user, @Res() res: Response ): Promise { @@ -348,4 +447,4 @@ export class AgentController { return res.status(HttpStatus.OK).json(finalResponse); } -} \ No newline at end of file +} diff --git a/apps/api-gateway/src/agent-service/agent-service.service.ts b/apps/api-gateway/src/agent-service/agent-service.service.ts index 3601588f8..557027f7a 100644 --- a/apps/api-gateway/src/agent-service/agent-service.service.ts +++ b/apps/api-gateway/src/agent-service/agent-service.service.ts @@ -13,80 +13,92 @@ import { NATSClient } from '@credebl/common/NATSClient'; @Injectable() export class AgentService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly agentServiceProxy: ClientProxy, - private readonly natsClient : NATSClient - ) { - super('AgentService'); - } - - /** - * Spinup the agent by organization - * @param agentSpinupDto - * @param user - * @returns Get agent status - */ - async agentSpinup(agentSpinupDto: AgentSpinupDto, user: user): Promise { - const payload = { agentSpinupDto, user }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'agent-spinup', payload); - } - - async createTenant(createTenantDto: CreateTenantDto, user: user): Promise { - const payload = { createTenantDto, user }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'create-tenant', payload); - } - - async createDid(createDidDto: CreateDidDto, orgId:string, user: user): Promise { - const payload = { createDidDto, orgId, user }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'create-did', payload); - } - - async createWallet(createWalletDto: CreateWalletDto, user: user): Promise { - const payload = { createWalletDto, user }; - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'create-wallet', payload); - } - - async getAgentHealth(user: user, orgId:string): Promise { - const payload = { user, orgId }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'agent-health', payload); - - } - - async getLedgerConfig(user: user): Promise { - const payload = { user }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'get-ledger-config', payload); - } - - async createSecp256k1KeyPair(orgId:string): Promise { - const payload = {orgId}; - // NATS call - - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'polygon-create-keys', payload); - } - - async agentConfigure(agentConfigureDto: AgentConfigureDto, user: user): Promise { - const payload = { agentConfigureDto, user }; - // NATS call - - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'agent-configure', payload); - } - - async deleteWallet(orgId: string, user: user): Promise { - const payload = { orgId, user }; - // NATS call - - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'delete-wallet', payload); - } - -} \ No newline at end of file + constructor( + @Inject('NATS_CLIENT') private readonly agentServiceProxy: ClientProxy, + private readonly natsClient: NATSClient + ) { + super('AgentService'); + } + + /** + * Spinup the agent by organization + * @param agentSpinupDto + * @param user + * @returns Get agent status + */ + async agentSpinup(agentSpinupDto: AgentSpinupDto, user: user): Promise { + const payload = { agentSpinupDto, user }; + + // NATS call + return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'agent-spinup', payload); + } + + async createTenant(createTenantDto: CreateTenantDto, user: user): Promise { + const payload = { createTenantDto, user }; + + // NATS call + return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'create-tenant', payload); + } + + async createDid(createDidDto: CreateDidDto, orgId: string, user: user): Promise { + const payload = { createDidDto, orgId, user }; + + // NATS call + return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'create-did', payload); + } + + async createWallet(createWalletDto: CreateWalletDto, user: user): Promise { + const payload = { createWalletDto, user }; + // NATS call + return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'create-wallet', payload); + } + + async getAgentHealth(user: user, orgId: string): Promise { + const payload = { user, orgId }; + + // NATS call + return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'agent-health', payload); + } + + async signData(data: unknown, orgId: string): Promise { + const payload = { data, orgId }; + + // NATS call + return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'sign-data-from-agent', payload); + } + + async verifysignature(data: unknown, orgId: string): Promise { + const payload = { data, orgId }; + + // NATS call + return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'verify-signature-from-agent', payload); + } + + async getLedgerConfig(user: user): Promise { + const payload = { user }; + + // NATS call + return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'get-ledger-config', payload); + } + + async createSecp256k1KeyPair(orgId: string): Promise { + const payload = { orgId }; + // NATS call + + return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'polygon-create-keys', payload); + } + + async agentConfigure(agentConfigureDto: AgentConfigureDto, user: user): Promise { + const payload = { agentConfigureDto, user }; + // NATS call + + return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'agent-configure', payload); + } + + async deleteWallet(orgId: string, user: user): Promise { + const payload = { orgId, user }; + // NATS call + + return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'delete-wallet', payload); + } +} diff --git a/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts b/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts index d04cce6a9..140bc5fcd 100644 --- a/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts +++ b/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts @@ -1,8 +1,29 @@ import { trim } from '@credebl/common/cast.helper'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { IsBoolean, IsNotEmpty, IsOptional, IsString, Matches, MaxLength, MinLength } from 'class-validator'; +import { Transform, Type } from 'class-transformer'; +import { + IsArray, + IsBoolean, + IsEnum, + IsIn, + IsISO8601, + IsNotEmpty, + IsOptional, + IsString, + Matches, + MaxLength, + MinLength, + Validate, + ValidateIf, + ValidateNested, + ValidationArguments, + ValidatorConstraint, + ValidatorConstraintInterface +} from 'class-validator'; import { CreateDidDto } from './create-did.dto'; +import { KeyType } from '@credebl/enum/enum'; +import { RewriteValidationOptions } from '@credebl/common/custom-overrideable-validation-pipe'; +import { BadRequestException } from '@nestjs/common'; const regex = /^[a-zA-Z0-9 ]*$/; export class AgentSpinupDto extends CreateDidDto { @ApiProperty() @@ -37,6 +58,242 @@ export class AgentSpinupDto extends CreateDidDto { @IsOptional() @IsBoolean() tenant?: boolean; - + orgId: string; } + +// class W3cIssuerDto { +// @ApiProperty() +// @IsString() +// id: string; +// } + +class W3cCredentialSubjectDto { + @ApiPropertyOptional() + @IsOptional() + @IsString() + id?: string; + + [key: string]: unknown; +} + +export class W3cCredentialDto { + @ApiProperty({ type: [String], example: ['https://www.w3.org/2018/credentials/v1'] }) + @IsArray() + '@context': string[]; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + id?: string; + + @ApiProperty({ type: [String], example: ['VerifiableCredential'] }) + @IsArray() + type: string[]; + + // TODO: Add or W3cIssuerDto + @ApiProperty({ type: String, example: 'did:key:z6Mkpz1qHuoMamuHj8YXJNBDu2GrLz3LzinA5t4GiYtYKSv8' }) + @IsString() + issuer: string; + + @ApiProperty() + @IsISO8601() + issuanceDate: string; + + @ApiPropertyOptional() + @IsOptional() + @IsISO8601() + expirationDate?: string; + + @ApiProperty({ type: W3cCredentialSubjectDto }) + @ValidateNested() + @Type(() => W3cCredentialSubjectDto) + credentialSubject: W3cCredentialSubjectDto; + + [key: string]: unknown; +} + +export class W3cJsonLdSignCredentialDto { + @ApiProperty({ type: W3cCredentialDto }) + @ValidateNested() + @Type(() => W3cCredentialDto) + credential: W3cCredentialDto; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + verificationMethod?: string; + + @ApiProperty() + @IsString() + proofType: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + proofPurpose?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + created?: string; + + [key: string]: unknown; +} + +@ValidatorConstraint({ name: 'AtLeastOneKey', async: false }) +class AtLeastOneKeyConstraint implements ValidatorConstraintInterface { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + validate(_: any, args: ValidationArguments): boolean { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const obj = args.object as any; + return Boolean(obj.publicKeyBase58 || obj.did || obj.method); + } + + defaultMessage(): string { + throw new BadRequestException('At least one of publicKeyBase58, did, or method must be provided in SignRawDataDto'); + } +} + +export class SignRawDataDto { + @ApiProperty({ description: 'Data to be signed as a string (e.g., base64, JSON)' }) + @IsString() + data: string; + + @ApiProperty({ enum: KeyType, enumName: 'KeyType' }) + @IsEnum(KeyType, { message: 'keyType must be a valid KeyType value' }) + keyType: KeyType; + + @ApiPropertyOptional({ description: 'Base58-encoded public key' }) + @IsOptional() + @IsString() + publicKeyBase58?: string; + + @ApiPropertyOptional({ description: 'DID to derive signing key from' }) + @IsOptional() + @IsString() + did?: string; + + @ApiPropertyOptional({ description: 'Verification method ID' }) + @IsOptional() + @IsString() + method?: string; + + @Validate(AtLeastOneKeyConstraint) + private readonly _atLeastOneKeyCheck = true; // dummy property to trigger class-level validation +} + +@RewriteValidationOptions({ whitelist: false }) +export class SignDataDto { + @ApiProperty({ + description: "Type of data being signed. Use 'jsonLd' for W3C credentials or 'rawData' for any other JSON.", + enum: ['jsonLd', 'rawData'] + }) + @IsIn(['jsonLd', 'rawData']) + dataTypeToSign: 'jsonLd' | 'rawData'; + + @ApiProperty({ + description: 'Store credential boolean if we want to credential after signing it', + enum: [true, false] + }) + @ValidateIf((o) => 'jsonLd' === o.dataTypeToSign) + @IsBoolean() + storeCredential: boolean = false; + + @ApiProperty({ + type: W3cJsonLdSignCredentialDto + }) + @ValidateIf((o) => 'jsonLd' === o.dataTypeToSign) + @ValidateNested() + @Type(() => W3cJsonLdSignCredentialDto) + credentialPayload?: W3cJsonLdSignCredentialDto; + + @ApiPropertyOptional({ description: 'Any data object if dataTypeToSign is "rawData"', type: SignRawDataDto }) + @ValidateIf((o) => 'data' === o.dataTypeToSign) + @Type(() => SignRawDataDto) + rawPayload?: SignRawDataDto; +} + +export class VerifySignatureDto { + @ApiProperty({ + description: 'This is the signed w3c-jsonLd credential, which we want to verify', + example: { + id: 'http://example.com/credential/9290ae08-8959-4723-a26a-921aab97de6e', + name: 'Teamwork v2', + type: ['VerifiableCredential', 'OpenBadgeCredential'], + image: { + id: 'https://example.com/BadgeDesigns/a99a4121-e04e-477c-a890-15e9af789f73/1c61a4b2-dca4-4684-8f63-221333fd5c86-Teamwork%20v2.png', + type: 'Image' + }, + proof: { + jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..PN0gqrfiMhO_2AIT9i3Tv1Qt9Owlh5ZBPnplCrta49-6oyEpHg4aQwjGADe-B2RADb_cvdgVqTJe1G-ds9UYDg', + type: 'Ed25519Signature2018', + created: '2025-06-13T13:47:35Z', + proofPurpose: 'assertionMethod', + verificationMethod: + 'did:key:z6MkwSMgqptU9M5AiymSSXv3HLhEhXfE6RonJwMSG2dmURdE#z6MkwSMgqptU9M5AiymSSXv3HLhEhXfE6RonJwMSG2dmURdE' + }, + issuer: { + id: 'did:key:z6MkwSMgqptU9M5AiymSSXv3HLhEhXfE6RonJwMSG2dmURdE' + }, + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/security/data-integrity/v2', + 'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json' + ], + validFrom: '2025-06-13T13:47:35.201Z', + awardedDate: '2025-06-12T18:30:00.000Z', + description: + 'EricTech is a cutting-edge product innovation company specialising in Web 3.0 development with extensive expertise in modern technologies. Our unique approach to product invention involves a comprehensive process, starting from the discovery phase and progressing through planning, research, development, marketing, and launch, all tailored to assist businesses in achieving their goals.', + issuanceDate: '2025-06-13T13:47:35.201Z', + expirationDate: '2026-12-30T18:30:00.000Z', + credentialStatus: { + id: 'http://example.com/revocation/addfc5a0-258f-4977-a138-f0d9f33d6dc6', + type: '1EdTechRevocationList' + }, + credentialSubject: { + type: ['AchievementSubject'], + identifier: [ + { + type: 'IdentityObject', + hashed: 'FALSE', + identityHash: 'test@yopmail.com', + identityType: 'emailAddress' + } + ], + achievement: { + id: 'ds', + Tag: ['fghfgh', 'fhfgh'], + Type: ['Achievement'], + name: 'sddgh', + image: { + id: 'sdsd', + type: 'sdd' + }, + version: '2', + criteria: { + narrative: 'sdd' + }, + humanCode: 'sd', + description: 'sdd', + fieldOfStudy: 'sdsd', + specialization: 'fghfg', + achievementType: 'LearningProgram', + otherIdentifier: [ + { + type: 'fhgf', + identifier: 'fghfgh', + identifierType: 'fghfgh' + } + ] + } + } + } + }) + credential: unknown; + + @ApiPropertyOptional({ default: false }) + @IsOptional() + @IsBoolean() + verifyCredentialStatus?: boolean; +} diff --git a/apps/api-gateway/src/agent-service/interface/agent-service.interface.ts b/apps/api-gateway/src/agent-service/interface/agent-service.interface.ts index 25511321e..5cf2ca2ff 100644 --- a/apps/api-gateway/src/agent-service/interface/agent-service.interface.ts +++ b/apps/api-gateway/src/agent-service/interface/agent-service.interface.ts @@ -1,26 +1,31 @@ export interface AgentSpinUpSatus { - agentSpinupStatus: number; + agentSpinupStatus: number; } export interface AgentStatus { - label: string; - endpoints: string[]; - isInitialized: boolean; + label: string; + endpoints: string[]; + isInitialized: boolean; } interface IWalletConfig { - id: string; - key: string; - keyDerivationMethod: string; + id: string; + key: string; + keyDerivationMethod: string; } interface IConfig { - label: string; - walletConfig: IWalletConfig; + label: string; + walletConfig: IWalletConfig; } export interface IWalletRecord { - _tags: string; - metadata: string; - id: string; - createdAt: string; - config: IConfig; - updatedAt: string; -} \ No newline at end of file + _tags: string; + metadata: string; + id: string; + createdAt: string; + config: IConfig; + updatedAt: string; +} + +export interface IVerifySignature { + credential: unknown; + verifyCredentialStatus?: boolean; +} diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index 61eadf5a2..b94ff1777 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -513,7 +513,7 @@ export class IssuanceController { ) async issueBulkCredentials( @Body() clientDetails: ClientDetails, - @Param('requestId') requestId: string, + @Param(new ValidationPipe({ transform: true })) params: RequestIdQuery, @Param('orgId') orgId: string, @User() user: user, @Query(new ValidationPipe({ transform: true })) query: CredentialQuery, @@ -522,6 +522,7 @@ export class IssuanceController { @Body() fileDetails?: object, @UploadedFile() file?: Express.Multer.File ): Promise { + const { requestId } = params; const { credDefId } = query; clientDetails.userId = user.id; let reqPayload; diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 3d47d558b..47fa61094 100644 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -2,10 +2,10 @@ import * as dotenv from 'dotenv'; import * as express from 'express'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import { Logger, ValidationPipe, VERSION_NEUTRAL, VersioningType } from '@nestjs/common'; +import { Logger, VERSION_NEUTRAL, VersioningType } from '@nestjs/common'; import { AppModule } from './app.module'; -import { HttpAdapterHost, NestFactory } from '@nestjs/core'; +import { HttpAdapterHost, NestFactory, Reflector } from '@nestjs/core'; import { AllExceptionsFilter } from '@credebl/common/exception-handler'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; @@ -14,6 +14,7 @@ import helmet from 'helmet'; import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { NatsInterceptor } from '@credebl/common'; +import { UpdatableValidationPipe } from '@credebl/common/custom-overrideable-validation-pipe'; dotenv.config(); async function bootstrap(): Promise { @@ -90,7 +91,9 @@ async function bootstrap(): Promise { app.use(express.static('invoice-pdf')); app.use(express.static('uploadedFiles/bulk-verification-templates')); app.use(express.static('uploadedFiles/import')); - app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })); + // Use custom updatable global pipes + const reflector = app.get(Reflector); + app.useGlobalPipes(new UpdatableValidationPipe(reflector, { whitelist: true, transform: true })); app.use( helmet({ xssFilter: true diff --git a/apps/issuance/src/issuance.processor.ts b/apps/issuance/src/issuance.processor.ts index be45d2a36..23365f909 100644 --- a/apps/issuance/src/issuance.processor.ts +++ b/apps/issuance/src/issuance.processor.ts @@ -11,16 +11,12 @@ export class BulkIssuanceProcessor { @OnQueueActive() onActive(job: Job): void { - this.logger.log( - `Emitting job status${job.id} of type ${job.name} with data ${JSON.stringify(job.data)}...` - ); + this.logger.log(`Emitting job status${job.id} of type ${job.name} ...`); } @Process() - async issueCredential(job: Job):Promise { - this.logger.log( - `Processing job ${job.id} of type ${job.name} with data ${JSON.stringify(job.data)}...` - ); + async issueCredential(job: Job): Promise { + this.logger.log(`Processing job ${job.id} of type ${job.name} ...`); this.issuanceService.processIssuanceData(job.data); } diff --git a/apps/organization/interfaces/organization.interface.ts b/apps/organization/interfaces/organization.interface.ts index 2c58d00fe..0401e4468 100644 --- a/apps/organization/interfaces/organization.interface.ts +++ b/apps/organization/interfaces/organization.interface.ts @@ -257,7 +257,7 @@ interface IDidDocument { verificationMethod: IVerificationMethod[]; } -interface IVerificationMethod { +export interface IVerificationMethod { id: string; type: string; controller: string; diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index dfec7845b..017c6816f 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -1,45 +1,45 @@ /* eslint-disable prefer-destructuring */ /* eslint-disable camelcase */ -import { ConflictException, Injectable, Logger, NotFoundException, InternalServerErrorException } from '@nestjs/common'; -// eslint-disable-next-line camelcase +import { ConflictException, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { - Prisma, - agent_invitations, - org_agents, - org_invitations, - user, - user_org_roles, - organisation, - org_roles -} from '@prisma/client'; - -import { CreateOrganizationDto } from '../dtos/create-organization.dto'; + IDeleteOrganization, + IOrganization, + IOrganizationDashboard, + IOrganizationInvitations +} from '@credebl/common/interfaces/organization.interface'; import { - IGetDids, IDidDetails, IDidList, + IGetDids, IGetOrgById, IGetOrganization, - IPrimaryDidDetails, - IUpdateOrganization, - ILedgerNameSpace, - OrgInvitation, ILedgerDetails, + ILedgerNameSpace, + IOrgDetails, IOrgRoleDetails, - IOrgDetails + IPrimaryDidDetails, + IUpdateOrganization, + OrgInvitation } from '../interfaces/organization.interface'; import { Invitation, PrismaTables, SortValue } from '@credebl/enum/enum'; -import { PrismaService } from '@credebl/prisma-service'; -import { UserOrgRolesService } from '@credebl/user-org-roles'; -import { ResponseMessages } from '@credebl/common/response-messages'; +// eslint-disable-next-line camelcase import { - IOrganizationInvitations, - IOrganization, - IOrganizationDashboard, - IDeleteOrganization -} from '@credebl/common/interfaces/organization.interface'; + Prisma, + agent_invitations, + org_agents, + org_invitations, + org_roles, + organisation, + user, + user_org_roles +} from '@prisma/client'; + +import { CreateOrganizationDto } from '../dtos/create-organization.dto'; import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; +import { PrismaService } from '@credebl/prisma-service'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { UserOrgRolesService } from '@credebl/user-org-roles'; @Injectable() export class OrganizationRepository { @@ -479,6 +479,13 @@ export class OrganizationRepository { createDateTime: true, tenantId: true, agent_invitations: { + where: { + multiUse: true + }, + orderBy: { + lastChangedDateTime: SortValue.DESC + }, + take: 1, select: { id: true, connectionInvitation: true, diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 1abbb5aac..1698f3f43 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -32,10 +32,8 @@ export enum CommonConstants { // WALLET SERVICES URL_WALLET_CREATE_DID = '/wallet/did/create', URL_WALLET_LIST_DID = '/wallet/did', - URL_WALLET_FETCH_CURR_PUB_DID = '/wallet/did/public', - URL_WALLET_ASSIGN_CURR_DID_PUB = '/wallet/did/public', - URL_WALLET_GET_TAGGING_POLICY = '/wallet/tag-policy/#', - URL_WALLET_SET_TAGGING_POLICY = '/wallet/tag-policy/#', + URL_WALLET_FETCH_OR_ASSIGN_CURR_PUB_DID = '/wallet/did/public', + URL_WALLET_GET_OR_SET_TAGGING_POLICY = '/wallet/tag-policy/#', URL_WALLET_PROVISION = '/wallet/provision', // LEDGER SERVICES @@ -72,6 +70,7 @@ export enum CommonConstants { URL_ISSUE_CREATE_CRED_OFFER_AFJ = '/credentials/create-offer', // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values URL_ISSUE_GET_CREDS_AFJ = '/credentials', + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values URL_ISSUE_GET_CREDS_AFJ_BY_CRED_REC_ID = '/credentials', URL_OUT_OF_BAND_CREDENTIAL_OFFER = '/credentials/create-offer-oob', URL_ACCEPT_CREDENTIALS = '/credentials/accept-offer', @@ -120,10 +119,11 @@ export enum CommonConstants { URL_SHAGENT_SEND_ANSWER = '/multi-tenancy/question-answer/answer/#/@', URL_SHAGENT_QUESTION_ANSWER_RECORD = '/multi-tenancy/question-answer/#', URL_SHAGENT_DELETE_SUB_WALLET = '/multi-tenancy/#', - URL_SHARED_SEND_BASIC_MESSAGE = '/multi-tenancy/basic-messages/#/@', + URL_SHARED_SEND_BASIC_MESSAGE = '/multi-tenancy/basic-messages/#/@', URL_SHAGENT_ACCEPT_PROOF_REQUEST = '/multi-tenancy/proofs/#/accept-request/@', + URL_SHARED_AGENT_SIGN_DATA = '/multi-tenancy/credential/sign/#', + URL_SHARED_AGENT_VERIFY_SIGNED_DATA = '/multi-tenancy/credential/verify/#', - // PROOF SERVICES URL_SEND_PROOF_REQUEST = '/proofs/request-proof', URL_GET_PROOF_PRESENTATIONS = '/proofs', @@ -138,9 +138,13 @@ export enum CommonConstants { URL_AGENT_GET_DID = '/dids', URL_AGENT_GET_ENDPOINT = '/agent', + // sign data from agent + URL_AGENT_SIGN_DATA = '/credential/sign', + URL_AGENT_VERIFY_SIGNED_DATA = '/credential/verify', + // CREATE KEYS CREATE_POLYGON_SECP256k1_KEY = '/polygon/create-keys', - + // Nested attribute separator NESTED_ATTRIBUTE_SEPARATOR = '~', @@ -161,7 +165,7 @@ export enum CommonConstants { // POLYGON KEYWORDS POLYGON = 'polygon', - + // DOMAIN EVENTS DOMAIN_EVENT_SCHEMA_CREATED = 'Schema Created', DOMAIN_EVENT_CRED_DEF_CREATED = 'Cred-Def Created', @@ -185,6 +189,7 @@ export enum CommonConstants { // Roles And Permissions PERMISSION_PLATFORM_MANAGEMENT = 'Platform Management', PERMISSION_USER_MANAGEMENT = 'User Management', + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values PERMISSION_ROLE_MANAGEMENT = 'Role Management', PERMISSION_CONNECTIONS = 'Connections', @@ -222,8 +227,10 @@ export enum CommonConstants { URL_UPDATE_FILE = '/revocation/registry/#', URL_REVOC_PUBLISH = '/revocation/registry/#/publish', URL_REVOC_GETBY_CREDDEF = '/revocation/active-registry/#', + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values URL_REVOC_REG_BYID = '/revocation/registry/#', + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values DEFAULT_CACHE_TTL = 60000, DEFAULT_FIELD_UPLOAD_SIZE = 10485760, @@ -253,49 +260,64 @@ export enum CommonConstants { // delete wallet URL_DELETE_WALLET = '/agent/wallet', + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values URL_DELETE_SHARED_WALLET = '/multi-tenancy/#', // agent status + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values URL_AGENT_STATUS = '/agent', - + // Tenant Status PENDING_STATE = 0, REJECT_STATE = 2, APPROVE_STATE = 1, //User roles + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values TENANT_ROLE = 2, SUPER_ADMIN_ROLE = 4, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values PLATFORM_ADMIN_ROLE = 1, ORG_ROLE = 3, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values ORG_PLATFORM_ROLE = 1, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values ORG_TENANT_ROLE = 2, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values ORG_ENTITY_ROLE = 3, // Organizations Status + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values PENDING_ORG = 0, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values REJECT_ORG = 2, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values APPROVE_ORG = 1, // Organizations Status + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values PENDING_NON_ADMIN_USER = 0, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values INACTIVE_NON_ADMIN_USER = 2, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values ACTIVE_NON_ADMIN_USER = 1, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values ALL_NON_ADMIN_USER = 3, - // Platform admin Details - PLATFORM_ADMIN_EMAIL='platform.admin@yopmail.com', - PLATFORM_ADMIN_ORG='Platform-admin', - PLATFORM_ADMIN_ORG_ROLE='platform_admin', - - USER_HOLDER_ROLE='holder', + PLATFORM_ADMIN_EMAIL = 'platform.admin@yopmail.com', + PLATFORM_ADMIN_ORG = 'Platform-admin', + PLATFORM_ADMIN_ORG_ROLE = 'platform_admin', + USER_HOLDER_ROLE = 'holder', //onBoarding Type + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values ONBOARDING_TYPE_ADMIN = 0, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values ONBOARDING_TYPE_EXTERNAL = 1, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values ONBOARDING_TYPE_INVITATION = 2, // Network @@ -306,9 +328,13 @@ export enum CommonConstants { LIVENET = 'livenet', // Features Id + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values SCHEMA_CREATION = 1, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values CREATE_CREDENTIAL_DEFINITION = 2, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values CREATION_OF_ATTRIBUTE = 3, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values CREDENTIAL_ISSUANCE = 4, REVOCATION_REGISTRY = 5, REVOCATION_UPDATE = 6, @@ -322,54 +348,54 @@ export enum CommonConstants { KEYTYPE = 'ed25519', METHOD = 'indy', NETWORK = 'bcovrin:testnet', - ROLE = 'endorser', - - //CacheInfo -CACHE_SHARED_APIKEY_KEY = "dedicatedApiKey", -CACHE_APIKEY_KEY = "sharedApiKey", -CACHE_TTL_SECONDS = 604800, - -CLOUD_WALLET_GET_PROOF_REQUEST = '/multi-tenancy/proofs', -CLOUD_WALLET_CREATE_CONNECTION_INVITATION = '/multi-tenancy/create-invitation', -CLOUD_WALLET_ACCEPT_PROOF_REQUEST = '/accept-request/', -CLOUD_WALLET_DID_LIST = '/multi-tenancy/dids/', -CLOUD_WALLET_CONNECTION_BY_ID = '/multi-tenancy/connections/', -CLOUD_WALLET_CREDENTIAL = '/multi-tenancy/credentials', -CLOUD_WALLET_BASIC_MESSAGE = '/multi-tenancy/basic-messages/', - -// Bulk-issuance -BATCH_SIZE = 100, -MAX_CONCURRENT_OPERATIONS = 50, -ISSUANCE_BATCH_SIZE = 2000, -ISSUANCE_MAX_CONCURRENT_OPERATIONS = 1000, -ISSUANCE_BATCH_DELAY = 60000, //Intially 60000 - - -// MICROSERVICES NAMES -API_GATEWAY_SERVICE = 'api-gateway', -ORGANIZATION_SERVICE = 'organization', -USER_SERVICE = 'user', -AUTH_SERVICE = 'authz', -FIDO_SERVICE = 'fido', -UTILITY_SERVICE = 'utilitites', -CONNECTION_SERVICE = 'connection', -LEDGER_SERVICE = 'ledger', -PLATFORM_SERVICE = 'platform', -SCHEMA_SERVICE = 'schema', -CREDENTIAL_DEFINITION_SERVICE = 'credential-definition', -AGENT_SERVICE = 'agent-service', -AGENT_PROVISIONING = 'agent-provisioning', -ISSUANCE_SERVICE = 'issuance', -VERIFICATION_SERVICE = 'verification', -WEBHOOK_SERVICE = 'webhook', -NOTIFICATION_SERVICE = 'notification', -GEO_LOCATION_SERVICE = 'geo-location', -CLOUD_WALLET_SERVICE = 'cloud-wallet', - -//CLOUD WALLET -RECEIVE_INVITATION_BY_URL = '/multi-tenancy/receive-invitation-url/', -ACCEPT_OFFER = '/multi-tenancy/credentials/accept-offer/', -SEED_LENGTH = 32 + ROLE = 'endorser', + + //CacheInfo + CACHE_SHARED_APIKEY_KEY = 'dedicatedApiKey', + CACHE_APIKEY_KEY = 'sharedApiKey', + CACHE_TTL_SECONDS = 604800, + + CLOUD_WALLET_GET_PROOF_REQUEST = '/multi-tenancy/proofs', + CLOUD_WALLET_CREATE_CONNECTION_INVITATION = '/multi-tenancy/create-invitation', + CLOUD_WALLET_ACCEPT_PROOF_REQUEST = '/accept-request/', + CLOUD_WALLET_DID_LIST = '/multi-tenancy/dids/', + CLOUD_WALLET_CONNECTION_BY_ID = '/multi-tenancy/connections/', + CLOUD_WALLET_CREDENTIAL = '/multi-tenancy/credentials', + CLOUD_WALLET_BASIC_MESSAGE = '/multi-tenancy/basic-messages/', + + // Bulk-issuance + BATCH_SIZE = 100, + MAX_CONCURRENT_OPERATIONS = 50, + ISSUANCE_BATCH_SIZE = 2000, + ISSUANCE_MAX_CONCURRENT_OPERATIONS = 1000, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + ISSUANCE_BATCH_DELAY = 60000, //Intially 60000 + + // MICROSERVICES NAMES + API_GATEWAY_SERVICE = 'api-gateway', + ORGANIZATION_SERVICE = 'organization', + USER_SERVICE = 'user', + AUTH_SERVICE = 'authz', + FIDO_SERVICE = 'fido', + UTILITY_SERVICE = 'utilitites', + CONNECTION_SERVICE = 'connection', + LEDGER_SERVICE = 'ledger', + PLATFORM_SERVICE = 'platform', + SCHEMA_SERVICE = 'schema', + CREDENTIAL_DEFINITION_SERVICE = 'credential-definition', + AGENT_SERVICE = 'agent-service', + AGENT_PROVISIONING = 'agent-provisioning', + ISSUANCE_SERVICE = 'issuance', + VERIFICATION_SERVICE = 'verification', + WEBHOOK_SERVICE = 'webhook', + NOTIFICATION_SERVICE = 'notification', + GEO_LOCATION_SERVICE = 'geo-location', + CLOUD_WALLET_SERVICE = 'cloud-wallet', + + //CLOUD WALLET + RECEIVE_INVITATION_BY_URL = '/multi-tenancy/receive-invitation-url/', + ACCEPT_OFFER = '/multi-tenancy/credentials/accept-offer/', + SEED_LENGTH = 32 } export const MICRO_SERVICE_NAME = Symbol('MICRO_SERVICE_NAME'); export const ATTRIBUTE_NAME_REGEX = /\['(.*?)'\]/; @@ -428,8 +454,7 @@ postgresqlErrorCodes['22019'] = 'invalid_escape_character'; postgresqlErrorCodes['22P02'] = 'invalid_datatype'; postgresqlErrorCodes[''] = ''; - -export const DISALLOWED_EMAIL_DOMAIN = [ +export const DISALLOWED_EMAIL_DOMAIN = [ '0x01.gq', '0x01.tk', '10mail.org', @@ -829,4 +854,4 @@ export const DISALLOWED_EMAIL_DOMAIN = [ 'zapto.org', 'ze.cx', 'zeroe.ml' -]; \ No newline at end of file +]; diff --git a/libs/common/src/custom-overrideable-validation-pipe.ts b/libs/common/src/custom-overrideable-validation-pipe.ts new file mode 100644 index 000000000..2955af90a --- /dev/null +++ b/libs/common/src/custom-overrideable-validation-pipe.ts @@ -0,0 +1,64 @@ +/** + * With this we can override global level validation pipe config using '@RewriteValidationOptions({ whitelist: false })' decorator + * Here, options can be anything from 'ValidatorOptions'. + * I've designed this approach from reference from: https://github.com/nestjs/nest/issues/2390#issuecomment-5020%E2%80%8C%E2%80%8B42256 + * (Specifically, from here: https://github.com/nestjs/nest/issues/2390#issuecomment-517623971) + * + * This is the best workaround we've found until this issue/feature request is addressed: + * https://github.com/typestack/class-validator/issues/1486 + * + * Also the NestJs team doesn't seem to up for taking this issue any time soon, as per the comment here: https://github.com/nestjs/nest/issues/7779 + * + * Read more about this apprach here: https://gist.github.com/GHkrishna/3b38872ba8c2eb1d299d0a943013de49 + */ + +import { ArgumentMetadata, Injectable, SetMetadata, ValidationPipe, ValidationPipeOptions } from '@nestjs/common'; +import { ValidatorOptions } from 'class-validator'; +import { Reflector } from '@nestjs/core'; + +export const REWRITE_VALIDATION_OPTIONS = 'rewrite_validation_options'; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export function RewriteValidationOptions(options: ValidatorOptions) { + return SetMetadata(REWRITE_VALIDATION_OPTIONS, options); +} + +@Injectable() +export class UpdatableValidationPipe extends ValidationPipe { + private readonly defaultValidatorOptions: ValidatorOptions; + + constructor( + private reflector: Reflector, + globalOptions: ValidationPipeOptions = {} + ) { + super(globalOptions); + // Store only class-validator relevant options + this.defaultValidatorOptions = { + whitelist: globalOptions.whitelist, + forbidNonWhitelisted: globalOptions.forbidNonWhitelisted, + skipMissingProperties: globalOptions.skipMissingProperties, + forbidUnknownValues: globalOptions.forbidUnknownValues + }; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-function-return-type + async transform(value: any, metadata: ArgumentMetadata) { + const overrideOptions = this.reflector.get(REWRITE_VALIDATION_OPTIONS, metadata.metatype); + + if (overrideOptions) { + const originalOptions = { ...this.validatorOptions }; + this.validatorOptions = { ...this.defaultValidatorOptions, ...overrideOptions }; + + try { + const result = await super.transform(value, metadata); + this.validatorOptions = originalOptions; + return result; + } catch (err) { + this.validatorOptions = originalOptions; + throw err; + } + } + + return super.transform(value, metadata); + } +} diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 7771d1244..3a2457f08 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -212,6 +212,8 @@ export const ResponseMessages = { createDid: 'Did created successfully', health: 'Agent health details retrieved successfully.', ledgerConfig: 'Ledger config details fetched successfully.', + sign: 'Payload signed successfully.', + verify: 'Payload verified successfully.', webhookUrlRegister: 'Webhook Url registered successfully', getWebhookUrl: 'Webhook Url fetched successfully', createKeys: 'Key-pair created successfully', diff --git a/libs/enum/src/enum.ts b/libs/enum/src/enum.ts index e0811acbb..fa0eba90b 100644 --- a/libs/enum/src/enum.ts +++ b/libs/enum/src/enum.ts @@ -37,7 +37,7 @@ export enum DevelopmentEnvironment { TEST = 'test' } -export declare enum KeyType { +export enum KeyType { Ed25519 = 'ed25519', Bls12381g1g2 = 'bls12381g1g2', Bls12381g1 = 'bls12381g1', diff --git a/libs/prisma-service/prisma/scripts/geo_location_data_import.sh b/libs/prisma-service/prisma/scripts/geo_location_data_import.sh index e598ce5f8..61247b088 100755 --- a/libs/prisma-service/prisma/scripts/geo_location_data_import.sh +++ b/libs/prisma-service/prisma/scripts/geo_location_data_import.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # Database connection details DB_URL=$1 diff --git a/libs/prisma-service/prisma/scripts/update_client_credential_data.sh b/libs/prisma-service/prisma/scripts/update_client_credential_data.sh index 2b6d49a54..1c62ffa4a 100755 --- a/libs/prisma-service/prisma/scripts/update_client_credential_data.sh +++ b/libs/prisma-service/prisma/scripts/update_client_credential_data.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # Database connection URL DB_URL=$1 @@ -30,7 +30,7 @@ update_client_credentials() { } # Check if CLIENT_ID and CLIENT_SECRET are not empty -if [[ -n "$CLIENT_ID" && -n "$CLIENT_SECRET" ]]; then +if [ -n "$CLIENT_ID" ] && [ -n "$CLIENT_SECRET" ]; then # Update client credentials if both CLIENT_ID and CLIENT_SECRET are provided update_client_credentials else