@oxy.so/federation 2.0.0 → 2.1.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/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/actorObject.js +30 -0
- package/dist/cjs/apContext.js +5 -0
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/node/actorRouter.js +1 -0
- package/dist/cjs/node/delivery.js +1 -0
- package/dist/cjs/node/inboundDispatch.js +51 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/actorObject.js +29 -0
- package/dist/esm/apContext.js +5 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/node/actorRouter.js +1 -0
- package/dist/esm/node/delivery.js +1 -0
- package/dist/esm/node/inboundDispatch.js +51 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/actorObject.d.ts +16 -0
- package/dist/types/apContext.d.ts +4 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/node/actorRouter.d.ts +6 -0
- package/dist/types/node/delivery.d.ts +6 -0
- package/dist/types/node/inboundDispatch.d.ts +22 -0
- package/dist/types/node/index.d.ts +1 -1
- package/package.json +2 -2
- package/src/__tests__/actorObject.test.ts +27 -0
- package/src/__tests__/inboundDispatch.test.ts +60 -0
- package/src/actorObject.ts +37 -0
- package/src/apContext.ts +5 -0
- package/src/index.ts +1 -0
- package/src/node/actorRouter.ts +7 -0
- package/src/node/delivery.ts +7 -0
- package/src/node/inboundDispatch.ts +65 -0
- package/src/node/index.ts +1 -0
package/dist/cjs/actorObject.js
CHANGED
|
@@ -22,6 +22,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
exports.LOCAL_ACTOR_TYPE_BY_ACCOUNT_KIND = exports.AP_ACTOR_TYPES = void 0;
|
|
23
23
|
exports.isApActorType = isApActorType;
|
|
24
24
|
exports.localActorTypeForAccountKind = localActorTypeForAccountKind;
|
|
25
|
+
exports.normalizeAlsoKnownAs = normalizeAlsoKnownAs;
|
|
25
26
|
exports.createLocalActorBuilder = createLocalActorBuilder;
|
|
26
27
|
const contracts_1 = require("@oxy.so/contracts");
|
|
27
28
|
/**
|
|
@@ -177,6 +178,31 @@ function buildActorImage(config, banner) {
|
|
|
177
178
|
}
|
|
178
179
|
return apImageObject(resolved);
|
|
179
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* The publishable subset of an `alsoKnownAs` input: absolute `https:` URIs,
|
|
183
|
+
* first occurrence wins, input order kept. Exported so every actor builder
|
|
184
|
+
* (Oxy's own included) applies the same rule.
|
|
185
|
+
*/
|
|
186
|
+
function normalizeAlsoKnownAs(values) {
|
|
187
|
+
if (!values)
|
|
188
|
+
return [];
|
|
189
|
+
const seen = new Set();
|
|
190
|
+
const result = [];
|
|
191
|
+
for (const value of values) {
|
|
192
|
+
if (typeof value !== 'string' || seen.has(value))
|
|
193
|
+
continue;
|
|
194
|
+
try {
|
|
195
|
+
if (new URL(value).protocol !== 'https:')
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
seen.add(value);
|
|
202
|
+
result.push(value);
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
180
206
|
/**
|
|
181
207
|
* Build the per-instance local-actor builder. Bind it once with an app's domain +
|
|
182
208
|
* media resolver; call the returned function per user.
|
|
@@ -207,6 +233,10 @@ function createLocalActorBuilder(config) {
|
|
|
207
233
|
publicKeyPem: publicKey.publicKeyPem,
|
|
208
234
|
},
|
|
209
235
|
};
|
|
236
|
+
const aliases = normalizeAlsoKnownAs(params.alsoKnownAs);
|
|
237
|
+
if (aliases.length > 0) {
|
|
238
|
+
actorObject.alsoKnownAs = aliases;
|
|
239
|
+
}
|
|
210
240
|
// `published` (account creation date) is advertised when the API provides it.
|
|
211
241
|
if (createdAt) {
|
|
212
242
|
actorObject.published = new Date(createdAt).toISOString();
|
package/dist/cjs/apContext.js
CHANGED
|
@@ -33,7 +33,12 @@ exports.AP_CONTEXT = [
|
|
|
33
33
|
// is typed `@id` (an IRI, not a literal); the `misskey`/`fedibird` namespaces
|
|
34
34
|
// and the AS2 `Link` type back the FEP-e232 `Link` quote tag. Without these
|
|
35
35
|
// declarations a strict JSON-LD consumer DROPS the quote fields.
|
|
36
|
+
//
|
|
37
|
+
// `alsoKnownAs` is the account-alias term a Mastodon `Move` verifies. It is
|
|
38
|
+
// `as:alsoKnownAs` typed `@id`, exactly as Mastodon declares it; without the
|
|
39
|
+
// declaration a strict consumer drops the aliases and the move is refused.
|
|
36
40
|
{
|
|
41
|
+
alsoKnownAs: { '@id': 'as:alsoKnownAs', '@type': '@id' },
|
|
37
42
|
sensitive: 'as:sensitive',
|
|
38
43
|
toot: 'http://joinmastodon.org/ns#',
|
|
39
44
|
votersCount: 'toot:votersCount',
|
package/dist/cjs/index.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* knowledge of any app's post shape.
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.LOCAL_ACTOR_TYPE_BY_ACCOUNT_KIND = exports.AP_ACTOR_TYPES = exports.isApActorType = exports.localActorTypeForAccountKind = exports.createLocalActorBuilder = exports.createDomainPolicy = exports.extractActorUriFromActivityId = exports.isSameFederationHost = exports.canonicalFederationHost = exports.AP_CONTEXT = exports.readProxyDeclarations = exports.upstreamHandleFromProxyOf = exports.upstreamHandleFromPreferredUsername = exports.upstreamHandleFromAutomatedActor = exports.upstreamHandleFromAlsoKnownAs = exports.upstreamHandleFromProfileField = exports.federatedUsernameFromUpstreamUrl = exports.parseUpstreamProfileUrl = exports.upstreamProfileUrl = exports.stripBridgeBoilerplate = exports.createBridgeRelabeller = exports.blueskyUsernameFromHandle = exports.BSKY_NETWORK_DOMAIN = exports.FEDERATION_NETWORKS = exports.INSTANCE_ACTOR_USERNAME = exports.normalizeActorUsername = exports.createUrlBuilders = exports.DEFAULT_SIGNED_CONTENT_TYPE = exports.HTTP_SIGNATURE_ALGORITHM = exports.verifyHttpSignature = exports.signRequest = void 0;
|
|
25
|
+
exports.LOCAL_ACTOR_TYPE_BY_ACCOUNT_KIND = exports.AP_ACTOR_TYPES = exports.isApActorType = exports.localActorTypeForAccountKind = exports.normalizeAlsoKnownAs = exports.createLocalActorBuilder = exports.createDomainPolicy = exports.extractActorUriFromActivityId = exports.isSameFederationHost = exports.canonicalFederationHost = exports.AP_CONTEXT = exports.readProxyDeclarations = exports.upstreamHandleFromProxyOf = exports.upstreamHandleFromPreferredUsername = exports.upstreamHandleFromAutomatedActor = exports.upstreamHandleFromAlsoKnownAs = exports.upstreamHandleFromProfileField = exports.federatedUsernameFromUpstreamUrl = exports.parseUpstreamProfileUrl = exports.upstreamProfileUrl = exports.stripBridgeBoilerplate = exports.createBridgeRelabeller = exports.blueskyUsernameFromHandle = exports.BSKY_NETWORK_DOMAIN = exports.FEDERATION_NETWORKS = exports.INSTANCE_ACTOR_USERNAME = exports.normalizeActorUsername = exports.createUrlBuilders = exports.DEFAULT_SIGNED_CONTENT_TYPE = exports.HTTP_SIGNATURE_ALGORITHM = exports.verifyHttpSignature = exports.signRequest = void 0;
|
|
26
26
|
/**
|
|
27
27
|
* HTTP Signatures (draft-cavage) — the pure sign/verify crypto every Oxy app's
|
|
28
28
|
* ActivityPub federation shares. Private-key custody is injected; the key never
|
|
@@ -93,6 +93,7 @@ Object.defineProperty(exports, "createDomainPolicy", { enumerable: true, get: fu
|
|
|
93
93
|
*/
|
|
94
94
|
var actorObject_1 = require("./actorObject");
|
|
95
95
|
Object.defineProperty(exports, "createLocalActorBuilder", { enumerable: true, get: function () { return actorObject_1.createLocalActorBuilder; } });
|
|
96
|
+
Object.defineProperty(exports, "normalizeAlsoKnownAs", { enumerable: true, get: function () { return actorObject_1.normalizeAlsoKnownAs; } });
|
|
96
97
|
Object.defineProperty(exports, "localActorTypeForAccountKind", { enumerable: true, get: function () { return actorObject_1.localActorTypeForAccountKind; } });
|
|
97
98
|
Object.defineProperty(exports, "isApActorType", { enumerable: true, get: function () { return actorObject_1.isApActorType; } });
|
|
98
99
|
Object.defineProperty(exports, "AP_ACTOR_TYPES", { enumerable: true, get: function () { return actorObject_1.AP_ACTOR_TYPES; } });
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
* post/engagement handlers live. The consent gate + notification side effects are
|
|
14
14
|
* injected so the engine holds no app knowledge.
|
|
15
15
|
*
|
|
16
|
+
* `Move` (account migration) is handed to {@link InboundDispatcherConfig.onMove}
|
|
17
|
+
* after a SHAPE check only: the engine proves the activity is the signing actor
|
|
18
|
+
* moving itself, and the app forwards it to Oxy (`POST /federation/move`), which
|
|
19
|
+
* owns the identity decision — the alias check and the re-fetch of the old
|
|
20
|
+
* actor's `movedTo`.
|
|
21
|
+
*
|
|
16
22
|
* Extracted behaviour-identically from Mention's former `InboxProcessingService`
|
|
17
23
|
* dispatcher + `handleIncomingFollow` / `handleUndo(Follow)` / `handleAccept` /
|
|
18
24
|
* `handleReject`.
|
|
@@ -43,6 +49,38 @@ class ActorResolutionPendingError extends Error {
|
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
exports.ActorResolutionPendingError = ActorResolutionPendingError;
|
|
52
|
+
/**
|
|
53
|
+
* The shape check for an inbound `Move`.
|
|
54
|
+
*
|
|
55
|
+
* A Move is only meaningful as an actor moving ITSELF: `actor` and `object` must
|
|
56
|
+
* both be the actor whose HTTP signature was verified, so a relay or a third
|
|
57
|
+
* party cannot move somebody else's followers. `target` must be an absolute
|
|
58
|
+
* https URI, and differ from the old actor.
|
|
59
|
+
*/
|
|
60
|
+
function parseInboundMove(activity, verifiedActorUri) {
|
|
61
|
+
const activityId = typeof activity.id === 'string' ? activity.id : undefined;
|
|
62
|
+
if (!activityId)
|
|
63
|
+
return { ok: false, reason: 'missing id' };
|
|
64
|
+
const actor = objectTargetUri(activity.actor);
|
|
65
|
+
if (actor !== verifiedActorUri)
|
|
66
|
+
return { ok: false, reason: 'actor is not the signer' };
|
|
67
|
+
const object = objectTargetUri(activity.object);
|
|
68
|
+
if (object !== verifiedActorUri)
|
|
69
|
+
return { ok: false, reason: 'object is not the moving actor' };
|
|
70
|
+
const target = objectTargetUri(activity.target);
|
|
71
|
+
if (!target)
|
|
72
|
+
return { ok: false, reason: 'missing target' };
|
|
73
|
+
try {
|
|
74
|
+
if (new URL(target).protocol !== 'https:')
|
|
75
|
+
return { ok: false, reason: 'target is not https' };
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return { ok: false, reason: 'target is not a URL' };
|
|
79
|
+
}
|
|
80
|
+
if (target === verifiedActorUri)
|
|
81
|
+
return { ok: false, reason: 'target is the moving actor' };
|
|
82
|
+
return { ok: true, move: { activityId, oldActorUri: verifiedActorUri, targetActorUri: target } };
|
|
83
|
+
}
|
|
46
84
|
/**
|
|
47
85
|
* The lowercased host of an actor URI, or null when it is not a parseable absolute
|
|
48
86
|
* URL. Callers treat null as blocked: an origin whose host cannot be determined
|
|
@@ -260,6 +298,19 @@ function createInboundDispatcher(config) {
|
|
|
260
298
|
case 'Update':
|
|
261
299
|
await config.onContentActivity(activity, verifiedActorUri);
|
|
262
300
|
break;
|
|
301
|
+
case 'Move': {
|
|
302
|
+
const parsed = parseInboundMove(activity, verifiedActorUri);
|
|
303
|
+
if (!parsed.ok) {
|
|
304
|
+
logger.warn(`[Federation] dropping Move from ${verifiedActorUri}: ${parsed.reason}`);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
if (!config.onMove) {
|
|
308
|
+
logger.debug(`Unhandled Move from ${verifiedActorUri} (no onMove handler)`);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
await config.onMove(parsed.move);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
263
314
|
default:
|
|
264
315
|
logger.debug(`Unhandled activity type: ${validation.type}`);
|
|
265
316
|
}
|