@ccmsg/protocol 2.2.0 → 2.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.
- package/package.json +1 -1
- package/src/attributes.ts +58 -16
- package/src/common/auth.ts +504 -260
- package/src/errors.ts +10 -0
- package/src/fixtures/common.ts +166 -41
- package/src/fixtures/ids.ts +20 -8
- package/src/fixtures/index.ts +13 -1
- package/src/fixtures/topics.ts +114 -25
- package/src/identifiers.ts +46 -35
- package/src/schemas.ts +18 -3
package/package.json
CHANGED
package/src/attributes.ts
CHANGED
|
@@ -39,13 +39,17 @@ export interface OpAttributes {
|
|
|
39
39
|
* cannot, and they answer before any identity is settled. The route each is
|
|
40
40
|
* published at belongs to the instance, not here.
|
|
41
41
|
*
|
|
42
|
-
* Being reachable from a page is also what gives the
|
|
42
|
+
* Being reachable from a page is also what gives the four that settle an
|
|
43
43
|
* identity the only headers this contract reads over HTTP: the `Origin` a
|
|
44
|
-
* browser states, held to the origin
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
44
|
+
* browser states, held to the enrolment URL's origin on the two that spend one
|
|
45
|
+
* (`auth.register`, `auth.enroll`) and to the credential's or the family's on
|
|
46
|
+
* the two that do not (`auth.assert`, `auth.token.refresh`), and
|
|
47
|
+
* `Sec-Fetch-Site`, which has to be one of
|
|
48
|
+
* `same-origin`, `same-site` or `cross-site` — the three that say a page made
|
|
49
|
+
* the call. Anything else fails: `none`, which is a request with no initiator
|
|
50
|
+
* at all, a header that is absent, and a value this contract does not know.
|
|
51
|
+
* Both headers are read as an allowlist rather than a blocklist, and either
|
|
52
|
+
* failing answers `auth_invalid` without saying which. `auth.challenge` is checked
|
|
49
53
|
* against neither, having nothing yet to be checked against; what it hands
|
|
50
54
|
* out is spendable only at its issuer. */
|
|
51
55
|
readonly carrier?: "http";
|
|
@@ -68,7 +72,7 @@ const INSTANCE_ONLY = ["instance"] as const;
|
|
|
68
72
|
* facts live: authorization, capability gating and forwarding all read it
|
|
69
73
|
* rather than each carrying their own copy. */
|
|
70
74
|
export const OP_ATTRIBUTES = {
|
|
71
|
-
// --- common: connect, declare the end, and subscribe (
|
|
75
|
+
// --- common: connect, declare the end, and subscribe (18) ---
|
|
72
76
|
// A greeting settles what the connection is, and there is one per role: what
|
|
73
77
|
// each must carry is then the op's own schema rather than a rule read off a
|
|
74
78
|
// field, and a connection cannot be settled as something neither side meant.
|
|
@@ -136,7 +140,7 @@ export const OP_ATTRIBUTES = {
|
|
|
136
140
|
errors: ["topic_unknown"],
|
|
137
141
|
},
|
|
138
142
|
|
|
139
|
-
// The
|
|
143
|
+
// The five ops that authenticate a person are open to every role for the
|
|
140
144
|
// same reason the greetings are: they run before there is an identity to
|
|
141
145
|
// check, and what they answer is what settles one. They are `any_instance` because whichever
|
|
142
146
|
// instance is reached answers — behind a load balancer that is not a choice
|
|
@@ -166,6 +170,17 @@ export const OP_ATTRIBUTES = {
|
|
|
166
170
|
carrier: "http",
|
|
167
171
|
errors: ["auth_invalid", "auth_expired", "auth_unknown_issuer"],
|
|
168
172
|
},
|
|
173
|
+
// Adding an instance to a person who already exists: the same enrolment URL
|
|
174
|
+
// and the same six digits as a registration, answered by an assertion instead
|
|
175
|
+
// of a new credential. Its attributes are the registration's for that reason.
|
|
176
|
+
"auth.enroll": {
|
|
177
|
+
plane: "common",
|
|
178
|
+
roles: ALL_ROLES,
|
|
179
|
+
needs_hello: false,
|
|
180
|
+
locality: "any_instance",
|
|
181
|
+
carrier: "http",
|
|
182
|
+
errors: ["auth_invalid", "auth_expired", "auth_unknown_issuer"],
|
|
183
|
+
},
|
|
169
184
|
"auth.token.refresh": {
|
|
170
185
|
plane: "common",
|
|
171
186
|
roles: ALL_ROLES,
|
|
@@ -183,17 +198,44 @@ export const OP_ATTRIBUTES = {
|
|
|
183
198
|
locality: "any_instance",
|
|
184
199
|
errors: ["auth_invalid", "auth_expired"],
|
|
185
200
|
},
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
// `
|
|
189
|
-
|
|
201
|
+
// Reads back the caller's own account, which the connection already settled
|
|
202
|
+
// who is — so there is nothing further to check and nothing to name.
|
|
203
|
+
// `scope: "role"` because the role is what decides the reply's contents: it
|
|
204
|
+
// is the caller's own user, passkeys and instances and nobody else's.
|
|
205
|
+
"auth.account.read": {
|
|
190
206
|
plane: "common",
|
|
191
|
-
roles:
|
|
207
|
+
roles: USER_ONLY,
|
|
192
208
|
needs_hello: true,
|
|
193
|
-
locality: "
|
|
194
|
-
|
|
209
|
+
locality: "any_instance",
|
|
210
|
+
scope: "role",
|
|
211
|
+
errors: [],
|
|
212
|
+
},
|
|
213
|
+
// Letting go of one of the two things a person holds. Both act on the
|
|
214
|
+
// caller's own records and nobody else's, which is what `scope: "role"` says
|
|
215
|
+
// here as it does on the read; what is removed is named, and who it belongs
|
|
216
|
+
// to is the connection. `auth_in_use` is the refusal when the thing named is
|
|
217
|
+
// what the call is being made with.
|
|
218
|
+
"auth.ownership.remove": {
|
|
219
|
+
plane: "common",
|
|
220
|
+
roles: USER_ONLY,
|
|
221
|
+
needs_hello: true,
|
|
222
|
+
locality: "any_instance",
|
|
223
|
+
scope: "role",
|
|
224
|
+
errors: ["not_found", "auth_in_use"],
|
|
195
225
|
},
|
|
196
|
-
"auth.
|
|
226
|
+
"auth.credential.remove": {
|
|
227
|
+
plane: "common",
|
|
228
|
+
roles: USER_ONLY,
|
|
229
|
+
needs_hello: true,
|
|
230
|
+
locality: "any_instance",
|
|
231
|
+
scope: "role",
|
|
232
|
+
errors: ["not_found", "auth_in_use"],
|
|
233
|
+
},
|
|
234
|
+
// Between instances: what an issuer alone can answer — an enrolment URL's
|
|
235
|
+
// secret and a challenge that may be spent once. `owner_instance` because
|
|
236
|
+
// both live in one instance's memory, and it is reached by `to_instance`
|
|
237
|
+
// being that instance's id.
|
|
238
|
+
"auth.resolve": {
|
|
197
239
|
plane: "common",
|
|
198
240
|
roles: INSTANCE_ONLY,
|
|
199
241
|
needs_hello: true,
|