domma-cms 0.109.3 → 0.109.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.109.3",
3
+ "version": "0.109.4",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -117,7 +117,7 @@ export function classifyEntitlement(slug, entitlement = readEntitlement(slug), n
117
117
  return {state: 'unlicensed', reason: `entitlement is for "${entitlement.slug}"`, entitlement};
118
118
  }
119
119
 
120
- const pem = trustedKey(entitlement.keyId);
120
+ const pem = trustedKey(entitlement.keyId, 'entitlement');
121
121
  if (!pem) {
122
122
  return {state: 'unlicensed', reason: `unknown signing key "${entitlement.keyId}"`, entitlement};
123
123
  }
@@ -42,14 +42,32 @@ const AUDIT_LOG = path.join(ROOT, 'content', 'instances_log.json');
42
42
  // every CMS accepts as ours, permanently. Nothing had ever been signed with it,
43
43
  // so there was no artifact to stay compatible with.
44
44
  //
45
- // Private counterpart: generated 2026-09-22, held off-repo. See the marketplace
46
- // design's Signing keys section for custody.
45
+ // Each key says what it may sign: `bundle` (plugin code - every CMS installs
46
+ // what it signs) and/or `entitlement` (a licence for a plugin a site already
47
+ // has). A key a manager must hold online, to mint licences as it grants them,
48
+ // is trusted for entitlements ONLY: if it leaks, the damage is forged
49
+ // licences, never code every CMS would run.
50
+ //
51
+ // marketplace-1: generated 2026-09-22, held off-repo; signs the catalogue and
52
+ // bundles, and svr-1's licences. See the marketplace design's Signing keys
53
+ // section for custody.
54
+ // svr-2-1: generated 2026-10-01 on svr-2 (aurelius), where it stays; svr-2's
55
+ // licences only.
47
56
  // ---------------------------------------------------------------------------
48
57
 
49
58
  const TRUSTED_KEYS = {
50
- 'marketplace-1': `-----BEGIN PUBLIC KEY-----
59
+ 'marketplace-1': {
60
+ uses: ['bundle', 'entitlement'],
61
+ pem: `-----BEGIN PUBLIC KEY-----
51
62
  MCowBQYDK2VwAyEAkgYWv0IckLy2lCeNoaNot++KMJLpJa6FTd6RW0sH2zg=
52
63
  -----END PUBLIC KEY-----`
64
+ },
65
+ 'svr-2-1': {
66
+ uses: ['entitlement'],
67
+ pem: `-----BEGIN PUBLIC KEY-----
68
+ MCowBQYDK2VwAyEAqAlx1lfHeCaNiwJmoXZMDY56yafU2GlpNG3VInw9Q14=
69
+ -----END PUBLIC KEY-----`
70
+ }
53
71
  };
54
72
 
55
73
  /**
@@ -71,16 +89,20 @@ function requireSignedPlugins() {
71
89
  }
72
90
 
73
91
  /**
74
- * One trusted public key by id, or null.
92
+ * One trusted public key by id, for one use, or null.
75
93
  *
76
- * Entitlement verification needs the same keys bundle installation does, and a
77
- * second copy of them would be a second thing to rotate.
94
+ * Bundles and entitlements share this one list, so there is one place to
95
+ * rotate - but not every key may sign both. A key not trusted for `use` is
96
+ * answered exactly like an unknown one. The default is `bundle`, the stricter
97
+ * of the two, so a caller that forgets to say gets the safe answer.
78
98
  *
79
99
  * @param {string} keyId
100
+ * @param {'bundle'|'entitlement'} [use]
80
101
  * @returns {string|null} PEM
81
102
  */
82
- export function trustedKey(keyId) {
83
- return TRUSTED_KEYS[keyId] ?? null;
103
+ export function trustedKey(keyId, use = 'bundle') {
104
+ const key = Object.hasOwn(TRUSTED_KEYS, keyId) ? TRUSTED_KEYS[keyId] : null;
105
+ return key && key.uses.includes(use) ? key.pem : null;
84
106
  }
85
107
 
86
108
  /**
@@ -90,10 +112,11 @@ export function trustedKey(keyId) {
90
112
  * trusting a key it should not, or having lost the one it should, is the kind of
91
113
  * mistake that is invisible until a bundle is refused in the field.
92
114
  *
115
+ * @param {'bundle'|'entitlement'} [use] - only the keys trusted for this; all when omitted
93
116
  * @returns {string[]}
94
117
  */
95
- export function trustedKeyIds() {
96
- return Object.keys(TRUSTED_KEYS);
118
+ export function trustedKeyIds(use) {
119
+ return Object.keys(TRUSTED_KEYS).filter((id) => !use || TRUSTED_KEYS[id].uses.includes(use));
97
120
  }
98
121
 
99
122
  // ---------------------------------------------------------------------------
@@ -333,7 +356,7 @@ export async function installPlugin(slug, { tarball, signature, publicKeyId, all
333
356
  }
334
357
  }
335
358
 
336
- const pubKeyPem = unsigned ? null : TRUSTED_KEYS[publicKeyId];
359
+ const pubKeyPem = unsigned ? null : trustedKey(publicKeyId, 'bundle');
337
360
  if (!unsigned && !pubKeyPem) {
338
361
  throw new Error(`Unknown public key: ${publicKeyId}`);
339
362
  }