@serfab/quereus-plugin-sereus 0.8.0 → 0.8.1
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/dist/compose-strand.d.ts +4 -14
- package/dist/compose-strand.js.map +1 -1
- package/dist/connect-browser.js.map +1 -1
- package/dist/plugin-browser.js +61420 -44460
- package/dist/plugin-browser.js.map +4 -4
- package/dist/strand-schema.d.ts +2 -2
- package/dist/strand-schema.js +21 -11
- package/dist/strand-schema.js.map +1 -1
- package/package.json +8 -8
- package/src/compose-strand.ts +11 -11
- package/src/connect-browser.ts +5 -5
- package/src/strand-schema.ts +21 -11
package/dist/strand-schema.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Crypto idiom (matches `schemas/control.qsql` and the sApp RBAC fixture
|
|
20
20
|
* `packages/integration-tests/fixtures/simple-sapp.qsql`): every signed write
|
|
21
|
-
* proves itself with `verify(digest(<
|
|
21
|
+
* proves itself with `verify(digest(<single joined payload>),
|
|
22
22
|
* <signature>, <pubkey>, 'ed25519')`. Member keys are ed25519, so the explicit
|
|
23
23
|
* curve arg is REQUIRED (verify() otherwise defaults to secp256k1).
|
|
24
24
|
*
|
|
@@ -27,4 +27,4 @@
|
|
|
27
27
|
* the founding `Authority`/`Member`, and the invite/peer flows is owned by
|
|
28
28
|
* `strand-membership-lifecycle-population`.
|
|
29
29
|
*/
|
|
30
|
-
export declare const STRAND_SCHEMA = " table Header (\n Id text, -- UUID of this network - generated at inception - immutable\n\n -- Types: 'o' = Open, 'c' = Closed\n -- Any peers can join an open network, but only members can join a closed network\n -- Open can still control writes in the app, but only Closed prevents reads\n Type text check (Type in ('o', 'c')),\n\n -- The app that this strand faciliates - public key from author to prevent tampering\n sAppId text,\n\n -- The version of the sApp that this strand faciliates\n sAppVersion text,\n\n -- The schema of the sApp that this strand faciliates\n sAppSchema text,\n\n -- The author's signature on the schema to guarantee authorship\n sAppSignature text,\n\n -- The engine (rules logic system) that is assumed to manage this strand\n Engine text,\n\n -- The version of the engine that is assumed to manage this strand\n EngineVersion text,\n\n primary key (/* empty - singleton */),\n constraint InsertOnly check on update, delete (false), -- One-time insert only for now - TODO: revisit for versioning\n );\n\n -- An invitation to join a strand as a member\n table Invite (\n Key text primary key,\n Expiration datetime null,\n -- TODO: ability to deactivate?\n constraint InsertOnly check on update, delete (false),\n constraint OnlyClosed check (\n exists (select 1 from Header H where H.Type = 'c')\n ),\n constraint InviteValid check on insert (\n -- Can only be inserted by an authority,\n exists (select 1 from Authority A\n where A.MemberKey = context.AuthorityKey\n and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')
|
|
30
|
+
export declare const STRAND_SCHEMA = " table Header (\n Id text, -- UUID of this network - generated at inception - immutable\n\n -- Types: 'o' = Open, 'c' = Closed\n -- Any peers can join an open network, but only members can join a closed network\n -- Open can still control writes in the app, but only Closed prevents reads\n Type text check (Type in ('o', 'c')),\n\n -- The app that this strand faciliates - public key from author to prevent tampering\n sAppId text,\n\n -- The version of the sApp that this strand faciliates\n sAppVersion text,\n\n -- The schema of the sApp that this strand faciliates\n sAppSchema text,\n\n -- The author's signature on the schema to guarantee authorship\n sAppSignature text,\n\n -- The engine (rules logic system) that is assumed to manage this strand\n Engine text,\n\n -- The version of the engine that is assumed to manage this strand\n EngineVersion text,\n\n primary key (/* empty - singleton */),\n constraint InsertOnly check on update, delete (false), -- One-time insert only for now - TODO: revisit for versioning\n );\n\n -- An invitation to join a strand as a member\n table Invite (\n Key text primary key,\n Expiration datetime null,\n -- TODO: ability to deactivate?\n constraint InsertOnly check on update, delete (false),\n constraint OnlyClosed check (\n exists (select 1 from Header H where H.Type = 'c')\n ),\n constraint InviteValid check on insert (\n -- Can only be inserted by an authority,\n exists (select 1 from Authority A\n where A.MemberKey = context.AuthorityKey\n and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.AuthoritySignature, A.MemberKey, 'ed25519')\n )\n -- and must also prove invite private key held by issuing authority\n and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.InviteSignature, new.Key, 'ed25519')\n )\n ) with context (AuthorityKey text null, AuthoritySignature text null, InviteSignature text null);\n\n -- Invite [InviteKey] has been used to add [MemberKey] as a member\n table ConsumedInvite (\n InviteKey text primary key,\n MemberKey text,\n constraint InsertOnly check on update, delete (false),\n constraint InviteExists check (exists (select 1 from Invite I where I.Key = new.InviteKey)),\n constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),\n constraint ValidUsage check on insert (\n exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey), context.InviteSignature, new.InviteKey, 'ed25519'))\n ),\n -- An invite with a non-null Expiration may only be consumed while it is still\n -- in the future. context.Now is the canonical-datetime \"now\" supplied by the\n -- consumeInvite writer (same canonicalDatetime() transform used to store\n -- Invite.Expiration), so both sides of the comparison are byte-identical\n -- canonical strings and the lexical \">\" comparison orders chronologically. A\n -- null Expiration never expires. Mirrors CadreControl.FormationUsage's\n -- \"FI.ExpiresAt is null or FI.ExpiresAt > context.Now\" gate.\n constraint NotExpired check on insert (\n exists (select 1 from Invite I where I.Key = new.InviteKey and (I.Expiration is null or I.Expiration > context.Now))\n ),\n constraint MemberValid check (exists (select 1 from Member M where M.Key = new.MemberKey))\n ) with context (InviteSignature text null, Now datetime null);\n\n -- A party in the closed strand network\n table Member (\n Key text primary key,\n constraint NoUpdate check on update (false),\n constraint OnlyClosed check (\n exists (select 1 from Header H where H.Type = 'c')\n ),\n constraint Authorized check on insert (\n -- There are no other records - first member needs no authorization\n (select count(1) from Member) <= 1\n\n -- or added directly by authority\n or exists (\n select 1 from Authority A\n where A.MemberKey = context.AuthorityKey\n and verify(digest(new.Key), context.AuthoritySignature, A.MemberKey, 'ed25519')\n )\n\n -- or added by invite\n or exists (\n select 1 from ConsumedInvite CI where CI.MemberKey = new.Key\n )\n ),\n -- TODO: handle member revocation constraint\n ) with context (AuthorityKey text null, AuthoritySignature text null);\n\n -- A member-associated peer (node)\n table MemberPeer (\n MemberKey text,\n PeerId text,\n primary key (MemberKey, PeerId),\n constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),\n constraint Authorized check on insert, update, delete (\n verify(\n digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId)),\n context.Signature,\n coalesce(new.MemberKey, old.MemberKey),\n 'ed25519'\n )\n ),\n ) with context (Signature text null);\n\n -- An authority is a member that can issue invites, authorize members, and rotate authorities\n table Authority (\n MemberKey text primary key,\n constraint OnlyClosed check (\n exists (select 1 from Header H where H.Type = 'c')\n ),\n constraint Authorized check on insert, update, delete (\n -- There are no existing records - first authority needs no authorization\n (select count(1) from Authority) <= 1\n\n -- or authorized by this former authority\n or (\n old.MemberKey is not null\n and old.MemberKey = context.AuthorityKey\n and verify(digest(old.MemberKey), context.Signature, old.MemberKey, 'ed25519')\n )\n\n -- or authorized by another existing authority\n or exists (\n select 1 from Authority A\n where A.MemberKey = context.AuthorityKey\n and verify(digest(coalesce(new.MemberKey, old.MemberKey)), context.Signature, A.MemberKey, 'ed25519')\n )\n )\n ) with context (AuthorityKey text null, Signature text null);\n";
|
package/dist/strand-schema.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Crypto idiom (matches `schemas/control.qsql` and the sApp RBAC fixture
|
|
20
20
|
* `packages/integration-tests/fixtures/simple-sapp.qsql`): every signed write
|
|
21
|
-
* proves itself with `verify(digest(<
|
|
21
|
+
* proves itself with `verify(digest(<single joined payload>),
|
|
22
22
|
* <signature>, <pubkey>, 'ed25519')`. Member keys are ed25519, so the explicit
|
|
23
23
|
* curve arg is REQUIRED (verify() otherwise defaults to secp256k1).
|
|
24
24
|
*
|
|
@@ -70,10 +70,10 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
70
70
|
-- Can only be inserted by an authority,
|
|
71
71
|
exists (select 1 from Authority A
|
|
72
72
|
where A.MemberKey = context.AuthorityKey
|
|
73
|
-
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')
|
|
73
|
+
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.AuthoritySignature, A.MemberKey, 'ed25519')
|
|
74
74
|
)
|
|
75
75
|
-- and must also prove invite private key held by issuing authority
|
|
76
|
-
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')
|
|
76
|
+
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.InviteSignature, new.Key, 'ed25519')
|
|
77
77
|
)
|
|
78
78
|
) with context (AuthorityKey text null, AuthoritySignature text null, InviteSignature text null);
|
|
79
79
|
|
|
@@ -85,10 +85,20 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
85
85
|
constraint InviteExists check (exists (select 1 from Invite I where I.Key = new.InviteKey)),
|
|
86
86
|
constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),
|
|
87
87
|
constraint ValidUsage check on insert (
|
|
88
|
-
exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey
|
|
88
|
+
exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey), context.InviteSignature, new.InviteKey, 'ed25519'))
|
|
89
|
+
),
|
|
90
|
+
-- An invite with a non-null Expiration may only be consumed while it is still
|
|
91
|
+
-- in the future. context.Now is the canonical-datetime "now" supplied by the
|
|
92
|
+
-- consumeInvite writer (same canonicalDatetime() transform used to store
|
|
93
|
+
-- Invite.Expiration), so both sides of the comparison are byte-identical
|
|
94
|
+
-- canonical strings and the lexical ">" comparison orders chronologically. A
|
|
95
|
+
-- null Expiration never expires. Mirrors CadreControl.FormationUsage's
|
|
96
|
+
-- "FI.ExpiresAt is null or FI.ExpiresAt > context.Now" gate.
|
|
97
|
+
constraint NotExpired check on insert (
|
|
98
|
+
exists (select 1 from Invite I where I.Key = new.InviteKey and (I.Expiration is null or I.Expiration > context.Now))
|
|
89
99
|
),
|
|
90
100
|
constraint MemberValid check (exists (select 1 from Member M where M.Key = new.MemberKey))
|
|
91
|
-
) with context (InviteSignature text null);
|
|
101
|
+
) with context (InviteSignature text null, Now datetime null);
|
|
92
102
|
|
|
93
103
|
-- A party in the closed strand network
|
|
94
104
|
table Member (
|
|
@@ -105,7 +115,7 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
105
115
|
or exists (
|
|
106
116
|
select 1 from Authority A
|
|
107
117
|
where A.MemberKey = context.AuthorityKey
|
|
108
|
-
and verify(digest(new.Key
|
|
118
|
+
and verify(digest(new.Key), context.AuthoritySignature, A.MemberKey, 'ed25519')
|
|
109
119
|
)
|
|
110
120
|
|
|
111
121
|
-- or added by invite
|
|
@@ -122,9 +132,9 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
122
132
|
PeerId text,
|
|
123
133
|
primary key (MemberKey, PeerId),
|
|
124
134
|
constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),
|
|
125
|
-
constraint Authorized check (
|
|
135
|
+
constraint Authorized check on insert, update, delete (
|
|
126
136
|
verify(
|
|
127
|
-
digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId)
|
|
137
|
+
digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId)),
|
|
128
138
|
context.Signature,
|
|
129
139
|
coalesce(new.MemberKey, old.MemberKey),
|
|
130
140
|
'ed25519'
|
|
@@ -138,7 +148,7 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
138
148
|
constraint OnlyClosed check (
|
|
139
149
|
exists (select 1 from Header H where H.Type = 'c')
|
|
140
150
|
),
|
|
141
|
-
constraint Authorized check (
|
|
151
|
+
constraint Authorized check on insert, update, delete (
|
|
142
152
|
-- There are no existing records - first authority needs no authorization
|
|
143
153
|
(select count(1) from Authority) <= 1
|
|
144
154
|
|
|
@@ -146,14 +156,14 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
146
156
|
or (
|
|
147
157
|
old.MemberKey is not null
|
|
148
158
|
and old.MemberKey = context.AuthorityKey
|
|
149
|
-
and verify(digest(old.MemberKey
|
|
159
|
+
and verify(digest(old.MemberKey), context.Signature, old.MemberKey, 'ed25519')
|
|
150
160
|
)
|
|
151
161
|
|
|
152
162
|
-- or authorized by another existing authority
|
|
153
163
|
or exists (
|
|
154
164
|
select 1 from Authority A
|
|
155
165
|
where A.MemberKey = context.AuthorityKey
|
|
156
|
-
and verify(digest(coalesce(new.MemberKey, old.MemberKey)
|
|
166
|
+
and verify(digest(coalesce(new.MemberKey, old.MemberKey)), context.Signature, A.MemberKey, 'ed25519')
|
|
157
167
|
)
|
|
158
168
|
)
|
|
159
169
|
) with context (AuthorityKey text null, Signature text null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strand-schema.js","sourceRoot":"","sources":["../src/strand-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG
|
|
1
|
+
{"version":3,"file":"strand-schema.js","sourceRoot":"","sources":["../src/strand-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4I5B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serfab/quereus-plugin-sereus",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Quereus plugin that connects to a Sereus strand for SQL access",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -67,13 +67,13 @@
|
|
|
67
67
|
"@libp2p/circuit-relay-v2": "^4.1.3",
|
|
68
68
|
"@libp2p/interface": "^3.1.0",
|
|
69
69
|
"@libp2p/websockets": "^10.1.3",
|
|
70
|
-
"@optimystic/db-core": "^0.
|
|
71
|
-
"@optimystic/db-p2p": "^0.
|
|
72
|
-
"@optimystic/db-p2p-storage-fs": "^0.
|
|
73
|
-
"@optimystic/db-p2p-storage-web": "^0.
|
|
74
|
-
"@optimystic/quereus-plugin-crypto": "^0.
|
|
75
|
-
"@optimystic/quereus-plugin-optimystic": "^0.
|
|
76
|
-
"@quereus/quereus": "^
|
|
70
|
+
"@optimystic/db-core": "^0.14.1",
|
|
71
|
+
"@optimystic/db-p2p": "^0.14.1",
|
|
72
|
+
"@optimystic/db-p2p-storage-fs": "^0.14.1",
|
|
73
|
+
"@optimystic/db-p2p-storage-web": "^0.14.1",
|
|
74
|
+
"@optimystic/quereus-plugin-crypto": "^0.14.1",
|
|
75
|
+
"@optimystic/quereus-plugin-optimystic": "^0.14.1",
|
|
76
|
+
"@quereus/quereus": "^4.2.1",
|
|
77
77
|
"debug": "^4.4.3"
|
|
78
78
|
},
|
|
79
79
|
"quereus": {
|
package/src/compose-strand.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
|
-
import type { Database } from '@quereus/quereus';
|
|
2
|
+
import type { Database, VTablePluginInfo, FunctionPluginInfo, CollationPluginInfo } from '@quereus/quereus';
|
|
3
3
|
import optimysticPlugin from '@optimystic/quereus-plugin-optimystic/plugin';
|
|
4
4
|
import type { Libp2p } from '@libp2p/interface';
|
|
5
5
|
import type { IRepo } from '@optimystic/db-core';
|
|
@@ -27,9 +27,9 @@ interface CollectionFactory {
|
|
|
27
27
|
*/
|
|
28
28
|
interface OptimysticPluginResult {
|
|
29
29
|
collectionFactory: CollectionFactory;
|
|
30
|
-
vtables:
|
|
31
|
-
functions:
|
|
32
|
-
collations?:
|
|
30
|
+
vtables: VTablePluginInfo[];
|
|
31
|
+
functions: FunctionPluginInfo[];
|
|
32
|
+
collations?: CollationPluginInfo[];
|
|
33
33
|
/**
|
|
34
34
|
* Hydrate Quereus's in-memory catalog from persisted optimystic vtab schemas.
|
|
35
35
|
* Must run BEFORE `apply schema App;` on a warm restart so the declarative
|
|
@@ -43,9 +43,9 @@ interface OptimysticPluginResult {
|
|
|
43
43
|
|
|
44
44
|
/** Registration shape shared by the crypto and optimystic plugin results. */
|
|
45
45
|
interface PluginRegistrations {
|
|
46
|
-
vtables?:
|
|
47
|
-
functions?:
|
|
48
|
-
collations?:
|
|
46
|
+
vtables?: VTablePluginInfo[];
|
|
47
|
+
functions?: FunctionPluginInfo[];
|
|
48
|
+
collations?: CollationPluginInfo[];
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -57,13 +57,13 @@ interface PluginRegistrations {
|
|
|
57
57
|
*/
|
|
58
58
|
export function applyRegistrations(db: Database, result: PluginRegistrations): void {
|
|
59
59
|
for (const vtable of result.vtables ?? []) {
|
|
60
|
-
db.registerModule(vtable.name, vtable.module
|
|
60
|
+
db.registerModule(vtable.name, vtable.module, vtable.auxData);
|
|
61
61
|
}
|
|
62
62
|
for (const func of result.functions ?? []) {
|
|
63
|
-
db.registerFunction(func.schema
|
|
63
|
+
db.registerFunction(func.schema);
|
|
64
64
|
}
|
|
65
65
|
for (const collation of result.collations ?? []) {
|
|
66
|
-
db.registerCollation(collation.name, collation.func
|
|
66
|
+
db.registerCollation(collation.name, collation.func, collation.normalizer);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -185,7 +185,7 @@ export async function composeStrand(
|
|
|
185
185
|
const pluginResult = optimysticPlugin(
|
|
186
186
|
db,
|
|
187
187
|
pluginConfig as unknown as Parameters<typeof optimysticPlugin>[1],
|
|
188
|
-
) as OptimysticPluginResult;
|
|
188
|
+
) as unknown as OptimysticPluginResult;
|
|
189
189
|
applyRegistrations(db, pluginResult);
|
|
190
190
|
log('Registered optimystic vtables and functions');
|
|
191
191
|
|
package/src/connect-browser.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Database } from '@quereus/quereus';
|
|
1
|
+
import type { Database, VTablePluginInfo, FunctionPluginInfo, CollationPluginInfo } from '@quereus/quereus';
|
|
2
2
|
import cryptoPlugin from '@optimystic/quereus-plugin-crypto/plugin';
|
|
3
3
|
import { createLibp2pNode } from '@optimystic/db-p2p/rn';
|
|
4
4
|
import { webSockets } from '@libp2p/websockets';
|
|
@@ -10,9 +10,9 @@ import { composeStrand, applyRegistrations } from './compose-strand.js';
|
|
|
10
10
|
|
|
11
11
|
/** The subset of the crypto plugin result we register inline in the browser. */
|
|
12
12
|
interface CryptoPluginResult {
|
|
13
|
-
vtables:
|
|
14
|
-
functions:
|
|
15
|
-
collations:
|
|
13
|
+
vtables: VTablePluginInfo[];
|
|
14
|
+
functions: FunctionPluginInfo[];
|
|
15
|
+
collations: CollationPluginInfo[];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -32,7 +32,7 @@ export async function connectToStrandBrowser(
|
|
|
32
32
|
): Promise<SereusPluginResult> {
|
|
33
33
|
return composeStrand(db, options, {
|
|
34
34
|
registerCrypto(database) {
|
|
35
|
-
applyRegistrations(database, cryptoPlugin(database, {}) as CryptoPluginResult);
|
|
35
|
+
applyRegistrations(database, cryptoPlugin(database, {}) as unknown as CryptoPluginResult);
|
|
36
36
|
},
|
|
37
37
|
async resolveStorage({ strandId, resolvedTransactor, requestedStorage }) {
|
|
38
38
|
if (requestedStorage) return requestedStorage;
|
package/src/strand-schema.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Crypto idiom (matches `schemas/control.qsql` and the sApp RBAC fixture
|
|
20
20
|
* `packages/integration-tests/fixtures/simple-sapp.qsql`): every signed write
|
|
21
|
-
* proves itself with `verify(digest(<
|
|
21
|
+
* proves itself with `verify(digest(<single joined payload>),
|
|
22
22
|
* <signature>, <pubkey>, 'ed25519')`. Member keys are ed25519, so the explicit
|
|
23
23
|
* curve arg is REQUIRED (verify() otherwise defaults to secp256k1).
|
|
24
24
|
*
|
|
@@ -70,10 +70,10 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
70
70
|
-- Can only be inserted by an authority,
|
|
71
71
|
exists (select 1 from Authority A
|
|
72
72
|
where A.MemberKey = context.AuthorityKey
|
|
73
|
-
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')
|
|
73
|
+
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.AuthoritySignature, A.MemberKey, 'ed25519')
|
|
74
74
|
)
|
|
75
75
|
-- and must also prove invite private key held by issuing authority
|
|
76
|
-
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')
|
|
76
|
+
and verify(digest(new.Key || '|' || coalesce(new.Expiration, '')), context.InviteSignature, new.Key, 'ed25519')
|
|
77
77
|
)
|
|
78
78
|
) with context (AuthorityKey text null, AuthoritySignature text null, InviteSignature text null);
|
|
79
79
|
|
|
@@ -85,10 +85,20 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
85
85
|
constraint InviteExists check (exists (select 1 from Invite I where I.Key = new.InviteKey)),
|
|
86
86
|
constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),
|
|
87
87
|
constraint ValidUsage check on insert (
|
|
88
|
-
exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey
|
|
88
|
+
exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey), context.InviteSignature, new.InviteKey, 'ed25519'))
|
|
89
|
+
),
|
|
90
|
+
-- An invite with a non-null Expiration may only be consumed while it is still
|
|
91
|
+
-- in the future. context.Now is the canonical-datetime "now" supplied by the
|
|
92
|
+
-- consumeInvite writer (same canonicalDatetime() transform used to store
|
|
93
|
+
-- Invite.Expiration), so both sides of the comparison are byte-identical
|
|
94
|
+
-- canonical strings and the lexical ">" comparison orders chronologically. A
|
|
95
|
+
-- null Expiration never expires. Mirrors CadreControl.FormationUsage's
|
|
96
|
+
-- "FI.ExpiresAt is null or FI.ExpiresAt > context.Now" gate.
|
|
97
|
+
constraint NotExpired check on insert (
|
|
98
|
+
exists (select 1 from Invite I where I.Key = new.InviteKey and (I.Expiration is null or I.Expiration > context.Now))
|
|
89
99
|
),
|
|
90
100
|
constraint MemberValid check (exists (select 1 from Member M where M.Key = new.MemberKey))
|
|
91
|
-
) with context (InviteSignature text null);
|
|
101
|
+
) with context (InviteSignature text null, Now datetime null);
|
|
92
102
|
|
|
93
103
|
-- A party in the closed strand network
|
|
94
104
|
table Member (
|
|
@@ -105,7 +115,7 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
105
115
|
or exists (
|
|
106
116
|
select 1 from Authority A
|
|
107
117
|
where A.MemberKey = context.AuthorityKey
|
|
108
|
-
and verify(digest(new.Key
|
|
118
|
+
and verify(digest(new.Key), context.AuthoritySignature, A.MemberKey, 'ed25519')
|
|
109
119
|
)
|
|
110
120
|
|
|
111
121
|
-- or added by invite
|
|
@@ -122,9 +132,9 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
122
132
|
PeerId text,
|
|
123
133
|
primary key (MemberKey, PeerId),
|
|
124
134
|
constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),
|
|
125
|
-
constraint Authorized check (
|
|
135
|
+
constraint Authorized check on insert, update, delete (
|
|
126
136
|
verify(
|
|
127
|
-
digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId)
|
|
137
|
+
digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId)),
|
|
128
138
|
context.Signature,
|
|
129
139
|
coalesce(new.MemberKey, old.MemberKey),
|
|
130
140
|
'ed25519'
|
|
@@ -138,7 +148,7 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
138
148
|
constraint OnlyClosed check (
|
|
139
149
|
exists (select 1 from Header H where H.Type = 'c')
|
|
140
150
|
),
|
|
141
|
-
constraint Authorized check (
|
|
151
|
+
constraint Authorized check on insert, update, delete (
|
|
142
152
|
-- There are no existing records - first authority needs no authorization
|
|
143
153
|
(select count(1) from Authority) <= 1
|
|
144
154
|
|
|
@@ -146,14 +156,14 @@ export const STRAND_SCHEMA = ` table Header (
|
|
|
146
156
|
or (
|
|
147
157
|
old.MemberKey is not null
|
|
148
158
|
and old.MemberKey = context.AuthorityKey
|
|
149
|
-
and verify(digest(old.MemberKey
|
|
159
|
+
and verify(digest(old.MemberKey), context.Signature, old.MemberKey, 'ed25519')
|
|
150
160
|
)
|
|
151
161
|
|
|
152
162
|
-- or authorized by another existing authority
|
|
153
163
|
or exists (
|
|
154
164
|
select 1 from Authority A
|
|
155
165
|
where A.MemberKey = context.AuthorityKey
|
|
156
|
-
and verify(digest(coalesce(new.MemberKey, old.MemberKey)
|
|
166
|
+
and verify(digest(coalesce(new.MemberKey, old.MemberKey)), context.Signature, A.MemberKey, 'ed25519')
|
|
157
167
|
)
|
|
158
168
|
)
|
|
159
169
|
) with context (AuthorityKey text null, Signature text null);
|