diff --git a/docs/api/classes/GnosisIam.GnosisIam-1.md b/docs/api/classes/GnosisIam.GnosisIam-1.md index 72698102..55abae20 100644 --- a/docs/api/classes/GnosisIam.GnosisIam-1.md +++ b/docs/api/classes/GnosisIam.GnosisIam-1.md @@ -1398,6 +1398,7 @@ ___ | Name | Type | | :------ | :------ | | `__namedParameters` | `Object` | +| `__namedParameters.claimParams?` | `Record`<`string`, `string`\> | | `__namedParameters.id` | `string` | | `__namedParameters.registrationTypes` | [`RegistrationTypes`](../enums/cacheServerClient_cacheServerClient_types.RegistrationTypes.md)[] | | `__namedParameters.requester` | `string` | diff --git a/docs/api/classes/iam.IAM.md b/docs/api/classes/iam.IAM.md index e42c60e8..4b1ff556 100644 --- a/docs/api/classes/iam.IAM.md +++ b/docs/api/classes/iam.IAM.md @@ -1203,6 +1203,7 @@ ___ | Name | Type | | :------ | :------ | | `__namedParameters` | `Object` | +| `__namedParameters.claimParams?` | `Record`<`string`, `string`\> | | `__namedParameters.id` | `string` | | `__namedParameters.registrationTypes` | [`RegistrationTypes`](../enums/cacheServerClient_cacheServerClient_types.RegistrationTypes.md)[] | | `__namedParameters.requester` | `string` | diff --git a/docs/api/interfaces/cacheServerClient_cacheServerClient_types.ClaimData.md b/docs/api/interfaces/cacheServerClient_cacheServerClient_types.ClaimData.md index 48e8531d..2690f7c5 100644 --- a/docs/api/interfaces/cacheServerClient_cacheServerClient_types.ClaimData.md +++ b/docs/api/interfaces/cacheServerClient_cacheServerClient_types.ClaimData.md @@ -12,12 +12,19 @@ ### Properties +- [claimParams](cacheServerClient_cacheServerClient_types.ClaimData.md#claimparams) - [claimType](cacheServerClient_cacheServerClient_types.ClaimData.md#claimtype) - [claimTypeVersion](cacheServerClient_cacheServerClient_types.ClaimData.md#claimtypeversion) - [profile](cacheServerClient_cacheServerClient_types.ClaimData.md#profile) ## Properties +### claimParams + +• `Optional` **claimParams**: `Record`<`string`, `string`\> + +___ + ### claimType • `Optional` **claimType**: `string` diff --git a/src/cacheServerClient/cacheServerClient.types.ts b/src/cacheServerClient/cacheServerClient.types.ts index b3d40f56..dd39c237 100644 --- a/src/cacheServerClient/cacheServerClient.types.ts +++ b/src/cacheServerClient/cacheServerClient.types.ts @@ -92,6 +92,7 @@ export interface ClaimData extends Record { profile?: Profile; claimType?: string; claimTypeVersion?: number; + claimParams?: Record; } export enum Order { diff --git a/src/iam.ts b/src/iam.ts index 72f1029a..f79996cc 100644 --- a/src/iam.ts +++ b/src/iam.ts @@ -1627,12 +1627,14 @@ export class IAM extends IAMBase { id, subjectAgreement, registrationTypes, + claimParams, }: { requester: string; token: string; id: string; subjectAgreement: string; registrationTypes: RegistrationTypes[]; + claimParams?: Record; }) { if (!this._did) { throw new Error(ERROR_MESSAGES.USER_NOT_LOGGED_IN); @@ -1661,7 +1663,7 @@ export class IAM extends IAMBase { const publicClaim: IPublicClaim = { did: sub, signer: this._did, - claimData, + claimData: { ...claimData, ...(claimParams && { claimParams }) }, }; message.issuedToken = await this.issuePublicClaim({ publicClaim, diff --git a/test/claimsTests/claims.testSuite.ts b/test/claimsTests/claims.testSuite.ts index d0ed4734..feb1ab08 100644 --- a/test/claimsTests/claims.testSuite.ts +++ b/test/claimsTests/claims.testSuite.ts @@ -1,8 +1,10 @@ import { createIdentityTests } from "./createIdentityTests"; import { enrollmentClaimsTests } from "./enrollmentClaimsTests"; import { selfsignedClaimsTests } from "./selfsignedClaimsTests"; +import { issueClaimRequestTests } from "./issueClaimRequestTests"; export const claimsTests = () => { + describe("Issue Claim Request tests", issueClaimRequestTests); describe("Selfsigned claim tests", selfsignedClaimsTests); describe("Enrollment claims tests", enrollmentClaimsTests); describe("Create Identity tests", createIdentityTests); diff --git a/test/claimsTests/issueClaimRequestTests.ts b/test/claimsTests/issueClaimRequestTests.ts new file mode 100644 index 00000000..d33fd2e2 --- /dev/null +++ b/test/claimsTests/issueClaimRequestTests.ts @@ -0,0 +1,97 @@ +import { Wallet } from "ethers"; +import { Methods } from "@ew-did-registry/did"; +import jwtDecode from "jwt-decode"; + +import { IAM, IRoleDefinition, RegistrationTypes } from "../../src/iam-client-lib"; +import { replenish, provider } from "../setup_contracts"; +import { createIam, root, rootOwner } from "../iam.test"; +import { mockJsonCodec, mockNats, restoreJsonCodec, restoreNats } from "../testUtils/mocks"; + +export const issueClaimRequestTests = (): void => { + const serviceProvider = Wallet.createRandom().connect(provider); + const patron = Wallet.createRandom().connect(provider); + const patronDID = `did:${Methods.Erc1056}:${patron.address}`; + + const orgName = "orgname"; + let serviceProviderIam: IAM; + let patronIam: IAM; + let rootOwnerIam: IAM; + const patronRole = "patronRole"; + + beforeAll(async () => { + await replenish(serviceProvider.address); + serviceProviderIam = await createIam(serviceProvider.privateKey, { createDocument: true }); + await replenish(patron.address); + patronIam = await createIam(patron.privateKey, { createDocument: true }); + await replenish(rootOwner.address); + rootOwnerIam = await createIam(rootOwner.privateKey, { createDocument: true }); + + const data: IRoleDefinition = { + fields: [], + issuer: { + issuerType: "DID", + did: [`did:${Methods.Erc1056}:${serviceProvider.address}`], + }, + metadata: [], + roleName: patronRole, + roleType: "test", + version: 1, + enrolmentPreconditions: [], + }; + + await rootOwnerIam.createRole({ + roleName: patronRole, + namespace: root, + data, + }); + await rootOwnerIam.createOrganization({ + orgName, + namespace: root, + data: { orgName }, + returnSteps: false, + }); + await rootOwnerIam.changeOrgOwnership({ + namespace: `${orgName}.${root}`, + newOwner: serviceProvider.address, + }); + }); + + afterAll(() => { + restoreNats(); + restoreJsonCodec(); + }); + + test("should issue claim request with additional params", async () => { + const { publish } = mockNats(); + const jsonCodec = mockJsonCodec(); + const registrationTypes = [RegistrationTypes.OnChain, RegistrationTypes.OffChain]; + + await patronIam.createClaimRequest({ + claim: { claimType: `${patronRole}.${root}`, claimTypeVersion: 1, fields: [] }, + registrationTypes, + }); + const [, encodedMsg1] = publish.mock.calls.pop(); + const { id, subjectAgreement, token } = jsonCodec.decode(encodedMsg1) as { + id; + subjectAgreement; + token; + }; + const claimParams: Record = { + "document ID": "ASG 123222", + DOB: "1990-01-07", + }; + await serviceProviderIam.issueClaimRequest({ + id, + registrationTypes, + requester: patronDID, + subjectAgreement, + token, + claimParams, + }); + + const [, encodedMsg2] = publish.mock.calls.pop(); + const { issuedToken } = jsonCodec.decode(encodedMsg2) as { issuedToken }; + const data = jwtDecode<{ claimData: { claimParams } }>(issuedToken); + expect(data.claimData.claimParams).toEqual(claimParams); + }); +}; diff --git a/test/organization.testSuite.ts b/test/organization.testSuite.ts index d0001fd4..59dc0967 100644 --- a/test/organization.testSuite.ts +++ b/test/organization.testSuite.ts @@ -9,85 +9,86 @@ import { chainConfigs } from "../src/iam/chainConfig"; export const org1 = "org1"; export const orgTests = () => { - const orgName = "Organization 1"; + const orgName = "Organization 1"; - test("can create organization", async () => { - await rootOwnerIam.createOrganization({ orgName: org1, namespace: root, data: { orgName } }); + test("can create organization", async () => { + await rootOwnerIam.createOrganization({ orgName: org1, namespace: root, data: { orgName } }); - expect(await rootOwnerIam.checkExistenceOfDomain({ domain: `${org1}.${root}` })).toBe(true); - expect( - await rootOwnerIam.checkExistenceOfDomain({ - domain: `${ENSNamespaceTypes.Application}.${org1}.${root}` - }) - ).toBe(true); - expect( - await rootOwnerIam.checkExistenceOfDomain({ domain: `${ENSNamespaceTypes.Roles}.${org1}.${root}` }) - ).toBe(true); - expect(await rootOwnerIam.getSubdomains({ domain: root })).toContain(`${org1}.${root}`); - expect(await rootOwnerIam.isOwner({ domain: `${org1}.${root}`, user: rootOwner.address })); - }); - - test("suborganization can be created", async () => { - const org1_1 = "org1-1"; - await rootOwnerIam.createOrganization({ - orgName: org1_1, - data: { - orgName: "Organization 1_1" - }, - namespace: `${org1}.${root}` + expect(await rootOwnerIam.checkExistenceOfDomain({ domain: `${org1}.${root}` })).toBe(true); + expect( + await rootOwnerIam.checkExistenceOfDomain({ + domain: `${ENSNamespaceTypes.Application}.${org1}.${root}`, + }), + ).toBe(true); + expect( + await rootOwnerIam.checkExistenceOfDomain({ domain: `${ENSNamespaceTypes.Roles}.${org1}.${root}` }), + ).toBe(true); + expect(await rootOwnerIam.getSubdomains({ domain: root })).toContain(`${org1}.${root}`); + expect(await rootOwnerIam.isOwner({ domain: `${org1}.${root}`, user: rootOwner.address })); }); - expect(await rootOwnerIam.checkExistenceOfDomain({ domain: `${org1_1}.${org1}.${root}` })).toBe(true); - expect(await rootOwnerIam.getSubdomains({ domain: `${org1}.${root}` })).toContain( - `${org1_1}.${org1}.${root}` - ); - }); + test("suborganization can be created", async () => { + const org1_1 = "org1-1"; + await rootOwnerIam.createOrganization({ + orgName: org1_1, + data: { + orgName: "Organization 1_1", + }, + namespace: `${org1}.${root}`, + }); - test("org role can be created", async () => { - const namespace = `${org1}.${root}`; - const roleName = `${org1}-role1`; - const roleDomain = `${roleName}.${namespace}`; - const roleNode = namehash(roleDomain); + expect(await rootOwnerIam.checkExistenceOfDomain({ domain: `${org1_1}.${org1}.${root}` })).toBe(true); + expect(await rootOwnerIam.getSubdomains({ domain: `${org1}.${root}` })).toContain(`${org1_1}.${org1}.${root}`); + }); - const data: IRoleDefinition = { - fields: [], - issuer: { - issuerType: "DID", - did: [`did:${Methods.Erc1056}:${rootOwner.address}`] - }, - metadata: [], - roleName, - roleType: "test", - version: 1, - enrolmentPreconditions: [{ - type: PreconditionTypes.Role, - conditions: [roleDomain] // Circular condition but sufficient for test - }] - }; + test("org role can be created", async () => { + const namespace = `${org1}.${root}`; + const roleName = `${org1}-role1`; + const roleDomain = `${roleName}.${namespace}`; + const roleNode = namehash(roleDomain); - await rootOwnerIam.createRole({ - roleName, - namespace, - data - }); + const data: IRoleDefinition = { + fields: [], + issuer: { + issuerType: "DID", + did: [`did:${Methods.Erc1056}:${rootOwner.address}`], + }, + metadata: [], + roleName, + roleType: "test", + version: 1, + enrolmentPreconditions: [ + { + type: PreconditionTypes.Role, + conditions: [roleDomain], // Circular condition but sufficient for test + }, + ], + }; - const roleDef = await rootOwnerIam.getDefinition({ - namespace: roleDomain, - type: ENSNamespaceTypes.Roles - }); - expect(roleDef).toMatchObject(data); + await rootOwnerIam.createRole({ + roleName, + namespace, + data, + }); + + const roleDef = await rootOwnerIam.getDefinition({ + namespace: roleDomain, + type: ENSNamespaceTypes.Roles, + }); + expect(roleDef).toMatchObject(data); - const reverseName = await ensResolver.name(roleNode); - expect(reverseName).toEqual(roleDomain); + const reverseName = await ensResolver.name(roleNode); + expect(reverseName).toEqual(roleDomain); - const resolver = await ensRegistry.resolver(roleNode); - const actualTypes = (await rootOwnerIam.registrationTypesOfRoles([roleDomain]))[roleDomain]; - const { chainId } = await provider.getNetwork(); - const expectedTypes = resolver === chainConfigs[chainId].ensPublicResolverAddress ? - new Set([RegistrationTypes.OffChain]) : - resolver === chainConfigs[chainId].ensResolverAddress ? - new Set([RegistrationTypes.OffChain, RegistrationTypes.OnChain]) : - []; - expect(actualTypes).toEqual(expectedTypes); - }); + const resolver = await ensRegistry.resolver(roleNode); + const actualTypes = (await rootOwnerIam.registrationTypesOfRoles([roleDomain]))[roleDomain]; + const { chainId } = await provider.getNetwork(); + const expectedTypes = + resolver === chainConfigs[chainId].ensPublicResolverAddress + ? new Set([RegistrationTypes.OffChain]) + : resolver === chainConfigs[chainId].ensResolverAddress + ? new Set([RegistrationTypes.OffChain, RegistrationTypes.OnChain]) + : []; + expect(actualTypes).toEqual(expectedTypes); + }); }; diff --git a/tsconfig.json b/tsconfig.json index 373982f6..e5d72130 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,36 +1,36 @@ { - "compilerOptions": { - "allowJs": false, - "allowSyntheticDefaultImports": true, - "alwaysStrict": true, - "declaration": true, - "downlevelIteration": true, - "emitDecoratorMetadata": true, - "esModuleInterop": true, - "experimentalDecorators": true, - "forceConsistentCasingInFileNames": true, - "importHelpers": true, - "lib": ["es2017", "dom", "esnext.asynciterable"], - "module": "esnext", - "moduleResolution": "node", - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noImplicitThis": true, - "noLib": false, - "noUnusedLocals": true, - "noUnusedParameters": false, - "preserveSymlinks": true, - "removeComments": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "strictFunctionTypes": true, - "strictNullChecks": true, - "strictPropertyInitialization": false, - "suppressImplicitAnyIndexErrors": true, - "target": "es6" - }, - "include": ["src/**/*", "ethers/**/*"] + "compilerOptions": { + "allowJs": false, + "allowSyntheticDefaultImports": true, + "alwaysStrict": true, + "declaration": true, + "downlevelIteration": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "importHelpers": true, + "lib": ["es2017", "dom", "esnext.asynciterable"], + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noImplicitThis": true, + "noLib": false, + "noUnusedLocals": true, + "noUnusedParameters": false, + "preserveSymlinks": true, + "removeComments": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "strictPropertyInitialization": false, + "suppressImplicitAnyIndexErrors": true, + "target": "es6" + }, + "include": ["src/**/*", "ethers/**/*", "test/**/*"] }