diff --git a/drizzle.config.ts b/drizzle.config.ts index 75ff7e9..c8a02ae 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "drizzle-kit"; export default defineConfig({ dialect: "sqlite", - schema: "./src/schema.ts", + schema: "./src/db/schema.ts", out: "./drizzle", dbCredentials: { "url": "./brainz-social.db" diff --git a/package-lock.json b/package-lock.json index 8318932..a1a9e60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "devDependencies": { "@types/bcrypt": "^5.0.2", "@types/better-sqlite3": "^7.6.11", + "@types/jsonld": "^1.5.15", "@types/node": "^22.0.0", "@types/prompt": "^1.1.8", "@types/react": "^18.3.3", @@ -1380,6 +1381,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonld": { + "version": "1.5.15", + "resolved": "https://registry.npmjs.org/@types/jsonld/-/jsonld-1.5.15.tgz", + "integrity": "sha512-PlAFPZjL+AuGYmwlqwKEL0IMP8M8RexH0NIPGfCVWSQ041H2rR/8OlyZSD7KsCVoN8vCfWdtWDBxX8yBVP+xow==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.0.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.0.tgz", diff --git a/package.json b/package.json index 283355a..5e2c4b7 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "devDependencies": { "@types/bcrypt": "^5.0.2", "@types/better-sqlite3": "^7.6.11", + "@types/jsonld": "^1.5.15", "@types/node": "^22.0.0", "@types/prompt": "^1.1.8", "@types/react": "^18.3.3", diff --git a/src/app/.well-known/webfinger/route.ts b/src/app/.well-known/webfinger/route.ts index d592adc..8582449 100644 --- a/src/app/.well-known/webfinger/route.ts +++ b/src/app/.well-known/webfinger/route.ts @@ -1,5 +1,5 @@ -import db from "@/../db/drizzle"; -import {user} from "@/schema"; +import db from "@/db/drizzle"; +import {user} from "@/db/schema"; import { NextRequest, NextResponse } from "next/server"; export const dynamic = 'force-dynamic'; diff --git a/db/drizzle.ts b/src/db/drizzle.ts similarity index 100% rename from db/drizzle.ts rename to src/db/drizzle.ts diff --git a/src/schema.ts b/src/db/schema.ts similarity index 100% rename from src/schema.ts rename to src/db/schema.ts diff --git a/scripts/create_user.ts b/src/scripts/create_user.ts similarity index 96% rename from scripts/create_user.ts rename to src/scripts/create_user.ts index 5d5ec28..069474f 100644 --- a/scripts/create_user.ts +++ b/src/scripts/create_user.ts @@ -1,5 +1,5 @@ -import { pubPrivKeys, user } from '@/schema'; -import db from '../db/drizzle'; +import { pubPrivKeys, user } from '@/db/schema'; +import db from '@/db/drizzle'; import { start as promptStart, get as promptGet } from 'prompt'; import * as bcrypt from 'bcrypt'; @@ -102,7 +102,7 @@ const main = async () => { } } ], - "id": `https://${result.Domain}/users/${result.Username}`, + "id": `https://${result.Domain}/api/activitypub/user/${result.Username}`, "type": "Person", "following": `https://${result.Domain}/users/${result.Username}/following`, "followers": `https://${result.Domain}/users/${result.Username}/followers`, @@ -122,8 +122,8 @@ const main = async () => { "devices": `https://${result.Domain}/users/${result.Username}/collections/devices`, "alsoKnownAs": [], "publicKey": { - "id": `https://${result.Domain}/users/${result.Username}#main-key`, - "owner": `https://${result.Domain}/users/${result.Username}`, + "id": `https://${result.Domain}/api/activitypub/user/${result.Username}#main-key`, + "owner": `https://${result.Domain}/api/activitypub/user/${result.Username}`, "publicKeyPem": publicKey.toString() }, "tag": [], diff --git a/src/scripts/rectify_activities.ts b/src/scripts/rectify_activities.ts new file mode 100644 index 0000000..4fb0dcb --- /dev/null +++ b/src/scripts/rectify_activities.ts @@ -0,0 +1,59 @@ +import db from '@/db/drizzle'; +import { pubPrivKeys, user } from '@/db/schema'; +import {desc, eq} from 'drizzle-orm'; +import jsonld from 'jsonld'; +import {get as promptGet, start as promptStart} from 'prompt'; +import { generateKeyPair as generateKeyPairCallback } from 'crypto'; +import { promisify } from 'util'; + +const generateKeyPair = promisify(generateKeyPairCallback); + +async function main() { + const rows = await db.select({id: user.id, activity: user.activity, name: user.name}).from(user); + for ( let row of rows ) { + if ( row.activity ) { + const activityJson = JSON.parse(row.activity); + const compactedActivity = await jsonld.compact(activityJson, ["https://www.w3.org/ns/activitystreams"]); + promptStart(); + const {domain} = await promptGet(['domain']); + // ID to match API url format. + compactedActivity.id = `https://${domain}/api/activitypub/user/${row.name}`; + + // Ensure we have the up to date public key. + const secKeyRow = await db.select({publicKey: pubPrivKeys.publicKey, privateKey: pubPrivKeys.privateKey, id: pubPrivKeys.id}).from(pubPrivKeys).where(eq(pubPrivKeys.id, row.id)).orderBy(desc(pubPrivKeys.id)); + if ( secKeyRow[0].publicKey !== null && secKeyRow[0].privateKey !== null ) { + compactedActivity["https://w3id.org/security#publicKey"] = { + id: `https://${domain}/api/activitypub/user/${row.name}#main-key`, + owner: `https://${domain}/api/activitypub/user/${row.name}`, + "https://w3id.org/security#publicKeyPem": secKeyRow[0].publicKey + } + } else { + const keyPair = await generateKeyPair('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem' + } + }); + await db.insert(pubPrivKeys).values({ + user: row.id, + publicKey: keyPair.publicKey, + privateKey: keyPair.privateKey + }) + compactedActivity["https://w3id.org/security#publicKey"] = { + id: `https://${domain}/api/activitypub/user/${row.name}#main-key`, + owner: `https://${domain}/api/activitypub/user/${row.name}`, + "https://w3id.org/security#publicKeyPem": keyPair.publicKey + } + } + + await db.update(user).set({activity: JSON.stringify(compactedActivity, null, 4)}); + } + } +} + +main(); \ No newline at end of file diff --git a/types/jsonld.d.ts b/types/jsonld.d.ts new file mode 100644 index 0000000..096a1d0 --- /dev/null +++ b/types/jsonld.d.ts @@ -0,0 +1,5 @@ +import { ContextDefinition } from "jsonld"; + +declare module 'jsonld' { + export function compact(input: JsonLdDocument, ctx?: ContextDefinition|(ContextDefinition|string)[]|string, options?: Options.Compact): Promise; +} \ No newline at end of file