@certysign/sdk 2.3.0 → 2.5.0
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 +76 -0
- package/package.json +54 -54
- package/src/index.js +228 -214
- package/src/lib/BillingResource.js +90 -0
- package/src/lib/EnvelopeResource.js +306 -249
- package/src/lib/HashSigningResource.js +20 -5
- package/src/lib/HttpClient.js +211 -198
- package/src/lib/IdentityResource.js +143 -0
- package/src/lib/SignerResource.js +120 -0
- package/src/lib/SigningResource.js +11 -7
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* client.signers — the people you sign on behalf of.
|
|
3
|
+
*
|
|
4
|
+
* Signing in someone's name needs a certificate that belongs to THEM, not to your
|
|
5
|
+
* organisation. This resource covers getting them one and checking they have it:
|
|
6
|
+
*
|
|
7
|
+
* signingStatus() can this person sign yet, and with what
|
|
8
|
+
* sponsor() buy them a work certificate, funded from your wallet
|
|
9
|
+
* resendSetup() re-send the account-setup link if they lost it
|
|
10
|
+
*
|
|
11
|
+
* The boundary these keep: an API key can START a certificate but never FINISH one.
|
|
12
|
+
* The person proves their identity themselves, to a human reviewer. A key that could
|
|
13
|
+
* both invent a signer and obtain their certificate would produce signatures that
|
|
14
|
+
* attest to nothing.
|
|
15
|
+
*/
|
|
16
|
+
class SignerResource {
|
|
17
|
+
constructor(http) {
|
|
18
|
+
this._http = http;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Can this person sign yet, and with which certificate?
|
|
23
|
+
*
|
|
24
|
+
* Worth calling BEFORE a batch: it turns a per-document failure — discovered
|
|
25
|
+
* halfway through, after the wallet has been charged — into one answer up front.
|
|
26
|
+
*
|
|
27
|
+
* Requires `cert:status`.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} email
|
|
30
|
+
* @param {object} [opts]
|
|
31
|
+
* @param {boolean} [opts.issueCompletionUrl=false]
|
|
32
|
+
* For a signer stuck at `AWAITING_IDENTITY_VERIFICATION`, also return a URL you
|
|
33
|
+
* can redirect them to, so they need not hunt for the invitation email.
|
|
34
|
+
*
|
|
35
|
+
* This MINTS A NEW LINK and the previous one stops working — including the one
|
|
36
|
+
* already sitting in their inbox. Ask for it when you are about to redirect
|
|
37
|
+
* someone, not on every poll. Needs `signer:sponsor`, since minting an
|
|
38
|
+
* onboarding credential is the same class of act as sponsoring.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const { data } = await client.signers.signingStatus('jane@example.com');
|
|
42
|
+
* if (!data.canSign) {
|
|
43
|
+
* // data.reason is NO_SIGNING_CERTIFICATE or AWAITING_IDENTITY_VERIFICATION
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* @example <caption>Redirect a signer who never finished verifying</caption>
|
|
47
|
+
* const { data } = await client.signers.signingStatus(email, { issueCompletionUrl: true });
|
|
48
|
+
* if (data.pendingSponsorship?.completionUrl) {
|
|
49
|
+
* return redirect(data.pendingSponsorship.completionUrl);
|
|
50
|
+
* }
|
|
51
|
+
*/
|
|
52
|
+
signingStatus(email, { issueCompletionUrl = false } = {}) {
|
|
53
|
+
if (!email) throw new Error('signers.signingStatus: email is required');
|
|
54
|
+
const path = `/sdk/v1/signers/${encodeURIComponent(email)}/signing-status`;
|
|
55
|
+
return this._http.get(issueCompletionUrl ? `${path}?issueCompletionUrl=true` : path);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Sponsor a work certificate for a named person.
|
|
60
|
+
*
|
|
61
|
+
* Charges your wallet, creates the sponsorship and emails the signer. It also
|
|
62
|
+
* makes them a MEMBER of your organisation once the certificate issues — they get
|
|
63
|
+
* an account, appear in your member list, and their certificate falls under your
|
|
64
|
+
* governance.
|
|
65
|
+
*
|
|
66
|
+
* They cannot sign when this returns: `status` is `awaiting_member` until they
|
|
67
|
+
* complete identity verification. Redirect them to `data.completionUrl`, or let
|
|
68
|
+
* the invitation email carry them there.
|
|
69
|
+
*
|
|
70
|
+
* Requires `signer:sponsor`, a paid plan, and a funded wallet.
|
|
71
|
+
*
|
|
72
|
+
* @param {object} opts
|
|
73
|
+
* @param {string} opts.email
|
|
74
|
+
* @param {string} [opts.name] shown on the invitation; the certificate
|
|
75
|
+
* carries the name on their verified ID
|
|
76
|
+
* @param {'ses'|'ades'|'qes'} [opts.tier='ades']
|
|
77
|
+
* @param {boolean} [opts.restrictToSponsor=true] only signs your documents
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* const { data } = await client.signers.sponsor({ email: 'jane@example.com', tier: 'ades' });
|
|
81
|
+
* return redirect(data.completionUrl);
|
|
82
|
+
*/
|
|
83
|
+
sponsor({ email, name, tier = 'ades', restrictToSponsor = true } = {}) {
|
|
84
|
+
if (!email) throw new Error('signers.sponsor: email is required');
|
|
85
|
+
return this._http.post('/sdk/v1/signers/sponsor', {
|
|
86
|
+
data: { email, ...(name && { name }), tier, restrictToSponsor },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Re-send the account-setup link to a signer you sponsored.
|
|
92
|
+
*
|
|
93
|
+
* The original link is returned once and stored hashed, so it cannot be looked up.
|
|
94
|
+
* This mints a FRESH one, which means any previous link stops working.
|
|
95
|
+
*
|
|
96
|
+
* Only works while the account is unclaimed: once the signer has chosen a
|
|
97
|
+
* password this returns 409 ALREADY_SET_UP, because handing a live account a new
|
|
98
|
+
* setup link would be a way to take it over. Rate limited to one send every five
|
|
99
|
+
* minutes per signer.
|
|
100
|
+
*
|
|
101
|
+
* Requires `signer:sponsor`.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* try {
|
|
105
|
+
* await client.signers.resendSetup('jane@example.com');
|
|
106
|
+
* } catch (e) {
|
|
107
|
+
* if (e.code === 'ALREADY_SET_UP') // she can sign in, or use forgot-password
|
|
108
|
+
* if (e.code === 'RESEND_TOO_SOON') // e.details.data.retryAfterSeconds
|
|
109
|
+
* }
|
|
110
|
+
*/
|
|
111
|
+
resendSetup(email) {
|
|
112
|
+
if (!email) throw new Error('signers.resendSetup: email is required');
|
|
113
|
+
return this._http.post(
|
|
114
|
+
`/sdk/v1/signers/${encodeURIComponent(email)}/resend-setup`,
|
|
115
|
+
{ data: {} },
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = { SignerResource };
|
|
@@ -109,7 +109,7 @@ class SigningResource {
|
|
|
109
109
|
|
|
110
110
|
const form = new FormData();
|
|
111
111
|
for (const doc of documents) {
|
|
112
|
-
_appendDocument(form, doc.data, doc.filename || 'document.pdf');
|
|
112
|
+
_appendDocument(form, doc.data, doc.filename || 'document.pdf', 'documents');
|
|
113
113
|
}
|
|
114
114
|
form.append('signerName', signerName);
|
|
115
115
|
if (signerEmail) form.append('signerEmail', signerEmail);
|
|
@@ -157,12 +157,16 @@ class SigningResource {
|
|
|
157
157
|
|
|
158
158
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
160
|
+
/**
|
|
161
|
+
* @param {string} [field] multipart field name. The single-document endpoint reads
|
|
162
|
+
* `document` (multer `.single('document')`); the batch endpoint reads `documents`
|
|
163
|
+
* (multer `.array('documents')`). This was hard-coded to 'document' for both, so
|
|
164
|
+
* every batchSign call had its files rejected with LIMIT_UNEXPECTED_FILE before
|
|
165
|
+
* reaching the handler — the method could never have worked.
|
|
166
|
+
*/
|
|
167
|
+
function _appendDocument(form, document, filename, field = 'document') {
|
|
168
|
+
if (Buffer.isBuffer(document) || (document && typeof document.pipe === 'function')) {
|
|
169
|
+
form.append(field, document, { filename, contentType: 'application/pdf' });
|
|
166
170
|
} else {
|
|
167
171
|
throw new Error(`Document must be a Buffer or ReadStream, got ${typeof document}`);
|
|
168
172
|
}
|