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