Skip to content

Commit

Permalink
Merge pull request #188 from credebl/out-of-band
Browse files Browse the repository at this point in the history
feat: Issuance and Verification Out-of-Band Functionality
  • Loading branch information
bhavanakarwade authored Oct 26, 2023
2 parents 6d0ea0a + f6a38bc commit 0b26784
Show file tree
Hide file tree
Showing 37 changed files with 688 additions and 209 deletions.
7 changes: 6 additions & 1 deletion apps/agent-service/src/agent-service.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { AgentServiceService } from './agent-service.service';
import { GetCredDefAgentRedirection, GetSchemaAgentRedirection, IAgentSpinupDto, IIssuanceCreateOffer, ITenantCredDef, ITenantDto, ITenantSchema } from './interface/agent-service.interface';
import { GetCredDefAgentRedirection, GetSchemaAgentRedirection, IAgentSpinupDto, IIssuanceCreateOffer, ITenantCredDef, ITenantDto, ITenantSchema, OutOfBandCredentialOffer } from './interface/agent-service.interface';
import { IConnectionDetails, IUserRequestInterface } from './interface/agent-service.interface';
import { ISendProofRequestPayload } from './interface/agent-service.interface';
import { user } from '@prisma/client';
Expand Down Expand Up @@ -124,4 +124,9 @@ export class AgentServiceController {
async submitTransaction(payload: { url: string, apiKey: string, submitEndorsementPayload:object }): Promise<object> {
return this.agentServiceService.sumbitTransaction(payload.url, payload.apiKey, payload.submitEndorsementPayload);
}

@MessagePattern({ cmd: 'agent-out-of-band-credential-offer' })
async outOfBandCredentialOffer(payload: { outOfBandIssuancePayload: OutOfBandCredentialOffer, url: string, apiKey: string }): Promise<object> {
return this.agentServiceService.outOfBandCredentialOffer(payload.outOfBandIssuancePayload, payload.url, payload.apiKey);
}
}
17 changes: 14 additions & 3 deletions apps/agent-service/src/agent-service.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import * as dotenv from 'dotenv';
import * as fs from 'fs';
import { catchError, map } from 'rxjs/operators';
dotenv.config();
import { GetCredDefAgentRedirection, IAgentSpinupDto, IStoreOrgAgentDetails, ITenantCredDef, ITenantDto, ITenantSchema, IWalletProvision, ISendProofRequestPayload, IIssuanceCreateOffer } from './interface/agent-service.interface';
import { GetCredDefAgentRedirection, IAgentSpinupDto, IStoreOrgAgentDetails, ITenantCredDef, ITenantDto, ITenantSchema, IWalletProvision, ISendProofRequestPayload, IIssuanceCreateOffer, OutOfBandCredentialOffer } from './interface/agent-service.interface';
import { AgentType, OrgAgentType } from '@credebl/enum/enum';
import { IConnectionDetails, IUserRequestInterface } from './interface/agent-service.interface';
import { AgentServiceRepository } from './repositories/agent-service.repository';
Expand Down Expand Up @@ -133,7 +133,7 @@ export class AgentServiceService {

agentSpinupDto.agentType = agentSpinupDto.agentType ? agentSpinupDto.agentType : 1;
agentSpinupDto.tenant = agentSpinupDto.tenant ? agentSpinupDto.tenant : false;
agentSpinupDto.ledgerId = !agentSpinupDto.ledgerId || 0 === agentSpinupDto.ledgerId.length ? [3] : agentSpinupDto.ledgerId;
agentSpinupDto.ledgerId = !agentSpinupDto.ledgerId || 0 === agentSpinupDto.ledgerId?.length ? [3] : agentSpinupDto.ledgerId;


const platformConfig: platform_config = await this.agentServiceRepository.getPlatformConfigDetails();
Expand Down Expand Up @@ -487,7 +487,7 @@ export class AgentServiceService {
async _createTenant(payload: ITenantDto, user: IUserRequestInterface): Promise<void> {
try {

payload.ledgerId = !payload.ledgerId || 0 === payload.ledgerId.length ? [3] : payload.ledgerId;
payload.ledgerId = !payload.ledgerId || 0 === payload.ledgerId?.length ? [3] : payload.ledgerId;

const ledgerDetails: ledgers[] = await this.agentServiceRepository.getGenesisUrl(payload.ledgerId);
const sharedAgentSpinUpResponse = new Promise(async (resolve, _reject) => {
Expand Down Expand Up @@ -944,5 +944,16 @@ export class AgentServiceService {
}
}

async outOfBandCredentialOffer(outOfBandIssuancePayload: OutOfBandCredentialOffer, url: string, apiKey: string): Promise<object> {
try {
const sendOutOfbandCredentialOffer = await this.commonService
.httpPost(url, outOfBandIssuancePayload, { headers: { 'x-api-key': apiKey } })
.then(async response => response);
return sendOutOfbandCredentialOffer;
} catch (error) {
this.logger.error(`Error in out-of-band credential in agent service : ${JSON.stringify(error)}`);
throw new RpcException(error);
}
}
}

13 changes: 11 additions & 2 deletions apps/agent-service/src/interface/agent-service.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ export interface IAgentSpinupDto {
tenant?: boolean;
}

export interface OutOfBandCredentialOffer {
emailId: string;
attributes: Attributes[];
credentialDefinitionId: string;
comment: string;
protocolVersion?: string;
orgId: number;
}

export interface ITenantDto {
label: string;
seed: string;
Expand Down Expand Up @@ -231,10 +240,10 @@ export interface ICredentialFormats {
}

export interface IIndy {
attributes: IAttributes[];
attributes: Attributes[];
}

export interface IAttributes {
export interface Attributes {
name: string;
value: string;
}
Expand Down
51 changes: 48 additions & 3 deletions apps/api-gateway/src/issuance/dtos/issuance.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { IsArray, IsNotEmpty, IsOptional, IsString, IsEmail } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

interface attribute {
interface CredentialOffer {
emailId: string;
attributes: Attribute[];
}

interface Attribute {
name: string;
value: string;
}
Expand All @@ -11,7 +16,7 @@ export class IssueCredentialDto {
@ApiProperty({ example: [{ 'value': 'string', 'name': 'string' }] })
@IsNotEmpty({ message: 'Please provide valid attributes' })
@IsArray({ message: 'attributes should be array' })
attributes: attribute[];
attributes: Attribute[];

@ApiProperty({ example: 'string' })
@IsNotEmpty({ message: 'Please provide valid credentialDefinitionId' })
Expand Down Expand Up @@ -98,3 +103,43 @@ export class CredentialAttributes {
value: string;
}

export class OutOfBandCredentialDto {

@ApiProperty({ example: [{ 'emailId': 'abc@example.com', 'attribute': [{ 'value': 'string', 'name': 'string' }] }] })
@IsNotEmpty({ message: 'Please provide valid attributes' })
@IsArray({ message: 'attributes should be array' })
@IsOptional()
credentialOffer: CredentialOffer[];

@ApiProperty({ example: 'awqx@getnada.com' })
@IsEmail()
@IsNotEmpty({ message: 'Please provide valid email' })
@IsString({ message: 'email should be string' })
@IsOptional()
emailId: string;

@ApiProperty({ example: [{ 'value': 'string', 'name': 'string' }] })
@IsNotEmpty({ message: 'Please provide valid attributes' })
@IsArray({ message: 'attributes should be array' })
@IsOptional()
attributes: Attribute[];

@ApiProperty({ example: 'string' })
@IsNotEmpty({ message: 'Please provide valid credential definition id' })
@IsString({ message: 'credential definition id should be string' })
credentialDefinitionId: string;

@ApiProperty({ example: 'string' })
@IsNotEmpty({ message: 'Please provide valid comment' })
@IsString({ message: 'comment should be string' })
@IsOptional()
comment: string;

@ApiProperty({ example: 'v1' })
@IsOptional()
@IsNotEmpty({ message: 'Please provide valid protocol version' })
@IsString({ message: 'protocol version should be string' })
protocolVersion?: string;

orgId: number;
}
51 changes: 50 additions & 1 deletion apps/api-gateway/src/issuance/issuance.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { CommonService } from '@credebl/common/common.service';
import { Response } from 'express';
import IResponseType from '@credebl/common/interfaces/response.interface';
import { IssuanceService } from './issuance.service';
import { IssuanceDto, IssueCredentialDto } from './dtos/issuance.dto';
import { IssuanceDto, IssueCredentialDto, OutOfBandCredentialDto } from './dtos/issuance.dto';
import { IUserRequest } from '@credebl/user-request/user-request.interface';
import { User } from '../authz/decorators/user.decorator';
import { ResponseMessages } from '@credebl/common/response-messages';
Expand All @@ -43,6 +43,7 @@ import { Roles } from '../authz/decorators/roles.decorator';
import { OrgRoles } from 'libs/org-roles/enums';
import { OrgRolesGuard } from '../authz/guards/org-roles.guard';
import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler';
import { ImageServiceService } from '@credebl/image-service';
import { FileExportResponse } from './interfaces';

@Controller()
Expand All @@ -54,11 +55,23 @@ import { FileExportResponse } from './interfaces';
export class IssuanceController {
constructor(
private readonly issueCredentialService: IssuanceService,
private readonly imageServiceService: ImageServiceService,
private readonly commonService: CommonService

) { }
private readonly logger = new Logger('IssuanceController');

@Get('/issuance/oob/qr/:base64Image')
@ApiOperation({ summary: 'Out-Of-Band issuance QR', description: 'Out-Of-Band issuance QR' })
@ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto })
@ApiExcludeEndpoint()
async getQrCode(@Param('base64Image') base64Image: string, @Res() res: Response): Promise<Response> {

const getImageBuffer = await this.imageServiceService.getBase64Image(base64Image);
res.setHeader('Content-Type', 'image/png');
return res.send(getImageBuffer);
}

/**
* Description: Get all issued credentials
* @param user
Expand Down Expand Up @@ -185,6 +198,42 @@ export class IssuanceController {
return res.status(HttpStatus.CREATED).json(finalResponse);
}

/**
* Description: credential issuance out-of-band
* @param user
* @param outOfBandCredentialDto
* @param orgId
* @param res
* @returns
*/
@Post('/orgs/:orgId/credentials/oob')
@UseGuards(AuthGuard('jwt'))
@ApiOperation({
summary: `Create out-of-band credential offer`,
description: `Create out-of-band credential offer`
})
@ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto })
@ApiBearerAuth()
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
@Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER)
async outOfBandCredentialOffer(
@User() user: IUserRequest,
@Body() outOfBandCredentialDto: OutOfBandCredentialDto,
@Param('orgId') orgId: number,
@Res() res: Response
): Promise<Response> {

outOfBandCredentialDto.orgId = orgId;
const getCredentialDetails = await this.issueCredentialService.outOfBandCredentialOffer(user, outOfBandCredentialDto);

const finalResponse: IResponseType = {
statusCode: HttpStatus.CREATED,
message: ResponseMessages.issuance.success.fetch,
data: getCredentialDetails.response
};
return res.status(HttpStatus.CREATED).json(finalResponse);
}

/**
* Description: webhook Save issued credential details
* @param user
Expand Down
3 changes: 2 additions & 1 deletion apps/api-gateway/src/issuance/issuance.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IssuanceController } from './issuance.controller';
import { IssuanceService } from './issuance.service';
import { CommonService } from '@credebl/common';
import { HttpModule } from '@nestjs/axios';
import { ImageServiceService } from '@credebl/image-service';

@Module({
imports: [
Expand All @@ -19,6 +20,6 @@ import { HttpModule } from '@nestjs/axios';
])
],
controllers: [IssuanceController],
providers: [IssuanceService, CommonService]
providers: [IssuanceService, ImageServiceService, CommonService]
})
export class IssuanceModule { }
17 changes: 12 additions & 5 deletions apps/api-gateway/src/issuance/issuance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { BaseService } from 'libs/service/base.service';
import { IUserRequest } from '@credebl/user-request/user-request.interface';
import { IssuanceDto, IssueCredentialDto } from './dtos/issuance.dto';
import { IssuanceDto, IssueCredentialDto, OutOfBandCredentialDto } from './dtos/issuance.dto';
import { FileExportResponse } from './interfaces';

@Injectable()
Expand Down Expand Up @@ -51,10 +51,17 @@ export class IssuanceService extends BaseService {
return this.sendNats(this.issuanceProxy, 'webhook-get-issue-credential', payload);
}

outOfBandCredentialOffer(user: IUserRequest, outOfBandCredentialDto: OutOfBandCredentialDto): Promise<{
response: object;
}> {
const payload = { user, outOfBandCredentialDto };
return this.sendNats(this.issuanceProxy, 'out-of-band-credential-offer', payload);
}

async exportSchemaToCSV(credentialDefinitionId: string
): Promise<FileExportResponse> {
const payload = {credentialDefinitionId};
return (await this.sendNats(this.issuanceProxy, 'export-schema-to-csv-by-credDefId', payload)).response;
}
): Promise<FileExportResponse> {
const payload = { credentialDefinitionId };
return (await this.sendNats(this.issuanceProxy, 'export-schema-to-csv-by-credDefId', payload)).response;
}

}
8 changes: 5 additions & 3 deletions apps/api-gateway/src/organization/organization.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { UpdateOrganizationDto } from './dtos/update-organization-dto';
import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler';
import { IUserRequestInterface } from '../interfaces/IUserRequestInterface';
import { GetAllUsersDto } from '../user/dto/get-all-users.dto';
import { ImageServiceService } from '@credebl/image-service';

@UseFilters(CustomExceptionFilter)
@Controller('orgs')
Expand All @@ -38,6 +39,7 @@ export class OrganizationController {

constructor(
private readonly organizationService: OrganizationService,
private readonly imageServiceService: ImageServiceService,
private readonly commonService: CommonService
) { }

Expand All @@ -47,11 +49,11 @@ export class OrganizationController {
async getOgPofile(@Param('orgId') orgId: number, @Res() res: Response): Promise<Response> {
const orgProfile = await this.organizationService.getOgPofile(orgId);

const base64Data = orgProfile.response["logoUrl"].replace(/^data:image\/\w+;base64,/, '');
const base64Data = orgProfile.response["logoUrl"];
const getImageBuffer = await this.imageServiceService.getBase64Image(base64Data);

const imageBuffer = Buffer.from(base64Data, 'base64');
res.setHeader('Content-Type', 'image/png');
return res.send(imageBuffer);
return res.send(getImageBuffer);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion apps/api-gateway/src/organization/organization.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { OrganizationController } from './organization.controller';
import { OrganizationService } from './organization.service';
import { ImageServiceService } from '@credebl/image-service';

@Module({
imports: [
Expand All @@ -23,7 +24,7 @@ import { OrganizationService } from './organization.service';
])
],
controllers: [OrganizationController],
providers: [OrganizationService, CommonService]
providers: [OrganizationService, CommonService, ImageServiceService]
})
export class OrganizationModule { }

15 changes: 5 additions & 10 deletions apps/api-gateway/src/verification/dto/request-proof.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IsArray, IsEmail, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator';
import { IsArray, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator';
import { toLowerCase, trim } from '@credebl/common/cast.helper';
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
Expand Down Expand Up @@ -85,15 +85,10 @@ export class OutOfBandRequestProof {
@IsNotEmpty({ message: 'please provide valid attributes' })
attributes: ProofRequestAttribute[];

@ApiProperty({ example: 'string' })
@IsNotEmpty({ message: 'Please provide valid emailId' })
@Transform(({ value }) => trim(value))
@Transform(({ value }) => toLowerCase(value))
@IsNotEmpty({ message: 'Email is required.' })
@MaxLength(256, { message: 'Email must be at most 256 character.' })
@IsEmail()
emailId: string;

@ApiProperty()
@IsString({ each: true, message: 'Each emailId in the array should be a string' })
emailId: string | string[];

@ApiProperty()
@IsOptional()
comment: string;
Expand Down
Loading

0 comments on commit 0b26784

Please sign in to comment.