vet-sdk-core-ts 0.4.7 → 0.4.9
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/README.md
CHANGED
|
@@ -46,6 +46,10 @@ compatibility envelope for integrations that already own the OpenID fields.
|
|
|
46
46
|
The numeric animal identity and external evidence contract is documented in
|
|
47
47
|
[`docs/ANIMAL_IDENTITY.md`](docs/ANIMAL_IDENTITY.md).
|
|
48
48
|
|
|
49
|
+
The personal controller identity flow uses only the high-level reusable BFF
|
|
50
|
+
methods documented in
|
|
51
|
+
[`docs/101-PERSONAL-IDENTITY-EVIDENCE.md`](docs/101-PERSONAL-IDENTITY-EVIDENCE.md).
|
|
52
|
+
|
|
49
53
|
## Animal species
|
|
50
54
|
|
|
51
55
|
`VetChainDomesticAnimalSpecies` provides stable application keys, official
|
package/dist/reusable-bff.d.ts
CHANGED
|
@@ -20,8 +20,53 @@ export type ReusableProfessionalBffRoutes = Readonly<{
|
|
|
20
20
|
subject: string;
|
|
21
21
|
digitalTwinSearch: string;
|
|
22
22
|
breakGlass: string;
|
|
23
|
+
identityEvidence: string;
|
|
23
24
|
}>;
|
|
24
25
|
export declare const DefaultReusableProfessionalBffRoutes: ReusableProfessionalBffRoutes;
|
|
26
|
+
/** Business actions understood by every reusable identity-evidence BFF. */
|
|
27
|
+
export declare const IdentityEvidenceActions: Readonly<{
|
|
28
|
+
PrepareDeclaration: "prepare-declaration";
|
|
29
|
+
Upload: "upload";
|
|
30
|
+
VerifySignaturePdf: "verify-signature-pdf";
|
|
31
|
+
}>;
|
|
32
|
+
/** Controller relationships supported by the first animal declaration. */
|
|
33
|
+
export declare const IdentityEvidenceControllerCapacities: Readonly<{
|
|
34
|
+
Owner: "owner";
|
|
35
|
+
ResponsiblePerson: "responsible-person";
|
|
36
|
+
Keeper: "keeper";
|
|
37
|
+
}>;
|
|
38
|
+
export type IdentityEvidenceDeclarationInput = Readonly<{
|
|
39
|
+
subjectId: string;
|
|
40
|
+
locale?: 'en' | 'es';
|
|
41
|
+
fullLegalName: string;
|
|
42
|
+
countryCode: string;
|
|
43
|
+
regional: boolean;
|
|
44
|
+
subdivisionCode?: string;
|
|
45
|
+
identifierKind: string;
|
|
46
|
+
identifierValue: string;
|
|
47
|
+
controllerCapacity: typeof IdentityEvidenceControllerCapacities[keyof typeof IdentityEvidenceControllerCapacities];
|
|
48
|
+
}>;
|
|
49
|
+
export type IdentityEvidenceUploadInput = Readonly<{
|
|
50
|
+
subjectId: string;
|
|
51
|
+
controllerIdentifierId: string;
|
|
52
|
+
pdf: Blob;
|
|
53
|
+
}>;
|
|
54
|
+
export type IdentityEvidenceIdentifierSummary = Readonly<{
|
|
55
|
+
id: string;
|
|
56
|
+
typeLabel: string;
|
|
57
|
+
jurisdictionLabel: string;
|
|
58
|
+
displayValue: string;
|
|
59
|
+
status: 'pending' | 'verified' | 'rejected';
|
|
60
|
+
}>;
|
|
61
|
+
export type IdentityEvidenceDocumentSummary = Readonly<{
|
|
62
|
+
id: string;
|
|
63
|
+
fileName: string;
|
|
64
|
+
status: 'pending' | 'verified' | 'rejected';
|
|
65
|
+
}>;
|
|
66
|
+
export type IdentityEvidenceListResult = Readonly<{
|
|
67
|
+
identifiers: readonly IdentityEvidenceIdentifierSummary[];
|
|
68
|
+
documents: readonly IdentityEvidenceDocumentSummary[];
|
|
69
|
+
}>;
|
|
25
70
|
type BusinessPayload = Readonly<Record<string, unknown>>;
|
|
26
71
|
/**
|
|
27
72
|
* Business-level client for the reusable professional BFF shared by UHC UNID,
|
|
@@ -82,13 +127,33 @@ export declare class ReusableProfessionalBffClient {
|
|
|
82
127
|
readonly breakGlass: Readonly<{
|
|
83
128
|
request: (input: BusinessPayload) => Promise<unknown>;
|
|
84
129
|
}>;
|
|
130
|
+
/**
|
|
131
|
+
* Personal identity evidence without transport or certificate plumbing.
|
|
132
|
+
*
|
|
133
|
+
* `upload()` stores one signed PDF as pending evidence. Only
|
|
134
|
+
* `verifySignaturePdf()` may ask the trusted provider adapter to change its
|
|
135
|
+
* status. Neither a browser digest nor a successful upload means verified.
|
|
136
|
+
*/
|
|
137
|
+
readonly identityEvidence: Readonly<{
|
|
138
|
+
list: (input: Readonly<{
|
|
139
|
+
subjectId: string;
|
|
140
|
+
}>) => Promise<IdentityEvidenceListResult>;
|
|
141
|
+
prepareDeclaration: (input: IdentityEvidenceDeclarationInput) => Promise<Blob>;
|
|
142
|
+
upload: (input: IdentityEvidenceUploadInput) => Promise<unknown>;
|
|
143
|
+
verifySignaturePdf: (input: Readonly<{
|
|
144
|
+
evidenceId: string;
|
|
145
|
+
}>) => Promise<unknown>;
|
|
146
|
+
}>;
|
|
85
147
|
private readonly routes;
|
|
86
148
|
private readonly fetcher;
|
|
87
149
|
private readonly baseUrl;
|
|
88
150
|
constructor(extension: ReusableBffProductExtension);
|
|
151
|
+
private uploadIdentityEvidence;
|
|
89
152
|
private subjectAction;
|
|
90
153
|
private applicationRoute;
|
|
91
154
|
private request;
|
|
155
|
+
private requestForm;
|
|
156
|
+
private requestBlob;
|
|
92
157
|
}
|
|
93
158
|
export declare class ReusableBffError extends Error {
|
|
94
159
|
readonly status: number;
|
package/dist/reusable-bff.js
CHANGED
|
@@ -12,6 +12,19 @@ export const DefaultReusableProfessionalBffRoutes = Object.freeze({
|
|
|
12
12
|
subject: '/api/subject',
|
|
13
13
|
digitalTwinSearch: '/api/test-twins/search',
|
|
14
14
|
breakGlass: '/api/professional/break-glass',
|
|
15
|
+
identityEvidence: '/api/identity-evidence',
|
|
16
|
+
});
|
|
17
|
+
/** Business actions understood by every reusable identity-evidence BFF. */
|
|
18
|
+
export const IdentityEvidenceActions = Object.freeze({
|
|
19
|
+
PrepareDeclaration: 'prepare-declaration',
|
|
20
|
+
Upload: 'upload',
|
|
21
|
+
VerifySignaturePdf: 'verify-signature-pdf',
|
|
22
|
+
});
|
|
23
|
+
/** Controller relationships supported by the first animal declaration. */
|
|
24
|
+
export const IdentityEvidenceControllerCapacities = Object.freeze({
|
|
25
|
+
Owner: 'owner',
|
|
26
|
+
ResponsiblePerson: 'responsible-person',
|
|
27
|
+
Keeper: 'keeper',
|
|
15
28
|
});
|
|
16
29
|
/**
|
|
17
30
|
* Business-level client for the reusable professional BFF shared by UHC UNID,
|
|
@@ -73,6 +86,29 @@ export class ReusableProfessionalBffClient {
|
|
|
73
86
|
});
|
|
74
87
|
this.digitalTwins = Object.freeze({ search: input => this.request(this.routes.digitalTwinSearch, 'POST', input) });
|
|
75
88
|
this.breakGlass = Object.freeze({ request: input => this.request(this.routes.breakGlass, 'POST', input) });
|
|
89
|
+
this.identityEvidence = Object.freeze({
|
|
90
|
+
list: ({ subjectId }) => this.request(`${this.routes.identityEvidence}?subjectId=${encodeURIComponent(required(subjectId, 'bff_subject_id_required'))}`, 'GET'),
|
|
91
|
+
prepareDeclaration: input => this.requestBlob(this.routes.identityEvidence, {
|
|
92
|
+
action: IdentityEvidenceActions.PrepareDeclaration,
|
|
93
|
+
...input,
|
|
94
|
+
}),
|
|
95
|
+
upload: input => this.uploadIdentityEvidence(input),
|
|
96
|
+
verifySignaturePdf: ({ evidenceId }) => this.request(this.routes.identityEvidence, 'POST', {
|
|
97
|
+
action: IdentityEvidenceActions.VerifySignaturePdf,
|
|
98
|
+
evidenceId: required(evidenceId, 'bff_identity_evidence_id_required'),
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
uploadIdentityEvidence(input) {
|
|
103
|
+
if (!(input.pdf instanceof Blob) || input.pdf.type !== 'application/pdf') {
|
|
104
|
+
return Promise.reject(new TypeError('bff_identity_evidence_pdf_required'));
|
|
105
|
+
}
|
|
106
|
+
const form = new FormData();
|
|
107
|
+
form.set('action', IdentityEvidenceActions.Upload);
|
|
108
|
+
form.set('subjectId', required(input.subjectId, 'bff_subject_id_required'));
|
|
109
|
+
form.set('controllerIdentifierId', required(input.controllerIdentifierId, 'bff_controller_identifier_id_required'));
|
|
110
|
+
form.set('pdf', input.pdf);
|
|
111
|
+
return this.requestForm(this.routes.identityEvidence, form);
|
|
76
112
|
}
|
|
77
113
|
subjectAction(action, input) {
|
|
78
114
|
return this.request(this.routes.subject, 'POST', { ...input, action });
|
|
@@ -100,6 +136,42 @@ export class ReusableProfessionalBffClient {
|
|
|
100
136
|
}
|
|
101
137
|
return payload;
|
|
102
138
|
}
|
|
139
|
+
async requestForm(path, body) {
|
|
140
|
+
const response = await this.fetcher(`${this.baseUrl}${path}`, {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
credentials: 'include',
|
|
143
|
+
headers: {
|
|
144
|
+
accept: 'application/json',
|
|
145
|
+
'x-product-extension': this.extension.productId,
|
|
146
|
+
},
|
|
147
|
+
body,
|
|
148
|
+
});
|
|
149
|
+
const payload = response.status === 204 ? undefined : await response.json().catch(() => undefined);
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
const error = asRecord(payload).error;
|
|
152
|
+
throw new ReusableBffError(response.status, typeof error === 'string' ? error : 'bff_operation_failed');
|
|
153
|
+
}
|
|
154
|
+
return payload;
|
|
155
|
+
}
|
|
156
|
+
async requestBlob(path, body) {
|
|
157
|
+
assertBusinessPayload(body, false);
|
|
158
|
+
const response = await this.fetcher(`${this.baseUrl}${path}`, {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
credentials: 'include',
|
|
161
|
+
headers: {
|
|
162
|
+
accept: 'application/pdf',
|
|
163
|
+
'content-type': 'application/json',
|
|
164
|
+
'x-product-extension': this.extension.productId,
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify(body),
|
|
167
|
+
});
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
const payload = await response.json().catch(() => undefined);
|
|
170
|
+
const error = asRecord(payload).error;
|
|
171
|
+
throw new ReusableBffError(response.status, typeof error === 'string' ? error : 'bff_operation_failed');
|
|
172
|
+
}
|
|
173
|
+
return response.blob();
|
|
174
|
+
}
|
|
103
175
|
}
|
|
104
176
|
export class ReusableBffError extends Error {
|
|
105
177
|
constructor(status, message) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Personal identity evidence: beginner guide
|
|
2
|
+
|
|
3
|
+
This flow starts in PetChain and is reusable from SOSChain and UHC UNID. A
|
|
4
|
+
person may have several identities: for example a Spanish national identity,
|
|
5
|
+
a Canadian driving licence and a passport. Each is independent and may have
|
|
6
|
+
different evidence and verification dates.
|
|
7
|
+
|
|
8
|
+
## What the app does
|
|
9
|
+
|
|
10
|
+
1. Show only a person or animal the signed-in account may already manage.
|
|
11
|
+
2. Let the person prepare a declaration for one of their identities.
|
|
12
|
+
3. Download the declaration, sign it outside the portal and upload the signed
|
|
13
|
+
PDF.
|
|
14
|
+
4. Ask the trusted provider to verify the PDF signature.
|
|
15
|
+
5. Refresh the provider result. Only that result may say `verified`.
|
|
16
|
+
|
|
17
|
+
For an animal, the declaration also states the person's relationship to that
|
|
18
|
+
animal. A successful PDF upload does not prove that relationship by itself.
|
|
19
|
+
An authorized veterinarian or registry may provide separate evidence.
|
|
20
|
+
|
|
21
|
+
## The four UI methods
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
const evidence = client.identityEvidence
|
|
25
|
+
|
|
26
|
+
const current = await evidence.list({ subjectId })
|
|
27
|
+
const unsignedPdf = await evidence.prepareDeclaration(declaration)
|
|
28
|
+
await evidence.upload({ subjectId, controllerIdentifierId, pdf: signedPdf })
|
|
29
|
+
await evidence.verifySignaturePdf({ evidenceId })
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Those are application methods, not transport helpers. The browser does not
|
|
33
|
+
parse certificates, choose trust roots, perform revocation checks, write a
|
|
34
|
+
ledger record, build DIDComm or construct gateway claims.
|
|
35
|
+
|
|
36
|
+
## What is stored
|
|
37
|
+
|
|
38
|
+
- Each civil identity is its own `schema.org/Person` resource.
|
|
39
|
+
- Application fields use flat `resource.meta.claims`.
|
|
40
|
+
- The provider stores the signed PDF privately and keeps uploader, signers and
|
|
41
|
+
verifier separate.
|
|
42
|
+
- SOSChain embeds the same flow but does not copy the identity into its own
|
|
43
|
+
subject index.
|
|
44
|
+
|
|
45
|
+
Emergency calls require a current provider-returned verified identity. Login,
|
|
46
|
+
email, telephone, a selected card, a PDF upload or a browser hash never grants
|
|
47
|
+
that permission.
|
|
48
|
+
|
|
49
|
+
See [`snippets/personal-identity-evidence.ts`](snippets/personal-identity-evidence.ts).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ReusableProfessionalBffClient } from 'vet-sdk-core-ts/reusable-bff'
|
|
2
|
+
|
|
3
|
+
// The product adapter supplies the BFF route and authenticated session.
|
|
4
|
+
const client = new ReusableProfessionalBffClient({
|
|
5
|
+
productId: 'vetchain',
|
|
6
|
+
allowedSectors: ['animal-care'],
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
// Step 1. Read provider-authorized identities and evidence for the selected card.
|
|
10
|
+
const current = await client.identityEvidence.list({ subjectId })
|
|
11
|
+
|
|
12
|
+
// Step 2. Prepare the human-readable declaration; signing happens outside the portal.
|
|
13
|
+
const declarationPdf = await client.identityEvidence.prepareDeclaration(declaration)
|
|
14
|
+
|
|
15
|
+
// Step 3. Upload stays pending. A local digest is not verification.
|
|
16
|
+
await client.identityEvidence.upload({ subjectId, controllerIdentifierId, pdf: signedPdf })
|
|
17
|
+
|
|
18
|
+
// Step 4. Only the trusted server adapter checks the PDF signature and updates status.
|
|
19
|
+
await client.identityEvidence.verifySignaturePdf({ evidenceId })
|
|
20
|
+
|
|
21
|
+
// Step 5. Render only refreshed provider readback.
|
|
22
|
+
void current
|
|
23
|
+
void declarationPdf
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vet-sdk-core-ts",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "Browser-safe VetChain core contracts and governed animal species identifiers",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -59,6 +59,6 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@noble/hashes": "^2.2.0",
|
|
62
|
-
"vet-data-utils-ts": "0.4.
|
|
62
|
+
"vet-data-utils-ts": "0.4.7"
|
|
63
63
|
}
|
|
64
64
|
}
|