Skip to content

Commit

Permalink
fix: use resolveCredentialAndVerify to verify enrolment prerequisites (
Browse files Browse the repository at this point in the history
…#630)

* fix(infura): replace config with ipfs daemon for initUser methods

* fix: use resolveCredentialAndVerify to verify enrolment prerequisites

* fix: test to verify that issuer can issue a role when he is not issuer of the pre-requisite role

* fix: remove commented out roles

* fix: update comments

* fix: update interface to V2

* fix: update key from verifyIssuer result

* fix: resolve conflicts

* fix: perform typecheck on issuer did
  • Loading branch information
whitneypurdum committed Aug 17, 2022
1 parent 51e0921 commit 9208ad3
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 26 deletions.
140 changes: 134 additions & 6 deletions e2e/claims.service.e2e.ts
Expand Up @@ -54,6 +54,8 @@ const staticIssuer = Wallet.createRandom().connect(provider);
const staticIssuerDID = `did:${Methods.Erc1056}:${Chain.VOLTA}:${staticIssuer.address}`;
const dynamicIssuer = Wallet.createRandom().connect(provider);
const dynamicIssuerDID = `did:${Methods.Erc1056}:${Chain.VOLTA}:${dynamicIssuer.address}`;
const projectInstallerCandidate = Wallet.createRandom().connect(provider);
const projectInstallerCandidateDID = `did:${Methods.Erc1056}:${Chain.VOLTA}:${projectInstallerCandidate.address}`;
const rootOwner = Wallet.createRandom().connect(provider);
const rootOwnerDID = `did:${Methods.Erc1056}:${Chain.VOLTA}:${rootOwner.address}`;
const roleName1 = 'myrole1';
Expand All @@ -68,6 +70,9 @@ const resolveVC = 'resolvevc';
const verifyResolvedVcExpired = 'vcResolvedExpired';
const eip191JwtExpired = 'eip191JwtExpired';
const vcExpired = 'vcExpired';
const electrician = 'electrician';
const projectElectrician = 'projectElectrician';
const projectInstaller = 'projectInstaller';
const namespace = root;
const version = 1;
const baseRoleDef = {
Expand Down Expand Up @@ -137,6 +142,24 @@ const roles: Record<string, IRoleDefinitionV2> = {
roleName: eip191JwtExpired,
issuer: { issuerType: 'DID', did: [staticIssuerDID] },
},
[`${electrician}.${root}`]: {
...baseRoleDef,
roleName: electrician,
issuer: { issuerType: 'DID', did: [staticIssuerDID] },
},
[`${projectElectrician}.${root}`]: {
...baseRoleDef,
roleName: projectElectrician,
issuer: { issuerType: 'DID', did: [projectInstallerCandidateDID] },
enrolmentPreconditions: [
{ type: PreconditionType.Role, conditions: [`${electrician}.${root}`] },
],
},
[`${projectInstaller}.${root}`]: {
...baseRoleDef,
roleName: projectInstaller,
issuer: { issuerType: 'DID', did: [staticIssuerDID] },
},
};
const mockGetRoleDefinition = jest
.fn()
Expand Down Expand Up @@ -220,6 +243,7 @@ describe('Сlaim tests', () => {
await replenish(await staticIssuer.getAddress());
await replenish(await rootOwner.getAddress());
await replenish(await dynamicIssuer.getAddress());
await replenish(await projectInstallerCandidate.getAddress());

await setupENS(await rootOwner.getAddress());
let connectToCacheServer;
Expand Down Expand Up @@ -295,6 +319,25 @@ describe('Сlaim tests', () => {
data: roles[`${eip191JwtExpired}.${root}`],
returnSteps: false,
});
await domainsService.createRole({
roleName: electrician,
namespace,
data: roles[`${electrician}.${root}`],
returnSteps: false,
});
await domainsService.createRole({
roleName: projectElectrician,
namespace,
data: roles[`${projectElectrician}.${root}`],
returnSteps: false,
});
await domainsService.createRole({
roleName: projectInstaller,
namespace,
data: roles[`${projectInstaller}.${root}`],
returnSteps: false,
});

({ didRegistry, claimsService } = await connectToDidRegistry(
await spawnIpfsDaemon()
));
Expand Down Expand Up @@ -659,14 +702,34 @@ describe('Сlaim tests', () => {
returnSteps: false,
});

await enrolAndIssue(rootOwner, staticIssuer, {
const roleOneIssuance = await enrolAndIssue(rootOwner, staticIssuer, {
subjectDID: rootOwnerDID,
claimType: `${roleName1}.${root}`,
registrationTypes: [
RegistrationTypes.OnChain,
RegistrationTypes.OffChain,
],
publishOnChain: true,
issuerFields: [],
});
await signerService.connect(rootOwner, ProviderType.PrivateKey);
await claimsService.publishPublicClaim({
claim: { token: roleOneIssuance.issuedToken },
});

await enrolAndIssue(rootOwner, staticIssuer, {
const roleTwoIssuance = await enrolAndIssue(rootOwner, staticIssuer, {
subjectDID: rootOwnerDID,
claimType: `${roleName3}.${root}`,
registrationTypes: [
RegistrationTypes.OnChain,
RegistrationTypes.OffChain,
],
publishOnChain: true,
issuerFields: [],
});
await signerService.connect(rootOwner, ProviderType.PrivateKey);
await claimsService.publishPublicClaim({
claim: { token: roleTwoIssuance.issuedToken },
});
return expect(
await claimsService.hasOnChainRole(
Expand All @@ -676,6 +739,75 @@ describe('Сlaim tests', () => {
)
).toBe(true);
});
test('should enrol when prerequisite role is met and the issuer is not the issuer of the prerequisite role', async () => {
//Root Owner enrols to 'Electrician' role
const enrolToElectrician = await enrolAndIssue(rootOwner, staticIssuer, {
subjectDID: rootOwnerDID,
claimType: `${electrician}.${root}`,
registrationTypes: [
RegistrationTypes.OnChain,
RegistrationTypes.OffChain,
],
publishOnChain: true,
issuerFields: [],
});
await signerService.connect(rootOwner, ProviderType.PrivateKey);
await claimsService.publishPublicClaim({
claim: { token: enrolToElectrician.issuedToken },
});

//Project Installer Candidate enrols to 'Project Installer' role
const enrolToProjectInstaller = await enrolAndIssue(
projectInstallerCandidate,
staticIssuer,
{
subjectDID: projectInstallerCandidateDID,
claimType: `${projectInstaller}.${root}`,
registrationTypes: [
RegistrationTypes.OnChain,
RegistrationTypes.OffChain,
],
publishOnChain: true,
issuerFields: [],
}
);
await signerService.connect(
projectInstallerCandidate,
ProviderType.PrivateKey
);
await claimsService.publishPublicClaim({
claim: { token: enrolToProjectInstaller.issuedToken },
});

//Root Owner enrols to 'Project Electrician' role. 'Electrician' is pre-requisite role; Project Installer is issuer of 'Project Electrician' role, not issuer of 'Electrician' role
const enrolToProjectElectrician = await enrolAndIssue(
rootOwner,
projectInstallerCandidate,
{
subjectDID: rootOwnerDID,
claimType: `${projectElectrician}.${root}`,
registrationTypes: [
RegistrationTypes.OnChain,
RegistrationTypes.OffChain,
],
publishOnChain: true,
issuerFields: [],
}
);
await signerService.connect(rootOwner, ProviderType.PrivateKey);
await claimsService.publishPublicClaim({
claim: { token: enrolToProjectElectrician.issuedToken },
});

//Expect Root Owner to have role of Project Electrician
return expect(
await claimsService.hasOnChainRole(
rootOwnerDID,
`${projectElectrician}.${root}`,
version
)
).toBe(true);
});

test('should reject to enrol when prerequisites are not met', async () => {
await signerService.connect(rootOwner, ProviderType.PrivateKey);
Expand Down Expand Up @@ -966,7 +1098,6 @@ describe('Сlaim tests', () => {
await claimsService.publishPublicClaim({
claim: { token: issuedToken },
});
await signerService.connect(staticIssuer, ProviderType.PrivateKey);
const result = await claimsService.resolveCredentialAndVerify(
rootOwnerDID,
roleName
Expand All @@ -991,7 +1122,6 @@ describe('Сlaim tests', () => {
await claimsService.publishPublicClaim({
claim: { token: issuedToken },
});
await signerService.connect(staticIssuer, ProviderType.PrivateKey);
const result = await claimsService.resolveCredentialAndVerify(
rootOwnerDID,
roleName
Expand All @@ -1001,7 +1131,6 @@ describe('Сlaim tests', () => {
});
test('resolveCredentialAndVerify should return a "No claim found" error if no credential and no claim is resolved', async () => {
const roleName = `${resolveVC}.${root}`;
await signerService.connect(staticIssuer, ProviderType.PrivateKey);
const result = await claimsService.resolveCredentialAndVerify(
staticIssuerDID,
roleName
Expand Down Expand Up @@ -1036,7 +1165,6 @@ describe('Сlaim tests', () => {
});
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
await delay(8000);
await signerService.connect(staticIssuer, ProviderType.PrivateKey);
const result = await claimsService.resolveCredentialAndVerify(
rootOwnerDID,
roleName
Expand Down
1 change: 1 addition & 0 deletions src/errors/error-messages.ts
Expand Up @@ -36,4 +36,5 @@ export enum ERROR_MESSAGES {
OFFCHAIN_ISSUER_NOT_AUTHORIZED = 'Issuer of OffChain Claim is not authorized',
NO_CLAIM_RESOLVED = 'No claim found for given DID and role',
CREDENTIAL_EXPIRED = 'Credential Expired',
NO_ISSUER_SPECIFIED = 'No issuer specified for credential',
}
50 changes: 30 additions & 20 deletions src/modules/claims/claims.service.ts
Expand Up @@ -2,7 +2,7 @@ import { providers, utils, Wallet } from 'ethers';
import jsonwebtoken from 'jsonwebtoken';
import { v4 } from 'uuid';
import {
IRoleDefinition,
IRoleDefinitionV2,
PreconditionType,
RoleCredentialSubject,
} from '@energyweb/credential-governance';
Expand Down Expand Up @@ -1252,24 +1252,24 @@ export class ClaimsService {
throw new Error(ERROR_MESSAGES.ROLE_NOT_EXISTS);
}

const { enrolmentPreconditions } = roleDefinition as IRoleDefinition;
const { enrolmentPreconditions } = roleDefinition as IRoleDefinitionV2;

if (!enrolmentPreconditions || enrolmentPreconditions.length === 0) return;

const enroledRoles = new Set(
(await this.getClaimsBySubject({ did: subject, isAccepted: true })).map(
({ claimType }) => claimType
)
);
const requiredRoles = new Set(
enrolmentPreconditions
.filter(({ type }) => type === PreconditionType.Role)
.map(({ conditions }) => conditions)
.reduce((all, cur) => all.concat(cur), [])
const requiredRoles = enrolmentPreconditions
.filter(({ type }) => type === PreconditionType.Role)
.map(({ conditions }) => conditions)
.reduce((all, cur) => all.concat(cur), []);
await Promise.all(
requiredRoles.map(async (role) => {
const verificationResult = await this.resolveCredentialAndVerify(
subject,
role
);
if (!verificationResult.isVerified) {
throw new Error(ERROR_MESSAGES.ROLE_PREREQUISITES_NOT_MET);
}
})
);
if (Array.from(requiredRoles).some((role) => !enroledRoles.has(role))) {
throw new Error(ERROR_MESSAGES.ROLE_PREREQUISITES_NOT_MET);
}
}

/**
Expand Down Expand Up @@ -1437,7 +1437,10 @@ export class ClaimsService {
vc: VerifiableCredential<RoleCredentialSubject>
): Promise<CredentialVerificationResult> {
const errors: string[] = [];
const issuerDID = this._signerService.did;
const issuerDID = vc.issuer;
if (!issuerDID) {
throw new Error(ERROR_MESSAGES.NO_ISSUER_SPECIFIED);
}

let proofVerified;
try {
Expand All @@ -1453,7 +1456,11 @@ export class ClaimsService {
const role = vc.credentialSubject.role.namespace;
let issuerVerified = true;
try {
await this._issuerVerification.verifyIssuer(issuerDID, role);
if (typeof issuerDID === 'string') {
await this._issuerVerification.verifyIssuer(issuerDID, role);
} else {
await this._issuerVerification.verifyIssuer(issuerDID.id, role);
}
} catch (e) {
issuerVerified = false;
errors.push((e as Error).message);
Expand Down Expand Up @@ -1486,14 +1493,17 @@ export class ClaimsService {
): Promise<CredentialVerificationResult> {
const { payload, eip191Jwt } = roleEIP191JWT;
const errors: string[] = [];
const issuerDID = this._signerService.did;
const issuerDID = roleEIP191JWT.payload?.iss;
if (!issuerDID) {
throw new Error(ERROR_MESSAGES.NO_ISSUER_SPECIFIED);
}
const { verified: issuerVerified, error } =
await this._issuerVerification.verifyIssuer(
issuerDID,
payload?.claimData?.claimType
);
if (!issuerVerified && error) {
errors.push(error);
throw new Error(ERROR_MESSAGES.NO_ISSUER_SPECIFIED);
}
const proofVerified = await this._didRegistry.verifyPublicClaim(
eip191Jwt,
Expand Down

0 comments on commit 9208ad3

Please sign in to comment.