More federation work
This commit is contained in:
parent
aea8a2d826
commit
ce30ca9d34
|
@ -33,6 +33,9 @@ export interface UndoActivity extends Activity {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NoteObject extends Object {
|
export interface NoteObject extends Object {
|
||||||
|
to: string[];
|
||||||
|
cc: string[];
|
||||||
|
directMessage?: boolean;
|
||||||
attributedTo: string;
|
attributedTo: string;
|
||||||
content: string;
|
content: string;
|
||||||
published: string;
|
published: string;
|
||||||
|
@ -46,7 +49,9 @@ export interface DeleteActivity extends Activity {
|
||||||
|
|
||||||
export interface ActorObject {
|
export interface ActorObject {
|
||||||
id: string;
|
id: string;
|
||||||
|
url: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
preferredUsername: string;
|
||||||
inbox: string;
|
inbox: string;
|
||||||
endpoints?: {
|
endpoints?: {
|
||||||
sharedInbox?: string;
|
sharedInbox?: string;
|
||||||
|
|
|
@ -2,7 +2,13 @@ import { promises as fs } from "fs";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import uuidv4 from "uuid/v4";
|
import uuidv4 from "uuid/v4";
|
||||||
import request from "request";
|
import request from "request";
|
||||||
import { Activity, ArticleObject, FollowActivity, AcceptActivity, ActorObject, CreateActivity } from "./activity";
|
import {
|
||||||
|
Activity,
|
||||||
|
ArticleObject,
|
||||||
|
ActorObject,
|
||||||
|
CreateActivity,
|
||||||
|
NoteObject
|
||||||
|
} from "./activity";
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
import { getConnection } from "typeorm";
|
import { getConnection } from "typeorm";
|
||||||
import Actor from "../entity/Actor";
|
import Actor from "../entity/Actor";
|
||||||
|
@ -10,7 +16,7 @@ import Article from "../entity/Article";
|
||||||
|
|
||||||
const domain = process.env.DOMAIN;
|
const domain = process.env.DOMAIN;
|
||||||
|
|
||||||
function createActivity(article: ArticleObject): CreateActivity {
|
export function createActivity(object: ArticleObject | NoteObject): CreateActivity {
|
||||||
const uuid = uuidv4();
|
const uuid = uuidv4();
|
||||||
const createObject = {
|
const createObject = {
|
||||||
"@context": [
|
"@context": [
|
||||||
|
@ -19,9 +25,9 @@ function createActivity(article: ArticleObject): CreateActivity {
|
||||||
"type": "Create",
|
"type": "Create",
|
||||||
"id": `https://${domain}/ap/${uuid}`,
|
"id": `https://${domain}/ap/${uuid}`,
|
||||||
"actor": `https://${domain}/ap/actor`,
|
"actor": `https://${domain}/ap/actor`,
|
||||||
"to": article.to,
|
"to": object.to,
|
||||||
"cc": article.cc,
|
"cc": object.cc,
|
||||||
"object": article
|
"object": object
|
||||||
};
|
};
|
||||||
return createObject;
|
return createObject;
|
||||||
}
|
}
|
||||||
|
@ -44,15 +50,7 @@ export async function getCachedActor(url: string): Promise<ActorObject | null> {
|
||||||
const result = await getConnection().manager.findByIds(Actor, [url]);
|
const result = await getConnection().manager.findByIds(Actor, [url]);
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
const actor = result[0];
|
const actor = result[0];
|
||||||
return {
|
return actor.actorObject;
|
||||||
id: actor.id,
|
|
||||||
name: actor.displayName,
|
|
||||||
inbox: actor.inbox,
|
|
||||||
icon: actor.iconURL,
|
|
||||||
publicKey: {
|
|
||||||
publicKeyPem: actor.publicKeyPem
|
|
||||||
}
|
|
||||||
} as ActorObject;
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Router, Request, Response } from "express";
|
import { Router, Request, Response } from "express";
|
||||||
import uuidv4 from "uuid/v4";
|
import uuidv4 from "uuid/v4";
|
||||||
import { getActor, signAndSend } from "./federate";
|
import {createActivity, getActor, signAndSend} from "./federate";
|
||||||
import { Activity, FollowActivity, AcceptActivity, UndoActivity, CreateActivity, NoteObject, DeleteActivity } from "./activity";
|
import { Activity, FollowActivity, AcceptActivity, UndoActivity, CreateActivity, NoteObject, DeleteActivity } from "./activity";
|
||||||
import { Database } from "sqlite3";
|
import { Database } from "sqlite3";
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
|
@ -8,6 +8,7 @@ import sanitizeHtml from "sanitize-html";
|
||||||
import Note from "../entity/Note";
|
import Note from "../entity/Note";
|
||||||
import { getConnection } from "typeorm";
|
import { getConnection } from "typeorm";
|
||||||
import Actor from "../entity/Actor";
|
import Actor from "../entity/Actor";
|
||||||
|
import Article from "../entity/Article";
|
||||||
|
|
||||||
const domain = process.env.DOMAIN;
|
const domain = process.env.DOMAIN;
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ export default function inbox(router: Router) {
|
||||||
router.post("/inbox", handleInbox);
|
router.post("/inbox", handleInbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleInbox(req: Request, res: Response) {
|
function handleInbox(req: Request, res: Response) {
|
||||||
console.log(req.body);
|
console.log(req.body);
|
||||||
const activity = req.body as Activity;
|
const activity = req.body as Activity;
|
||||||
if (activity.type === "Follow") {
|
if (activity.type === "Follow") {
|
||||||
|
@ -55,15 +56,22 @@ async function handleFollow(activity: Activity, req: Request, res: Response) {
|
||||||
"actor": `https://${domain}/ap/actor`,
|
"actor": `https://${domain}/ap/actor`,
|
||||||
"object": follow
|
"object": follow
|
||||||
} as AcceptActivity;
|
} as AcceptActivity;
|
||||||
signAndSend(acceptObject, actor.inbox);
|
try {
|
||||||
|
await signAndSend(acceptObject, actor.inbox);
|
||||||
// prefer shared server inbox
|
// prefer shared server inbox
|
||||||
const serverInbox = actor.endpoints && actor.endpoints.sharedInbox ? actor.endpoints.sharedInbox : actor.inbox;
|
const serverInbox = actor.endpoints && actor.endpoints.sharedInbox ? actor.endpoints.sharedInbox : actor.inbox;
|
||||||
await getConnection().createQueryBuilder()
|
await getConnection().createQueryBuilder()
|
||||||
.update(Actor)
|
.update(Actor)
|
||||||
.set({ isFollower: true })
|
.set({
|
||||||
|
isFollower: true,
|
||||||
|
inbox: serverInbox
|
||||||
|
})
|
||||||
.where("id = :id", {id: actor.id})
|
.where("id = :id", {id: actor.id})
|
||||||
.execute();
|
.execute();
|
||||||
res.end();
|
res.end();
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ message: "Encountered error handling follow.", error: err }).end();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCreate(activity: Activity, req: Request, res: Response) {
|
async function handleCreate(activity: Activity, req: Request, res: Response) {
|
||||||
|
@ -91,6 +99,36 @@ function sanitizeStatus(content: string): string {
|
||||||
async function handleCreateNote(create: CreateActivity, req: Request, res: Response) {
|
async function handleCreateNote(create: CreateActivity, req: Request, res: Response) {
|
||||||
const noteObject = create.object as NoteObject;
|
const noteObject = create.object as NoteObject;
|
||||||
const actor = await getActor(noteObject.attributedTo); // get and cache the actor if it's not already cached
|
const actor = await getActor(noteObject.attributedTo); // get and cache the actor if it's not already cached
|
||||||
|
|
||||||
|
const asPublic = "https://www.w3.org/ns/activitystreams#Public";
|
||||||
|
const article = await getConnection().getRepository(Article).findOne({ where: { conversation: noteObject.conversation } });
|
||||||
|
if (!article) {
|
||||||
|
console.log(`Ignoring message not in response to an article: ${noteObject.id}`);
|
||||||
|
res.end();
|
||||||
|
} else if (!process.env.ACCEPT_NON_PUBLIC_NOTES && !noteObject.to.includes(asPublic) && !noteObject.cc.includes(asPublic)) {
|
||||||
|
console.log(`Ignoring non-public post: ${noteObject.id}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const note: NoteObject = {
|
||||||
|
type: "Note",
|
||||||
|
id: `https://${domain}/ap/object/${uuidv4()}`,
|
||||||
|
to: [actor.id],
|
||||||
|
cc: [],
|
||||||
|
directMessage: true,
|
||||||
|
attributedTo: `https://${domain}/ap/actor`,
|
||||||
|
content: `<a href="${actor.url}" class="mention">@<span>${actor.preferredUsername}</span></a> Non-public posts are not accepted. To respond to a blog post, use either Public or Unlisted.`,
|
||||||
|
published: new Date().toISOString(),
|
||||||
|
inReplyTo: noteObject.id,
|
||||||
|
conversation: noteObject.conversation || `https://${domain}/ap/conversation/${uuidv4()}`
|
||||||
|
};
|
||||||
|
const responseCreate = createActivity(note);
|
||||||
|
signAndSend(responseCreate, actor.inbox);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Couldn't send non-public reply Note to ${noteObject.id}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
const sanitizedContent = sanitizeStatus(noteObject.content);
|
const sanitizedContent = sanitizeStatus(noteObject.content);
|
||||||
const note = new Note();
|
const note = new Note();
|
||||||
note.id = noteObject.id;
|
note.id = noteObject.id;
|
||||||
|
@ -102,10 +140,12 @@ async function handleCreateNote(create: CreateActivity, req: Request, res: Respo
|
||||||
note.published = noteObject.published;
|
note.published = noteObject.published;
|
||||||
try {
|
try {
|
||||||
await getConnection().getRepository(Note).save(note);
|
await getConnection().getRepository(Note).save(note);
|
||||||
|
res.end();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Encountered error storing reply ${noteObject.id}`, err);
|
console.error(`Encountered error storing reply ${noteObject.id}`, err);
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res.end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDelete(activity: Activity, req: Request, res: Response) {
|
async function handleDelete(activity: Activity, req: Request, res: Response) {
|
||||||
|
@ -123,7 +163,7 @@ async function handleDelete(activity: Activity, req: Request, res: Response) {
|
||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUndo(activity: Activity, req: Request, res: Response) {
|
function handleUndo(activity: Activity, req: Request, res: Response) {
|
||||||
const undo = activity as UndoActivity;
|
const undo = activity as UndoActivity;
|
||||||
if (undo.object.type === "Follow") {
|
if (undo.object.type === "Follow") {
|
||||||
handleUndoFollow(undo, req, res);
|
handleUndoFollow(undo, req, res);
|
||||||
|
|
Loading…
Reference in New Issue