Some small refactoring and bug fixing

This commit is contained in:
Gordon Pedersen 2023-10-03 09:19:04 +11:00
parent fa5ef0e282
commit 983d828f92
7 changed files with 70 additions and 103 deletions

View file

@ -1,4 +1,4 @@
const ACTOR = require("./actor") const ACTOR = require("./actor").default
module.exports = function(eleventyConfig) { module.exports = function(eleventyConfig) {
// I'm .gitignoring my content for now, so 11ty should not ignore that // I'm .gitignoring my content for now, so 11ty should not ignore that

View file

@ -43,7 +43,7 @@ const attachment:{ type:"PropertyValue", name:string, value:string }[] = [
} }
] ]
export default { const ACTOR = {
"@context": [ "@context": [
"https://www.w3.org/ns/activitystreams", "https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1", "https://w3id.org/security/v1",
@ -89,3 +89,26 @@ export default {
icon, icon,
image image
} }
export const handle = `${ACTOR.preferredUsername}@${HOSTNAME}`
export const webfinger = {
subject: `acct:${ACTOR.preferredUsername}@${HOSTNAME}`,
aliases: [ACTOR.id],
links: [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": ACTOR.url
},
{
rel: "self",
type: "application/activity+json",
href: ACTOR.id,
},
],
}
export default ACTOR

View file

@ -1,8 +1,9 @@
import * as db from "./db" import * as db from "./db"
import { reqIsActivityPub, verify } from "./request" import { verify } from "./request"
import outbox from "./outbox" import outbox from "./outbox"
import inbox from "./inbox" import inbox from "./inbox"
import ACTOR from "../actor" import ACTOR from "../actor"
import { activityPubTypes } from "./env"
export default (req: Request): Response | Promise<Response> | undefined => { export default (req: Request): Response | Promise<Response> | undefined => {
const url = new URL(req.url) const url = new URL(req.url)
@ -22,6 +23,10 @@ export default (req: Request): Response | Promise<Response> | undefined => {
return undefined return undefined
} }
export function reqIsActivityPub(req:Request) {
const contentType = req.headers.get("Accept")
return activityPubTypes.some(t => contentType?.includes(t))
}
export function idsFromValue(value:any):string[] { export function idsFromValue(value:any):string[] {
if (!value) return [] if (!value) return []

View file

@ -1,8 +1,8 @@
import { idsFromValue } from "./activitypub" import { idsFromValue } from "./activitypub"
import * as db from "./db" import * as db from "./db"
import { ADMIN_PASSWORD, ADMIN_USERNAME } from "./env" import { ADMIN_PASSWORD, ADMIN_USERNAME, activityPubTypes } from "./env"
import outbox from "./outbox" import outbox from "./outbox"
import { activityMimeTypes, fetchObject } from "./request" import { fetchObject } from "./request"
import ACTOR from "../actor" import ACTOR from "../actor"
export default (req: Request): Response | Promise<Response> | undefined => { export default (req: Request): Response | Promise<Response> | undefined => {
@ -94,15 +94,19 @@ const follow = async (req:Request, handle:string):Promise<Response> => {
catch { catch {
// this is not a valid url. Probably a someone@domain.tld format // this is not a valid url. Probably a someone@domain.tld format
const [_, host] = handle.split('@') const [_, host] = handle.split('@')
const res = await fetch(`https://${host}/.well-known/webfinger/?resource=acct:${handle}`) if(!host) return new Response('account not url or name@domain.tld', { status: 400 })
const res = await fetch(`https://${host}/.well-known/webfinger/?resource=acct:${handle}`, { headers: { 'accept': 'application/jrd+json'}})
const webfinger = await res.json() const webfinger = await res.json()
if(!webfinger.links) return new Response("", { status: 404 }) if(!webfinger.links) return new Response("", { status: 404 })
const links:any[] = webfinger.links const links:any[] = webfinger.links
const actorLink = links.find(l => l.rel === "self" && (activityMimeTypes.includes(l.type))) const actorLink = links.find(l => l.rel === "self" && (activityPubTypes.includes(l.type)))
if(!actorLink) return new Response("", { status: 404 }) if(!actorLink) return new Response("", { status: 404 })
url = actorLink.href url = actorLink.href
} }
console.log(`Follow ${url}`) console.log(`Following ${url}`)
// send the follow request to the supplied actor // send the follow request to the supplied actor
return await outbox({ return await outbox({

View file

@ -45,57 +45,8 @@ export const DEFAULT_DOCUMENTS = process.env.DEFAULT_DOCUMENTS || [
'default.htm' 'default.htm'
] ]
// export const ACTOR_OBJ = { export const activityPubTypes = [
// "@context": [ 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
// "https://www.w3.org/ns/activitystreams", 'application/activity+json'
// "https://w3id.org/security/v1", ]
// ], export const contentTypeHeader = { 'Content-Type': activityPubTypes[0]}
// id: ACTOR,
// type: "Person",
// following: `${ACTOR}/following`,
// followers: `${ACTOR}/followers`,
// inbox: `${ACTOR}/inbox`,
// outbox: `${ACTOR}/outbox`,
// //featured: `${ACTOR}/featured`,
// //featuredTags: `${ACTOR}/tags`,
// preferredUsername: ACCOUNT,
// name: REAL_NAME,
// summary: SUMMARY,
// url: ACTOR,
// manuallyApprovesFollowers: false,
// discoverable: true,
// published: "2023-09-14T00:00:00Z",
// //devices: `${ACTOR}/devices`
// alsoKnownAs: ALSO_KNOWN_AS,
// publicKey: {
// id: `${ACTOR}#main-key`,
// owner: ACTOR,
// publicKeyPem: PUBLIC_KEY,
// },
// // attachment: [
// // {
// // "type": "PropertyValue",
// // "name": "Pronouns",
// // "value": "he/him"
// // },
// // {
// // "type": "PropertyValue",
// // "name": "Website",
// // "value": "<a href=\"https://death.id.au\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"><span class=\"invisible\">https://</span>death.id.au</a>"
// // }
// // ],
// // tag: [],
// // endpoints: {
// // sharedInbox: N/A
// // },
// icon: {
// type: "Image",
// mediaType: "image/svg+xml",
// url: BASE_URL + "/assets/img/avatar-tt.svg"
// },
// image: {
// type: "Image",
// mediaType: "image/jpeg",
// url: BASE_URL + "/assets/img/banner-1500x500.jpg"
// }
// }

View file

@ -1,11 +1,11 @@
import { DEFAULT_DOCUMENTS, HOSTNAME, STATIC_PATH } from "./env" import { DEFAULT_DOCUMENTS, STATIC_PATH } from "./env"
import admin from './admin' import admin from './admin'
import activitypub from "./activitypub" import activitypub from "./activitypub"
import { fetchObject } from "./request" import { fetchObject } from "./request"
import path from "path" import path from "path"
import { BunFile } from "bun" import { BunFile } from "bun"
import { rebuild } from "./db" import { rebuild } from "./db"
import ACTOR from "../actor" import { handle, webfinger } from "../actor"
rebuild() rebuild()
@ -14,13 +14,30 @@ const server = Bun.serve({
fetch(req: Request): Response | Promise<Response> { fetch(req: Request): Response | Promise<Response> {
const url = new URL(req.url) const url = new URL(req.url)
console.log(`${new Date().toISOString()} ${req.method} ${req.url}`) // log the incoming request info
console.info(`${new Date().toISOString()} 📥 ${req.method} ${req.url}`)
if(req.method === "GET" && url.pathname === "/.well-known/webfinger") { // CORS route (for now, any domain has access)
return webfinger(req, url.searchParams.get("resource")) if(req.method === "OPTIONS") {
const headers:any = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, DELETE',
'Access-Control-Max-Age': '86400',
}
let h
if (h = req.headers.get('Access-Control-Request-Headers'))
headers['Access-Control-Allow-Headers'] = 'Accept, Content-Type, Authorization, Signature, Digest, Date, Host'
return new Response('', { status: 204, headers })
} }
else if(req.method === "GET" && url.pathname === "/fetch") { // Webfinger route
const object_url = url.searchParams.get('url') else if(req.method === "GET" && url.pathname === "/.well-known/webfinger") {
// make sure the resource matches the current handle. If it doesn't, 404
if(url.searchParams.get("resource") !== `acct:${handle}`) return new Response("", { status: 404 })
// return the webfinger
return Response.json(webfinger, { headers: { 'content-type': 'application/jrd+json' }})
}
else if(req.method === "GET" && url.pathname.startsWith("/fetch/")) {
const object_url = url.pathname.substring(7)
if(!object_url) return new Response("No url supplied", { status: 400}) if(!object_url) return new Response("No url supplied", { status: 400})
return fetchObject(object_url) return fetchObject(object_url)
@ -29,29 +46,6 @@ const server = Bun.serve({
return admin(req) || activitypub(req) || staticFile(req) return admin(req) || activitypub(req) || staticFile(req)
}, },
}); });
const webfinger = async (req: Request, resource: string | null) => {
if(resource !== `acct:${ACTOR.preferredUsername}@${HOSTNAME}`) return new Response("", { status: 404 })
return Response.json({
subject: `acct:${ACTOR.preferredUsername}@${HOSTNAME}`,
aliases: [ACTOR.id],
links: [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": ACTOR.url
},
{
rel: "self",
type: "application/activity+json",
href: ACTOR.id,
},
],
}, { headers: { "content-type": "application/activity+json" }})
}
const getDefaultDocument = async(base_path: string) => { const getDefaultDocument = async(base_path: string) => {
for(const d of DEFAULT_DOCUMENTS){ for(const d of DEFAULT_DOCUMENTS){
const filePath = path.join(base_path, d) const filePath = path.join(base_path, d)

View file

@ -2,16 +2,6 @@ import forge from "node-forge" // import crypto from "node:crypto"
import { PRIVATE_KEY } from "./env"; import { PRIVATE_KEY } from "./env";
import ACTOR from "../actor" import ACTOR from "../actor"
export const activityMimeTypes:string[] = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"']
export function reqIsActivityPub(req:Request) {
const contentType = req.headers.get("Accept")
const activityPub = contentType?.includes('application/activity+json')
|| contentType?.includes('application/ld+json; profile="https://www.w3.org/ns/activitystreams"')
|| contentType?.includes('application/ld+json')
return activityPub
}
// this function adds / modifies the appropriate headers for signing a request, then calls fetch // this function adds / modifies the appropriate headers for signing a request, then calls fetch
export function signedFetch(url: string | URL | Request, init?: FetchRequestInit): Promise<Response> export function signedFetch(url: string | URL | Request, init?: FetchRequestInit): Promise<Response>
{ {