-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(workflows): copy resident-card exchange test to workflows
Signed-off-by: jrhender <[email protected]>
- Loading branch information
Showing
3 changed files
with
333 additions
and
0 deletions.
There are no files selected for viewing
125 changes: 125 additions & 0 deletions
125
apps/vc-api/test/vc-api/workflows/resident-card/resident-card-issuance.exchange.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* | ||
* Copyright 2021 - 2023 Energy Web Foundation | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { plainToClass } from 'class-transformer'; | ||
import { WalletClient } from '../../../wallet-client'; | ||
import { VerifiablePresentationDto } from '../../../../src/vc-api/credentials/dtos/verifiable-presentation.dto'; | ||
import { CredentialDto } from '../../../../src/vc-api/credentials/dtos/credential.dto'; | ||
import { Presentation } from '../../../../src/vc-api/exchanges/types/presentation'; | ||
import { ExchangeDefinitionDto } from '../../../../src/vc-api/exchanges/dtos/exchange-definition.dto'; | ||
import { VpRequestInteractServiceType } from '../../../../src/vc-api/exchanges/types/vp-request-interact-service-type'; | ||
import { VpRequestQueryType } from '../../../../src/vc-api/exchanges/types/vp-request-query-type'; | ||
import { ProvePresentationOptionsDto } from '../../../../src/vc-api/credentials/dtos/prove-presentation-options.dto'; | ||
|
||
export class ResidentCardIssuance { | ||
#exchangeId = 'permanent-resident-card-issuance'; | ||
#callbackUrl: string; | ||
queryType = VpRequestQueryType.didAuth; | ||
|
||
constructor(callbackUrl: string) { | ||
this.#callbackUrl = callbackUrl; | ||
} | ||
|
||
getExchangeId(): string { | ||
return this.#exchangeId; | ||
} | ||
|
||
getExchangeDefinition(): ExchangeDefinitionDto { | ||
const exchangeDefinition: ExchangeDefinitionDto = { | ||
exchangeId: this.#exchangeId, | ||
query: [ | ||
{ | ||
type: this.queryType, | ||
credentialQuery: [] | ||
} | ||
], | ||
interactServices: [ | ||
{ | ||
type: VpRequestInteractServiceType.mediatedPresentation | ||
} | ||
], | ||
isOneTime: false, | ||
callback: [ | ||
{ | ||
url: this.#callbackUrl | ||
} | ||
] | ||
}; | ||
return plainToClass(ExchangeDefinitionDto, exchangeDefinition); | ||
} | ||
|
||
/** | ||
* | ||
* TODO: get and approve presentation review | ||
* @param vp | ||
* @param walletClient | ||
* @returns | ||
*/ | ||
async issueCredential(vp: VerifiablePresentationDto, walletClient: WalletClient) { | ||
if (!vp.holder) { | ||
return { errors: ['holder of vp not provided'] }; | ||
} | ||
const issuingDID = await walletClient.createDID('key'); | ||
const credential = this.fillCredential(issuingDID.id, vp.holder); | ||
const issueCredentialDto = { | ||
options: {}, | ||
credential | ||
}; | ||
const vc = await walletClient.issueVC(issueCredentialDto); | ||
const presentation: Presentation = { | ||
'@context': ['https://www.w3.org/2018/credentials/v1'], | ||
type: ['VerifiablePresentation'], | ||
verifiableCredential: [vc] | ||
}; | ||
const verificationMethodURI = issuingDID?.verificationMethod[0]?.id; | ||
if (!verificationMethodURI) { | ||
return { errors: ['verification method for issuance not available'] }; | ||
} | ||
const presentationOptions: ProvePresentationOptionsDto = { | ||
verificationMethod: verificationMethodURI, | ||
challenge: 'some-challenge' | ||
}; | ||
const provePresentationDto = { | ||
options: presentationOptions, | ||
presentation | ||
}; | ||
const returnVp = await walletClient.provePresentation(provePresentationDto); | ||
return { | ||
errors: [], | ||
vp: returnVp | ||
}; | ||
} | ||
|
||
private fillCredential(issuingDID: string, holderDID: string): CredentialDto { | ||
// This hard-coded example is from https://w3c-ccg.github.io/citizenship-vocab/#example | ||
return { | ||
'@context': [ | ||
'https://www.w3.org/2018/credentials/v1', | ||
'https://w3id.org/citizenship/v1' | ||
// optional country-specific context can be added below | ||
// e.g., https://uscis.gov/prc/v1 | ||
], | ||
id: 'https://issuer.oidp.uscis.gov/credentials/83627465', | ||
type: ['VerifiableCredential', 'PermanentResidentCard'], | ||
issuer: issuingDID, | ||
issuanceDate: '2019-12-03T12:19:52Z', | ||
expirationDate: '2029-12-03T12:19:52Z', | ||
credentialSubject: { | ||
id: holderDID, | ||
type: ['PermanentResident', 'Person'], | ||
givenName: 'JOHN', | ||
familyName: 'SMITH', | ||
gender: 'Male', | ||
image: 'data:image/png;base64,iVBORw0KGgo...kJggg==', | ||
residentSince: '2015-01-01', | ||
lprCategory: 'C09', | ||
lprNumber: '999-999-999', | ||
commuterClassification: 'C1', | ||
birthCountry: 'Bahamas', | ||
birthDate: '1958-07-17' | ||
} | ||
}; | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
apps/vc-api/test/vc-api/workflows/resident-card/resident-card-presentation.exchange.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* Copyright 2021 - 2023 Energy Web Foundation | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { plainToClass } from 'class-transformer'; | ||
import { ExchangeDefinitionDto } from '../../../../src/vc-api/exchanges/dtos/exchange-definition.dto'; | ||
import { VpRequestInteractServiceType } from '../../../../src/vc-api/exchanges/types/vp-request-interact-service-type'; | ||
import { VpRequestQueryType } from '../../../../src/vc-api/exchanges/types/vp-request-query-type'; | ||
|
||
export class ResidentCardPresentation { | ||
#exchangeId = `b229a18f-db45-4b33-8d36-25d442467bab`; | ||
#callbackUrl: string; | ||
queryType = VpRequestQueryType.presentationDefinition; | ||
|
||
constructor(callbackUrl: string) { | ||
this.#callbackUrl = callbackUrl; | ||
} | ||
|
||
getExchangeId(): string { | ||
return this.#exchangeId; | ||
} | ||
|
||
getExchangeDefinition(): ExchangeDefinitionDto { | ||
const exchangeDefinition: ExchangeDefinitionDto = { | ||
exchangeId: this.#exchangeId, | ||
query: [ | ||
{ | ||
type: this.queryType, | ||
credentialQuery: [ | ||
{ | ||
presentationDefinition: { | ||
id: '286bc1e0-f1bd-488a-a873-8d71be3c690e', | ||
input_descriptors: [ | ||
{ | ||
id: 'permanent_resident_card', | ||
name: 'Permanent Resident Card', | ||
purpose: 'We can only allow permanent residents into the application', | ||
constraints: { | ||
fields: [ | ||
{ | ||
path: ['$.type'], | ||
filter: { | ||
type: 'array', | ||
contains: { | ||
type: 'string', | ||
const: 'PermanentResidentCard' | ||
} | ||
} | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
], | ||
interactServices: [ | ||
{ | ||
type: VpRequestInteractServiceType.unmediatedPresentation | ||
} | ||
], | ||
isOneTime: true, | ||
callback: [ | ||
{ | ||
url: this.#callbackUrl | ||
} | ||
] | ||
}; | ||
return plainToClass(ExchangeDefinitionDto, exchangeDefinition); | ||
} | ||
} |
134 changes: 134 additions & 0 deletions
134
apps/vc-api/test/vc-api/workflows/resident-card/resident-card.e2e-suite.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* | ||
* Copyright 2021 - 2023 Energy Web Foundation | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { ProofPurpose } from '@sphereon/pex'; | ||
import * as request from 'supertest'; | ||
import * as nock from 'nock'; | ||
import { PresentationDto } from '../../../../src/vc-api/credentials/dtos/presentation.dto'; | ||
import { | ||
ReviewResult, | ||
SubmissionReviewDto | ||
} from '../../../../src/vc-api/exchanges/dtos/submission-review.dto'; | ||
import { ResidentCardIssuance } from './resident-card-issuance.exchange'; | ||
import { ResidentCardPresentation } from './resident-card-presentation.exchange'; | ||
import { app, getContinuationEndpoint, vcApiBaseUrl, walletClient } from '../../../app.e2e-spec'; | ||
import { ProvePresentationOptionsDto } from 'src/vc-api/credentials/dtos/prove-presentation-options.dto'; | ||
|
||
const callbackUrlBase = 'http://example.com'; | ||
const callbackUrlPath = '/endpoint'; | ||
const callbackUrl = `${callbackUrlBase}${callbackUrlPath}`; | ||
|
||
export const residentCardExchangeSuite = () => { | ||
it('should support Resident Card issuance and presentation', async () => { | ||
// As issuer, configure credential issuance exchange | ||
// POST /exchanges | ||
const exchange = new ResidentCardIssuance(callbackUrl); | ||
const numHolderQueriesPriorToIssuance = 2; | ||
const issuanceCallbackScope = nock(callbackUrlBase) | ||
.post(callbackUrlPath) | ||
.times(numHolderQueriesPriorToIssuance) | ||
.reply(201); | ||
await request(app.getHttpServer()) | ||
.post(`${vcApiBaseUrl}/exchanges`) | ||
.send(exchange.getExchangeDefinition()) | ||
.expect(201); | ||
|
||
// As holder, start issuance exchange | ||
// POST /exchanges/{exchangeId} | ||
const issuanceExchangeEndpoint = `${vcApiBaseUrl}/exchanges/${exchange.getExchangeId()}`; | ||
const issuanceVpRequest = await walletClient.startExchange(issuanceExchangeEndpoint, exchange.queryType); | ||
const issuanceExchangeContinuationEndpoint = getContinuationEndpoint(issuanceVpRequest); | ||
expect(issuanceExchangeContinuationEndpoint).toContain(issuanceExchangeEndpoint); | ||
|
||
// As holder, create new DID and presentation to authentication as this DID | ||
const holderDIDDoc = await walletClient.createDID('key'); | ||
const holderVerificationMethod = holderDIDDoc.verificationMethod[0].id; | ||
const options: ProvePresentationOptionsDto = { | ||
verificationMethod: holderVerificationMethod, | ||
proofPurpose: ProofPurpose.authentication, | ||
challenge: issuanceVpRequest.challenge | ||
}; | ||
const didAuthResponse = await request(app.getHttpServer()) | ||
.post(`${vcApiBaseUrl}/presentations/prove/authentication`) | ||
.send({ did: holderDIDDoc.id, options }) | ||
.expect(201); | ||
const didAuthVp = didAuthResponse.body; | ||
expect(didAuthVp).toBeDefined(); | ||
|
||
// As holder, continue exchange by submitting did auth presentation | ||
for (let i = 0; i < numHolderQueriesPriorToIssuance; i++) { | ||
await walletClient.continueExchange(issuanceExchangeContinuationEndpoint, didAuthVp, true, true); | ||
} | ||
issuanceCallbackScope.done(); | ||
|
||
// As the issuer, get the transaction | ||
// TODO TODO TODO!!! How does the issuer know the transactionId? -> Maybe can rely on notification | ||
const urlComponents = issuanceExchangeContinuationEndpoint.split('/'); | ||
const transactionId = urlComponents.pop(); | ||
const transaction = await walletClient.getExchangeTransaction(exchange.getExchangeId(), transactionId); | ||
|
||
// As the issuer, check the result of the transaction verification | ||
expect(transaction.presentationSubmission.verificationResult.verified).toBeTruthy(); | ||
expect(transaction.presentationSubmission.verificationResult.errors).toHaveLength(0); | ||
|
||
// As the issuer, create a presentation to provide the credential to the holder | ||
const issueResult = await exchange.issueCredential(didAuthVp, walletClient); | ||
const issuedVP = issueResult.vp; // VP used to wrapped issued credentials | ||
const submissionReview: SubmissionReviewDto = { | ||
result: ReviewResult.approved, | ||
vp: issuedVP | ||
}; | ||
await walletClient.addSubmissionReview(exchange.getExchangeId(), transactionId, submissionReview); | ||
|
||
// As the holder, check for a reviewed submission | ||
const secondContinuationResponse = await walletClient.continueExchange( | ||
issuanceExchangeContinuationEndpoint, | ||
didAuthVp, | ||
false | ||
); | ||
const issuedVc = secondContinuationResponse.vp.verifiableCredential[0]; | ||
expect(issuedVc).toBeDefined(); | ||
|
||
// Configure presentation exchange | ||
// POST /exchanges | ||
const presentationExchange = new ResidentCardPresentation(callbackUrl); | ||
const presentationCallbackScope = nock(callbackUrlBase).post(callbackUrlPath).reply(201); | ||
const exchangeDef = presentationExchange.getExchangeDefinition(); | ||
await request(app.getHttpServer()).post(`${vcApiBaseUrl}/exchanges`).send(exchangeDef).expect(201); | ||
|
||
// Start presentation exchange | ||
// POST /exchanges/{exchangeId} | ||
const exchangeEndpoint = `${vcApiBaseUrl}/exchanges/${presentationExchange.getExchangeId()}`; | ||
const presentationVpRequest = await walletClient.startExchange( | ||
exchangeEndpoint, | ||
presentationExchange.queryType | ||
); | ||
const presentationExchangeContinuationEndpoint = getContinuationEndpoint(presentationVpRequest); | ||
expect(presentationExchangeContinuationEndpoint).toContain(exchangeEndpoint); | ||
|
||
// Holder should parse VP Request for correct credentials... | ||
// Assume that holder figures out which VC they need and can prep presentation | ||
const presentation: PresentationDto = { | ||
'@context': [ | ||
'https://www.w3.org/2018/credentials/v1', | ||
'https://www.w3.org/2018/credentials/examples/v1' | ||
], | ||
type: ['VerifiablePresentation'], | ||
verifiableCredential: [issuedVc], | ||
holder: holderDIDDoc.id | ||
}; | ||
const presentationOptions: ProvePresentationOptionsDto = { | ||
verificationMethod: holderVerificationMethod, | ||
proofPurpose: ProofPurpose.authentication, | ||
created: '2021-11-16T14:52:19.514Z', | ||
challenge: presentationVpRequest.challenge | ||
}; | ||
const vp = await walletClient.provePresentation({ presentation, options: presentationOptions }); | ||
|
||
// Holder submits presentation | ||
await walletClient.continueExchange(presentationExchangeContinuationEndpoint, vp, false); | ||
presentationCallbackScope.done(); | ||
}); | ||
}; |