@fedify/interaction-controls 2.5.0-dev.2339 → 2.5.0-dev.2355
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/mod.cjs +269 -77
- package/dist/mod.d.cts +200 -15
- package/dist/mod.d.ts +200 -15
- package/dist/mod.js +270 -78
- package/package.json +4 -4
package/dist/mod.cjs
CHANGED
|
@@ -30,7 +30,7 @@ function createInteractionControl(config) {
|
|
|
30
30
|
createRevocation: (options) => new _fedify_vocab.Delete({
|
|
31
31
|
id: options.id,
|
|
32
32
|
actor: options.actor,
|
|
33
|
-
object: getRequiredId(options.authorization, "authorization"),
|
|
33
|
+
object: options.embedAuthorization && !(options.authorization instanceof URL) ? createEmbeddedAuthorization(options.authorization, config) : getRequiredId(options.authorization, "authorization"),
|
|
34
34
|
...audience(options)
|
|
35
35
|
}),
|
|
36
36
|
recognizeImpolite: config.recognizeImpolite,
|
|
@@ -59,6 +59,17 @@ function getRequiredId(value, name) {
|
|
|
59
59
|
if (value.id == null) throw new TypeError(`The ${name} must have an id.`);
|
|
60
60
|
return value.id;
|
|
61
61
|
}
|
|
62
|
+
function createEmbeddedAuthorization(authorization, config) {
|
|
63
|
+
const { interactingObjectId, interactionTargetId } = authorization;
|
|
64
|
+
if (interactingObjectId == null) throw new TypeError("The authorization's interactingObject must have an id.");
|
|
65
|
+
if (interactionTargetId == null) throw new TypeError("The authorization's interactionTarget must have an id.");
|
|
66
|
+
return new config.authorizationClass({
|
|
67
|
+
id: getRequiredId(authorization, "authorization"),
|
|
68
|
+
attributions: authorization.attributionIds,
|
|
69
|
+
interactingObject: interactingObjectId,
|
|
70
|
+
interactionTarget: interactionTargetId
|
|
71
|
+
});
|
|
72
|
+
}
|
|
62
73
|
function getId(value) {
|
|
63
74
|
return value instanceof URL ? value : value.id;
|
|
64
75
|
}
|
|
@@ -70,8 +81,9 @@ function getTypeId(object) {
|
|
|
70
81
|
}
|
|
71
82
|
async function verifyRequest(context, options, config) {
|
|
72
83
|
const documentLoader = options.documentLoader ?? context.documentLoader;
|
|
84
|
+
const contextLoader = options.contextLoader ?? documentLoader;
|
|
73
85
|
const expectedRequestId = options.request instanceof URL ? options.request : null;
|
|
74
|
-
const requestResult = await materialize(options.request, config.requestClass, documentLoader);
|
|
86
|
+
const requestResult = await materialize(options.request, config.requestClass, documentLoader, contextLoader);
|
|
75
87
|
if (!requestResult.ok) return {
|
|
76
88
|
verified: false,
|
|
77
89
|
failure: requestResult.failure
|
|
@@ -107,12 +119,17 @@ async function verifyRequest(context, options, config) {
|
|
|
107
119
|
actual: request.id
|
|
108
120
|
}
|
|
109
121
|
};
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
const targetResult = options.resolvedInteractionTarget != null && request.objectId != null ? {
|
|
123
|
+
ok: true,
|
|
124
|
+
value: options.resolvedInteractionTarget
|
|
125
|
+
} : await dereferenceField(request, request.objectId, config.getInteractionTarget, documentLoader, contextLoader);
|
|
126
|
+
if (!targetResult.ok) return {
|
|
127
|
+
verified: false,
|
|
128
|
+
request,
|
|
129
|
+
requestId: request.id,
|
|
130
|
+
failure: targetResult.failure
|
|
114
131
|
};
|
|
115
|
-
const interactionTarget =
|
|
132
|
+
const interactionTarget = targetResult.value;
|
|
116
133
|
if (interactionTarget == null) return {
|
|
117
134
|
verified: false,
|
|
118
135
|
request,
|
|
@@ -133,7 +150,17 @@ async function verifyRequest(context, options, config) {
|
|
|
133
150
|
actualTypes: [getTypeId(interactionTarget)]
|
|
134
151
|
}
|
|
135
152
|
};
|
|
136
|
-
const
|
|
153
|
+
const interactingResult = options.resolvedInteractingObject != null && request.instrumentId != null ? {
|
|
154
|
+
ok: true,
|
|
155
|
+
value: options.resolvedInteractingObject
|
|
156
|
+
} : await dereferenceField(request, request.instrumentId, config.getInteractingObject, documentLoader, contextLoader);
|
|
157
|
+
if (!interactingResult.ok) return {
|
|
158
|
+
verified: false,
|
|
159
|
+
request,
|
|
160
|
+
requestId: request.id,
|
|
161
|
+
failure: interactingResult.failure
|
|
162
|
+
};
|
|
163
|
+
const interactingObject = interactingResult.value;
|
|
137
164
|
if (interactingObject == null) return {
|
|
138
165
|
verified: false,
|
|
139
166
|
request,
|
|
@@ -184,7 +211,7 @@ async function verifyRequest(context, options, config) {
|
|
|
184
211
|
type: "missingObjectId"
|
|
185
212
|
}
|
|
186
213
|
};
|
|
187
|
-
const validation = config.validateRequest(request, interactingObject, interactionTarget, requester);
|
|
214
|
+
const validation = config.validateRequest(request, interactingObject, interactionTarget, requester, options);
|
|
188
215
|
if (validation != null) return {
|
|
189
216
|
verified: false,
|
|
190
217
|
request,
|
|
@@ -213,23 +240,35 @@ async function verifyRequest(context, options, config) {
|
|
|
213
240
|
};
|
|
214
241
|
}
|
|
215
242
|
async function verifyAuthorization(context, options, config) {
|
|
216
|
-
const
|
|
217
|
-
const
|
|
243
|
+
const authorizationUrl = options.authorization instanceof URL ? options.authorization : null;
|
|
244
|
+
const embeddedAuthorization = authorizationUrl == null;
|
|
245
|
+
const expectedAuthorizationId = options.authorizationId ?? authorizationUrl;
|
|
246
|
+
const checkOrigin = !options.allowOffOrigin || options.verifyAuthenticity == null;
|
|
218
247
|
const expectedAttribution = options.attributedTo ?? (!(options.interactionTarget instanceof URL) ? config.getSelfActor(options.interactionTarget) : null);
|
|
219
|
-
if (
|
|
248
|
+
if (authorizationUrl != null && options.authorizationId != null && !idsEqual(authorizationUrl, options.authorizationId)) return {
|
|
249
|
+
verified: false,
|
|
250
|
+
authorizationId: authorizationUrl,
|
|
251
|
+
failure: {
|
|
252
|
+
category: "unauthorized",
|
|
253
|
+
type: "idMismatch",
|
|
254
|
+
expected: options.authorizationId,
|
|
255
|
+
actual: authorizationUrl
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
if (authorizationUrl != null && expectedAttribution == null) return {
|
|
220
259
|
verified: false,
|
|
221
|
-
authorizationId:
|
|
260
|
+
authorizationId: authorizationUrl,
|
|
222
261
|
failure: {
|
|
223
262
|
category: "unauthorized",
|
|
224
263
|
type: "missingAttribution"
|
|
225
264
|
}
|
|
226
265
|
};
|
|
227
|
-
if (
|
|
266
|
+
if (checkOrigin && authorizationUrl != null && expectedAttribution != null) {
|
|
228
267
|
const expectedOrigin = getAuthorizationOrigin(expectedAttribution);
|
|
229
|
-
const actualOrigin = getAuthorizationOrigin(
|
|
268
|
+
const actualOrigin = getAuthorizationOrigin(authorizationUrl);
|
|
230
269
|
if (expectedOrigin === "null" || actualOrigin === "null" || expectedOrigin !== actualOrigin) return {
|
|
231
270
|
verified: false,
|
|
232
|
-
authorizationId:
|
|
271
|
+
authorizationId: authorizationUrl,
|
|
233
272
|
failure: {
|
|
234
273
|
category: "unauthorized",
|
|
235
274
|
type: "originMismatch",
|
|
@@ -238,7 +277,8 @@ async function verifyAuthorization(context, options, config) {
|
|
|
238
277
|
}
|
|
239
278
|
};
|
|
240
279
|
}
|
|
241
|
-
const
|
|
280
|
+
const documentLoader = options.documentLoader ?? context.documentLoader;
|
|
281
|
+
const authorizationResult = await materialize(options.authorization, config.authorizationClass, documentLoader, options.contextLoader ?? documentLoader);
|
|
242
282
|
if (!authorizationResult.ok) return {
|
|
243
283
|
verified: false,
|
|
244
284
|
failure: authorizationResult.failure
|
|
@@ -362,7 +402,7 @@ async function verifyAuthorization(context, options, config) {
|
|
|
362
402
|
};
|
|
363
403
|
const expectedOrigin = getAuthorizationOrigin(expectedAttribution);
|
|
364
404
|
const actualOrigin = getAuthorizationOrigin(authorization.id);
|
|
365
|
-
if (expectedOrigin === "null" || actualOrigin === "null" || expectedOrigin !== actualOrigin) return {
|
|
405
|
+
if (checkOrigin && (expectedOrigin === "null" || actualOrigin === "null" || expectedOrigin !== actualOrigin)) return {
|
|
366
406
|
verified: false,
|
|
367
407
|
authorization,
|
|
368
408
|
authorizationId: authorization.id,
|
|
@@ -415,60 +455,163 @@ async function verifyAuthorization(context, options, config) {
|
|
|
415
455
|
authorizationId: authorization.id
|
|
416
456
|
};
|
|
417
457
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
458
|
+
/**
|
|
459
|
+
* Wraps the loaders for a single dereferencing operation so that errors they
|
|
460
|
+
* throw can be told apart from other errors afterwards. Parsed objects keep
|
|
461
|
+
* the loaders they were given, so the wrappers stop recording and pass calls
|
|
462
|
+
* through once the operation is released.
|
|
463
|
+
*/
|
|
464
|
+
function trackLoaders(documentLoader, contextLoader) {
|
|
465
|
+
const records = /* @__PURE__ */ new Map();
|
|
466
|
+
let released = false;
|
|
467
|
+
const wrap = (loader) => {
|
|
468
|
+
if (loader == null) return void 0;
|
|
469
|
+
return async (url, options) => {
|
|
470
|
+
if (released) return await loader(url, options);
|
|
471
|
+
let remoteDocument;
|
|
472
|
+
try {
|
|
473
|
+
remoteDocument = await loader(url, options);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
if (!released) records.set(error, url);
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
if (remoteDocument == null) {
|
|
479
|
+
const message = "The document loader returned no document.";
|
|
480
|
+
let error;
|
|
481
|
+
try {
|
|
482
|
+
error = new _fedify_vocab_runtime.FetchError((0, _fedify_vocab_runtime.parseIri)(url), message);
|
|
483
|
+
} catch {
|
|
484
|
+
error = /* @__PURE__ */ new Error(`${url}: ${message}`);
|
|
485
|
+
}
|
|
486
|
+
if (!released) records.set(error, url);
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
return remoteDocument;
|
|
490
|
+
};
|
|
422
491
|
};
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
492
|
+
const wrappedDocumentLoader = wrap(documentLoader);
|
|
493
|
+
return {
|
|
494
|
+
documentLoader: wrappedDocumentLoader,
|
|
495
|
+
contextLoader: contextLoader === documentLoader ? wrappedDocumentLoader : wrap(contextLoader),
|
|
496
|
+
records,
|
|
497
|
+
release: () => {
|
|
498
|
+
released = true;
|
|
499
|
+
records.clear();
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
const MAX_CAUSE_DEPTH = 8;
|
|
504
|
+
function collectCauses(error, causes = [], depth = 0) {
|
|
505
|
+
if (depth > MAX_CAUSE_DEPTH || causes.includes(error)) return causes;
|
|
506
|
+
causes.push(error);
|
|
507
|
+
if (typeof error !== "object" || error == null) return causes;
|
|
508
|
+
if (error instanceof AggregateError) for (const e of error.errors) collectCauses(e, causes, depth + 1);
|
|
509
|
+
if ("cause" in error) collectCauses(error.cause, causes, depth + 1);
|
|
510
|
+
const details = error.details;
|
|
511
|
+
if (typeof details === "object" && details != null && "cause" in details) collectCauses(details.cause, causes, depth + 1);
|
|
512
|
+
return causes;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Classifies the error thrown by a dereferencing operation. It is a fetch
|
|
516
|
+
* failure only if a loader error is among its causes; errors that loaders
|
|
517
|
+
* threw during attempts the operation recovered from are ignored.
|
|
518
|
+
*/
|
|
519
|
+
function classifyFailure(error, tracker) {
|
|
520
|
+
const loaderErrors = collectCauses(error).filter((e) => tracker.records.has(e));
|
|
521
|
+
const transientIndex = loaderErrors.findIndex(_fedify_vocab_runtime.isTransientFetchError);
|
|
522
|
+
const selected = loaderErrors[transientIndex < 0 ? 0 : transientIndex];
|
|
523
|
+
const url = loaderErrors.length < 1 ? null : getFailedUrl(selected, tracker.records.get(selected));
|
|
524
|
+
if (url == null) return {
|
|
525
|
+
category: "unverifiable",
|
|
526
|
+
type: "invalidJsonLd",
|
|
527
|
+
cause: error,
|
|
528
|
+
transient: false
|
|
529
|
+
};
|
|
530
|
+
return {
|
|
531
|
+
category: "unverifiable",
|
|
532
|
+
type: "notDereferenceable",
|
|
533
|
+
url,
|
|
534
|
+
cause: loaderErrors.length === 1 ? selected : error,
|
|
535
|
+
transient: transientIndex >= 0
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function getFailedUrl(error, requestedUrl) {
|
|
539
|
+
const url = error?.url;
|
|
540
|
+
if (url instanceof URL) return url;
|
|
541
|
+
for (const candidate of [url, requestedUrl]) {
|
|
542
|
+
if (typeof candidate !== "string") continue;
|
|
543
|
+
try {
|
|
544
|
+
return (0, _fedify_vocab_runtime.parseIri)(candidate);
|
|
545
|
+
} catch {
|
|
546
|
+
continue;
|
|
430
547
|
}
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
function notDereferenceable(url) {
|
|
552
|
+
return {
|
|
553
|
+
category: "unverifiable",
|
|
554
|
+
type: "notDereferenceable",
|
|
555
|
+
url,
|
|
556
|
+
transient: false
|
|
431
557
|
};
|
|
432
|
-
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Dereferences a field of the request, such as its `object` or `instrument`.
|
|
561
|
+
* Returns `null` if the request has no such field at all.
|
|
562
|
+
*/
|
|
563
|
+
async function dereferenceField(request, reference, dereference, documentLoader, contextLoader) {
|
|
564
|
+
const tracker = trackLoaders(documentLoader, contextLoader);
|
|
433
565
|
try {
|
|
434
|
-
|
|
435
|
-
|
|
566
|
+
const value = await dereference(request, {
|
|
567
|
+
documentLoader: tracker.documentLoader,
|
|
568
|
+
contextLoader: tracker.contextLoader,
|
|
569
|
+
suppressError: false
|
|
570
|
+
});
|
|
571
|
+
if (value != null || reference == null) return {
|
|
572
|
+
ok: true,
|
|
573
|
+
value
|
|
574
|
+
};
|
|
436
575
|
return {
|
|
437
576
|
ok: false,
|
|
438
|
-
failure:
|
|
439
|
-
category: "unverifiable",
|
|
440
|
-
type: "notDereferenceable",
|
|
441
|
-
url: value,
|
|
442
|
-
cause
|
|
443
|
-
}
|
|
577
|
+
failure: notDereferenceable(reference)
|
|
444
578
|
};
|
|
579
|
+
} catch (error) {
|
|
580
|
+
return {
|
|
581
|
+
ok: false,
|
|
582
|
+
failure: classifyFailure(error, tracker)
|
|
583
|
+
};
|
|
584
|
+
} finally {
|
|
585
|
+
tracker.release();
|
|
445
586
|
}
|
|
446
|
-
|
|
587
|
+
}
|
|
588
|
+
async function materialize(value, constructor, documentLoader, contextLoader) {
|
|
589
|
+
if (!(value instanceof URL)) return {
|
|
590
|
+
ok: true,
|
|
591
|
+
object: value
|
|
592
|
+
};
|
|
593
|
+
if (documentLoader == null) return {
|
|
447
594
|
ok: false,
|
|
448
|
-
failure:
|
|
449
|
-
category: "unverifiable",
|
|
450
|
-
type: "notDereferenceable",
|
|
451
|
-
url: value
|
|
452
|
-
}
|
|
595
|
+
failure: notDereferenceable(value)
|
|
453
596
|
};
|
|
597
|
+
const tracker = trackLoaders(documentLoader, contextLoader);
|
|
454
598
|
try {
|
|
599
|
+
const remoteDocument = await tracker.documentLoader(value.href);
|
|
455
600
|
return {
|
|
456
601
|
ok: true,
|
|
457
602
|
object: await constructor.fromJsonLd(remoteDocument.document, {
|
|
458
|
-
documentLoader:
|
|
459
|
-
contextLoader:
|
|
603
|
+
documentLoader: tracker.documentLoader,
|
|
604
|
+
contextLoader: tracker.contextLoader,
|
|
460
605
|
baseUrl: value
|
|
461
606
|
})
|
|
462
607
|
};
|
|
463
|
-
} catch (
|
|
608
|
+
} catch (error) {
|
|
464
609
|
return {
|
|
465
610
|
ok: false,
|
|
466
|
-
failure:
|
|
467
|
-
category: "unverifiable",
|
|
468
|
-
type: "invalidJsonLd",
|
|
469
|
-
cause
|
|
470
|
-
}
|
|
611
|
+
failure: classifyFailure(error, tracker)
|
|
471
612
|
};
|
|
613
|
+
} finally {
|
|
614
|
+
tracker.release();
|
|
472
615
|
}
|
|
473
616
|
}
|
|
474
617
|
async function evaluatePolicy(context, options, config) {
|
|
@@ -485,30 +628,60 @@ async function evaluatePolicy(context, options, config) {
|
|
|
485
628
|
reason: implicitAutomatic
|
|
486
629
|
};
|
|
487
630
|
const policy = options.subject.interactionPolicy;
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const
|
|
631
|
+
let rule = getApprovalRule(policy?.[config.policyProperty]);
|
|
632
|
+
if (rule == null) {
|
|
633
|
+
rule = getApprovalRule(options.fallbackRule);
|
|
634
|
+
if (rule == null) return policy == null ? missingPolicyDecision(config) : missingRuleDecision(config);
|
|
635
|
+
}
|
|
636
|
+
const { automaticApprovals, manualApprovals } = rule;
|
|
637
|
+
const matchOptions = {
|
|
638
|
+
requester: options.requester,
|
|
639
|
+
context,
|
|
640
|
+
matchesApprovalCollection: options.matchesApprovalCollection,
|
|
641
|
+
throwCollectionErrors: options.collectionErrors === "throw"
|
|
642
|
+
};
|
|
643
|
+
if (options.precedence === "automatic") {
|
|
644
|
+
const automatic = await matchRule(automaticApprovals, matchOptions);
|
|
645
|
+
if (automatic?.result === "matched") return {
|
|
646
|
+
result: "automatic",
|
|
647
|
+
reason: automatic.reason
|
|
648
|
+
};
|
|
649
|
+
else if (automatic?.result === "unverifiableCollection") return deniedUnverifiableCollection(automatic);
|
|
650
|
+
const manual = await matchRule(manualApprovals, matchOptions);
|
|
651
|
+
if (manual?.result === "matched") return {
|
|
652
|
+
result: "manual",
|
|
653
|
+
reason: manual.reason
|
|
654
|
+
};
|
|
655
|
+
else if (manual?.result === "unverifiableCollection") return deniedUnverifiableCollection(manual);
|
|
656
|
+
return {
|
|
657
|
+
result: "denied",
|
|
658
|
+
reason: { type: "noMatch" }
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
const automatic = await matchRule(automaticApprovals, {
|
|
662
|
+
...matchOptions,
|
|
663
|
+
actorOnly: true
|
|
664
|
+
});
|
|
495
665
|
if (automatic?.result === "matched") return {
|
|
496
666
|
result: "automatic",
|
|
497
667
|
reason: automatic.reason
|
|
498
668
|
};
|
|
499
|
-
const manual = await matchRule(manualApprovals,
|
|
669
|
+
const manual = await matchRule(manualApprovals, {
|
|
670
|
+
...matchOptions,
|
|
671
|
+
actorOnly: true
|
|
672
|
+
});
|
|
500
673
|
if (manual?.result === "matched") return {
|
|
501
674
|
result: "manual",
|
|
502
675
|
reason: manual.reason
|
|
503
676
|
};
|
|
504
|
-
const broadAutomatic = await matchRule(automaticApprovals,
|
|
505
|
-
const unverifiableAutomatic = broadAutomatic?.result === "unverifiableCollection" ? broadAutomatic
|
|
677
|
+
const broadAutomatic = await matchRule(automaticApprovals, matchOptions);
|
|
678
|
+
const unverifiableAutomatic = broadAutomatic?.result === "unverifiableCollection" ? broadAutomatic : null;
|
|
506
679
|
if (broadAutomatic?.result === "matched") return {
|
|
507
680
|
result: "automatic",
|
|
508
681
|
reason: broadAutomatic.reason
|
|
509
682
|
};
|
|
510
|
-
const broadManual = await matchRule(manualApprovals,
|
|
511
|
-
if (broadManual?.result === "unverifiableCollection") return deniedUnverifiableCollection(broadManual
|
|
683
|
+
const broadManual = await matchRule(manualApprovals, matchOptions);
|
|
684
|
+
if (broadManual?.result === "unverifiableCollection") return deniedUnverifiableCollection(broadManual);
|
|
512
685
|
else if (broadManual?.result === "matched") return {
|
|
513
686
|
result: "manual",
|
|
514
687
|
reason: broadManual.reason
|
|
@@ -519,6 +692,16 @@ async function evaluatePolicy(context, options, config) {
|
|
|
519
692
|
reason: { type: "noMatch" }
|
|
520
693
|
};
|
|
521
694
|
}
|
|
695
|
+
function getApprovalRule(rule) {
|
|
696
|
+
if (rule == null) return null;
|
|
697
|
+
const automaticApprovals = rule.automaticApprovals ?? [];
|
|
698
|
+
const manualApprovals = rule.manualApprovals ?? [];
|
|
699
|
+
if (automaticApprovals.length < 1 && manualApprovals.length < 1) return null;
|
|
700
|
+
return {
|
|
701
|
+
automaticApprovals,
|
|
702
|
+
manualApprovals
|
|
703
|
+
};
|
|
704
|
+
}
|
|
522
705
|
async function matchImplicitAutomaticActors(subject, requester, options, config) {
|
|
523
706
|
if (config.getImplicitAutomaticActors == null) return null;
|
|
524
707
|
for await (const actor of config.getImplicitAutomaticActors(subject, options)) if (idsEqual(actor, requester)) return {
|
|
@@ -553,7 +736,8 @@ function missingRuleDecision(config) {
|
|
|
553
736
|
reason: { type: "missingRule" }
|
|
554
737
|
};
|
|
555
738
|
}
|
|
556
|
-
async function matchRule(entries,
|
|
739
|
+
async function matchRule(entries, options) {
|
|
740
|
+
const { requester, matchesApprovalCollection } = options;
|
|
557
741
|
for (const entry of entries) if (entry.href === requester.href) return {
|
|
558
742
|
result: "matched",
|
|
559
743
|
reason: {
|
|
@@ -567,14 +751,18 @@ async function matchRule(entries, requester, context, matchesApprovalCollection,
|
|
|
567
751
|
reason: { type: "public" }
|
|
568
752
|
};
|
|
569
753
|
if (matchesApprovalCollection != null) {
|
|
570
|
-
let
|
|
754
|
+
let unverifiable = null;
|
|
571
755
|
for (const entry of entries) {
|
|
572
756
|
if (entry.href === _fedify_vocab.PUBLIC_COLLECTION.href || entry.href === requester.href) continue;
|
|
573
757
|
let matched;
|
|
574
758
|
try {
|
|
575
|
-
matched = await matchesApprovalCollection(entry, requester, context);
|
|
576
|
-
} catch {
|
|
577
|
-
|
|
759
|
+
matched = await matchesApprovalCollection(entry, requester, options.context);
|
|
760
|
+
} catch (cause) {
|
|
761
|
+
if (options.throwCollectionErrors) throw cause;
|
|
762
|
+
unverifiable ??= {
|
|
763
|
+
collection: entry,
|
|
764
|
+
cause
|
|
765
|
+
};
|
|
578
766
|
continue;
|
|
579
767
|
}
|
|
580
768
|
if (matched) return {
|
|
@@ -585,19 +773,20 @@ async function matchRule(entries, requester, context, matchesApprovalCollection,
|
|
|
585
773
|
}
|
|
586
774
|
};
|
|
587
775
|
}
|
|
588
|
-
if (
|
|
776
|
+
if (unverifiable != null) return {
|
|
589
777
|
result: "unverifiableCollection",
|
|
590
|
-
|
|
778
|
+
...unverifiable
|
|
591
779
|
};
|
|
592
780
|
}
|
|
593
781
|
return null;
|
|
594
782
|
}
|
|
595
|
-
function deniedUnverifiableCollection(
|
|
783
|
+
function deniedUnverifiableCollection(failure) {
|
|
596
784
|
return {
|
|
597
785
|
result: "denied",
|
|
598
786
|
reason: {
|
|
599
787
|
type: "unverifiableCollection",
|
|
600
|
-
collection
|
|
788
|
+
collection: failure.collection,
|
|
789
|
+
cause: failure.cause
|
|
601
790
|
}
|
|
602
791
|
};
|
|
603
792
|
}
|
|
@@ -798,20 +987,23 @@ const quoteInteraction = createInteractionControl({
|
|
|
798
987
|
_fedify_vocab.ChatMessage.typeId
|
|
799
988
|
],
|
|
800
989
|
getInteractionTarget: (request, options) => request.getObject(options),
|
|
801
|
-
validateRequest: (_request, quote, target, requester) => {
|
|
990
|
+
validateRequest: (_request, quote, target, requester, options) => {
|
|
802
991
|
const targetId = getRequiredId(target, "interactionTarget");
|
|
803
|
-
|
|
992
|
+
const quoteReference = options.quoteReference ?? "strict";
|
|
993
|
+
if (quoteReference === "strict" && quote.quoteId != null && quote.quoteUrl != null && !idsEqual(quote.quoteUrl, quote.quoteId)) return {
|
|
804
994
|
type: "objectMismatch",
|
|
805
995
|
expected: quote.quoteId,
|
|
806
996
|
actual: quote.quoteUrl
|
|
807
997
|
};
|
|
808
998
|
const quoteTargetId = getQuoteTargetId(quote);
|
|
809
|
-
if (!idsEqual(quoteTargetId, targetId)) return {
|
|
999
|
+
if (!(quoteReference === "any" ? idsEqual(quote.quoteId, targetId) || idsEqual(quote.quoteUrl, targetId) : idsEqual(quoteTargetId, targetId))) return {
|
|
810
1000
|
type: "objectMismatch",
|
|
811
1001
|
expected: targetId,
|
|
812
1002
|
actual: quoteTargetId ?? void 0
|
|
813
1003
|
};
|
|
814
|
-
|
|
1004
|
+
const attributionIds = quote.attributionIds;
|
|
1005
|
+
if (attributionIds.length < 1 && options.missingAttribution === "requester") return null;
|
|
1006
|
+
if (!(options.attribution === "any" ? attributionIds.some((id) => idsEqual(id, requester)) : idsEqual(quote.attributionId, requester))) return {
|
|
815
1007
|
type: "requesterMismatch",
|
|
816
1008
|
expected: requester,
|
|
817
1009
|
actual: quote.attributionId ?? void 0
|