Skip to content

Commit

Permalink
Add accredit/issue api
Browse files Browse the repository at this point in the history
  • Loading branch information
DaevMithran committed Sep 23, 2024
1 parent 332471b commit e9b4bb0
Show file tree
Hide file tree
Showing 6 changed files with 633 additions and 25 deletions.
222 changes: 200 additions & 22 deletions src/controllers/api/did.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,42 @@ import { IdentityServiceStrategySetup } from '../../services/identity/index.js';
import { decryptPrivateKey, generateDidDoc, getQueryParams } from '../../helpers/helpers.js';
import { bases } from 'multiformats/basics';
import { base64ToBytes } from 'did-jwt';
import type {
CreateDidRequestBody,
CreateDidResponseBody,
DeactivateDidResponseBody,
ListDidsResponseBody,
QueryDidResponseBody,
ResolveDidResponseBody,
UnsuccessfulCreateDidResponseBody,
UnsuccessfulDeactivateDidResponseBody,
UnsuccessfulGetDidResponseBody,
UnsuccessfulResolveDidResponseBody,
UnsuccessfulUpdateDidResponseBody,
UpdateDidResponseBody,
UpdateDidRequestBody,
ImportDidRequestBody,
DeactivateDIDRequestParams,
GetDIDRequestParams,
ResolveDIDRequestParams,
DeactivateDIDRequestBody,
import {
type CreateDidRequestBody,
type CreateDidResponseBody,
type DeactivateDidResponseBody,
type ListDidsResponseBody,
type QueryDidResponseBody,
type ResolveDidResponseBody,
type UnsuccessfulCreateDidResponseBody,
type UnsuccessfulDeactivateDidResponseBody,
type UnsuccessfulGetDidResponseBody,
type UnsuccessfulResolveDidResponseBody,
type UnsuccessfulUpdateDidResponseBody,
type UpdateDidResponseBody,
type UpdateDidRequestBody,
type ImportDidRequestBody,
type DeactivateDIDRequestParams,
type GetDIDRequestParams,
type ResolveDIDRequestParams,
type DeactivateDIDRequestBody,
DIDAccreditationTypes,
DIDAccreditationRequestParams,
DIDAccreditationRequestBody,
} from '../../types/did.js';
import { check, param } from '../validator/index.js';
import type { IKey, RequireOnly } from '@veramo/core';
import { check, param, body } from '../validator/index.js';
import type { IKey, RequireOnly, VerifiableCredential } from '@veramo/core';
import { SupportedKeyTypes, extractPublicKeyHex } from '@veramo/utils';
import type { KeyImport } from '../../types/key.js';
import { eventTracker } from '../../services/track/tracker.js';
import { OperationCategoryNameEnum, OperationNameEnum } from '../../types/constants.js';
import type { IDIDTrack, ITrackOperation } from '../../types/track.js';
import type { ICredentialTrack, IDIDTrack, ITrackOperation } from '../../types/track.js';
import { arePublicKeyHexsInWallet } from '../../services/helpers.js';
import { CheqdProviderErrorCodes } from '@cheqd/did-provider-cheqd';
import type { CheqdProviderError } from '@cheqd/did-provider-cheqd';
import { validate } from '../validator/decorator.js';
import { Credentials } from '../../services/api/credentials.js';
import { CredentialConnectors, type CredentialRequest } from '../../types/credential.js';

export class DIDController {
public static createDIDValidator = [
Expand Down Expand Up @@ -127,6 +132,25 @@ export class DIDController {
.bail(),
];

public static accreditDIDValidator = [
param('did').exists().isString().isDID().bail(),
param('type').exists().isString().isIn(['authorize', 'accredit', 'attest']).bail(),
body('subjectDid').exists().isString().isDID().bail(),
body('schemas').exists().isArray().bail(),
body('schemas.*.url').isURL().bail(),
body('schemas.*.type').isArray().bail(),
body('schemas.*.type.*').isString().bail(),
body('parentAccreditation').optional().isURL().bail(),
body('rootAuthorisation').optional().isURL().bail(),
body('type').custom((value, { req }) => {
if (value === 'accredit' || value === 'attest') {
return req.body.parentAccreditation && req.body.rootAuthorisation;
}

return true;
}),
];

/**
* @openapi
*
Expand Down Expand Up @@ -758,4 +782,158 @@ export class DIDController {
} satisfies UnsuccessfulResolveDidResponseBody);
}
}

/**
* @openapi
*
* /did/{did}/accredit/issue:
* post:
* tags: [ DID ]
* summary: Publish a verifiable accreditation for a DID.
* description: Generate and publish a verifiable accreditation for a subject DID accrediting schema's as a DID Linked resource.
* operationId: accredit
* parameters:
* - in: path
* name: did
* description: DID identifier of the Accreditor.
* schema:
* type: string
* required: true
* - in: query
* name: type
* description: Set whether the StatusList2021 resource should be published to the ledger or not. If set to `false`, the StatusList2021 publisher should manually publish the resource.
* schema:
* type: string
* enum:
* - authorize
* - accredit
* - attest
* required: true
* requestBody:
* content:
* application/x-www-form-urlencoded:
* schema:
* $ref: '#/components/schemas/DIDAccreditationRequest'
* application/json:
* schema:
* $ref: '#/components/schemas/DIDAccreditationRequest'
* responses:
* 200:
* description: The request was successful.
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Credential'
* 400:
* $ref: '#/components/schemas/InvalidRequest'
* 401:
* $ref: '#/components/schemas/UnauthorizedError'
* 500:
* $ref: '#/components/schemas/InternalError'
*/
public async accredit(request: Request, response: Response) {
// Get strategy e.g. postgres or local
const identityServiceStrategySetup = new IdentityServiceStrategySetup();
// Extract did from params
const { did } = request.params as DIDAccreditationRequestParams;
const { subjectDid, schemas, type, parentAccreditation, rootAuthorisation, attributes } =
request.body as DIDAccreditationRequestBody;

try {
// Validte issuer DID
const resolvedResult = await identityServiceStrategySetup.agent.resolve(did);
// check if DID-Document is resolved
const body = await resolvedResult.json();
if (!body?.didDocument) {
return response.status(StatusCodes.BAD_REQUEST).send({
error: `DID ${did} is not resolved because of error from resolver: ${body.didResolutionMetadata.error}.`,
});
}
if (body.didDocumentMetadata.deactivated) {
return response.status(StatusCodes.BAD_REQUEST).send({
error: `${did} is deactivated`,
});
}

// Validate subject DID
const res = await identityServiceStrategySetup.agent.resolve(subjectDid);
const subjectDidRes = await res.json();
if (!subjectDidRes?.didDocument) {
return response.status(StatusCodes.BAD_REQUEST).send({
error: `DID ${subjectDid} is not resolved because of error from resolver: ${body.didResolutionMetadata.error}.`,
});
}
if (subjectDidRes.didDocumentMetadata.deactivated) {
return response.status(StatusCodes.BAD_REQUEST).send({
error: `${subjectDid} is deactivated`,
});
}

// construct credential request
const credentialRequest: CredentialRequest = {
subjectDid,
attributes: {
...attributes,
accreditedFor: schemas.map(({ url, type }: any) => ({
schemaId: url,
type,
})),
id: subjectDid,
},
issuerDid: did,
format: 'jwt',
connector: CredentialConnectors.Resource, // resource connector
};
switch (type) {
case 'authorize':
credentialRequest.type = [DIDAccreditationTypes.VerifiableAuthorisationForTrustChain];
credentialRequest.termsOfUse = {
type,
trustFramework: 'cheqd Governance Framework',
trustFrameworkId: 'https://learn.cheqd.io/governance/start',
};
break;
case 'accredit':
credentialRequest.type = [DIDAccreditationTypes.VerifiableAccreditationToAccredit];
credentialRequest.termsOfUse = {
type,
parentAccreditation,
rootAuthorisation,
};
break;
case 'attest':
credentialRequest.type = [DIDAccreditationTypes.VerifiableAccreditationToAttest];
credentialRequest.termsOfUse = {
type,
parentAccreditation,
rootAuthorisation,
};
}

// issue credential
const credential: VerifiableCredential = await Credentials.instance.issue_credential(
credentialRequest,
response.locals.customer
);

// Track operation
const trackInfo = {
category: OperationCategoryNameEnum.CREDENTIAL,
name: OperationNameEnum.CREDENTIAL_ISSUE,
customer: response.locals.customer,
user: response.locals.user,
data: {
did,
} satisfies ICredentialTrack,
} as ITrackOperation;

eventTracker.emit('track', trackInfo);

return response.status(StatusCodes.OK).json(credential);
} catch (error) {
return response.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
error: `Internal error: ${(error as Error)?.message || error}`,
} satisfies UnsuccessfulResolveDidResponseBody);
}
}
}
2 changes: 1 addition & 1 deletion src/controllers/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { CheqdDidLinkedAlsoKnownAsValidator } from './resource-also-known-as.js'
import { CheqdW3CVerifiableCredentialValidator } from './credential.js';
import { CheqdW3CVerifiablePresentationValidator } from './presentation.js';

export const { check, validationResult, query, param } = new ExpressValidator({
export const { check, validationResult, query, param, body } = new ExpressValidator({
isDID: (value: Validatable) => {
const res = new DIDValidator().validate(value);
if (!res.valid) {
Expand Down
4 changes: 2 additions & 2 deletions src/services/connectors/resource.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as dotenv from 'dotenv';
import type { VerifiableCredential } from '@veramo/core';
import { IdentityServiceStrategySetup } from '../identity';
import { IdentityServiceStrategySetup } from '../identity/index.js';
import type { AlternativeUri, MsgCreateResourcePayload } from '@cheqd/ts-proto/cheqd/resource/v2';
import { v4 } from 'uuid';
import { fromString } from 'uint8arrays';
import type { CustomerEntity } from '../../database/entities/customer.entity';
import type { CustomerEntity } from '../../database/entities/customer.entity.js';
dotenv.config();

/**
Expand Down
Loading

0 comments on commit e9b4bb0

Please sign in to comment.