@semics-tech/emdash-esign 0.0.0-bootstrap.0 → 1.0.0-rc.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -2
  3. package/dist/index.d.mts +5 -0
  4. package/dist/index.mjs +346 -0
  5. package/dist/manifest.json +354 -0
  6. package/dist/plugin.d.mts +7 -0
  7. package/dist/plugin.mjs +38 -0
  8. package/dist/ts/astro/lib/call.d.ts +56 -0
  9. package/dist/ts/astro/lib/call.js +138 -0
  10. package/dist/ts/integration/index.d.ts +32 -0
  11. package/dist/ts/integration/index.js +49 -0
  12. package/dist/ts/ui/index.d.ts +6 -0
  13. package/dist/ts/ui/index.js +35 -0
  14. package/emdash-plugin.jsonc +144 -0
  15. package/package.json +70 -10
  16. package/src/astro/SelfServeBlock.astro +84 -0
  17. package/src/astro/SignPage.astro +53 -0
  18. package/src/astro/SignedCopyPage.astro +48 -0
  19. package/src/astro/SigningCeremony.astro +264 -0
  20. package/src/astro/SnapshotBlocks.astro +35 -0
  21. package/src/astro/SnapshotInline.astro +28 -0
  22. package/src/astro/VerifyDocument.astro +70 -0
  23. package/src/astro/VerifyPage.astro +41 -0
  24. package/src/astro/index.js +21 -0
  25. package/src/astro/lib/assets.ts +156 -0
  26. package/src/astro/lib/call.ts +205 -0
  27. package/src/astro/lib/csp.ts +57 -0
  28. package/src/astro/lib/csrf.ts +33 -0
  29. package/src/astro/lib/markdown.ts +363 -0
  30. package/src/astro/lib/self-serve.ts +149 -0
  31. package/src/astro/lib/sign.ts +421 -0
  32. package/src/astro/self-serve-endpoint.ts +11 -0
  33. package/src/astro/virtual.d.ts +10 -0
  34. package/src/client/ceremony.d.ts +31 -0
  35. package/src/client/ceremony.js +218 -0
  36. package/src/client/index.ts +5 -0
  37. package/src/client/pad.d.ts +24 -0
  38. package/src/client/pad.js +232 -0
  39. package/src/client/verify.d.ts +3 -0
  40. package/src/client/verify.js +88 -0
  41. package/src/i18n/ar.ts +132 -0
  42. package/src/i18n/en.ts +136 -0
  43. package/src/i18n/index.ts +121 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 semics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,105 @@
1
- # @semics-tech/emdash-esign
1
+ # semics E-sign
2
2
 
3
- Placeholder; no code. See https://github.com/semics-uk/emdash-plugins.
3
+ Simple electronic signatures for [EmDash CMS](https://emdashcms.com): Markdown templates, one signer per
4
+ envelope, a typed name with an optional drawn signature, a hash-chained audit trail, a signed PDF with a certificate, and a public
5
+ `/verify` page. MIT licensed.
6
+
7
+ The package holds the sandboxed core `semics-esign` (its default export) and the UI kit (`/ui`,
8
+ `/integration`, and the Astro pages and components).
9
+
10
+ **Documentation** (in `apps/docs/pages` of the repository):
11
+ [install](https://github.com/semics-uk/emdash-plugins/blob/main/apps/docs/pages/install.md),
12
+ [configure](https://github.com/semics-uk/emdash-plugins/blob/main/apps/docs/pages/configure.md), the
13
+ [E-sign guide](https://github.com/semics-uk/emdash-plugins/blob/main/apps/docs/pages/esign.md),
14
+ [data and retention](https://github.com/semics-uk/emdash-plugins/blob/main/apps/docs/pages/privacy.md).
15
+
16
+ ## Install
17
+
18
+ ```sh
19
+ pnpm add @semics-tech/emdash-esign @emdash-cms/sandbox-workerd
20
+ ```
21
+
22
+ ```js
23
+ // astro.config.mjs
24
+ import semicsEsign from "@semics-tech/emdash-esign";
25
+ import { esignIntegration } from "@semics-tech/emdash-esign/integration";
26
+ import { esignUi } from "@semics-tech/emdash-esign/ui";
27
+ import { defineConfig } from "astro/config";
28
+ import emdash from "emdash/astro";
29
+
30
+ export default defineConfig({
31
+ output: "server",
32
+ integrations: [
33
+ esignIntegration(),
34
+ emdash({
35
+ siteUrl: "https://sign.example.com",
36
+ sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox",
37
+ sandboxed: [semicsEsign],
38
+ plugins: [esignUi()],
39
+ }),
40
+ ],
41
+ });
42
+ ```
43
+
44
+ ## Retention (operators)
45
+
46
+ Settings: **Keep personal data (months)**, default 12, and **Keep signed records (years)**, default 7. Both count
47
+ from when an envelope closed (completed, declined, voided or expired; a draft from its last edit). A daily job
48
+ then:
49
+
50
+ - after the personal-data period, deletes the signer's name, email, IP address, browser, typed name and drawn
51
+ strokes, the values they entered, any decline reason and their links, the envelope's prefilled values and
52
+ return URL, and the audit trail's raw personal values together with the salt that could prove them. Drafts
53
+ are deleted. The audit trail itself still verifies. These are the separate copies: the signed record keeps
54
+ what it printed (next point).
55
+ - keeps the **signed record** (the signed PDF, the frozen document and the audit trail) until the
56
+ signed-records period ends. The signed PDF and document still show what was signed: the document may contain
57
+ the signer's name or email (`{{signer.name}}`, `{{signer.email}}`); the certificate prints the signer's name
58
+ and IP address, and the participant's name (a minor's name, when a guardian signed); and the signed PDF keeps
59
+ the values the signer entered under "Details entered by the signer". All of this stays until the
60
+ signed-records period ends. `/verify` keeps matching the PDF during this period.
61
+ - after the signed-records period, deletes the PDF, document, audit trail and signer record; `/verify` no
62
+ longer recognises the PDF.
63
+
64
+ Envelopes still out for signature are never purged. An unsigned one expires `expiryDays` after it was sent (an
65
+ hourly job closes it, so retention can age it out); you can also void it. To delete one person's data on
66
+ request, use **Delete personal data** on the Envelopes page (signer email or envelope id). It removes the
67
+ personal data straight away but keeps the signed record for its own period.
68
+
69
+ ## Develop
70
+
71
+ ```sh
72
+ pnpm install
73
+ pnpm run validate
74
+ pnpm run typecheck
75
+ pnpm run test
76
+ pnpm run build
77
+ ```
78
+
79
+ `pnpm run test` builds the plugin and runs its tests through Worker Loader using EmDash's production sandbox
80
+ wrapper and host bridge. To try it on a site, run `pnpm run dev` here (rebuilds on save) and link this folder
81
+ into the site.
82
+
83
+ ## Publish
84
+
85
+ ```sh
86
+ pnpm run login -- alice.example.com
87
+ pnpm run publish # builds and uploads artifacts to your PDS
88
+ ```
89
+
90
+ To publish from GitHub Actions, run `pnpm run release:setup`. The command creates one shared workflow at the
91
+ Git repository root.
92
+
93
+ The registry listing (name, keywords, the description, installation, FAQ and security sections, icon, banner
94
+ and screenshots) is declared in `emdash-plugin.jsonc` and kept in `listing/`. The images are made by the
95
+ playground's `e2e/t103-screenshots.spec.ts`.
96
+
97
+ ## Version bumps
98
+
99
+ Bump `version` in `package.json` when you ship a release. The scaffold's `emdash-plugin.jsonc` deliberately
100
+ omits `version`: the build reads it from `package.json` so there's a single source of truth. **Bump major** for
101
+ breaking changes, **bump minor** for new routes or hooks, **bump patch** for fixes.
102
+
103
+ You MUST bump version whenever you change `capabilities`, `allowedHosts`, or `storage` in the manifest.
104
+ Installed users have consented to the old trust contract; a change without a version bump would let new
105
+ behaviour slip past consent.
@@ -0,0 +1,5 @@
1
+ // Auto-generated by emdash-plugin build. Do not edit.
2
+ import type { PluginDescriptor } from "emdash";
3
+
4
+ declare const descriptor: PluginDescriptor;
5
+ export default descriptor;
package/dist/index.mjs ADDED
@@ -0,0 +1,346 @@
1
+ // Auto-generated by emdash-plugin build. Do not edit.
2
+ // Source: emdash-plugin.jsonc + package.json
3
+ //
4
+ // Default-exports a sandboxed plugin descriptor. Pass it directly into
5
+ // emdash's `plugins:` or `sandboxed:` array — no factory call needed.
6
+
7
+ /** @type {import("emdash").PluginDescriptor} */
8
+ const descriptor = Object.freeze({
9
+ "id": "semics-esign",
10
+ "version": "1.0.0-rc.0",
11
+ "format": "standard",
12
+ "entrypoint": "@semics-tech/emdash-esign/sandbox",
13
+ "capabilities": [
14
+ "media:read",
15
+ "media:bytes:read",
16
+ "network:request",
17
+ "email:send"
18
+ ],
19
+ "allowedHosts": [
20
+ "challenges.cloudflare.com"
21
+ ],
22
+ "storage": {
23
+ "templates": {
24
+ "indexes": [
25
+ "status",
26
+ "kind",
27
+ "public"
28
+ ]
29
+ },
30
+ "envelopes": {
31
+ "indexes": [
32
+ "status",
33
+ "expiresAt",
34
+ "createdAt",
35
+ "clientId",
36
+ "externalRef",
37
+ "pdfSha256",
38
+ "signerEmail",
39
+ [
40
+ "status",
41
+ "expiresAt"
42
+ ]
43
+ ]
44
+ },
45
+ "signers": {
46
+ "indexes": [
47
+ "envelopeId",
48
+ "email",
49
+ "copyTokenHash"
50
+ ],
51
+ "uniqueIndexes": [
52
+ "tokenHash"
53
+ ]
54
+ },
55
+ "snapshots": {
56
+ "indexes": [
57
+ "envelopeId"
58
+ ]
59
+ },
60
+ "artifacts": {
61
+ "indexes": [
62
+ "envelopeId",
63
+ [
64
+ "envelopeId",
65
+ "kind"
66
+ ]
67
+ ]
68
+ },
69
+ "audit_events": {
70
+ "indexes": [
71
+ "envelopeId",
72
+ [
73
+ "envelopeId",
74
+ "seq"
75
+ ]
76
+ ]
77
+ },
78
+ "clients": {
79
+ "indexes": [
80
+ "createdAt"
81
+ ],
82
+ "uniqueIndexes": [
83
+ "clientId"
84
+ ]
85
+ },
86
+ "nonces": {
87
+ "indexes": [
88
+ "expiresAt"
89
+ ],
90
+ "uniqueIndexes": [
91
+ "nonce"
92
+ ]
93
+ },
94
+ "outbox": {
95
+ "indexes": [
96
+ [
97
+ "status",
98
+ "nextAttemptAt"
99
+ ]
100
+ ]
101
+ }
102
+ },
103
+ "hooks": [
104
+ "plugin:activate",
105
+ "cron"
106
+ ],
107
+ "routes": [
108
+ {
109
+ "name": "public/envelopes/from-intent",
110
+ "methods": [
111
+ "POST"
112
+ ],
113
+ "request": {
114
+ "body": "json",
115
+ "maxBytes": 16384
116
+ },
117
+ "public": true
118
+ },
119
+ {
120
+ "name": "public/session",
121
+ "methods": [
122
+ "POST"
123
+ ],
124
+ "request": {
125
+ "body": "json",
126
+ "maxBytes": 8192
127
+ },
128
+ "public": true
129
+ },
130
+ {
131
+ "name": "public/otp/send",
132
+ "methods": [
133
+ "POST"
134
+ ],
135
+ "request": {
136
+ "body": "json",
137
+ "maxBytes": 8192
138
+ },
139
+ "public": true
140
+ },
141
+ {
142
+ "name": "public/otp/verify",
143
+ "methods": [
144
+ "POST"
145
+ ],
146
+ "request": {
147
+ "body": "json",
148
+ "maxBytes": 8192
149
+ },
150
+ "public": true
151
+ },
152
+ {
153
+ "name": "public/submit",
154
+ "methods": [
155
+ "POST"
156
+ ],
157
+ "request": {
158
+ "body": "json",
159
+ "maxBytes": 262144
160
+ },
161
+ "public": true
162
+ },
163
+ {
164
+ "name": "public/decline",
165
+ "methods": [
166
+ "POST"
167
+ ],
168
+ "request": {
169
+ "body": "json",
170
+ "maxBytes": 8192
171
+ },
172
+ "public": true
173
+ },
174
+ {
175
+ "name": "public/download",
176
+ "methods": [
177
+ "GET"
178
+ ],
179
+ "request": {
180
+ "body": "none"
181
+ },
182
+ "response": "raw",
183
+ "public": true
184
+ },
185
+ {
186
+ "name": "public/verify",
187
+ "methods": [
188
+ "POST"
189
+ ],
190
+ "request": {
191
+ "body": "json",
192
+ "maxBytes": 8192
193
+ },
194
+ "public": true
195
+ },
196
+ {
197
+ "name": "public/receipts/lookup",
198
+ "methods": [
199
+ "POST"
200
+ ],
201
+ "request": {
202
+ "body": "json",
203
+ "maxBytes": 8192
204
+ },
205
+ "public": true
206
+ },
207
+ {
208
+ "name": "public/templates/catalog",
209
+ "methods": [
210
+ "POST"
211
+ ],
212
+ "request": {
213
+ "body": "json",
214
+ "maxBytes": 65536
215
+ },
216
+ "public": true
217
+ },
218
+ {
219
+ "name": "public/self-serve/start",
220
+ "methods": [
221
+ "POST"
222
+ ],
223
+ "request": {
224
+ "body": "json",
225
+ "maxBytes": 65536
226
+ },
227
+ "public": true
228
+ },
229
+ "admin",
230
+ "templates/create",
231
+ "templates/update",
232
+ "templates/get",
233
+ "templates/list",
234
+ "templates/archive",
235
+ "envelopes/create",
236
+ "envelopes/send",
237
+ "envelopes/get",
238
+ "envelopes/list",
239
+ "envelopes/resend",
240
+ "envelopes/void",
241
+ "outbox/status"
242
+ ],
243
+ "adminPages": [
244
+ {
245
+ "path": "/envelopes",
246
+ "label": "Envelopes",
247
+ "icon": "file"
248
+ },
249
+ {
250
+ "path": "/templates",
251
+ "label": "Templates",
252
+ "icon": "file-text"
253
+ },
254
+ {
255
+ "path": "/integrations",
256
+ "label": "Integrations",
257
+ "icon": "link"
258
+ },
259
+ {
260
+ "path": "/settings",
261
+ "label": "Settings",
262
+ "icon": "gear"
263
+ }
264
+ ],
265
+ "settingsSchema": {
266
+ "senderName": {
267
+ "label": "Sender name",
268
+ "description": "Shown as the sender of signing emails.",
269
+ "type": "string"
270
+ },
271
+ "consentVersion": {
272
+ "label": "Consent version",
273
+ "description": "Recorded with every signature; bump when the wording changes.",
274
+ "type": "string",
275
+ "default": "1"
276
+ },
277
+ "consentWording": {
278
+ "label": "Consent wording",
279
+ "description": "Shown to signers before they sign.",
280
+ "type": "string",
281
+ "default": "I agree to use electronic records and an electronic signature for this document.",
282
+ "multiline": true
283
+ },
284
+ "expiryDays": {
285
+ "label": "Envelope expiry (days)",
286
+ "type": "number",
287
+ "default": 30,
288
+ "min": 1,
289
+ "max": 365
290
+ },
291
+ "reminderCadenceDays": {
292
+ "label": "Reminder cadence (days)",
293
+ "description": "Pro: automatic reminders.",
294
+ "type": "number",
295
+ "default": 3,
296
+ "min": 1,
297
+ "max": 30
298
+ },
299
+ "otpDefault": {
300
+ "label": "Require email code by default",
301
+ "description": "Pro: one-time code before signing.",
302
+ "type": "boolean",
303
+ "default": false
304
+ },
305
+ "otpContractTemplates": {
306
+ "label": "Require email code for contract templates",
307
+ "description": "Pro.",
308
+ "type": "boolean",
309
+ "default": true
310
+ },
311
+ "retentionPersonalDataMonths": {
312
+ "label": "Keep personal data (months)",
313
+ "type": "number",
314
+ "default": 12,
315
+ "min": 1,
316
+ "max": 120
317
+ },
318
+ "retentionSignedRecordsYears": {
319
+ "label": "Keep signed records (years)",
320
+ "type": "number",
321
+ "default": 7,
322
+ "min": 1,
323
+ "max": 50
324
+ },
325
+ "turnstileSiteKey": {
326
+ "label": "Turnstile site key",
327
+ "type": "string"
328
+ },
329
+ "turnstileSecretKey": {
330
+ "label": "Turnstile secret key",
331
+ "type": "secret"
332
+ },
333
+ "integrationMasterKey": {
334
+ "label": "Integration master key",
335
+ "description": "Client secrets are derived from this key; it is never shown again.",
336
+ "type": "secret"
337
+ },
338
+ "licenceKey": {
339
+ "label": "Licence key (Pro)",
340
+ "description": "Used by E-sign Pro only; the Free edition ignores it. Stored encrypted.",
341
+ "type": "secret"
342
+ }
343
+ }
344
+ });
345
+
346
+ export default descriptor;