gdc-common-utils-ts 2.5.3 → 2.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/data-capabilities.d.ts +7 -7
- package/dist/constants/device.d.ts +6 -0
- package/dist/constants/device.js +5 -0
- package/dist/constants/employee-lifecycle.d.ts +52 -0
- package/dist/constants/employee-lifecycle.js +48 -0
- package/dist/constants/healthcare.d.ts +2 -0
- package/dist/constants/healthcare.js +2 -0
- package/dist/constants/http.d.ts +28 -0
- package/dist/constants/http.js +28 -0
- package/dist/constants/identity-auth.d.ts +57 -0
- package/dist/constants/identity-auth.js +57 -0
- package/dist/constants/index.d.ts +4 -0
- package/dist/constants/index.js +4 -0
- package/dist/constants/organization-application.d.ts +27 -0
- package/dist/constants/organization-application.js +29 -0
- package/dist/constants/verifiable-credentials.d.ts +6 -2
- package/dist/constants/verifiable-credentials.js +6 -2
- package/dist/examples/inter-tenant-access-contract.d.ts +7 -7
- package/dist/examples/inter-tenant-access-contract.js +3 -2
- package/dist/examples/license.d.ts +111 -0
- package/dist/examples/license.js +85 -2
- package/dist/examples/shared.d.ts +33 -0
- package/dist/examples/shared.js +32 -0
- package/dist/models/device-license.d.ts +2 -2
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.js +2 -0
- package/dist/models/inter-tenant-access-contract.d.ts +1 -1
- package/dist/models/inter-tenant-access-contract.js +1 -1
- package/dist/models/organization-employee-lifecycle.d.ts +45 -0
- package/dist/models/organization-employee-lifecycle.js +1 -0
- package/dist/models/test-network-organization-registration-form.d.ts +71 -0
- package/dist/models/test-network-organization-registration-form.js +43 -0
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/index.js +3 -1
- package/dist/utils/jwk-thumbprint.d.ts +2 -0
- package/dist/utils/jwk-thumbprint.js +8 -0
- package/dist/utils/legal-organization-verification-transaction.d.ts +5 -0
- package/dist/utils/legal-organization-verification-transaction.js +3 -0
- package/dist/utils/license.js +3 -3
- package/dist/utils/organization-employee-lifecycle.d.ts +12 -0
- package/dist/utils/organization-employee-lifecycle.js +125 -0
- package/dist/utils/{organization-registration-authorization.d.ts → organization-test-network-credential.d.ts} +4 -4
- package/dist/utils/{organization-registration-authorization.js → organization-test-network-credential.js} +6 -6
- package/dist/utils/test-network-organization-credentials.d.ts +35 -0
- package/dist/utils/test-network-organization-credentials.js +144 -0
- package/package.json +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { ClaimsIndividualProductSchemaorg, ClaimsPersonSchemaorg } from '../constants/schemaorg.js';
|
|
2
|
+
import { DeviceBindingStatuses } from '../constants/device.js';
|
|
3
|
+
import { EmployeeDirectoryStatuses } from '../constants/employee-lifecycle.js';
|
|
4
|
+
import { IdentityAuthRequestFields, IdentityAuthResponseEntryTypes, } from '../constants/identity-auth.js';
|
|
5
|
+
import { DEFAULT_LICENSE_DEVICE_ALLOWANCE } from './license.js';
|
|
6
|
+
/** Builds the exact identity/auth revoke request body from a typed target. */
|
|
7
|
+
export function buildEmployeeDeviceRevocationBody(target) {
|
|
8
|
+
return Object.freeze({
|
|
9
|
+
[IdentityAuthRequestFields.LicenseId]: target.licenseId,
|
|
10
|
+
[IdentityAuthRequestFields.ClientId]: target.clientId,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
/** Reads an employee activation credential from canonical and legacy GW envelopes. */
|
|
14
|
+
export function readEmployeeActivationCode(value) {
|
|
15
|
+
for (const candidate of listNestedRecords(value)) {
|
|
16
|
+
if (candidate.type === IdentityAuthResponseEntryTypes.LicenseIssued) {
|
|
17
|
+
const issuedId = text(candidate.id);
|
|
18
|
+
if (issuedId)
|
|
19
|
+
return issuedId;
|
|
20
|
+
}
|
|
21
|
+
const claims = record(candidate.claims) || record(record(candidate.meta)?.claims);
|
|
22
|
+
const serial = text(claims?.[ClaimsIndividualProductSchemaorg.serialNumber]);
|
|
23
|
+
if (serial)
|
|
24
|
+
return serial;
|
|
25
|
+
}
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
/** Extracts GW search rows regardless of async job envelope depth. */
|
|
29
|
+
export function extractGwSearchResources(value) {
|
|
30
|
+
for (const candidate of listNestedRecords(value)) {
|
|
31
|
+
const data = record(candidate.resource)?.data || candidate.data;
|
|
32
|
+
if (Array.isArray(data))
|
|
33
|
+
return data.map(record).filter(isRecord);
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
/** Combines employee directory and license search results into one typed view. */
|
|
38
|
+
export function projectOrganizationEmployeeLifecycle(input) {
|
|
39
|
+
const licenses = extractGwSearchResources(input.licenseResponse);
|
|
40
|
+
return extractGwSearchResources(input.employeeResponse).map((employee) => {
|
|
41
|
+
const claims = record(employee.claims) || record(record(employee.meta)?.claims) || {};
|
|
42
|
+
const resourceId = text(employee.id);
|
|
43
|
+
const employeeDid = text(claims[ClaimsPersonSchemaorg.identifier]);
|
|
44
|
+
const license = licenses.find((candidate) => {
|
|
45
|
+
const meta = record(candidate.meta) || {};
|
|
46
|
+
const subjectId = text(meta.subjectId);
|
|
47
|
+
return subjectId === resourceId || subjectId === employeeDid;
|
|
48
|
+
});
|
|
49
|
+
const status = normalizeEmployeeDirectoryStatus(text(employee.status) || text(record(employee.meta)?.status));
|
|
50
|
+
if (!license)
|
|
51
|
+
return {
|
|
52
|
+
resourceId,
|
|
53
|
+
email: text(claims[ClaimsPersonSchemaorg.email]),
|
|
54
|
+
roleCode: text(claims[ClaimsPersonSchemaorg.hasOccupationalRoleValue]),
|
|
55
|
+
employeeDid,
|
|
56
|
+
status,
|
|
57
|
+
};
|
|
58
|
+
const meta = record(license.meta) || {};
|
|
59
|
+
const bindings = Array.isArray(meta.deviceBindings) ? meta.deviceBindings.map(record).filter(isRecord) : [];
|
|
60
|
+
const devices = bindings.map((binding) => {
|
|
61
|
+
const info = record(binding.deviceInfo) || {};
|
|
62
|
+
const clientId = text(binding.clientId);
|
|
63
|
+
return {
|
|
64
|
+
clientId,
|
|
65
|
+
name: text(info.model) || text(info.clientInstanceId) || clientId,
|
|
66
|
+
status: binding.status === DeviceBindingStatuses.Revoked
|
|
67
|
+
? DeviceBindingStatuses.Revoked
|
|
68
|
+
: DeviceBindingStatuses.Active,
|
|
69
|
+
};
|
|
70
|
+
}).filter((device) => Boolean(device.clientId));
|
|
71
|
+
return {
|
|
72
|
+
resourceId,
|
|
73
|
+
email: text(claims[ClaimsPersonSchemaorg.email]),
|
|
74
|
+
roleCode: text(claims[ClaimsPersonSchemaorg.hasOccupationalRoleValue]),
|
|
75
|
+
employeeDid,
|
|
76
|
+
status,
|
|
77
|
+
license: {
|
|
78
|
+
id: text(license.id),
|
|
79
|
+
status: text(meta.status),
|
|
80
|
+
maxDevices: positiveInteger(meta.maxDevices) || DEFAULT_LICENSE_DEVICE_ALLOWANCE,
|
|
81
|
+
activeDevices: devices.filter((device) => device.status === DeviceBindingStatuses.Active).length,
|
|
82
|
+
devices,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}).filter((employee) => Boolean(employee.resourceId));
|
|
86
|
+
}
|
|
87
|
+
function normalizeEmployeeDirectoryStatus(value) {
|
|
88
|
+
return Object.values(EmployeeDirectoryStatuses).includes(value)
|
|
89
|
+
? value
|
|
90
|
+
: EmployeeDirectoryStatuses.Unknown;
|
|
91
|
+
}
|
|
92
|
+
function listNestedRecords(root) {
|
|
93
|
+
const result = [];
|
|
94
|
+
const pending = [root];
|
|
95
|
+
const visited = new Set();
|
|
96
|
+
while (pending.length > 0) {
|
|
97
|
+
const value = pending.shift();
|
|
98
|
+
if (!value || typeof value !== 'object' || visited.has(value))
|
|
99
|
+
continue;
|
|
100
|
+
visited.add(value);
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
pending.push(...value);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const item = value;
|
|
106
|
+
result.push(item);
|
|
107
|
+
pending.push(...Object.values(item));
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
function record(value) {
|
|
112
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
113
|
+
? value
|
|
114
|
+
: undefined;
|
|
115
|
+
}
|
|
116
|
+
function isRecord(value) {
|
|
117
|
+
return Boolean(value);
|
|
118
|
+
}
|
|
119
|
+
function text(value) {
|
|
120
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
121
|
+
}
|
|
122
|
+
function positiveInteger(value) {
|
|
123
|
+
const parsed = Number(value);
|
|
124
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
125
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VerifiableCredentialV2 } from '../models/verifiable-credential';
|
|
2
|
-
/** Lifecycle of the one
|
|
2
|
+
/** Lifecycle of the one Test Network activation licence delivered to the legal postal address. */
|
|
3
3
|
export declare const PostalActivationLicenseStatuses: Readonly<{
|
|
4
4
|
readonly Issued: "issued";
|
|
5
5
|
readonly Delivered: "delivered";
|
|
@@ -35,7 +35,7 @@ export type PostalActivationLicenseBinding = Readonly<{
|
|
|
35
35
|
redeemedAt?: string;
|
|
36
36
|
}>;
|
|
37
37
|
/** Input for the credential returned out-of-band to the applicant controller. */
|
|
38
|
-
export type
|
|
38
|
+
export type OrganizationTestNetworkCredentialInput = Readonly<{
|
|
39
39
|
issuerDid: string;
|
|
40
40
|
subjectDid: string;
|
|
41
41
|
credentialId: string;
|
|
@@ -56,7 +56,7 @@ export type OrganizationRegistrationAuthorizationCredentialInput = Readonly<{
|
|
|
56
56
|
* to `Organization/_transaction`. The postal code itself never appears in the
|
|
57
57
|
* credential; its confirmed, purpose-bound licence record is referenced.
|
|
58
58
|
*/
|
|
59
|
-
export declare function
|
|
59
|
+
export declare function buildOrganizationTestNetworkCredential(input: OrganizationTestNetworkCredentialInput): VerifiableCredentialV2;
|
|
60
60
|
/**
|
|
61
61
|
* Applies the only legal state transitions for the purpose-bound postal code.
|
|
62
62
|
* Confirmation proves delivery but deliberately does not consume the code;
|
|
@@ -68,4 +68,4 @@ export declare function transitionPostalActivationLicense(current: PostalActivat
|
|
|
68
68
|
* same credential payload with the complete `proof` property removed, so a
|
|
69
69
|
* later counter-proof cannot invalidate an earlier one.
|
|
70
70
|
*/
|
|
71
|
-
export declare function
|
|
71
|
+
export declare function canonicalizeOrganizationTestNetworkCredential(credential: VerifiableCredentialV2): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContractCredentialTypes, W3cCredentialContexts, W3cCredentialTypes, } from '../constants/verifiable-credentials.js';
|
|
2
|
-
/** Lifecycle of the one
|
|
2
|
+
/** Lifecycle of the one Test Network activation licence delivered to the legal postal address. */
|
|
3
3
|
export const PostalActivationLicenseStatuses = Object.freeze({
|
|
4
4
|
Issued: 'issued',
|
|
5
5
|
Delivered: 'delivered',
|
|
@@ -18,7 +18,7 @@ function required(value, name) {
|
|
|
18
18
|
* to `Organization/_transaction`. The postal code itself never appears in the
|
|
19
19
|
* credential; its confirmed, purpose-bound licence record is referenced.
|
|
20
20
|
*/
|
|
21
|
-
export function
|
|
21
|
+
export function buildOrganizationTestNetworkCredential(input) {
|
|
22
22
|
const applicationId = required(input.applicationId, 'applicationId');
|
|
23
23
|
const organizationIdentifier = required(input.organizationIdentifier, 'organizationIdentifier');
|
|
24
24
|
const controllerEmail = required(input.controllerEmail, 'controllerEmail').toLowerCase();
|
|
@@ -37,7 +37,7 @@ export function buildOrganizationRegistrationAuthorizationCredential(input) {
|
|
|
37
37
|
id: required(input.credentialId, 'credentialId'),
|
|
38
38
|
type: [
|
|
39
39
|
W3cCredentialTypes.VerifiableCredential,
|
|
40
|
-
ContractCredentialTypes.
|
|
40
|
+
ContractCredentialTypes.OrganizationTestNetworkCredential,
|
|
41
41
|
],
|
|
42
42
|
issuer: required(input.issuerDid, 'issuerDid'),
|
|
43
43
|
credentialSubject: {
|
|
@@ -109,9 +109,9 @@ function canonicalizeValue(value) {
|
|
|
109
109
|
* same credential payload with the complete `proof` property removed, so a
|
|
110
110
|
* later counter-proof cannot invalidate an earlier one.
|
|
111
111
|
*/
|
|
112
|
-
export function
|
|
113
|
-
if (!credential.type.includes(ContractCredentialTypes.
|
|
114
|
-
throw new Error('Credential is not an organization
|
|
112
|
+
export function canonicalizeOrganizationTestNetworkCredential(credential) {
|
|
113
|
+
if (!credential.type.includes(ContractCredentialTypes.OrganizationTestNetworkCredential)) {
|
|
114
|
+
throw new Error('Credential is not an organization Test Network admission.');
|
|
115
115
|
}
|
|
116
116
|
return JSON.stringify(canonicalizeValue(credential));
|
|
117
117
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { VerifiableCredentialV2 } from '../models/verifiable-credential';
|
|
2
|
+
export declare const TestNetworkOrganizationCredentialTypes: readonly ["OrganizationCredential", "LegalRepresentativeCredential", "ServiceControllerCredential"];
|
|
3
|
+
export type TestNetworkOrganizationCredentialSet = readonly [
|
|
4
|
+
VerifiableCredentialV2,
|
|
5
|
+
VerifiableCredentialV2,
|
|
6
|
+
VerifiableCredentialV2
|
|
7
|
+
];
|
|
8
|
+
export type BuildTestNetworkOrganizationCredentialSetInput = Readonly<{
|
|
9
|
+
issuerDid: string;
|
|
10
|
+
organizationDid: string;
|
|
11
|
+
applicationId: string;
|
|
12
|
+
validFrom: string;
|
|
13
|
+
validUntil: string;
|
|
14
|
+
pdfSha256: string;
|
|
15
|
+
documentVersion: string;
|
|
16
|
+
legalName: string;
|
|
17
|
+
organizationIdentifier: string;
|
|
18
|
+
identifierType: string;
|
|
19
|
+
addressCountry: string;
|
|
20
|
+
serviceCategory: string;
|
|
21
|
+
legalRepresentativeEmail: string;
|
|
22
|
+
legalRepresentativeFullName: string;
|
|
23
|
+
controllerEmail: string;
|
|
24
|
+
controllerKeyMaterial: string;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Builds the three schema.org credentials used by organization activation in
|
|
28
|
+
* both environments. In this profile the reviewer/host-provider DID is the
|
|
29
|
+
* issuer, every subject is restricted to `test-network`, and the immutable PDF
|
|
30
|
+
* digest is common evidence. The function creates drafts; a reviewer proof
|
|
31
|
+
* must still be added to every returned credential.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildTestNetworkOrganizationCredentialSet(input: BuildTestNetworkOrganizationCredentialSetInput): TestNetworkOrganizationCredentialSet;
|
|
34
|
+
/** Deterministic detached-proof payload for one of the three Test Network VCs. */
|
|
35
|
+
export declare function canonicalizeTestNetworkOrganizationCredential(credential: VerifiableCredentialV2): string;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ActivationCredentialTypes, EnvironmentCredentialTypes, W3cCredentialContexts, W3cCredentialTypes, } from '../constants/verifiable-credentials.js';
|
|
2
|
+
import { buildStableActorIdentifier } from './actor-identifier.js';
|
|
3
|
+
export const TestNetworkOrganizationCredentialTypes = Object.freeze([
|
|
4
|
+
ActivationCredentialTypes.OrganizationCredential,
|
|
5
|
+
ActivationCredentialTypes.LegalRepresentativeCredential,
|
|
6
|
+
ActivationCredentialTypes.ServiceControllerCredential,
|
|
7
|
+
]);
|
|
8
|
+
function required(value, name) {
|
|
9
|
+
const normalized = String(value || '').trim();
|
|
10
|
+
if (!normalized)
|
|
11
|
+
throw new Error(`Test Network organization credentials require ${name}.`);
|
|
12
|
+
return normalized;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Builds the three schema.org credentials used by organization activation in
|
|
16
|
+
* both environments. In this profile the reviewer/host-provider DID is the
|
|
17
|
+
* issuer, every subject is restricted to `test-network`, and the immutable PDF
|
|
18
|
+
* digest is common evidence. The function creates drafts; a reviewer proof
|
|
19
|
+
* must still be added to every returned credential.
|
|
20
|
+
*/
|
|
21
|
+
export function buildTestNetworkOrganizationCredentialSet(input) {
|
|
22
|
+
const issuer = required(input.issuerDid, 'issuerDid');
|
|
23
|
+
const organizationDid = required(input.organizationDid, 'organizationDid');
|
|
24
|
+
const applicationId = required(input.applicationId, 'applicationId');
|
|
25
|
+
const validFrom = required(input.validFrom, 'validFrom');
|
|
26
|
+
const validUntil = required(input.validUntil, 'validUntil');
|
|
27
|
+
const legalName = required(input.legalName, 'legalName');
|
|
28
|
+
const organizationIdentifier = required(input.organizationIdentifier, 'organizationIdentifier');
|
|
29
|
+
const identifierType = required(input.identifierType, 'identifierType');
|
|
30
|
+
const addressCountry = required(input.addressCountry, 'addressCountry').toUpperCase();
|
|
31
|
+
const serviceCategory = required(input.serviceCategory, 'serviceCategory');
|
|
32
|
+
const legalRepresentativeEmail = required(input.legalRepresentativeEmail, 'legalRepresentativeEmail').toLowerCase();
|
|
33
|
+
const legalRepresentativeFullName = required(input.legalRepresentativeFullName, 'legalRepresentativeFullName');
|
|
34
|
+
const controllerEmail = required(input.controllerEmail, 'controllerEmail').toLowerCase();
|
|
35
|
+
const evidence = [{
|
|
36
|
+
type: 'DocumentVerification',
|
|
37
|
+
id: `urn:sha256:${required(input.pdfSha256, 'pdfSha256')}`,
|
|
38
|
+
documentVersion: required(input.documentVersion, 'documentVersion'),
|
|
39
|
+
applicationId,
|
|
40
|
+
targetNetwork: 'test-network',
|
|
41
|
+
}];
|
|
42
|
+
const base = {
|
|
43
|
+
'@context': [W3cCredentialContexts.V2, 'https://schema.org'],
|
|
44
|
+
issuer,
|
|
45
|
+
validFrom,
|
|
46
|
+
validUntil,
|
|
47
|
+
evidence,
|
|
48
|
+
};
|
|
49
|
+
const organization = {
|
|
50
|
+
...base,
|
|
51
|
+
id: `urn:uuid:${applicationId}:organization`,
|
|
52
|
+
type: [
|
|
53
|
+
W3cCredentialTypes.VerifiableCredential,
|
|
54
|
+
ActivationCredentialTypes.OrganizationCredential,
|
|
55
|
+
EnvironmentCredentialTypes.TestNetworkCredential,
|
|
56
|
+
],
|
|
57
|
+
credentialSubject: {
|
|
58
|
+
id: organizationDid,
|
|
59
|
+
'@type': 'Organization',
|
|
60
|
+
legalName,
|
|
61
|
+
identifier: { additionalType: identifierType, value: organizationIdentifier },
|
|
62
|
+
...(identifierType === 'taxID' ? { taxID: organizationIdentifier } : {}),
|
|
63
|
+
address: { '@type': 'PostalAddress', addressCountry },
|
|
64
|
+
makesOffer: { '@type': 'Offer', category: serviceCategory },
|
|
65
|
+
targetNetwork: 'test-network',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const representative = {
|
|
69
|
+
...base,
|
|
70
|
+
id: `urn:uuid:${applicationId}:legal-representative`,
|
|
71
|
+
type: [
|
|
72
|
+
W3cCredentialTypes.VerifiableCredential,
|
|
73
|
+
ActivationCredentialTypes.PersonCredential,
|
|
74
|
+
ActivationCredentialTypes.LegalRepresentativeCredential,
|
|
75
|
+
EnvironmentCredentialTypes.TestNetworkCredential,
|
|
76
|
+
],
|
|
77
|
+
credentialSubject: {
|
|
78
|
+
id: buildStableActorIdentifier({ contactKind: 'email', contact: legalRepresentativeEmail }),
|
|
79
|
+
'@type': 'Person',
|
|
80
|
+
name: legalRepresentativeFullName,
|
|
81
|
+
sameAs: buildStableActorIdentifier({ contactKind: 'email', contact: legalRepresentativeEmail }),
|
|
82
|
+
hasOccupation: { '@type': 'Occupation', occupationalCategory: 'ISCO-08|1120' },
|
|
83
|
+
memberOf: {
|
|
84
|
+
'@type': 'Organization',
|
|
85
|
+
legalName,
|
|
86
|
+
...(identifierType === 'taxID'
|
|
87
|
+
? { taxID: organizationIdentifier }
|
|
88
|
+
: { identifier: { additionalType: identifierType, value: organizationIdentifier } }),
|
|
89
|
+
},
|
|
90
|
+
targetNetwork: 'test-network',
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const controller = {
|
|
94
|
+
...base,
|
|
95
|
+
id: `urn:uuid:${applicationId}:service-controller`,
|
|
96
|
+
type: [
|
|
97
|
+
W3cCredentialTypes.VerifiableCredential,
|
|
98
|
+
'ServiceCredential',
|
|
99
|
+
ActivationCredentialTypes.ServiceControllerCredential,
|
|
100
|
+
EnvironmentCredentialTypes.TestNetworkCredential,
|
|
101
|
+
],
|
|
102
|
+
credentialSubject: {
|
|
103
|
+
id: organizationDid,
|
|
104
|
+
'@type': 'Service',
|
|
105
|
+
serviceType: 'OrganizationControllerService',
|
|
106
|
+
provider: {
|
|
107
|
+
'@type': 'Organization',
|
|
108
|
+
legalName,
|
|
109
|
+
...(identifierType === 'taxID'
|
|
110
|
+
? { taxID: organizationIdentifier }
|
|
111
|
+
: { identifier: { additionalType: identifierType, value: organizationIdentifier } }),
|
|
112
|
+
},
|
|
113
|
+
owner: {
|
|
114
|
+
'@type': 'Person',
|
|
115
|
+
additionalType: 'RESPRSN',
|
|
116
|
+
sameAs: buildStableActorIdentifier({ contactKind: 'email', contact: controllerEmail }),
|
|
117
|
+
hasOccupation: { '@type': 'Occupation', occupationalCategory: 'ISCO-08|1330' },
|
|
118
|
+
hasCredential: { material: required(input.controllerKeyMaterial, 'controllerKeyMaterial') },
|
|
119
|
+
},
|
|
120
|
+
targetNetwork: 'test-network',
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
return [organization, representative, controller];
|
|
124
|
+
}
|
|
125
|
+
function canonicalizeValue(value) {
|
|
126
|
+
if (Array.isArray(value))
|
|
127
|
+
return value.map(canonicalizeValue);
|
|
128
|
+
if (!value || typeof value !== 'object')
|
|
129
|
+
return value;
|
|
130
|
+
return Object.fromEntries(Object.entries(value)
|
|
131
|
+
.filter(([key]) => key !== 'proof')
|
|
132
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
133
|
+
.map(([key, nested]) => [key, canonicalizeValue(nested)]));
|
|
134
|
+
}
|
|
135
|
+
/** Deterministic detached-proof payload for one of the three Test Network VCs. */
|
|
136
|
+
export function canonicalizeTestNetworkOrganizationCredential(credential) {
|
|
137
|
+
if (!TestNetworkOrganizationCredentialTypes.some(type => credential.type.includes(type))) {
|
|
138
|
+
throw new Error('Credential is not a Test Network organization credential.');
|
|
139
|
+
}
|
|
140
|
+
if (credential.credentialSubject?.targetNetwork !== 'test-network') {
|
|
141
|
+
throw new Error('Test Network organization credential requires targetNetwork=test-network.');
|
|
142
|
+
}
|
|
143
|
+
return JSON.stringify(canonicalizeValue(credential));
|
|
144
|
+
}
|