@openbrf/plugin-sdk 1.0.0 → 1.4.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 (104) hide show
  1. package/README.md +95 -0
  2. package/dist/abort-signal.d.ts +23 -0
  3. package/dist/abort-signal.d.ts.map +1 -0
  4. package/dist/abort-signal.js +2 -0
  5. package/dist/abort-signal.js.map +1 -0
  6. package/dist/action-refusal.d.ts +101 -0
  7. package/dist/action-refusal.d.ts.map +1 -0
  8. package/dist/action-refusal.js +96 -0
  9. package/dist/action-refusal.js.map +1 -0
  10. package/dist/action-schema.d.ts.map +1 -1
  11. package/dist/action-schema.js +4 -1
  12. package/dist/action-schema.js.map +1 -1
  13. package/dist/actions.d.ts +94 -6
  14. package/dist/actions.d.ts.map +1 -1
  15. package/dist/actions.js.map +1 -1
  16. package/dist/blocks.d.ts +310 -0
  17. package/dist/blocks.d.ts.map +1 -0
  18. package/dist/blocks.js +185 -0
  19. package/dist/blocks.js.map +1 -0
  20. package/dist/catalog.d.ts +1092 -41
  21. package/dist/catalog.d.ts.map +1 -1
  22. package/dist/catalog.js +472 -15
  23. package/dist/catalog.js.map +1 -1
  24. package/dist/entitlements.d.ts +117 -0
  25. package/dist/entitlements.d.ts.map +1 -0
  26. package/dist/entitlements.js +62 -0
  27. package/dist/entitlements.js.map +1 -0
  28. package/dist/extensions.d.ts +183 -0
  29. package/dist/extensions.d.ts.map +1 -0
  30. package/dist/extensions.js +67 -0
  31. package/dist/extensions.js.map +1 -0
  32. package/dist/finances.d.ts +294 -0
  33. package/dist/finances.d.ts.map +1 -0
  34. package/dist/finances.js +56 -0
  35. package/dist/finances.js.map +1 -0
  36. package/dist/findings.d.ts +92 -1
  37. package/dist/findings.d.ts.map +1 -1
  38. package/dist/findings.js +91 -0
  39. package/dist/findings.js.map +1 -1
  40. package/dist/host.d.ts +729 -4
  41. package/dist/host.d.ts.map +1 -1
  42. package/dist/host.js +54 -0
  43. package/dist/host.js.map +1 -1
  44. package/dist/imports.d.ts +104 -0
  45. package/dist/imports.d.ts.map +1 -0
  46. package/dist/imports.js +70 -0
  47. package/dist/imports.js.map +1 -0
  48. package/dist/index.d.ts +17 -7
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +15 -6
  51. package/dist/index.js.map +1 -1
  52. package/dist/localized-text.d.ts +18 -0
  53. package/dist/localized-text.d.ts.map +1 -0
  54. package/dist/localized-text.js +17 -0
  55. package/dist/localized-text.js.map +1 -0
  56. package/dist/manifest.d.ts +859 -0
  57. package/dist/manifest.d.ts.map +1 -1
  58. package/dist/manifest.js +957 -12
  59. package/dist/manifest.js.map +1 -1
  60. package/dist/package-check.d.ts.map +1 -1
  61. package/dist/package-check.js +9 -1
  62. package/dist/package-check.js.map +1 -1
  63. package/dist/permissions.d.ts +170 -11
  64. package/dist/permissions.d.ts.map +1 -1
  65. package/dist/permissions.js +210 -2
  66. package/dist/permissions.js.map +1 -1
  67. package/dist/recipients.d.ts +139 -0
  68. package/dist/recipients.d.ts.map +1 -0
  69. package/dist/recipients.js +223 -0
  70. package/dist/recipients.js.map +1 -0
  71. package/dist/records.d.ts +130 -0
  72. package/dist/records.d.ts.map +1 -0
  73. package/dist/records.js +85 -0
  74. package/dist/records.js.map +1 -0
  75. package/dist/require-capability.d.ts +37 -0
  76. package/dist/require-capability.d.ts.map +1 -0
  77. package/dist/require-capability.js +88 -0
  78. package/dist/require-capability.js.map +1 -0
  79. package/dist/settings-schema.d.ts +35 -0
  80. package/dist/settings-schema.d.ts.map +1 -1
  81. package/dist/settings-schema.js +55 -2
  82. package/dist/settings-schema.js.map +1 -1
  83. package/package.json +4 -4
  84. package/src/abort-signal.ts +22 -0
  85. package/src/action-refusal.ts +178 -0
  86. package/src/action-schema.ts +4 -1
  87. package/src/actions.ts +103 -6
  88. package/src/blocks.ts +289 -0
  89. package/src/catalog.ts +630 -72
  90. package/src/entitlements.ts +150 -0
  91. package/src/extensions.ts +212 -0
  92. package/src/finances.ts +317 -0
  93. package/src/findings.ts +94 -0
  94. package/src/host.ts +768 -4
  95. package/src/imports.ts +135 -0
  96. package/src/index.ts +209 -0
  97. package/src/localized-text.ts +19 -0
  98. package/src/manifest.ts +1231 -12
  99. package/src/package-check.ts +15 -0
  100. package/src/permissions.ts +223 -3
  101. package/src/recipients.ts +294 -0
  102. package/src/records.ts +172 -0
  103. package/src/require-capability.ts +106 -0
  104. package/src/settings-schema.ts +64 -2
@@ -11,6 +11,12 @@
11
11
  *
12
12
  * No permission grants write access to the register in v0. A plugin that
13
13
  * needs to change resident data is a core feature request, not a plugin.
14
+ *
15
+ * A new value here is a new member of the catalog's permission list, and an
16
+ * instance that does not know it refuses the whole catalog index that carries
17
+ * it. The catalog therefore lists an entry asking for one of the content
18
+ * permissions below only in an index that older instances do not read: the
19
+ * version 2 index (ADR 0032).
14
20
  */
15
21
  export const PLUGIN_PERMISSIONS = [
16
22
  /**
@@ -44,17 +50,175 @@ export const PLUGIN_PERMISSIONS = [
44
50
  "sms:send",
45
51
  /** Register background workers and enqueue or schedule jobs. */
46
52
  "jobs:schedule",
53
+ /**
54
+ * The fee notification runs and their notices, and the charges to members,
55
+ * as the board's own documents state them, and the events that say one was
56
+ * issued, recorded, corrected or removed.
57
+ *
58
+ * Masked as the files the board hands over are, and further: the names on a
59
+ * notice whose household holds a person with protected personal data are
60
+ * withheld, as on the fee notice file, and a charge on such a person withholds
61
+ * who and where entirely, where the debiting list withholds only where. A
62
+ * plugin is further from the board than the economic manager is, and the
63
+ * address book's rule that a protected person never reaches a plugin holds
64
+ * here too. A personal identity number is never returned, and there is no
65
+ * payment state to return: core records none.
66
+ */
67
+ "finances:read",
68
+ /**
69
+ * Record that a fee notice or a charge was delivered: by which route, when,
70
+ * and under which reference the receiving system knows it. A receipt and
71
+ * never a paid flag. Separate from finances:read because it writes, and the
72
+ * board consents to a plugin that only reads the books knowing that it cannot
73
+ * add anything to them.
74
+ */
75
+ "finances:recordDelivery",
76
+ /**
77
+ * Add blocks to the association's website.
78
+ *
79
+ * What such a block renders is published to anybody who reads the page,
80
+ * account or not, which is why it is a permission the board consents to
81
+ * rather than something a manifest may simply declare. The host prints what
82
+ * the block renders through an allowlist of elements, so it is never a
83
+ * script, a stylesheet or a resource from another host - but the words in it
84
+ * are the plugin's to choose.
85
+ */
86
+ "site:blocks",
87
+ /**
88
+ * The association's published news items: the title, the body's text, when
89
+ * each was published and whether it is for the members or for anyone.
90
+ *
91
+ * Not a register permission. It reads what the board has already
92
+ * published, and never who wrote an item, a comment on it or who read it
93
+ * (ADR 0032). The text is the board's and may name somebody, which is why a
94
+ * plugin holding it declares `freeText`.
95
+ */
96
+ "news:read",
97
+ /**
98
+ * The published events and their occurrences, and the notice of every
99
+ * general meeting that has been called and not yet held.
100
+ *
101
+ * Never a sign-up, a roll-call, an attendance or a proxy: how many places
102
+ * are left at an occurrence is the most it says about who is coming. Like
103
+ * news:read, it reads the board's text and declares `freeText`.
104
+ */
105
+ "calendar:read",
106
+ /**
107
+ * The bookable resources the house offers, and when each is taken. Never by
108
+ * whom: no booker, no apartment, no booking's identifier, and two bookings
109
+ * that touch are one interval (ADR 0032).
110
+ */
111
+ "bookings:readAvailability",
112
+ /**
113
+ * A stored picture, by its id: one held for anyone or for the members, whose
114
+ * upload declared that it shows no identifiable person (ADR 0032).
115
+ *
116
+ * Never a document, never a household's or the board's file, and never a
117
+ * picture of people: a photograph of a person needs a publication consent,
118
+ * and the consent the core records is for the website, not for wherever a
119
+ * plugin puts the picture. A picture is still a photograph, which is why a
120
+ * plugin holding it declares `photograph`.
121
+ */
122
+ "media:read",
123
+ /**
124
+ * The association's addresses: the street and number of each entrance, and
125
+ * nothing about who lives at them. Not addressBook:read, which reads the
126
+ * apartments and the people in them.
127
+ */
128
+ "addresses:read",
129
+ /**
130
+ * Install and update themes on the instance (ADR 0046).
131
+ *
132
+ * A theme is data and passes the same install lint however it arrives, so
133
+ * this grants no way past the theme gate: what a board consents to is a
134
+ * plugin putting themes on the instance's theme screen, for an
135
+ * administrator to preview and activate there. Each install is made for a
136
+ * person in the web interface who may install themes, never by the plugin on
137
+ * its own.
138
+ */
139
+ "themes:install",
140
+ /**
141
+ * Hand a member list to the core for the board to review (ADR 0043).
142
+ *
143
+ * The one way a plugin brings data towards the register, and it writes
144
+ * nothing: the core holds the file as an import proposal on its own import
145
+ * screen, and a board member previews, decides and applies it there, or
146
+ * rejects it. It grants no read of the register and raises no route's
147
+ * capability floor, so the plugin learns nothing about what the file would
148
+ * do. It does hold the file, though, which can carry every personal identity
149
+ * number in the cooperative and rows about persons with protected personal
150
+ * data, so a manifest declaring it declares those categories too.
151
+ */
152
+ "addressBook:proposeImport",
47
153
  ];
48
154
  export function isPluginPermission(value) {
49
155
  return PLUGIN_PERMISSIONS.includes(value);
50
156
  }
157
+ /**
158
+ * Whether a permission lets a plugin read the register, or the finances that
159
+ * carry the names of the people in it.
160
+ *
161
+ * Every permission is classified, and the record is typed against
162
+ * `PluginPermission`, so a permission added to the list above fails to compile
163
+ * until somebody has said which it is. That is the point of a classification
164
+ * over a list of the refused: the rules that keep a plugin with a device route
165
+ * or a website block away from the register (ADR 0006, ADR 0031) hold a new
166
+ * permission that reads it out unless somebody remembers to add it to each.
167
+ * `finances:recordDelivery` writes a receipt and returns no one's details, so
168
+ * it is not a read. The content permissions (`news:read`, `calendar:read`,
169
+ * `bookings:readAvailability`, `media:read`, `addresses:read`) read what the
170
+ * website publishes, never the register, so a plugin with a device route may
171
+ * hold them; the host withholds text that names a resident from such a plugin.
172
+ */
173
+ export const PERMISSION_REGISTER_ACCESS = {
174
+ "addressBook:read": "reads",
175
+ "addressBook:readContact": "reads",
176
+ "mail:send": "none",
177
+ "sms:send": "none",
178
+ "jobs:schedule": "none",
179
+ "finances:read": "reads",
180
+ "finances:recordDelivery": "none",
181
+ "site:blocks": "none",
182
+ "themes:install": "none",
183
+ "news:read": "none",
184
+ "calendar:read": "none",
185
+ "bookings:readAvailability": "none",
186
+ "media:read": "none",
187
+ "addresses:read": "none",
188
+ // Reads nothing from the register, but holds the member list it proposes.
189
+ // The rules that keep a device route or a website block away from it name
190
+ // this permission alongside `readsRegister` (ADR 0043).
191
+ "addressBook:proposeImport": "none",
192
+ };
193
+ /** The permissions that read the register, in the order of the list above. */
194
+ export const REGISTER_READING_PERMISSIONS = PLUGIN_PERMISSIONS.filter((permission) => PERMISSION_REGISTER_ACCESS[permission] === "reads");
195
+ /** Whether any of the permissions a manifest asks for reads the register. */
196
+ export function readsRegister(permissions) {
197
+ return permissions.some((permission) => REGISTER_READING_PERMISSIONS.includes(permission));
198
+ }
51
199
  /**
52
200
  * Categories of personal data a plugin declares it will process.
53
201
  *
54
202
  * Shown on the consent screen before an install, and stored with the
55
- * installation as the snapshot the board agreed to. It is a declaration, not
203
+ * installation as the snapshot the board agreed to. Installing the plugin
204
+ * writes them into the association's record of processing activities (GDPR
205
+ * art. 30) as the categories that processing touches. It is a declaration, not
56
206
  * an enforcement point: what the plugin can actually reach follows from its
57
- * permissions above.
207
+ * permissions above and from the actions it is allowed to call.
208
+ *
209
+ * Not only the register. A plugin that dispatches core actions handles
210
+ * whatever those actions return: an assistant passing issue reports and
211
+ * minutes to a model provider handles free text, and one reading charges
212
+ * handles financial data. So the list is ACTION_PERSONAL_DATA, in its order.
213
+ *
214
+ * Two of them only one kind of plugin may declare. A personal identity number
215
+ * is never returned to a plugin, and a person with protected personal data
216
+ * (skyddade personuppgifter) never appears in one's reads at all, so for any
217
+ * other plugin a declaration naming either would describe something that
218
+ * cannot happen. A plugin that proposes an import carries the member list the
219
+ * other way, and that list can hold both (ADR 0043): the manifest refuses
220
+ * either category without `addressBook:proposeImport`, and requires both with
221
+ * it.
58
222
  */
59
223
  export const PLUGIN_PERSONAL_DATA_CATEGORIES = [
60
224
  /** Given and family name. */
@@ -65,5 +229,49 @@ export const PLUGIN_PERSONAL_DATA_CATEGORIES = [
65
229
  "residency",
66
230
  "email",
67
231
  "phone",
232
+ /** A postal address of the person's own, not the apartment's. */
233
+ "postalAddress",
234
+ /**
235
+ * Personal identity number (personnummer). Only with
236
+ * `addressBook:proposeImport`.
237
+ */
238
+ "personalIdentityNumber",
239
+ /** Sign-in credentials and sessions. */
240
+ "account",
241
+ /** Charges, debiting basis and what a person owes or has paid. */
242
+ "financial",
243
+ /**
244
+ * Health, an art. 9 special category. Declared by a plugin that handles text
245
+ * people wrote themselves, where it may be volunteered.
246
+ */
247
+ "health",
248
+ /** A photograph, whether or not anybody is recognisable in it. */
249
+ "photograph",
250
+ /** Text a person wrote themselves, which may name anybody. */
251
+ "freeText",
252
+ /** The audit log's record of who did what to whose data. */
253
+ "auditTrail",
254
+ /**
255
+ * Rows about a person with protected personal data (skyddade
256
+ * personuppgifter). Only with `addressBook:proposeImport`.
257
+ */
258
+ "protected",
259
+ ];
260
+ /**
261
+ * The personal data categories a plugin that proposes an import must declare,
262
+ * and the two no other plugin may.
263
+ */
264
+ export const PROPOSE_IMPORT_PERSONAL_DATA = [
265
+ "name",
266
+ "apartment",
267
+ "residency",
268
+ "email",
269
+ "phone",
270
+ "personalIdentityNumber",
271
+ "protected",
272
+ ];
273
+ export const PROPOSE_IMPORT_ONLY_PERSONAL_DATA = [
274
+ "personalIdentityNumber",
275
+ "protected",
68
276
  ];
69
277
  //# sourceMappingURL=permissions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC;;;;;;;;;OASG;IACH,kBAAkB;IAClB;;;;;OAKG;IACH,yBAAyB;IACzB,+DAA+D;IAC/D,WAAW;IACX;;;;;;;OAOG;IACH,UAAU;IACV,gEAAgE;IAChE,eAAe;CACP,CAAC;AAIX,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAQ,kBAAwC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG;IAC7C,6BAA6B;IAC7B,MAAM;IACN,4DAA4D;IAC5D,WAAW;IACX,sEAAsE;IACtE,WAAW;IACX,OAAO;IACP,OAAO;CACC,CAAC"}
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC;;;;;;;;;OASG;IACH,kBAAkB;IAClB;;;;;OAKG;IACH,yBAAyB;IACzB,+DAA+D;IAC/D,WAAW;IACX;;;;;;;OAOG;IACH,UAAU;IACV,gEAAgE;IAChE,eAAe;IACf;;;;;;;;;;;;;OAaG;IACH,eAAe;IACf;;;;;;OAMG;IACH,yBAAyB;IACzB;;;;;;;;;OASG;IACH,aAAa;IACb;;;;;;;;OAQG;IACH,WAAW;IACX;;;;;;;OAOG;IACH,eAAe;IACf;;;;OAIG;IACH,2BAA2B;IAC3B;;;;;;;;;OASG;IACH,YAAY;IACZ;;;;OAIG;IACH,gBAAgB;IAChB;;;;;;;;;OASG;IACH,gBAAgB;IAChB;;;;;;;;;;;OAWG;IACH,2BAA2B;CACnB,CAAC;AAIX,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAQ,kBAAwC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,kBAAkB,EAAE,OAAO;IAC3B,yBAAyB,EAAE,OAAO;IAClC,WAAW,EAAE,MAAM;IACnB,UAAU,EAAE,MAAM;IAClB,eAAe,EAAE,MAAM;IACvB,eAAe,EAAE,OAAO;IACxB,yBAAyB,EAAE,MAAM;IACjC,aAAa,EAAE,MAAM;IACrB,gBAAgB,EAAE,MAAM;IACxB,WAAW,EAAE,MAAM;IACnB,eAAe,EAAE,MAAM;IACvB,2BAA2B,EAAE,MAAM;IACnC,YAAY,EAAE,MAAM;IACpB,gBAAgB,EAAE,MAAM;IACxB,0EAA0E;IAC1E,0EAA0E;IAC1E,wDAAwD;IACxD,2BAA2B,EAAE,MAAM;CAC0B,CAAC;AAEhE,8EAA8E;AAC9E,MAAM,CAAC,MAAM,4BAA4B,GACvC,kBAAkB,CAAC,MAAM,CACvB,CAAC,UAAU,EAAE,EAAE,CAAC,0BAA0B,CAAC,UAAU,CAAC,KAAK,OAAO,CACnE,CAAC;AAEJ,6EAA6E;AAC7E,MAAM,UAAU,aAAa,CAAC,WAA8B;IAC1D,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CACpC,4BAAkD,CAAC,QAAQ,CAAC,UAAU,CAAC,CACzE,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG;IAC7C,6BAA6B;IAC7B,MAAM;IACN,4DAA4D;IAC5D,WAAW;IACX,sEAAsE;IACtE,WAAW;IACX,OAAO;IACP,OAAO;IACP,iEAAiE;IACjE,eAAe;IACf;;;OAGG;IACH,wBAAwB;IACxB,wCAAwC;IACxC,SAAS;IACT,kEAAkE;IAClE,WAAW;IACX;;;OAGG;IACH,QAAQ;IACR,kEAAkE;IAClE,YAAY;IACZ,8DAA8D;IAC9D,UAAU;IACV,4DAA4D;IAC5D,YAAY;IACZ;;;OAGG;IACH,WAAW;CACqC,CAAC;AAEnD;;;GAGG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG;IAC1C,MAAM;IACN,WAAW;IACX,WAAW;IACX,OAAO;IACP,OAAO;IACP,wBAAwB;IACxB,WAAW;CAC6C,CAAC;AAE3D,MAAM,CAAC,MAAM,iCAAiC,GAAG;IAC/C,wBAAwB;IACxB,WAAW;CAC6C,CAAC"}
@@ -0,0 +1,139 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * An `https` origin: the scheme, the host and a port only where it is not 443.
4
+ *
5
+ * Written as the WHATWG URL parser writes an origin, so one place has one
6
+ * spelling: `https://API.example.com:443/` and `https://api.example.com` are the
7
+ * same recipient, and only the second is accepted. No path, because the board
8
+ * consents to a party and not to one of its endpoints; no credentials, because
9
+ * a manifest is published; and nothing but `https`, because what crosses the
10
+ * connection is the association's data. The host is a name: not an IP literal
11
+ * or `localhost`, which name no party the board can recognise, and not a name
12
+ * with a trailing dot, which is the same host under a second spelling.
13
+ */
14
+ export declare const recipientOriginSchema: z.ZodString;
15
+ /** The host a recipient's origin names, which is what a board recognises. */
16
+ export declare function recipientHost(origin: string): string;
17
+ /**
18
+ * The hosts of the declared recipients that receive personal data, joined.
19
+ *
20
+ * The answer a declaration gives to who receives the data (GDPR art.
21
+ * 30(1)(d)): what the consent screen fills its recipient field with and what
22
+ * the record of processing activities lists. A recipient receiving none is a
23
+ * place the plugin connects to, not a recipient in the sense of art. 4(9), so
24
+ * it is left out; an empty string when none receives any.
25
+ */
26
+ export declare function receivingHosts(recipients: readonly Pick<PluginRecipient, "origin" | "personalData">[]): string;
27
+ /**
28
+ * The longest `receivingHosts` can be: every recipient the declaration may
29
+ * hold, each at the longest origin less its `https://`. A field the answer is
30
+ * written to has to take at least this, or a valid declaration fills in an
31
+ * answer the field then refuses.
32
+ */
33
+ export declare const RECEIVING_HOSTS_MAX_LENGTH: number;
34
+ export declare const pluginRecipientSchema: z.ZodObject<{
35
+ id: z.ZodString;
36
+ origin: z.ZodString;
37
+ purpose: z.ZodObject<{
38
+ sv: z.ZodString;
39
+ en: z.ZodString;
40
+ }, z.core.$strict>;
41
+ personalData: z.ZodArray<z.ZodEnum<{
42
+ account: "account";
43
+ apartment: "apartment";
44
+ auditTrail: "auditTrail";
45
+ email: "email";
46
+ financial: "financial";
47
+ freeText: "freeText";
48
+ health: "health";
49
+ name: "name";
50
+ personalIdentityNumber: "personalIdentityNumber";
51
+ phone: "phone";
52
+ photograph: "photograph";
53
+ postalAddress: "postalAddress";
54
+ protected: "protected";
55
+ residency: "residency";
56
+ }>>;
57
+ }, z.core.$strip>;
58
+ export type PluginRecipient = z.infer<typeof pluginRecipientSchema>;
59
+ /**
60
+ * The declared recipients over a given reading of one recipient.
61
+ *
62
+ * The manifest reads a recipient as `pluginRecipientSchema` does and the
63
+ * catalog index reads the same fields strictly, as it reads everything. Both
64
+ * keep the rules below, which is why the list is built here rather than
65
+ * written twice.
66
+ *
67
+ * An id appears once and an origin appears once. Two declarations of one id
68
+ * would leave the consent snapshot and the board's screen naming different
69
+ * things by it; two of one origin would let a plugin state a narrow purpose
70
+ * and a wide one for the same party, and a board reading the first would have
71
+ * consented to the second.
72
+ *
73
+ * Undefaulted, on the terms of `declaredActionsSchema`: a manifest and a
74
+ * catalog entry add `.default([])`, and the install request does not, so an
75
+ * echo that left the field out is not read as consent to none.
76
+ */
77
+ export declare function declaredRecipientsSchema<T extends z.ZodType<{
78
+ id: string;
79
+ origin: string;
80
+ }>>(recipient: T): z.ZodArray<T>;
81
+ export declare const pluginRecipientsSchema: z.ZodArray<z.ZodObject<{
82
+ id: z.ZodString;
83
+ origin: z.ZodString;
84
+ purpose: z.ZodObject<{
85
+ sv: z.ZodString;
86
+ en: z.ZodString;
87
+ }, z.core.$strict>;
88
+ personalData: z.ZodArray<z.ZodEnum<{
89
+ account: "account";
90
+ apartment: "apartment";
91
+ auditTrail: "auditTrail";
92
+ email: "email";
93
+ financial: "financial";
94
+ freeText: "freeText";
95
+ health: "health";
96
+ name: "name";
97
+ personalIdentityNumber: "personalIdentityNumber";
98
+ phone: "phone";
99
+ photograph: "photograph";
100
+ postalAddress: "postalAddress";
101
+ protected: "protected";
102
+ residency: "residency";
103
+ }>>;
104
+ }, z.core.$strip>>;
105
+ /**
106
+ * The canonical string a recipient is consented to and compared by.
107
+ *
108
+ * The id, the origin and the categories: what the plugin may reach and what
109
+ * goes there. The purpose is the plugin's words for it and is shown, not
110
+ * compared, so a republished version that rewords one is not refused for it.
111
+ */
112
+ export declare function canonicalRecipient(recipient: Pick<PluginRecipient, "id" | "origin" | "personalData">): string;
113
+ /**
114
+ * The declared recipients a consent given to `consented`, as
115
+ * `canonicalRecipient` wrote it, does not cover.
116
+ *
117
+ * A recipient is covered by a consented one with the same id and origin that
118
+ * received every category it now receives. Fewer categories are covered: the
119
+ * plugin's data reaching that party narrowed, and the board agreed to more.
120
+ * One more, a new id or an id moved to another origin is not.
121
+ */
122
+ export declare function recipientsBeyondConsent<T extends Pick<PluginRecipient, "id" | "origin" | "personalData">>(declared: readonly T[], consented: readonly string[]): T[];
123
+ /** Whether two declarations name the same recipients, as multisets. */
124
+ export declare function sameRecipientDeclaration(left: readonly Pick<PluginRecipient, "id" | "origin" | "personalData">[], right: readonly Pick<PluginRecipient, "id" | "origin" | "personalData">[]): boolean;
125
+ /**
126
+ * The recipients' categories that are not among the ones the plugin declares
127
+ * it handles, as issues at the path of each.
128
+ *
129
+ * What a recipient receives is part of what the plugin handles. One naming a
130
+ * category the plugin does not declare would put it on the consent screen, and
131
+ * into the record of who receives what, without the record of processing
132
+ * activities hearing of it. The manifest and the catalog entry repeat the same
133
+ * two fields, so both refuse it with this rule.
134
+ */
135
+ export declare function recipientCategoryIssues(recipients: readonly Pick<PluginRecipient, "personalData">[], handled: readonly string[]): {
136
+ path: (string | number)[];
137
+ message: string;
138
+ }[];
139
+ //# sourceMappingURL=recipients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipients.d.ts","sourceRoot":"","sources":["../src/recipients.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA4BxB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,aAO9B,CAAC;AAqDL,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,SAAS,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,cAAc,CAAC,EAAE,GACtE,MAAM,CAKR;AAED;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,QAEO,CAAC;AAE/C,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;iBAoBhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CACtC,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EACnD,SAAS,EAAE,CAAC,iBA0Bb;AAED,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;kBAElC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,QAAQ,GAAG,cAAc,CAAC,GACjE,MAAM,CAMR;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,CAAC,SAAS,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,QAAQ,GAAG,cAAc,CAAC,EACjE,QAAQ,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,EAAE,CAiB3D;AAED,uEAAuE;AACvE,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,SAAS,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,QAAQ,GAAG,cAAc,CAAC,EAAE,EACxE,KAAK,EAAE,SAAS,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,QAAQ,GAAG,cAAc,CAAC,EAAE,GACxE,OAAO,CAIT;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,SAAS,IAAI,CAAC,eAAe,EAAE,cAAc,CAAC,EAAE,EAC5D,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB;IAAE,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CAclD"}
@@ -0,0 +1,223 @@
1
+ import { z } from "zod";
2
+ import { localizedTextSchema } from "./localized-text.js";
3
+ import { PLUGIN_PERSONAL_DATA_CATEGORIES } from "./permissions.js";
4
+ /**
5
+ * Where a plugin connects to outside the instance (ADR 0037).
6
+ *
7
+ * Declared in the manifest, and repeated in the catalog entry, for the reason
8
+ * actions are: the board reads it on the install consent screen before anything
9
+ * has been downloaded, and it is what the screen's question about where the
10
+ * plugin sends personal data is answered from.
11
+ *
12
+ * A declaration rather than a fence. A backend plugin runs at full process
13
+ * privilege (ADR 0003), so nothing here stops one opening a connection it did
14
+ * not declare; the catalog's reading of the server bundle is what holds a
15
+ * listed plugin to it.
16
+ */
17
+ /** The longest origin a recipient may name. */
18
+ const RECIPIENT_ORIGIN_MAX_LENGTH = 200;
19
+ /** How many recipients a plugin may declare. */
20
+ const MAX_RECIPIENTS = 8;
21
+ /** What the joined hosts separate them with. */
22
+ const HOST_SEPARATOR = ", ";
23
+ /**
24
+ * An `https` origin: the scheme, the host and a port only where it is not 443.
25
+ *
26
+ * Written as the WHATWG URL parser writes an origin, so one place has one
27
+ * spelling: `https://API.example.com:443/` and `https://api.example.com` are the
28
+ * same recipient, and only the second is accepted. No path, because the board
29
+ * consents to a party and not to one of its endpoints; no credentials, because
30
+ * a manifest is published; and nothing but `https`, because what crosses the
31
+ * connection is the association's data. The host is a name: not an IP literal
32
+ * or `localhost`, which name no party the board can recognise, and not a name
33
+ * with a trailing dot, which is the same host under a second spelling.
34
+ */
35
+ export const recipientOriginSchema = z
36
+ .string()
37
+ .min(1)
38
+ .max(RECIPIENT_ORIGIN_MAX_LENGTH)
39
+ .refine(isCanonicalHttpsOrigin, {
40
+ error: 'must be an https origin as a browser writes it, such as "https://api.example.com": lowercase, with no path, no trailing slash, no port 443, and a host name that is not an IP address, localhost or written with a trailing dot',
41
+ });
42
+ const WhatwgUrl = globalThis.URL;
43
+ function isCanonicalHttpsOrigin(value) {
44
+ let parsed;
45
+ try {
46
+ parsed = new WhatwgUrl(value);
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ return (parsed.protocol === "https:" &&
52
+ parsed.username === "" &&
53
+ parsed.password === "" &&
54
+ parsed.origin === value &&
55
+ isNamedHost(parsed.hostname));
56
+ }
57
+ /**
58
+ * Whether a host is a registered-style name: not a trailing-dot spelling, an
59
+ * IPv6 literal, a dotted IPv4 address or `localhost`. The parser has already
60
+ * turned any other IPv4 spelling (`0x7f.1`, `2130706433`) into the dotted one.
61
+ */
62
+ function isNamedHost(hostname) {
63
+ return !(hostname.endsWith(".") ||
64
+ hostname.startsWith("[") ||
65
+ /^\d+\.\d+\.\d+\.\d+$/.test(hostname) ||
66
+ hostname === "localhost" ||
67
+ hostname.endsWith(".localhost"));
68
+ }
69
+ /** The host a recipient's origin names, which is what a board recognises. */
70
+ export function recipientHost(origin) {
71
+ return new WhatwgUrl(origin).host;
72
+ }
73
+ /**
74
+ * The hosts of the declared recipients that receive personal data, joined.
75
+ *
76
+ * The answer a declaration gives to who receives the data (GDPR art.
77
+ * 30(1)(d)): what the consent screen fills its recipient field with and what
78
+ * the record of processing activities lists. A recipient receiving none is a
79
+ * place the plugin connects to, not a recipient in the sense of art. 4(9), so
80
+ * it is left out; an empty string when none receives any.
81
+ */
82
+ export function receivingHosts(recipients) {
83
+ return recipients
84
+ .filter((recipient) => recipient.personalData.length > 0)
85
+ .map((recipient) => recipientHost(recipient.origin))
86
+ .join(HOST_SEPARATOR);
87
+ }
88
+ /**
89
+ * The longest `receivingHosts` can be: every recipient the declaration may
90
+ * hold, each at the longest origin less its `https://`. A field the answer is
91
+ * written to has to take at least this, or a valid declaration fills in an
92
+ * answer the field then refuses.
93
+ */
94
+ export const RECEIVING_HOSTS_MAX_LENGTH = MAX_RECIPIENTS * (RECIPIENT_ORIGIN_MAX_LENGTH - "https://".length) +
95
+ (MAX_RECIPIENTS - 1) * HOST_SEPARATOR.length;
96
+ export const pluginRecipientSchema = z.object({
97
+ /** Unique in the manifest. Lowercase letters, digits and single hyphens. */
98
+ id: z
99
+ .string()
100
+ .min(2)
101
+ .max(32)
102
+ .regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, "must be lowercase letters, digits and single hyphens, starting with a letter"),
103
+ origin: recipientOriginSchema,
104
+ /** What the plugin connects there for, in Swedish and English. */
105
+ purpose: localizedTextSchema,
106
+ /**
107
+ * The personal-data categories this recipient receives, from the plugin's
108
+ * own `personalData`. Empty for a connection that carries none, such as
109
+ * fetching a list of models or the day's exchange rates: it is declared all
110
+ * the same, because the board is shown every place the plugin connects to.
111
+ */
112
+ personalData: z.array(z.enum(PLUGIN_PERSONAL_DATA_CATEGORIES)).max(16),
113
+ });
114
+ /**
115
+ * The declared recipients over a given reading of one recipient.
116
+ *
117
+ * The manifest reads a recipient as `pluginRecipientSchema` does and the
118
+ * catalog index reads the same fields strictly, as it reads everything. Both
119
+ * keep the rules below, which is why the list is built here rather than
120
+ * written twice.
121
+ *
122
+ * An id appears once and an origin appears once. Two declarations of one id
123
+ * would leave the consent snapshot and the board's screen naming different
124
+ * things by it; two of one origin would let a plugin state a narrow purpose
125
+ * and a wide one for the same party, and a board reading the first would have
126
+ * consented to the second.
127
+ *
128
+ * Undefaulted, on the terms of `declaredActionsSchema`: a manifest and a
129
+ * catalog entry add `.default([])`, and the install request does not, so an
130
+ * echo that left the field out is not read as consent to none.
131
+ */
132
+ export function declaredRecipientsSchema(recipient) {
133
+ return z
134
+ .array(recipient)
135
+ .max(MAX_RECIPIENTS)
136
+ .superRefine((recipients, ctx) => {
137
+ const ids = new Set();
138
+ const origins = new Set();
139
+ for (const [index, declared] of recipients.entries()) {
140
+ if (ids.has(declared.id)) {
141
+ ctx.addIssue({
142
+ code: "custom",
143
+ path: [index, "id"],
144
+ message: `two recipients are declared with the id "${declared.id}"`,
145
+ });
146
+ }
147
+ if (origins.has(declared.origin)) {
148
+ ctx.addIssue({
149
+ code: "custom",
150
+ path: [index, "origin"],
151
+ message: `two recipients are declared with the origin "${declared.origin}"`,
152
+ });
153
+ }
154
+ ids.add(declared.id);
155
+ origins.add(declared.origin);
156
+ }
157
+ });
158
+ }
159
+ export const pluginRecipientsSchema = declaredRecipientsSchema(pluginRecipientSchema);
160
+ /**
161
+ * The canonical string a recipient is consented to and compared by.
162
+ *
163
+ * The id, the origin and the categories: what the plugin may reach and what
164
+ * goes there. The purpose is the plugin's words for it and is shown, not
165
+ * compared, so a republished version that rewords one is not refused for it.
166
+ */
167
+ export function canonicalRecipient(recipient) {
168
+ return [
169
+ recipient.id,
170
+ recipient.origin,
171
+ [...recipient.personalData].sort().join("|"),
172
+ ].join(" ");
173
+ }
174
+ /**
175
+ * The declared recipients a consent given to `consented`, as
176
+ * `canonicalRecipient` wrote it, does not cover.
177
+ *
178
+ * A recipient is covered by a consented one with the same id and origin that
179
+ * received every category it now receives. Fewer categories are covered: the
180
+ * plugin's data reaching that party narrowed, and the board agreed to more.
181
+ * One more, a new id or an id moved to another origin is not.
182
+ */
183
+ export function recipientsBeyondConsent(declared, consented) {
184
+ const agreed = new Map();
185
+ for (const canonical of consented) {
186
+ // Neither an id nor a canonical origin has a space in it.
187
+ const [id, origin, categories = ""] = canonical.split(" ");
188
+ agreed.set(`${id} ${origin}`, new Set(categories === "" ? [] : categories.split("|")));
189
+ }
190
+ return declared.filter((recipient) => {
191
+ const categories = agreed.get(`${recipient.id} ${recipient.origin}`);
192
+ return (categories === undefined ||
193
+ recipient.personalData.some((category) => !categories.has(category)));
194
+ });
195
+ }
196
+ /** Whether two declarations name the same recipients, as multisets. */
197
+ export function sameRecipientDeclaration(left, right) {
198
+ const a = left.map(canonicalRecipient).sort();
199
+ const b = right.map(canonicalRecipient).sort();
200
+ return a.length === b.length && a.every((value, index) => value === b[index]);
201
+ }
202
+ /**
203
+ * The recipients' categories that are not among the ones the plugin declares
204
+ * it handles, as issues at the path of each.
205
+ *
206
+ * What a recipient receives is part of what the plugin handles. One naming a
207
+ * category the plugin does not declare would put it on the consent screen, and
208
+ * into the record of who receives what, without the record of processing
209
+ * activities hearing of it. The manifest and the catalog entry repeat the same
210
+ * two fields, so both refuse it with this rule.
211
+ */
212
+ export function recipientCategoryIssues(recipients, handled) {
213
+ const declared = new Set(handled);
214
+ return recipients.flatMap((recipient, index) => recipient.personalData.flatMap((category, at) => declared.has(category)
215
+ ? []
216
+ : [
217
+ {
218
+ path: ["recipients", index, "personalData", at],
219
+ message: `"${category}" is not among the plugin's own personalData; a recipient receives only what the plugin declares it handles`,
220
+ },
221
+ ]));
222
+ }
223
+ //# sourceMappingURL=recipients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipients.js","sourceRoot":"","sources":["../src/recipients.ts"],"names":[],"mappings":"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"}