@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/src/errors.ts
CHANGED
|
@@ -79,6 +79,16 @@ export const ERROR_CODES = [
|
|
|
79
79
|
* spend it, is not one this mesh knows or could reach just now. The client
|
|
80
80
|
* asks for a fresh one, which the instance it is talking to can issue. */
|
|
81
81
|
"auth_unknown_issuer",
|
|
82
|
+
/** What the caller asked to remove is what they are using to ask. Removing
|
|
83
|
+
* the ownership of the instance this connection is on, or the credential this
|
|
84
|
+
* session was authenticated with, would be cutting the branch underneath — so
|
|
85
|
+
* it is refused rather than half-applied.
|
|
86
|
+
*
|
|
87
|
+
* Apart from `forbidden` because nothing about the caller's standing is
|
|
88
|
+
* wrong: it is theirs to remove, and doing it from somewhere else is all it
|
|
89
|
+
* takes. Stated as a code of its own so a client can say that rather than
|
|
90
|
+
* showing a refusal it cannot explain. */
|
|
91
|
+
"auth_in_use",
|
|
82
92
|
// --- translate ---
|
|
83
93
|
/** The helper process is present but failed on this call. (Its absence is
|
|
84
94
|
* `capability_unavailable` instead.) */
|
package/src/fixtures/common.ts
CHANGED
|
@@ -12,8 +12,14 @@ import type {
|
|
|
12
12
|
AuthRegisterResponse,
|
|
13
13
|
AuthResolveRequest,
|
|
14
14
|
AuthResolveResponse,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
AuthAccountReadRequest,
|
|
16
|
+
AuthAccountReadResponse,
|
|
17
|
+
AuthCredentialRemoveRequest,
|
|
18
|
+
AuthCredentialRemoveResponse,
|
|
19
|
+
AuthOwnershipRemoveRequest,
|
|
20
|
+
AuthOwnershipRemoveResponse,
|
|
21
|
+
AuthEnrollRequest,
|
|
22
|
+
AuthEnrollResponse,
|
|
17
23
|
} from "../common/auth.ts";
|
|
18
24
|
import type {
|
|
19
25
|
HelloInstanceRequest,
|
|
@@ -36,7 +42,18 @@ import type {
|
|
|
36
42
|
} from "../common/topics.ts";
|
|
37
43
|
import { FIXTURE_IDS, FIXTURE_NOW } from "./ids.ts";
|
|
38
44
|
|
|
39
|
-
const {
|
|
45
|
+
const {
|
|
46
|
+
sid,
|
|
47
|
+
instance,
|
|
48
|
+
other_instance,
|
|
49
|
+
endpoint,
|
|
50
|
+
other_endpoint,
|
|
51
|
+
origin,
|
|
52
|
+
same_site_origin,
|
|
53
|
+
hosting_endpoint,
|
|
54
|
+
user: USER,
|
|
55
|
+
request_id,
|
|
56
|
+
} = FIXTURE_IDS;
|
|
40
57
|
|
|
41
58
|
export const HELLO_SESSION_REQUEST: Static<typeof HelloSessionRequest> = {
|
|
42
59
|
request_id,
|
|
@@ -176,11 +193,8 @@ const CHALLENGE = {
|
|
|
176
193
|
};
|
|
177
194
|
|
|
178
195
|
const ACCESS = { value: "YWNjZXNzLXRva2Vu", expires_at: FIXTURE_NOW + 10_000_000 };
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
const REGISTER_CODE = "048213";
|
|
182
|
-
const SUBJECT = "personal-1";
|
|
183
|
-
|
|
196
|
+
const ENROLL_TOKEN = "eyJhbGciOiJIUzI1NiJ9.e30.c2ln";
|
|
197
|
+
const ENROLL_CODE = "048213";
|
|
184
198
|
export const AUTH_CHALLENGE_REQUEST: Static<typeof AuthChallengeRequest> = {
|
|
185
199
|
request_id,
|
|
186
200
|
op: "auth.challenge",
|
|
@@ -195,8 +209,8 @@ export const AUTH_CHALLENGE_RESPONSE: Static<typeof AuthChallengeResponse> = {
|
|
|
195
209
|
export const AUTH_REGISTER_REQUEST: Static<typeof AuthRegisterRequest> = {
|
|
196
210
|
request_id,
|
|
197
211
|
op: "auth.register",
|
|
198
|
-
token:
|
|
199
|
-
code:
|
|
212
|
+
token: ENROLL_TOKEN,
|
|
213
|
+
code: ENROLL_CODE,
|
|
200
214
|
device_label: "work laptop",
|
|
201
215
|
challenge: CHALLENGE,
|
|
202
216
|
credential: {
|
|
@@ -210,7 +224,7 @@ export const AUTH_REGISTER_REQUEST: Static<typeof AuthRegisterRequest> = {
|
|
|
210
224
|
export const AUTH_REGISTER_RESPONSE: Static<typeof AuthRegisterResponse> = {
|
|
211
225
|
ok: true,
|
|
212
226
|
request_id,
|
|
213
|
-
|
|
227
|
+
user: USER,
|
|
214
228
|
access: ACCESS,
|
|
215
229
|
};
|
|
216
230
|
|
|
@@ -223,14 +237,38 @@ export const AUTH_ASSERT_REQUEST: Static<typeof AuthAssertRequest> = {
|
|
|
223
237
|
client_data_json: "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0In0",
|
|
224
238
|
authenticator_data: "YXV0aC1kYXRh",
|
|
225
239
|
signature: "c2lnbmF0dXJl",
|
|
226
|
-
user_handle:
|
|
240
|
+
user_handle: USER,
|
|
227
241
|
},
|
|
228
242
|
};
|
|
229
243
|
|
|
230
244
|
export const AUTH_ASSERT_RESPONSE: Static<typeof AuthAssertResponse> = {
|
|
231
245
|
ok: true,
|
|
232
246
|
request_id,
|
|
233
|
-
|
|
247
|
+
user: USER,
|
|
248
|
+
access: ACCESS,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/** Taking a second instance as one's own: the same token and digits a
|
|
252
|
+
* registration carries, answered with the passkey that already exists. */
|
|
253
|
+
export const AUTH_ENROLL_REQUEST: Static<typeof AuthEnrollRequest> = {
|
|
254
|
+
request_id,
|
|
255
|
+
op: "auth.enroll",
|
|
256
|
+
token: ENROLL_TOKEN,
|
|
257
|
+
code: ENROLL_CODE,
|
|
258
|
+
challenge: CHALLENGE,
|
|
259
|
+
credential: {
|
|
260
|
+
raw_id: "Y3JlZC1pZA",
|
|
261
|
+
client_data_json: "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0In0",
|
|
262
|
+
authenticator_data: "YXV0aC1kYXRh",
|
|
263
|
+
signature: "c2lnbmF0dXJl",
|
|
264
|
+
user_handle: USER,
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export const AUTH_ENROLL_RESPONSE: Static<typeof AuthEnrollResponse> = {
|
|
269
|
+
ok: true,
|
|
270
|
+
request_id,
|
|
271
|
+
user: USER,
|
|
234
272
|
access: ACCESS,
|
|
235
273
|
};
|
|
236
274
|
|
|
@@ -243,7 +281,7 @@ export const AUTH_TOKEN_REFRESH_REQUEST: Static<typeof AuthTokenRefreshRequest>
|
|
|
243
281
|
export const AUTH_TOKEN_REFRESH_RESPONSE: Static<typeof AuthTokenRefreshResponse> = {
|
|
244
282
|
ok: true,
|
|
245
283
|
request_id,
|
|
246
|
-
|
|
284
|
+
user: USER,
|
|
247
285
|
access: ACCESS,
|
|
248
286
|
};
|
|
249
287
|
|
|
@@ -259,32 +297,137 @@ export const AUTH_EXTEND_RESPONSE: Static<typeof AuthExtendResponse> = {
|
|
|
259
297
|
auth_expires_at: FIXTURE_NOW + 20_000_000,
|
|
260
298
|
};
|
|
261
299
|
|
|
300
|
+
/** The three things a person has, read back at once: who they are, what answers
|
|
301
|
+
* for them at each origin, and the instances they own. Two instances here, one
|
|
302
|
+
* of them granted by nobody (they made it) and one added by an owner. */
|
|
303
|
+
export const AUTH_ACCOUNT_READ_REQUEST: Static<typeof AuthAccountReadRequest> = {
|
|
304
|
+
request_id,
|
|
305
|
+
op: "auth.account.read",
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export const AUTH_ACCOUNT_READ_RESPONSE: Static<typeof AuthAccountReadResponse> = {
|
|
309
|
+
ok: true,
|
|
310
|
+
request_id,
|
|
311
|
+
user: {
|
|
312
|
+
kind: "user",
|
|
313
|
+
user: USER,
|
|
314
|
+
display_name: "kawaz",
|
|
315
|
+
created_at: FIXTURE_NOW - 900_000,
|
|
316
|
+
},
|
|
317
|
+
credentials: [
|
|
318
|
+
{
|
|
319
|
+
kind: "credential",
|
|
320
|
+
user: USER,
|
|
321
|
+
credential_id: "Y3JlZC1pZA",
|
|
322
|
+
origin,
|
|
323
|
+
sign_count: 0,
|
|
324
|
+
issued_label: "for kawaz",
|
|
325
|
+
device_label: "work laptop",
|
|
326
|
+
registered_at: FIXTURE_NOW - 600_000,
|
|
327
|
+
registered_ip: "203.0.113.7",
|
|
328
|
+
registered_user_agent: "Mozilla/5.0",
|
|
329
|
+
last_used_at: FIXTURE_NOW,
|
|
330
|
+
last_used_ip: "203.0.113.7",
|
|
331
|
+
last_used_user_agent: "Mozilla/5.0",
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
kind: "credential",
|
|
335
|
+
user: USER,
|
|
336
|
+
credential_id: "Y3JlZC1pZC0y",
|
|
337
|
+
origin: same_site_origin,
|
|
338
|
+
device_label: "phone",
|
|
339
|
+
registered_at: FIXTURE_NOW - 300_000,
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
instances: [
|
|
343
|
+
{
|
|
344
|
+
instance,
|
|
345
|
+
endpoint,
|
|
346
|
+
granted_at: FIXTURE_NOW - 600_000,
|
|
347
|
+
granted_by: { kind: "instance", instance },
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
instance: other_instance,
|
|
351
|
+
endpoint: other_endpoint,
|
|
352
|
+
granted_at: FIXTURE_NOW - 60_000,
|
|
353
|
+
granted_by: { kind: "user", user: USER },
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/** Giving up an instance, named from another one the person owns — the
|
|
359
|
+
* connection's own instance is the one call that is refused. */
|
|
360
|
+
export const AUTH_OWNERSHIP_REMOVE_REQUEST: Static<typeof AuthOwnershipRemoveRequest> = {
|
|
361
|
+
request_id,
|
|
362
|
+
op: "auth.ownership.remove",
|
|
363
|
+
instance: other_instance,
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
export const AUTH_OWNERSHIP_REMOVE_RESPONSE: Static<typeof AuthOwnershipRemoveResponse> = {
|
|
367
|
+
ok: true,
|
|
368
|
+
request_id,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/** Removing a passkey that is not the one this session authenticated with. */
|
|
372
|
+
export const AUTH_CREDENTIAL_REMOVE_REQUEST: Static<typeof AuthCredentialRemoveRequest> = {
|
|
373
|
+
request_id,
|
|
374
|
+
op: "auth.credential.remove",
|
|
375
|
+
credential_id: "Y3JlZC1pZC0y",
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
export const AUTH_CREDENTIAL_REMOVE_RESPONSE: Static<typeof AuthCredentialRemoveResponse> = {
|
|
379
|
+
ok: true,
|
|
380
|
+
request_id,
|
|
381
|
+
};
|
|
382
|
+
|
|
262
383
|
export const AUTH_RESOLVE_REQUEST = {
|
|
263
384
|
request_id,
|
|
264
385
|
op: "auth.resolve",
|
|
265
386
|
to_instance: instance,
|
|
266
|
-
kind: "
|
|
267
|
-
token:
|
|
268
|
-
code:
|
|
387
|
+
kind: "claims",
|
|
388
|
+
token: ENROLL_TOKEN,
|
|
389
|
+
code: ENROLL_CODE,
|
|
269
390
|
} satisfies Static<typeof AuthResolveRequest>;
|
|
270
391
|
|
|
392
|
+
/** What the issuer answers about a URL that makes a person. It names the user
|
|
393
|
+
* the credential will be created against, and posts to the address in front of
|
|
394
|
+
* the instances rather than to the issuer's own — whichever of them the answer
|
|
395
|
+
* lands on completes it. */
|
|
271
396
|
export const AUTH_RESOLVE_RESPONSE = {
|
|
272
397
|
ok: true,
|
|
273
398
|
request_id,
|
|
274
|
-
kind: "
|
|
399
|
+
kind: "claims",
|
|
275
400
|
claims: {
|
|
276
401
|
iss: instance,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
402
|
+
purpose: "create_user",
|
|
403
|
+
instance,
|
|
404
|
+
origin,
|
|
405
|
+
endpoint: hosting_endpoint,
|
|
281
406
|
expires_at: FIXTURE_NOW + 600_000,
|
|
282
407
|
jti: "01J9Z3W2Q",
|
|
283
|
-
|
|
408
|
+
user: USER,
|
|
284
409
|
issued_label: "for kawaz",
|
|
285
410
|
},
|
|
286
411
|
} satisfies Static<typeof AuthResolveResponse>;
|
|
287
412
|
|
|
413
|
+
/** The other purpose: a URL that adds an instance to whoever asserts. It names
|
|
414
|
+
* no user — who arrives is what the assertion says. */
|
|
415
|
+
export const AUTH_RESOLVE_ADD_OWNER_RESPONSE = {
|
|
416
|
+
ok: true,
|
|
417
|
+
request_id,
|
|
418
|
+
kind: "claims",
|
|
419
|
+
claims: {
|
|
420
|
+
iss: other_instance,
|
|
421
|
+
purpose: "add_owner",
|
|
422
|
+
instance: other_instance,
|
|
423
|
+
origin,
|
|
424
|
+
endpoint: hosting_endpoint,
|
|
425
|
+
expires_at: FIXTURE_NOW + 600_000,
|
|
426
|
+
jti: "01J9Z3W2R",
|
|
427
|
+
issued_label: "nuc, for kawaz",
|
|
428
|
+
},
|
|
429
|
+
} satisfies Static<typeof AuthResolveResponse>;
|
|
430
|
+
|
|
288
431
|
/** The other half of the resolve union: spending a challenge, which answers
|
|
289
432
|
* nothing beyond having spent it. */
|
|
290
433
|
export const AUTH_RESOLVE_CHALLENGE_REQUEST = {
|
|
@@ -300,21 +443,3 @@ export const AUTH_RESOLVE_CHALLENGE_RESPONSE = {
|
|
|
300
443
|
request_id,
|
|
301
444
|
kind: "challenge",
|
|
302
445
|
} satisfies Static<typeof AuthResolveResponse>;
|
|
303
|
-
|
|
304
|
-
export const AUTH_ROTATE_REQUEST: Static<typeof AuthRotateRequest> = {
|
|
305
|
-
request_id,
|
|
306
|
-
op: "auth.rotate",
|
|
307
|
-
to_instance: instance,
|
|
308
|
-
refresh_token: REFRESH.value,
|
|
309
|
-
reason: "reconnect",
|
|
310
|
-
ip: "203.0.113.7",
|
|
311
|
-
user_agent: "Mozilla/5.0",
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
export const AUTH_ROTATE_RESPONSE: Static<typeof AuthRotateResponse> = {
|
|
315
|
-
ok: true,
|
|
316
|
-
request_id,
|
|
317
|
-
sub: SUBJECT,
|
|
318
|
-
access: ACCESS,
|
|
319
|
-
refresh: REFRESH,
|
|
320
|
-
};
|
package/src/fixtures/ids.ts
CHANGED
|
@@ -9,17 +9,29 @@ export const FIXTURE_IDS = {
|
|
|
9
9
|
other_instance: "a1b2c3d4e5f60718293a4b5c6d7e8f90",
|
|
10
10
|
endpoint: "https://mba.example.ts.net/ccmsg/personal/",
|
|
11
11
|
other_endpoint: "https://nuc.example.ts.net/ccmsg/personal/",
|
|
12
|
-
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
/** The person every auth fixture is about: sixteen bytes spelled base64url,
|
|
13
|
+
* which is at once their id in these records and the user handle their
|
|
14
|
+
* authenticator holds. Twenty-two characters, as sixteen bytes always are. */
|
|
15
|
+
user: "d2hvLWlzLXRoaXMtdXNlcg",
|
|
16
|
+
/** A second person, so that an instance with two owners can be written down.
|
|
17
|
+
* Nothing but the id distinguishes them here. */
|
|
18
|
+
other_user: "c29tZWJvZHktZWxzZS1oaQ",
|
|
19
|
+
/** Where the page a person opens is served from, which is nobody's endpoint:
|
|
20
|
+
* a credential is made here and used at whichever instance the person owns.
|
|
21
|
+
* Its registrable domain is not the endpoints' — so the two are cross-site
|
|
22
|
+
* and a refresh cookie between them is a partitioned one. */
|
|
23
|
+
origin: "https://ui.example.test",
|
|
24
|
+
/** A second origin, sharing the endpoints' registrable domain: same-site, and
|
|
19
25
|
* still an origin of its own. A credential made here is a separate
|
|
20
26
|
* credential, and the cookie between it and an endpoint is not partitioned —
|
|
21
27
|
* one contract, two shapes, which is why both are written down. */
|
|
22
|
-
|
|
28
|
+
same_site_origin: "https://ui.example.ts.net",
|
|
29
|
+
/** The address a person reaches the instances at when a load balancer is in
|
|
30
|
+
* front of them: an endpoint like any other, and the one an enrolment URL
|
|
31
|
+
* names as where to post. It is nobody's own address — which of the instances
|
|
32
|
+
* behind it answers is not a thing the caller chooses — so it is never
|
|
33
|
+
* compared with anything. */
|
|
34
|
+
hosting_endpoint: "https://ccmsg2.example.ts.net/",
|
|
23
35
|
mid: "3f9c1a7b5e2d48069c1a7b5e2d480691/1841",
|
|
24
36
|
request_id: "1",
|
|
25
37
|
} as const;
|
package/src/fixtures/index.ts
CHANGED
|
@@ -61,13 +61,25 @@ export const OP_FIXTURES = {
|
|
|
61
61
|
response: common.AUTH_REGISTER_RESPONSE,
|
|
62
62
|
},
|
|
63
63
|
"auth.assert": { request: common.AUTH_ASSERT_REQUEST, response: common.AUTH_ASSERT_RESPONSE },
|
|
64
|
+
"auth.enroll": { request: common.AUTH_ENROLL_REQUEST, response: common.AUTH_ENROLL_RESPONSE },
|
|
64
65
|
"auth.token.refresh": {
|
|
65
66
|
request: common.AUTH_TOKEN_REFRESH_REQUEST,
|
|
66
67
|
response: common.AUTH_TOKEN_REFRESH_RESPONSE,
|
|
67
68
|
},
|
|
68
69
|
"auth.extend": { request: common.AUTH_EXTEND_REQUEST, response: common.AUTH_EXTEND_RESPONSE },
|
|
70
|
+
"auth.account.read": {
|
|
71
|
+
request: common.AUTH_ACCOUNT_READ_REQUEST,
|
|
72
|
+
response: common.AUTH_ACCOUNT_READ_RESPONSE,
|
|
73
|
+
},
|
|
74
|
+
"auth.ownership.remove": {
|
|
75
|
+
request: common.AUTH_OWNERSHIP_REMOVE_REQUEST,
|
|
76
|
+
response: common.AUTH_OWNERSHIP_REMOVE_RESPONSE,
|
|
77
|
+
},
|
|
78
|
+
"auth.credential.remove": {
|
|
79
|
+
request: common.AUTH_CREDENTIAL_REMOVE_REQUEST,
|
|
80
|
+
response: common.AUTH_CREDENTIAL_REMOVE_RESPONSE,
|
|
81
|
+
},
|
|
69
82
|
"auth.resolve": { request: common.AUTH_RESOLVE_REQUEST, response: common.AUTH_RESOLVE_RESPONSE },
|
|
70
|
-
"auth.rotate": { request: common.AUTH_ROTATE_REQUEST, response: common.AUTH_ROTATE_RESPONSE },
|
|
71
83
|
|
|
72
84
|
"message.send": {
|
|
73
85
|
request: messaging.MESSAGE_SEND_REQUEST,
|
package/src/fixtures/topics.ts
CHANGED
|
@@ -21,8 +21,10 @@ const {
|
|
|
21
21
|
other_instance,
|
|
22
22
|
endpoint,
|
|
23
23
|
other_endpoint,
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
origin,
|
|
25
|
+
same_site_origin,
|
|
26
|
+
user: USER,
|
|
27
|
+
other_user: OTHER_USER,
|
|
26
28
|
mid,
|
|
27
29
|
} = FIXTURE_IDS;
|
|
28
30
|
|
|
@@ -504,6 +506,15 @@ export const KV_FRAME: Static<typeof KvFrame> = {
|
|
|
504
506
|
},
|
|
505
507
|
};
|
|
506
508
|
|
|
509
|
+
/** The grantings the fixtures name. A granting's id is random and never reused,
|
|
510
|
+
* which is what lets the same instance be given up and taken again. */
|
|
511
|
+
const GRANT = "Z3JhbnQtb25l";
|
|
512
|
+
const OTHER_GRANT = "Z3JhbnQtdHdv";
|
|
513
|
+
const THIRD_GRANT = "Z3JhbnQtdGhyZWU";
|
|
514
|
+
const FOURTH_GRANT = "Z3JhbnQtZm91cg";
|
|
515
|
+
|
|
516
|
+
/** The person, the passkeys that answer for them, and the instances they own —
|
|
517
|
+
* the records an instance needs to admit somebody, all replicated. */
|
|
507
518
|
export const AUTH_RECORDS_FRAME = {
|
|
508
519
|
ev: "topic",
|
|
509
520
|
topic: "auth.records",
|
|
@@ -512,16 +523,24 @@ export const AUTH_RECORDS_FRAME = {
|
|
|
512
523
|
data: {
|
|
513
524
|
records: [
|
|
514
525
|
{
|
|
515
|
-
key:
|
|
526
|
+
key: `user/${USER}`,
|
|
527
|
+
updated_at: FIXTURE_NOW - 900_000,
|
|
528
|
+
body: {
|
|
529
|
+
kind: "user",
|
|
530
|
+
user: USER,
|
|
531
|
+
display_name: "kawaz",
|
|
532
|
+
created_at: FIXTURE_NOW - 900_000,
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
key: "credential/Y3JlZC1pZA",
|
|
516
537
|
updated_at: FIXTURE_NOW,
|
|
517
538
|
body: {
|
|
518
539
|
kind: "credential",
|
|
519
|
-
|
|
540
|
+
user: USER,
|
|
520
541
|
credential_id: "Y3JlZC1pZA",
|
|
521
542
|
public_key: "pQECAyYgASFYIA",
|
|
522
|
-
|
|
523
|
-
endpoint,
|
|
524
|
-
webui,
|
|
543
|
+
origin,
|
|
525
544
|
sign_count: 0,
|
|
526
545
|
issued_label: "for kawaz",
|
|
527
546
|
device_label: "work laptop",
|
|
@@ -534,30 +553,77 @@ export const AUTH_RECORDS_FRAME = {
|
|
|
534
553
|
},
|
|
535
554
|
},
|
|
536
555
|
{
|
|
537
|
-
// The same person at a second
|
|
538
|
-
// registrable domain where the first does not. One credential per
|
|
539
|
-
// and the difference between the two is what decides whether
|
|
540
|
-
// refresh cookie for a session made here is a partitioned one.
|
|
541
|
-
key: "credential/
|
|
556
|
+
// The same person at a second origin, which shares the endpoints'
|
|
557
|
+
// registrable domain where the first does not. One credential per
|
|
558
|
+
// origin, and the difference between the two is what decides whether
|
|
559
|
+
// the refresh cookie for a session made here is a partitioned one.
|
|
560
|
+
key: "credential/Y3JlZC1pZC0y",
|
|
542
561
|
updated_at: FIXTURE_NOW,
|
|
543
562
|
body: {
|
|
544
563
|
kind: "credential",
|
|
545
|
-
|
|
564
|
+
user: USER,
|
|
546
565
|
credential_id: "Y3JlZC1pZC0y",
|
|
547
566
|
public_key: "pQECAyYgASFYIB",
|
|
548
|
-
|
|
549
|
-
endpoint,
|
|
550
|
-
webui: same_site_webui,
|
|
567
|
+
origin: same_site_origin,
|
|
551
568
|
device_label: "phone",
|
|
552
569
|
registered_at: FIXTURE_NOW - 300_000,
|
|
553
570
|
},
|
|
554
571
|
},
|
|
572
|
+
{
|
|
573
|
+
// The instance this person made themselves, from its command line:
|
|
574
|
+
// there was no user yet to do it, which is why a granting's author can
|
|
575
|
+
// be an instance at all.
|
|
576
|
+
key: `ownership/${instance}/${USER}/${GRANT}`,
|
|
577
|
+
updated_at: FIXTURE_NOW - 600_000,
|
|
578
|
+
body: {
|
|
579
|
+
kind: "ownership",
|
|
580
|
+
user: USER,
|
|
581
|
+
instance,
|
|
582
|
+
grant: GRANT,
|
|
583
|
+
granted_at: FIXTURE_NOW - 600_000,
|
|
584
|
+
granted_by: { kind: "instance", instance },
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
// A second instance, taken as their own afterwards. Neither credential
|
|
589
|
+
// is named here: which instances a person may enter is this record's
|
|
590
|
+
// answer, and which page may speak is the credential's.
|
|
591
|
+
//
|
|
592
|
+
// The frame is this instance's, and what it carries is an ownership of
|
|
593
|
+
// its peer: a granting is written wherever the operator is standing and
|
|
594
|
+
// replicated from there, peers trusting one another equally. Nothing
|
|
595
|
+
// requires the instance that wrote a granting to be the one granted.
|
|
596
|
+
key: `ownership/${other_instance}/${USER}/${OTHER_GRANT}`,
|
|
597
|
+
updated_at: FIXTURE_NOW - 60_000,
|
|
598
|
+
body: {
|
|
599
|
+
kind: "ownership",
|
|
600
|
+
user: USER,
|
|
601
|
+
instance: other_instance,
|
|
602
|
+
grant: OTHER_GRANT,
|
|
603
|
+
granted_at: FIXTURE_NOW - 60_000,
|
|
604
|
+
granted_by: { kind: "user", user: USER },
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
// A second person owning that same instance. One instance may have
|
|
609
|
+
// several owners, and they are told apart by the key alone.
|
|
610
|
+
key: `ownership/${other_instance}/${OTHER_USER}/${THIRD_GRANT}`,
|
|
611
|
+
updated_at: FIXTURE_NOW - 30_000,
|
|
612
|
+
body: {
|
|
613
|
+
kind: "ownership",
|
|
614
|
+
user: OTHER_USER,
|
|
615
|
+
instance: other_instance,
|
|
616
|
+
grant: THIRD_GRANT,
|
|
617
|
+
granted_at: FIXTURE_NOW - 30_000,
|
|
618
|
+
granted_by: { kind: "user", user: USER },
|
|
619
|
+
},
|
|
620
|
+
},
|
|
555
621
|
],
|
|
556
622
|
},
|
|
557
623
|
} satisfies Static<typeof AuthRecordsFrame>;
|
|
558
624
|
|
|
559
|
-
/** The token half of the same topic: one family,
|
|
560
|
-
*
|
|
625
|
+
/** The token half of the same topic: one family, minted by one instance and
|
|
626
|
+
* writable at every instance its owner owns. */
|
|
561
627
|
export const AUTH_RECORDS_FAMILY_FRAME = {
|
|
562
628
|
ev: "topic",
|
|
563
629
|
topic: "auth.records",
|
|
@@ -569,9 +635,9 @@ export const AUTH_RECORDS_FAMILY_FRAME = {
|
|
|
569
635
|
updated_at: FIXTURE_NOW + 100_000,
|
|
570
636
|
body: {
|
|
571
637
|
kind: "token_family",
|
|
572
|
-
|
|
638
|
+
user: USER,
|
|
573
639
|
iss: instance,
|
|
574
|
-
|
|
640
|
+
origin,
|
|
575
641
|
access: { value: "YWNjZXNz", expires_at: FIXTURE_NOW + 10_000_000 },
|
|
576
642
|
refresh: { value: "cmVmcmVzaA", expires_at: FIXTURE_NOW + 600_000_000 },
|
|
577
643
|
last_refresh: {
|
|
@@ -588,8 +654,10 @@ export const AUTH_RECORDS_FAMILY_FRAME = {
|
|
|
588
654
|
},
|
|
589
655
|
} satisfies Static<typeof AuthRecordsFrame>;
|
|
590
656
|
|
|
591
|
-
/** A removal travels as a record of its own
|
|
592
|
-
*
|
|
657
|
+
/** A removal travels as a record of its own, and says nothing but when: what
|
|
658
|
+
* was removed is the key it arrives under. A credential's and an ownership's
|
|
659
|
+
* never expire; a family's is kept only as long as a refresh token could still
|
|
660
|
+
* arrive. */
|
|
593
661
|
export const AUTH_RECORDS_TOMBSTONE_FRAME = {
|
|
594
662
|
ev: "topic",
|
|
595
663
|
topic: "auth.records",
|
|
@@ -597,16 +665,37 @@ export const AUTH_RECORDS_TOMBSTONE_FRAME = {
|
|
|
597
665
|
data: {
|
|
598
666
|
records: [
|
|
599
667
|
{
|
|
600
|
-
key: "credential/
|
|
668
|
+
key: "credential/Y3JlZC1pZA",
|
|
669
|
+
updated_at: FIXTURE_NOW + 100_000,
|
|
670
|
+
body: { kind: "tombstone", deleted_at: FIXTURE_NOW + 100_000 },
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
// The second person gives that instance up.
|
|
674
|
+
key: `ownership/${other_instance}/${OTHER_USER}/${THIRD_GRANT}`,
|
|
601
675
|
updated_at: FIXTURE_NOW + 100_000,
|
|
602
|
-
body: { kind: "tombstone",
|
|
676
|
+
body: { kind: "tombstone", deleted_at: FIXTURE_NOW + 100_000 },
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
// And is made an owner of it again. A granting has an id of its own, so
|
|
680
|
+
// this is a key no tombstone stands on — where a key made of the
|
|
681
|
+
// instance and the person alone would have made the removal above
|
|
682
|
+
// final, with nothing able to undo it.
|
|
683
|
+
key: `ownership/${other_instance}/${OTHER_USER}/${FOURTH_GRANT}`,
|
|
684
|
+
updated_at: FIXTURE_NOW + 200_000,
|
|
685
|
+
body: {
|
|
686
|
+
kind: "ownership",
|
|
687
|
+
user: OTHER_USER,
|
|
688
|
+
instance: other_instance,
|
|
689
|
+
grant: FOURTH_GRANT,
|
|
690
|
+
granted_at: FIXTURE_NOW + 200_000,
|
|
691
|
+
granted_by: { kind: "user", user: USER },
|
|
692
|
+
},
|
|
603
693
|
},
|
|
604
694
|
{
|
|
605
695
|
key: "family/01J9Z3W2Q",
|
|
606
696
|
updated_at: FIXTURE_NOW + 100_000,
|
|
607
697
|
body: {
|
|
608
698
|
kind: "tombstone",
|
|
609
|
-
sub: "personal-1",
|
|
610
699
|
deleted_at: FIXTURE_NOW + 100_000,
|
|
611
700
|
expires_at: FIXTURE_NOW + 604_900_000,
|
|
612
701
|
},
|