Skip to content

Commit

Permalink
fix: Support did:vda in validator [DEV-3498] (#459)
Browse files Browse the repository at this point in the history
* fix: Support did:vda in validator

* test: Add tests for did:vda

* Update validators

* Add namespace in did:vda validator

* Update build.yml

* Switch to truthy evaluation

Co-authored-by: Tasos Derisiotis <50984242+Eengineer1@users.noreply.github.com>

---------

Co-authored-by: Tasos Derisiotis <50984242+Eengineer1@users.noreply.github.com>
  • Loading branch information
DaevMithran and Eengineer1 committed Jan 5, 2024
1 parent b1c8d8e commit 2176e84
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 6 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
LOGTO_WEBHOOK_SECRET: ${{ secrets.LOGTO_WEBHOOK_SECRET }}
MAINNET_RPC_URL: ${{ vars.MAINNET_RPC_URL }}
POLYGON_PRIVATE_KEY: ${{ secrets.POLYGON_PRIVATE_KEY }}
POLYGON_RPC_URL: ${{ secrets.POLYGON_RPC_URL }}
RESOLVER_URL: ${{ vars.RESOLVER_URL }}
TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
Expand Down
6 changes: 6 additions & 0 deletions src/controllers/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ import { Cheqd } from '@cheqd/did-provider-cheqd';
import { OPERATION_CATEGORY_NAME_CREDENTIAL } from '../types/constants.js';
import { CheqdW3CVerifiableCredential } from '../services/w3c-credential.js';
import { isCredentialIssuerDidDeactivated } from '../services/helpers.js';
import { VeridaDIDValidator } from './validator/did.js';

export class CredentialController {
public static issueValidator = [
check(['subjectDid', 'issuerDid']).exists().withMessage('DID is required').bail().isDID().bail(),
check('subjectDid')
.custom((value, { req }) =>
new VeridaDIDValidator().validate(value).valid ? !!req.body.credentialSchema : true
)
.withMessage('credentialSchema is required for a verida DID subject'),
check('attributes')
.exists()
.withMessage('attributes are required')
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class KeyController {
check('encrypted')
.isBoolean()
.withMessage('encrypted is required')
.custom((value, { req }) => (value === true ? req.ivHex && req.salt : true))
.custom((value, { req }) => (value === true ? req.body.ivHex && req.body.salt : true))
.withMessage('Property ivHex, salt is required when encrypted is set to true')
.bail(),
];
Expand Down
73 changes: 71 additions & 2 deletions src/controllers/validator/did.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CheqdNetwork } from '@cheqd/sdk';
import type { IValidationResult, IValidator, Validatable } from './validator.js';
import { CheqdIdentifierValidator, KeyIdentifierValidator } from './identifier.js';
import { CheqdIdentifierValidator, KeyIdentifierValidator, VeridaIdentifierValidator } from './identifier.js';

export class BaseDidValidator implements IValidator {
validate(did: Validatable): IValidationResult {
Expand Down Expand Up @@ -140,12 +140,81 @@ export class KeyDIDValidator extends BaseDidValidator implements IValidator {
}
}

export class VeridaDIDValidator extends BaseDidValidator implements IValidator {
protected identifierValidator: IValidator;
subject = 'vda';

constructor(identifierValidator?: IValidator) {
super();
// Setup default CheqdIdentifierValidator
if (!identifierValidator) {
identifierValidator = new VeridaIdentifierValidator();
}
this.identifierValidator = identifierValidator;
}

public printable(): string {
return this.subject;
}

validate(did: Validatable): IValidationResult {
// Call base validation
let _v = super.validate(did);
if (!_v.valid) {
return _v;
}
did = did as string;
// Check if DID is vda
const method = did.split(':')[1];
if (method != this.subject) {
return {
valid: false,
error: 'DID Verida should have "did:vda:" prefix',
};
}

// Check namepsace
const namespace = did.split(':')[2];
if (!namespace) {
return {
valid: false,
error: 'Verida DID namespace is required ("did:vda:mainnet:..." or "did:vda:testnet:...")',
};
}
// Check if namespace is valid
if (namespace !== CheqdNetwork.Testnet && namespace !== CheqdNetwork.Mainnet) {
return {
valid: false,
error: `Verida DID namespace must be ${CheqdNetwork.Testnet} or ${CheqdNetwork.Mainnet}`,
};
}

// Check identifier
const id = did.split(':')[3];
if (!id) {
return {
valid: false,
error: 'Identifier is required after "did:vda:<namespace>:" prefix',
};
}
// Check that identifier is valid
_v = this.identifierValidator.validate(id);
if (!_v.valid) {
return {
valid: false,
error: _v.error,
};
}
return { valid: true };
}
}

export class DIDValidator implements IValidator {
protected didValidators: IValidator[];

constructor(didValidators?: IValidator[]) {
if (!didValidators) {
didValidators = [new CheqdDIDValidator(), new KeyDIDValidator()];
didValidators = [new CheqdDIDValidator(), new KeyDIDValidator(), new VeridaDIDValidator()];
}

this.didValidators = didValidators;
Expand Down
14 changes: 14 additions & 0 deletions src/controllers/validator/identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,17 @@ export class KeyIdentifierValidator implements IValidator {
return { valid: true };
}
}

export class VeridaIdentifierValidator implements IValidator {
validate(id: Validatable): IValidationResult {
if (typeof id !== 'string') {
return {
valid: false,
error: 'Verida DID identifier should be a string',
};
}
id = id as string;
// ToDo add more checks for did:vda identifier
return { valid: true };
}
}
37 changes: 34 additions & 3 deletions tests/e2e/credential/issue-verify-flow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ test.use({ storageState: 'playwright/.auth/user.json' });
let jwtCredential: VerifiableCredential, jsonldCredential: VerifiableCredential;

test(' Issue a jwt credential', async ({ request }) => {
const credentialData = JSON.parse(fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-jwt.json`, 'utf-8'));
const credentialData = JSON.parse(
fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-jwt.json`, 'utf-8')
);
const response = await request.post(`/credential/issue`, {
data: JSON.stringify(credentialData),
headers: {
Expand Down Expand Up @@ -48,7 +50,9 @@ test(' Verify a jwt credential', async ({ request }) => {
});

test(' Issue a jwt credential with a deactivated DID', async ({ request }) => {
const credentialData = JSON.parse(fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-jwt.json`, 'utf-8'));
const credentialData = JSON.parse(
fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-jwt.json`, 'utf-8')
);
credentialData.issuerDid = 'did:cheqd:testnet:edce6dfb-b59c-493b-a4b8-1d16a6184349';
const response = await request.post(`/credential/issue`, {
data: JSON.stringify(credentialData),
Expand All @@ -60,7 +64,9 @@ test(' Issue a jwt credential with a deactivated DID', async ({ request }) => {
});

test(' Issue a jsonLD credential', async ({ request }) => {
const credentialData = JSON.parse(fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-jsonld.json`, 'utf-8'));
const credentialData = JSON.parse(
fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-jsonld.json`, 'utf-8')
);
const response = await request.post(`/credential/issue`, {
data: JSON.stringify(credentialData),
headers: {
Expand Down Expand Up @@ -97,3 +103,28 @@ test(' Verify a jsonld credential', async ({ request }) => {
expect(response.status()).toBe(StatusCodes.OK);
expect(result.verified).toBe(true);
});

test(' Issue a jwt credential to a verida DID holder', async ({ request }) => {
const credentialData = JSON.parse(
fs.readFileSync(`${PAYLOADS_PATH.CREDENTIAL}/credential-issue-vda.json`, 'utf-8')
);
const response = await request.post(`/credential/issue`, {
data: JSON.stringify(credentialData),
headers: {
'Content-Type': CONTENT_TYPE.APPLICATION_JSON,
},
});
const credential = await response.json();
expect(response).toBeOK();
expect(response.status()).toBe(StatusCodes.OK);
expect(credential.proof.type).toBe('JwtProof2020');
expect(credential.proof).toHaveProperty('jwt');
expect(typeof credential.issuer === 'string' ? credential.issuer : credential.issuer.id).toBe(
credentialData.issuerDid
);
expect(credential.type).toContain('VerifiableCredential');
expect(credential.credentialSubject).toMatchObject({
...credentialData.attributes,
id: credentialData.subjectDid,
});
});
20 changes: 20 additions & 0 deletions tests/e2e/payloads/credential/credential-issue-vda.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"@context": ["https://common.schemas.verida.io/identity/kyc/FinClusive/individual-basic/v0.1.0/schema.json"],
"attributes": {
"firstName": "Alice",
"lastName": "Nikitin",
"dateOfBirth": "1984/07/03",
"streetAddress1": "321A",
"suburb": "Orange",
"state": "California",
"postcode": "90210",
"complianceProfileLevel": "member",
"complianceStatus": "active",
"creationDate": "2023/05/03",
"validUntil": "2032/05/03"
},
"credentialSchema": "https://common.schemas.verida.io/identity/kyc/FinClusive/individual-basic/v0.1.0/schema.json",
"format": "jwt",
"issuerDid": "did:cheqd:testnet:4JdgsZ4A8LegKXdsKE3v6X",
"subjectDid": "did:vda:testnet:0xdd5bB6467Cae1513ce253738332faBB3206b9583"
}

0 comments on commit 2176e84

Please sign in to comment.