Merge remote-tracking branch 'upstream/main' into fix-missing-apps-verify_credentials-endpoint

pull/255/head
Jorge Caballero (DataDrivenMD) 2023-02-23 16:40:29 -08:00
commit 980a779bc6
94 zmienionych plików z 718 dodań i 317 usunięć

Wyświetl plik

@ -97,8 +97,12 @@ jobs:
- name: retrieve D1 database
uses: cloudflare/wrangler-action@2.0.0
with:
command: d1 list | grep "wildebeest-${{ env.NAME_SUFFIX }}\s" | awk '{print "d1_id="$2}' >> $GITHUB_ENV
command: d1 list --json | jq -r '.[] | select(.name == "wildebeest-${{ env.NAME_SUFFIX }}") | .uuid' | awk '{print "d1_id="$1}' >> $GITHUB_ENV
apiToken: ${{ secrets.CF_API_TOKEN }}
preCommands: |
echo "*** pre commands ***"
apt-get update && apt-get -y install jq
echo "******"
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}

3
SECURITY.md 100644
Wyświetl plik

@ -0,0 +1,3 @@
# Reporting Security Vulnerabilities
Please see [this page](https://www.cloudflare.com/.well-known/security.txt) for information on how to report a vulnerability to Cloudflare. Thanks!

Wyświetl plik

@ -1,5 +1,6 @@
// https://docs.joinmastodon.org/methods/accounts/#get
import { type Database } from 'wildebeest/backend/src/database'
import { actorURL, getActorById } from 'wildebeest/backend/src/activitypub/actors'
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import type { Handle } from 'wildebeest/backend/src/utils/parse'
@ -8,7 +9,7 @@ import { loadExternalMastodonAccount, loadLocalMastodonAccount } from 'wildebees
import { MastodonAccount } from '../types'
import { adjustLocalHostDomain } from '../utils/adjustLocalHostDomain'
export async function getAccount(domain: string, accountId: string, db: D1Database): Promise<MastodonAccount | null> {
export async function getAccount(domain: string, accountId: string, db: Database): Promise<MastodonAccount | null> {
const handle = parseHandle(accountId)
if (handle.domain === null || (handle.domain !== null && handle.domain === domain)) {
@ -17,17 +18,17 @@ export async function getAccount(domain: string, accountId: string, db: D1Databa
} else if (handle.domain !== null) {
// Retrieve the statuses of a remote actor
const acct = `${handle.localPart}@${handle.domain}`
return getRemoteAccount(handle, acct)
return getRemoteAccount(handle, acct, db)
} else {
return null
}
}
async function getRemoteAccount(handle: Handle, acct: string): Promise<MastodonAccount | null> {
async function getRemoteAccount(handle: Handle, acct: string, db: D1Database): Promise<MastodonAccount | null> {
// TODO: using webfinger isn't the optimal implementation. We could cache
// the object in D1 and directly query the remote API, indicated by the actor's
// url field. For now, let's keep it simple.
const actor = await queryAcct(handle.domain!, acct)
const actor = await queryAcct(handle.domain!, db, acct)
if (actor === null) {
return null
}
@ -35,7 +36,7 @@ async function getRemoteAccount(handle: Handle, acct: string): Promise<MastodonA
return await loadExternalMastodonAccount(acct, actor, true)
}
async function getLocalAccount(domain: string, db: D1Database, handle: Handle): Promise<MastodonAccount | null> {
async function getLocalAccount(domain: string, db: Database, handle: Handle): Promise<MastodonAccount | null> {
const actorId = actorURL(adjustLocalHostDomain(domain), handle.localPart)
const actor = await getActorById(db, actorId)

Wyświetl plik

@ -27,6 +27,7 @@ import type { Activity } from 'wildebeest/backend/src/activitypub/activities'
import { originalActorIdSymbol, deleteObject } from 'wildebeest/backend/src/activitypub/objects'
import { hasReblog } from 'wildebeest/backend/src/mastodon/reblog'
import { getMetadata, loadItems } from 'wildebeest/backend/src/activitypub/objects/collection'
import { type Database } from 'wildebeest/backend/src/database'
function extractID(domain: string, s: string | URL): string {
return s.toString().replace(`https://${domain}/ap/users/`, '')
@ -87,7 +88,7 @@ export function makeGetActorAsId(activity: Activity) {
export async function handle(
domain: string,
activity: Activity,
db: D1Database,
db: Database,
userKEK: string,
adminEmail: string,
vapidKeys: JWK
@ -115,7 +116,7 @@ export async function handle(
}
// check current object
const object = await objects.getObjectBy(db, 'original_object_id', objectId.toString())
const object = await objects.getObjectBy(db, objects.ObjectByKey.originalObjectId, objectId.toString())
if (object === null) {
throw new Error(`object ${objectId} does not exist`)
}
@ -423,7 +424,7 @@ export async function handle(
async function cacheObject(
domain: string,
obj: APObject,
db: D1Database,
db: Database,
originalActorId: URL,
originalObjectId: URL
): Promise<{ created: boolean; object: APObject } | null> {

Wyświetl plik

@ -2,6 +2,7 @@ import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
import type { OrderedCollection } from 'wildebeest/backend/src/activitypub/objects/collection'
import { getMetadata, loadItems } from 'wildebeest/backend/src/activitypub/objects/collection'
import { type Database } from 'wildebeest/backend/src/database'
export async function countFollowing(actor: Actor): Promise<number> {
const collection = await getMetadata(actor.following)
@ -25,7 +26,7 @@ export async function getFollowing(actor: Actor): Promise<OrderedCollection<stri
return collection
}
export async function loadActors(db: D1Database, collection: OrderedCollection<string>): Promise<Array<Actor>> {
export async function loadActors(db: Database, collection: OrderedCollection<string>): Promise<Array<Actor>> {
const promises = collection.items.map((item) => {
const actorId = new URL(item)
return actors.getAndCache(actorId, db)

Wyświetl plik

@ -1,7 +1,8 @@
import type { APObject } from 'wildebeest/backend/src/activitypub/objects'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import { type Database } from 'wildebeest/backend/src/database'
export async function addObjectInInbox(db: D1Database, actor: Actor, obj: APObject) {
export async function addObjectInInbox(db: Database, actor: Actor, obj: APObject) {
const id = crypto.randomUUID()
const out = await db
.prepare('INSERT INTO inbox_objects(id, actor_id, object_id) VALUES(?, ?, ?)')

Wyświetl plik

@ -2,6 +2,7 @@ import { defaultImages } from 'wildebeest/config/accounts'
import { generateUserKey } from 'wildebeest/backend/src/utils/key-ops'
import { type APObject, sanitizeContent, getTextContent } from '../objects'
import { addPeer } from 'wildebeest/backend/src/activitypub/peers'
import { type Database } from 'wildebeest/backend/src/database'
const PERSON = 'Person'
const isTesting = typeof jest !== 'undefined'
@ -43,39 +44,48 @@ export async function get(url: string | URL): Promise<Actor> {
const data = await res.json<any>()
const actor: Actor = { ...data }
actor.id = new URL(data.id)
actor.id = new URL(actor.id)
if (data.content) {
actor.content = await sanitizeContent(data.content)
if (actor.summary) {
actor.summary = await sanitizeContent(actor.summary)
if (actor.summary.length > 500) {
actor.summary = actor.summary.substring(0, 500)
}
}
if (data.name) {
actor.name = await getTextContent(data.name)
if (actor.name) {
actor.name = await getTextContent(actor.name)
if (actor.name.length > 30) {
actor.name = actor.name.substring(0, 30)
}
}
if (data.preferredUsername) {
actor.preferredUsername = await getTextContent(data.preferredUsername)
if (actor.preferredUsername) {
actor.preferredUsername = await getTextContent(actor.preferredUsername)
if (actor.preferredUsername.length > 30) {
actor.preferredUsername = actor.preferredUsername.substring(0, 30)
}
}
// This is mostly for testing where for convenience not all values
// are provided.
// TODO: eventually clean that to better match production.
if (data.inbox !== undefined) {
actor.inbox = new URL(data.inbox)
if (actor.inbox !== undefined) {
actor.inbox = new URL(actor.inbox)
}
if (data.following !== undefined) {
actor.following = new URL(data.following)
if (actor.following !== undefined) {
actor.following = new URL(actor.following)
}
if (data.followers !== undefined) {
actor.followers = new URL(data.followers)
if (actor.followers !== undefined) {
actor.followers = new URL(actor.followers)
}
if (data.outbox !== undefined) {
actor.outbox = new URL(data.outbox)
if (actor.outbox !== undefined) {
actor.outbox = new URL(actor.outbox)
}
return actor
}
// Get and cache the Actor locally
export async function getAndCache(url: URL, db: D1Database): Promise<Actor> {
export async function getAndCache(url: URL, db: Database): Promise<Actor> {
{
const actor = await getActorById(db, url)
if (actor !== null) {
@ -111,7 +121,7 @@ export async function getAndCache(url: URL, db: D1Database): Promise<Actor> {
return actor
}
export async function getPersonByEmail(db: D1Database, email: string): Promise<Person | null> {
export async function getPersonByEmail(db: Database, email: string): Promise<Person | null> {
const stmt = db.prepare('SELECT * FROM actors WHERE email=? AND type=?').bind(email, PERSON)
const { results } = await stmt.all()
if (!results || results.length === 0) {
@ -137,7 +147,7 @@ type PersonProperties = {
// Create a local user
export async function createPerson(
domain: string,
db: D1Database,
db: Database,
userKEK: string,
email: string,
properties: PersonProperties = {}
@ -199,7 +209,7 @@ export async function createPerson(
return personFromRow(row)
}
export async function updateActorProperty(db: D1Database, actorId: URL, key: string, value: string) {
export async function updateActorProperty(db: Database, actorId: URL, key: string, value: string) {
const { success, error } = await db
.prepare(`UPDATE actors SET properties=json_set(properties, '$.${key}', ?) WHERE id=?`)
.bind(value, actorId.toString())
@ -209,7 +219,7 @@ export async function updateActorProperty(db: D1Database, actorId: URL, key: str
}
}
export async function setActorAlias(db: D1Database, actorId: URL, alias: URL) {
export async function setActorAlias(db: Database, actorId: URL, alias: URL) {
const { success, error } = await db
.prepare(`UPDATE actors SET properties=json_set(properties, '$.alsoKnownAs', json_array(?)) WHERE id=?`)
.bind(alias.toString(), actorId.toString())
@ -219,7 +229,7 @@ export async function setActorAlias(db: D1Database, actorId: URL, alias: URL) {
}
}
export async function getActorById(db: D1Database, id: URL): Promise<Actor | null> {
export async function getActorById(db: Database, id: URL): Promise<Actor | null> {
const stmt = db.prepare('SELECT * FROM actors WHERE id=?').bind(id.toString())
const { results } = await stmt.all()
if (!results || results.length === 0) {

Wyświetl plik

@ -4,9 +4,10 @@ import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import type { OrderedCollection } from 'wildebeest/backend/src/activitypub/objects/collection'
import { getMetadata, loadItems } from 'wildebeest/backend/src/activitypub/objects/collection'
import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
import { type Database } from 'wildebeest/backend/src/database'
export async function addObjectInOutbox(
db: D1Database,
db: Database,
actor: Actor,
obj: APObject,
published_date?: string,

Wyświetl plik

@ -8,6 +8,7 @@ import { generateDigestHeader } from 'wildebeest/backend/src/utils/http-signing-
import { signRequest } from 'wildebeest/backend/src/utils/http-signing'
import { getFollowers } from 'wildebeest/backend/src/mastodon/follow'
import { getFederationUA } from 'wildebeest/config/ua'
import { type Database } from 'wildebeest/backend/src/database'
const MAX_BATCH_SIZE = 100
@ -46,7 +47,7 @@ export async function deliverToActor(
// to a collection (followers) and the worker creates the indivual messages. More
// reliable and scalable.
export async function deliverFollowers(
db: D1Database,
db: Database,
userKEK: string,
from: Actor,
activity: Activity,

Wyświetl plik

@ -1,5 +1,6 @@
import * as objects from '.'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import { type Database } from 'wildebeest/backend/src/database'
export const IMAGE = 'Image'
@ -8,7 +9,7 @@ export interface Image extends objects.Document {
description?: string
}
export async function createImage(domain: string, db: D1Database, actor: Actor, properties: any): Promise<Image> {
export async function createImage(domain: string, db: Database, actor: Actor, properties: any): Promise<Image> {
const actorId = new URL(actor.id)
return (await objects.createObject(domain, db, IMAGE, properties, actorId, true)) as Image
}

Wyświetl plik

@ -1,5 +1,6 @@
import type { UUID } from 'wildebeest/backend/src/types'
import { addPeer } from 'wildebeest/backend/src/activitypub/peers'
import { type Database } from 'wildebeest/backend/src/database'
export const originalActorIdSymbol = Symbol()
export const originalObjectIdSymbol = Symbol()
@ -39,7 +40,7 @@ export function uri(domain: string, id: string): URL {
export async function createObject<Type extends APObject>(
domain: string,
db: D1Database,
db: Database,
type: string,
properties: any,
originalActorId: URL,
@ -86,7 +87,7 @@ type CacheObjectRes = {
export async function cacheObject(
domain: string,
db: D1Database,
db: Database,
properties: unknown,
originalActorId: URL,
originalObjectId: URL,
@ -94,7 +95,7 @@ export async function cacheObject(
): Promise<CacheObjectRes> {
const sanitizedProperties = await sanitizeObjectProperties(properties)
const cachedObject = await getObjectBy(db, 'original_object_id', originalObjectId.toString())
const cachedObject = await getObjectBy(db, ObjectByKey.originalObjectId, originalObjectId.toString())
if (cachedObject !== null) {
return {
created: false,
@ -144,7 +145,7 @@ export async function cacheObject(
}
}
export async function updateObject(db: D1Database, properties: any, id: URL): Promise<boolean> {
export async function updateObject(db: Database, properties: any, id: URL): Promise<boolean> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const res: any = await db
.prepare('UPDATE objects SET properties = ? WHERE id = ?')
@ -156,7 +157,7 @@ export async function updateObject(db: D1Database, properties: any, id: URL): Pr
return true
}
export async function updateObjectProperty(db: D1Database, obj: APObject, key: string, value: string) {
export async function updateObjectProperty(db: Database, obj: APObject, key: string, value: string) {
const { success, error } = await db
.prepare(`UPDATE objects SET properties=json_set(properties, '$.${key}', ?) WHERE id=?`)
.bind(value, obj.id.toString())
@ -166,24 +167,35 @@ export async function updateObjectProperty(db: D1Database, obj: APObject, key: s
}
}
export async function getObjectById(db: D1Database, id: string | URL): Promise<APObject | null> {
return getObjectBy(db, 'id', id.toString())
export async function getObjectById(db: Database, id: string | URL): Promise<APObject | null> {
return getObjectBy(db, ObjectByKey.id, id.toString())
}
export async function getObjectByOriginalId(db: D1Database, id: string | URL): Promise<APObject | null> {
return getObjectBy(db, 'original_object_id', id.toString())
export async function getObjectByOriginalId(db: Database, id: string | URL): Promise<APObject | null> {
return getObjectBy(db, ObjectByKey.originalObjectId, id.toString())
}
export async function getObjectByMastodonId(db: D1Database, id: UUID): Promise<APObject | null> {
return getObjectBy(db, 'mastodon_id', id)
export async function getObjectByMastodonId(db: Database, id: UUID): Promise<APObject | null> {
return getObjectBy(db, ObjectByKey.mastodonId, id)
}
export async function getObjectBy(db: D1Database, key: string, value: string) {
export enum ObjectByKey {
id = 'id',
originalObjectId = 'original_object_id',
mastodonId = 'mastodon_id',
}
const allowedObjectByKeysSet = new Set(Object.values(ObjectByKey))
export async function getObjectBy(db: Database, key: ObjectByKey, value: string) {
if (!allowedObjectByKeysSet.has(key)) {
throw new Error('getObjectBy run with invalid key: ' + key)
}
const query = `
SELECT *
FROM objects
WHERE objects.${key}=?
`
SELECT *
FROM objects
WHERE objects.${key}=?
`
const { results, success, error } = await db.prepare(query).bind(value).all()
if (!success) {
throw new Error('SQL error: ' + error)
@ -289,7 +301,7 @@ function getTextContentRewriter() {
// TODO: eventually use SQLite's `ON DELETE CASCADE` but requires writing the DB
// schema directly into D1, which D1 disallows at the moment.
// Some context at: https://stackoverflow.com/questions/13150075/add-on-delete-cascade-behavior-to-an-sqlite3-table-after-it-has-been-created
export async function deleteObject<T extends APObject>(db: D1Database, note: T) {
export async function deleteObject<T extends APObject>(db: Database, note: T) {
const nodeId = note.id.toString()
const batch = [
db.prepare('DELETE FROM outbox_objects WHERE object_id=?').bind(nodeId),

Wyświetl plik

@ -4,6 +4,7 @@ import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import type { Link } from 'wildebeest/backend/src/activitypub/objects/link'
import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
import * as objects from '.'
import { type Database } from 'wildebeest/backend/src/database'
const NOTE = 'Note'
@ -23,7 +24,7 @@ export interface Note extends objects.APObject {
export async function createPublicNote(
domain: string,
db: D1Database,
db: Database,
content: string,
actor: Actor,
attachments: Array<objects.APObject> = [],
@ -51,12 +52,12 @@ export async function createPublicNote(
return (await objects.createObject(domain, db, NOTE, properties, actorId, true)) as Note
}
export async function createPrivateNote(
export async function createDirectNote(
domain: string,
db: D1Database,
db: Database,
content: string,
actor: Actor,
targetActor: Actor,
targetActors: Array<Actor>,
attachment: Array<objects.APObject> = [],
extraProperties: any = {}
): Promise<Note> {
@ -65,7 +66,7 @@ export async function createPrivateNote(
const properties = {
attributedTo: actorId,
content,
to: [targetActor.id.toString()],
to: targetActors.map((a) => a.id.toString()),
cc: [],
// FIXME: stub values

Wyświetl plik

@ -1,13 +1,14 @@
import { getResultsField } from 'wildebeest/backend/src/mastodon/utils'
import { type Database } from 'wildebeest/backend/src/database'
export async function getPeers(db: D1Database): Promise<Array<String>> {
export async function getPeers(db: Database): Promise<Array<String>> {
const query = `SELECT domain FROM peers `
const statement = db.prepare(query)
return getResultsField(statement, 'domain')
}
export async function addPeer(db: D1Database, domain: string): Promise<void> {
export async function addPeer(db: Database, domain: string): Promise<void> {
const query = `
INSERT OR IGNORE INTO peers (domain)
VALUES (?)

Wyświetl plik

@ -0,0 +1,6 @@
import { type Database } from 'wildebeest/backend/src/database'
import type { Env } from 'wildebeest/backend/src/types/env'
export default function make(env: Env): Database {
return env.DATABASE
}

Wyświetl plik

@ -0,0 +1,28 @@
import type { Env } from 'wildebeest/backend/src/types/env'
import d1 from './d1'
export interface Result<T = unknown> {
results?: T[]
success: boolean
error?: string
meta: any
}
export interface Database {
prepare(query: string): PreparedStatement
dump(): Promise<ArrayBuffer>
batch<T = unknown>(statements: PreparedStatement[]): Promise<Result<T>[]>
exec<T = unknown>(query: string): Promise<Result<T>>
}
export interface PreparedStatement {
bind(...values: any[]): PreparedStatement
first<T = unknown>(colName?: string): Promise<T>
run<T = unknown>(): Promise<Result<T>>
all<T = unknown>(): Promise<Result<T>>
raw<T = unknown>(): Promise<T[]>
}
export function getDatabase(env: Env): Database {
return d1(env)
}

Wyświetl plik

@ -7,7 +7,7 @@ type ErrorResponse = {
const headers = {
...cors(),
'content-type': 'application/json',
'content-type': 'application/json; charset=utf-8',
} as const
function generateErrorResponse(error: string, status: number, errorDescription?: string): Response {
@ -65,3 +65,7 @@ export function exceededLimit(detail: string): Response {
export function resourceNotFound(name: string, id: string): Response {
return generateErrorResponse('Resource not found', 404, `${name} "${id}" not found`)
}
export function validationError(detail: string): Response {
return generateErrorResponse('Validation failed', 422, detail)
}

Wyświetl plik

@ -4,6 +4,7 @@ import { Actor } from '../activitypub/actors'
import { defaultImages } from 'wildebeest/config/accounts'
import * as apOutbox from 'wildebeest/backend/src/activitypub/actors/outbox'
import * as apFollow from 'wildebeest/backend/src/activitypub/actors/follow'
import { type Database } from 'wildebeest/backend/src/database'
function toMastodonAccount(acct: string, res: Actor): MastodonAccount {
const avatar = res.icon?.url.toString() ?? defaultImages.avatar
@ -55,7 +56,7 @@ export async function loadExternalMastodonAccount(
}
// Load a local user and return it as a MastodonAccount
export async function loadLocalMastodonAccount(db: D1Database, res: Actor): Promise<MastodonAccount> {
export async function loadLocalMastodonAccount(db: Database, res: Actor): Promise<MastodonAccount> {
const query = `
SELECT
(SELECT count(*)
@ -85,7 +86,7 @@ SELECT
return account
}
export async function getSigningKey(instanceKey: string, db: D1Database, actor: Actor): Promise<CryptoKey> {
export async function getSigningKey(instanceKey: string, db: Database, actor: Actor): Promise<CryptoKey> {
const stmt = db.prepare('SELECT privkey, privkey_salt FROM actors WHERE id=?').bind(actor.id.toString())
const { privkey, privkey_salt } = (await stmt.first()) as any
return unwrapPrivateKey(instanceKey, new Uint8Array(privkey), new Uint8Array(privkey_salt))

Wyświetl plik

@ -1,4 +1,5 @@
import { arrayBufferToBase64 } from 'wildebeest/backend/src/utils/key-ops'
import { type Database } from 'wildebeest/backend/src/database'
export interface Client {
id: string
@ -10,7 +11,7 @@ export interface Client {
}
export async function createClient(
db: D1Database,
db: Database,
name: string,
redirect_uris: string,
website: string,
@ -42,7 +43,7 @@ export async function createClient(
}
}
export async function getClientById(db: D1Database, id: string): Promise<Client | null> {
export async function getClientById(db: Database, id: string): Promise<Client | null> {
const stmt = db.prepare('SELECT * FROM clients WHERE id=?').bind(id)
const { results } = await stmt.all()
if (!results || results.length === 0) {

Wyświetl plik

@ -2,12 +2,13 @@ import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
import { getResultsField } from './utils'
import { type Database } from 'wildebeest/backend/src/database'
const STATE_PENDING = 'pending'
const STATE_ACCEPTED = 'accepted'
// During a migration we move the followers from the old Actor to the new
export async function moveFollowers(db: D1Database, actor: Actor, followers: Array<string>): Promise<void> {
export async function moveFollowers(db: Database, actor: Actor, followers: Array<string>): Promise<void> {
const batch = []
const stmt = db.prepare(`
INSERT OR IGNORE
@ -29,7 +30,7 @@ export async function moveFollowers(db: D1Database, actor: Actor, followers: Arr
await db.batch(batch)
}
export async function moveFollowing(db: D1Database, actor: Actor, followingActors: Array<string>): Promise<void> {
export async function moveFollowing(db: Database, actor: Actor, followingActors: Array<string>): Promise<void> {
const batch = []
const stmt = db.prepare(`
INSERT OR IGNORE
@ -52,7 +53,7 @@ export async function moveFollowing(db: D1Database, actor: Actor, followingActor
}
// Add a pending following
export async function addFollowing(db: D1Database, actor: Actor, target: Actor, targetAcct: string): Promise<string> {
export async function addFollowing(db: Database, actor: Actor, target: Actor, targetAcct: string): Promise<string> {
const id = crypto.randomUUID()
const query = `
@ -71,7 +72,7 @@ export async function addFollowing(db: D1Database, actor: Actor, target: Actor,
}
// Accept the pending following request
export async function acceptFollowing(db: D1Database, actor: Actor, target: Actor) {
export async function acceptFollowing(db: Database, actor: Actor, target: Actor) {
const query = `
UPDATE actor_following SET state=? WHERE actor_id=? AND target_actor_id=? AND state=?
`
@ -85,7 +86,7 @@ export async function acceptFollowing(db: D1Database, actor: Actor, target: Acto
}
}
export async function removeFollowing(db: D1Database, actor: Actor, target: Actor) {
export async function removeFollowing(db: Database, actor: Actor, target: Actor) {
const query = `
DELETE FROM actor_following WHERE actor_id=? AND target_actor_id=?
`
@ -96,7 +97,7 @@ export async function removeFollowing(db: D1Database, actor: Actor, target: Acto
}
}
export function getFollowingAcct(db: D1Database, actor: Actor): Promise<Array<string>> {
export function getFollowingAcct(db: Database, actor: Actor): Promise<Array<string>> {
const query = `
SELECT target_actor_acct FROM actor_following WHERE actor_id=? AND state=?
`
@ -105,7 +106,7 @@ export function getFollowingAcct(db: D1Database, actor: Actor): Promise<Array<st
return getResultsField(statement, 'target_actor_acct')
}
export function getFollowingRequestedAcct(db: D1Database, actor: Actor): Promise<Array<string>> {
export function getFollowingRequestedAcct(db: Database, actor: Actor): Promise<Array<string>> {
const query = `
SELECT target_actor_acct FROM actor_following WHERE actor_id=? AND state=?
`
@ -115,7 +116,7 @@ export function getFollowingRequestedAcct(db: D1Database, actor: Actor): Promise
return getResultsField(statement, 'target_actor_acct')
}
export function getFollowingId(db: D1Database, actor: Actor): Promise<Array<string>> {
export function getFollowingId(db: Database, actor: Actor): Promise<Array<string>> {
const query = `
SELECT target_actor_id FROM actor_following WHERE actor_id=? AND state=?
`
@ -125,7 +126,7 @@ export function getFollowingId(db: D1Database, actor: Actor): Promise<Array<stri
return getResultsField(statement, 'target_actor_id')
}
export function getFollowers(db: D1Database, actor: Actor): Promise<Array<string>> {
export function getFollowers(db: Database, actor: Actor): Promise<Array<string>> {
const query = `
SELECT actor_id FROM actor_following WHERE target_actor_id=? AND state=?
`

Wyświetl plik

@ -1,5 +1,6 @@
import type { Note } from 'wildebeest/backend/src/activitypub/objects/note'
import type { Tag } from 'wildebeest/backend/src/types/tag'
import { type Database } from 'wildebeest/backend/src/database'
export type Hashtag = string
@ -14,7 +15,7 @@ export function getHashtags(input: string): Array<Hashtag> {
return [...matches].map((match) => match[1])
}
export async function insertHashtags(db: D1Database, note: Note, values: Array<Hashtag>): Promise<void> {
export async function insertHashtags(db: Database, note: Note, values: Array<Hashtag>): Promise<void> {
const queries = []
const stmt = db.prepare(`
INSERT INTO note_hashtags (value, object_id)
@ -29,7 +30,7 @@ export async function insertHashtags(db: D1Database, note: Note, values: Array<H
await db.batch(queries)
}
export async function getTag(db: D1Database, domain: string, tag: string): Promise<Tag | null> {
export async function getTag(db: Database, domain: string, tag: string): Promise<Tag | null> {
const query = `
SELECT * FROM note_hashtags WHERE value=?
`

Wyświetl plik

@ -1,11 +1,12 @@
import type { APObject } from 'wildebeest/backend/src/activitypub/objects'
import { type Database } from 'wildebeest/backend/src/database'
import {
mastodonIdSymbol,
originalActorIdSymbol,
originalObjectIdSymbol,
} from 'wildebeest/backend/src/activitypub/objects'
export async function insertKey(db: D1Database, key: string, obj: APObject): Promise<void> {
export async function insertKey(db: Database, key: string, obj: APObject): Promise<void> {
const query = `
INSERT INTO idempotency_keys (key, object_id, expires_at)
VALUES (?1, ?2, datetime('now', '+1 hour'))
@ -17,7 +18,7 @@ export async function insertKey(db: D1Database, key: string, obj: APObject): Pro
}
}
export async function hasKey(db: D1Database, key: string): Promise<APObject | null> {
export async function hasKey(db: Database, key: string): Promise<APObject | null> {
const query = `
SELECT objects.*
FROM idempotency_keys

Wyświetl plik

@ -1,8 +1,9 @@
import type { APObject } from 'wildebeest/backend/src/activitypub/objects'
import { type Database } from 'wildebeest/backend/src/database'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import { getResultsField } from './utils'
export async function insertLike(db: D1Database, actor: Actor, obj: APObject) {
export async function insertLike(db: Database, actor: Actor, obj: APObject) {
const id = crypto.randomUUID()
const query = `
@ -16,7 +17,7 @@ export async function insertLike(db: D1Database, actor: Actor, obj: APObject) {
}
}
export function getLikes(db: D1Database, obj: APObject): Promise<Array<string>> {
export function getLikes(db: Database, obj: APObject): Promise<Array<string>> {
const query = `
SELECT actor_id FROM actor_favourites WHERE object_id=?
`

Wyświetl plik

@ -1,4 +1,5 @@
import type { APObject } from 'wildebeest/backend/src/activitypub/objects'
import { type Database } from 'wildebeest/backend/src/database'
import { defaultImages } from 'wildebeest/config/accounts'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
@ -18,7 +19,7 @@ import { getSubscriptionForAllClients } from 'wildebeest/backend/src/mastodon/su
import type { Cache } from 'wildebeest/backend/src/cache'
export async function createNotification(
db: D1Database,
db: Database,
type: NotificationType,
actor: Actor,
fromActor: Actor,
@ -36,7 +37,7 @@ export async function createNotification(
return row.id
}
export async function insertFollowNotification(db: D1Database, actor: Actor, fromActor: Actor): Promise<string> {
export async function insertFollowNotification(db: Database, actor: Actor, fromActor: Actor): Promise<string> {
const type: NotificationType = 'follow'
const query = `
@ -49,7 +50,7 @@ export async function insertFollowNotification(db: D1Database, actor: Actor, fro
}
export async function sendFollowNotification(
db: D1Database,
db: Database,
follower: Actor,
actor: Actor,
notificationId: string,
@ -81,7 +82,7 @@ export async function sendFollowNotification(
}
export async function sendLikeNotification(
db: D1Database,
db: Database,
fromActor: Actor,
actor: Actor,
notificationId: string,
@ -113,7 +114,7 @@ export async function sendLikeNotification(
}
export async function sendMentionNotification(
db: D1Database,
db: Database,
fromActor: Actor,
actor: Actor,
notificationId: string,
@ -145,7 +146,7 @@ export async function sendMentionNotification(
}
export async function sendReblogNotification(
db: D1Database,
db: Database,
fromActor: Actor,
actor: Actor,
notificationId: string,
@ -176,7 +177,7 @@ export async function sendReblogNotification(
return sendNotification(db, actor, message, vapidKeys)
}
async function sendNotification(db: D1Database, actor: Actor, message: WebPushMessage, vapidKeys: JWK) {
async function sendNotification(db: Database, actor: Actor, message: WebPushMessage, vapidKeys: JWK) {
const subscriptions = await getSubscriptionForAllClients(db, actor)
const promises = subscriptions.map(async (subscription) => {
@ -195,7 +196,7 @@ async function sendNotification(db: D1Database, actor: Actor, message: WebPushMe
await Promise.allSettled(promises)
}
export async function getNotifications(db: D1Database, actor: Actor, domain: string): Promise<Array<Notification>> {
export async function getNotifications(db: Database, actor: Actor, domain: string): Promise<Array<Notification>> {
const query = `
SELECT
objects.*,
@ -278,7 +279,7 @@ export async function getNotifications(db: D1Database, actor: Actor, domain: str
return out
}
export async function pregenerateNotifications(db: D1Database, cache: Cache, actor: Actor, domain: string) {
export async function pregenerateNotifications(db: Database, cache: Cache, actor: Actor, domain: string) {
const notifications = await getNotifications(db, actor, domain)
await cache.put(actor.id + '/notifications', notifications)
}

Wyświetl plik

@ -1,6 +1,7 @@
// Also known as boost.
import type { APObject } from 'wildebeest/backend/src/activitypub/objects'
import { type Database } from 'wildebeest/backend/src/database'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import { getResultsField } from './utils'
import { addObjectInOutbox } from '../activitypub/actors/outbox'
@ -8,15 +9,15 @@ import { addObjectInOutbox } from '../activitypub/actors/outbox'
/**
* Creates a reblog and inserts it in the reblog author's outbox
*
* @param db D1Database
* @param db Database
* @param actor Reblogger
* @param obj ActivityPub object to reblog
*/
export async function createReblog(db: D1Database, actor: Actor, obj: APObject) {
export async function createReblog(db: Database, actor: Actor, obj: APObject) {
await Promise.all([addObjectInOutbox(db, actor, obj), insertReblog(db, actor, obj)])
}
export async function insertReblog(db: D1Database, actor: Actor, obj: APObject) {
export async function insertReblog(db: Database, actor: Actor, obj: APObject) {
const id = crypto.randomUUID()
const query = `
@ -30,7 +31,7 @@ export async function insertReblog(db: D1Database, actor: Actor, obj: APObject)
}
}
export function getReblogs(db: D1Database, obj: APObject): Promise<Array<string>> {
export function getReblogs(db: Database, obj: APObject): Promise<Array<string>> {
const query = `
SELECT actor_id FROM actor_reblogs WHERE object_id=?
`
@ -40,7 +41,7 @@ export function getReblogs(db: D1Database, obj: APObject): Promise<Array<string>
return getResultsField(statement, 'actor_id')
}
export async function hasReblog(db: D1Database, actor: Actor, obj: APObject): Promise<boolean> {
export async function hasReblog(db: Database, actor: Actor, obj: APObject): Promise<boolean> {
const query = `
SELECT count(*) as count FROM actor_reblogs WHERE object_id=?1 AND actor_id=?2
`

Wyświetl plik

@ -1,9 +1,10 @@
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import { type Database } from 'wildebeest/backend/src/database'
import { toMastodonStatusFromRow } from './status'
import type { APObject } from 'wildebeest/backend/src/activitypub/objects'
import type { MastodonStatus } from 'wildebeest/backend/src/types/status'
export async function insertReply(db: D1Database, actor: Actor, obj: APObject, inReplyToObj: APObject) {
export async function insertReply(db: Database, actor: Actor, obj: APObject, inReplyToObj: APObject) {
const id = crypto.randomUUID()
const query = `
INSERT INTO actor_replies (id, actor_id, object_id, in_reply_to_object_id)
@ -18,7 +19,7 @@ export async function insertReply(db: D1Database, actor: Actor, obj: APObject, i
}
}
export async function getReplies(domain: string, db: D1Database, obj: APObject): Promise<Array<MastodonStatus>> {
export async function getReplies(domain: string, db: Database, obj: APObject): Promise<Array<MastodonStatus>> {
const QUERY = `
SELECT objects.*,
actors.id as actor_id,

Wyświetl plik

@ -17,8 +17,9 @@ import type { Person } from 'wildebeest/backend/src/activitypub/actors'
import { addObjectInOutbox } from '../activitypub/actors/outbox'
import type { APObject } from 'wildebeest/backend/src/activitypub/objects'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import { type Database } from 'wildebeest/backend/src/database'
export async function getMentions(input: string, instanceDomain: string): Promise<Array<Actor>> {
export async function getMentions(input: string, instanceDomain: string, db: Database): Promise<Array<Actor>> {
const mentions: Array<Actor> = []
for (let i = 0, len = input.length; i < len; i++) {
@ -33,7 +34,7 @@ export async function getMentions(input: string, instanceDomain: string): Promis
const handle = parseHandle(buffer)
const domain = handle.domain ? handle.domain : instanceDomain
const acct = `${handle.localPart}@${domain}`
const targetActor = await queryAcct(domain!, acct)
const targetActor = await queryAcct(domain!, db, acct)
if (targetActor === null) {
console.warn(`actor ${acct} not found`)
continue
@ -46,7 +47,7 @@ export async function getMentions(input: string, instanceDomain: string): Promis
}
export async function toMastodonStatusFromObject(
db: D1Database,
db: Database,
obj: Note,
domain: string
): Promise<MastodonStatus | null> {
@ -99,11 +100,7 @@ export async function toMastodonStatusFromObject(
// toMastodonStatusFromRow makes assumption about what field are available on
// the `row` object. This function is only used for timelines, which is optimized
// SQL. Otherwise don't use this function.
export async function toMastodonStatusFromRow(
domain: string,
db: D1Database,
row: any
): Promise<MastodonStatus | null> {
export async function toMastodonStatusFromRow(domain: string, db: Database, row: any): Promise<MastodonStatus | null> {
if (row.publisher_actor_id === undefined) {
console.warn('missing `row.publisher_actor_id`')
return null
@ -180,7 +177,7 @@ export async function toMastodonStatusFromRow(
return status
}
export async function getMastodonStatusById(db: D1Database, id: UUID, domain: string): Promise<MastodonStatus | null> {
export async function getMastodonStatusById(db: Database, id: UUID, domain: string): Promise<MastodonStatus | null> {
const obj = await getObjectByMastodonId(db, id)
if (obj === null) {
return null
@ -192,7 +189,7 @@ export async function getMastodonStatusById(db: D1Database, id: UUID, domain: st
* Creates a status object in the given actor's outbox.
*
* @param domain the domain to use
* @param db D1Database
* @param db Database
* @param actor Author of the reply
* @param content content of the reply
* @param attachments optional attachments for the status
@ -201,7 +198,7 @@ export async function getMastodonStatusById(db: D1Database, id: UUID, domain: st
*/
export async function createStatus(
domain: string,
db: D1Database,
db: Database,
actor: Person,
content: string,
attachments?: APObject[],

Wyświetl plik

@ -2,6 +2,7 @@ import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import { b64ToUrlEncoded, exportPublicKeyPair } from 'wildebeest/backend/src/webpush/util'
import { Client } from './client'
import { type Database } from 'wildebeest/backend/src/database'
export type PushSubscription = {
endpoint: string
@ -51,7 +52,7 @@ export type Subscription = {
}
export async function createSubscription(
db: D1Database,
db: Database,
actor: Actor,
client: Client,
req: CreateRequest
@ -85,7 +86,7 @@ export async function createSubscription(
return subscriptionFromRow(row)
}
export async function getSubscription(db: D1Database, actor: Actor, client: Client): Promise<Subscription | null> {
export async function getSubscription(db: Database, actor: Actor, client: Client): Promise<Subscription | null> {
const query = `
SELECT * FROM subscriptions WHERE actor_id=? AND client_id=?
`
@ -103,7 +104,7 @@ export async function getSubscription(db: D1Database, actor: Actor, client: Clie
return subscriptionFromRow(row)
}
export async function getSubscriptionForAllClients(db: D1Database, actor: Actor): Promise<Array<Subscription>> {
export async function getSubscriptionForAllClients(db: Database, actor: Actor): Promise<Array<Subscription>> {
const query = `
SELECT * FROM subscriptions WHERE actor_id=? ORDER BY cdate DESC LIMIT 5
`

Wyświetl plik

@ -3,13 +3,14 @@ import type { Actor } from 'wildebeest/backend/src/activitypub/actors/'
import { toMastodonStatusFromRow } from './status'
import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
import type { Cache } from 'wildebeest/backend/src/cache'
import { type Database } from 'wildebeest/backend/src/database'
export async function pregenerateTimelines(domain: string, db: D1Database, cache: Cache, actor: Actor) {
export async function pregenerateTimelines(domain: string, db: Database, cache: Cache, actor: Actor) {
const timeline = await getHomeTimeline(domain, db, actor)
await cache.put(actor.id + '/timeline/home', timeline)
}
export async function getHomeTimeline(domain: string, db: D1Database, actor: Actor): Promise<Array<MastodonStatus>> {
export async function getHomeTimeline(domain: string, db: Database, actor: Actor): Promise<Array<MastodonStatus>> {
const { results: following } = await db
.prepare(
`
@ -110,7 +111,7 @@ function localPreferenceQuery(preference: LocalPreference): string {
export async function getPublicTimeline(
domain: string,
db: D1Database,
db: Database,
localPreference: LocalPreference,
offset: number = 0,
hashtag?: string

Wyświetl plik

@ -3,8 +3,9 @@ import * as actors from 'wildebeest/backend/src/activitypub/actors'
import type { Env } from 'wildebeest/backend/src/types/env'
import * as errors from 'wildebeest/backend/src/errors'
import { cors } from 'wildebeest/backend/src/utils/cors'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
async function loadContextData(db: D1Database, clientId: string, email: string, ctx: any): Promise<boolean> {
async function loadContextData(db: Database, clientId: string, email: string, ctx: any): Promise<boolean> {
const query = `
SELECT *
FROM actors
@ -96,7 +97,7 @@ export async function main(context: EventContext<Env, any, any>) {
// configuration, which are used to verify the JWT.
// TODO: since we don't load the instance configuration anymore, we
// don't need to load the user before anymore.
if (!(await loadContextData(context.env.DATABASE, clientId, payload.email, context))) {
if (!(await loadContextData(getDatabase(context.env), clientId, payload.email, context))) {
return errors.notAuthorized('failed to load context data')
}

Wyświetl plik

@ -1,7 +1,8 @@
import type { Queue, MessageBody } from 'wildebeest/backend/src/types/queue'
import { type Database } from 'wildebeest/backend/src/database'
export interface Env {
DATABASE: D1Database
DATABASE: Database
// FIXME: shouldn't it be USER_KEY?
userKEK: string
QUEUE: Queue<MessageBody>

Wyświetl plik

@ -11,12 +11,12 @@ const headers = {
accept: 'application/jrd+json',
}
export async function queryAcct(domain: string, acct: string): Promise<Actor | null> {
export async function queryAcct(domain: string, db: D1Database, acct: string): Promise<Actor | null> {
const url = await queryAcctLink(domain, acct)
if (url === null) {
return null
}
return actors.get(url)
return actors.getAndCache(url, db)
}
export async function queryAcctLink(domain: string, acct: string): Promise<URL | null> {

Wyświetl plik

@ -3,7 +3,7 @@ import { MessageType } from 'wildebeest/backend/src/types/queue'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
import { createPrivateNote, createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
import { createDirectNote, createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
import { strict as assert } from 'node:assert/strict'
import { cacheObject } from 'wildebeest/backend/src/activitypub/objects/'
@ -22,43 +22,89 @@ const vapidKeys = {} as JWK
const domain = 'cloudflare.com'
describe('ActivityPub', () => {
test('fetch non-existant user by id', async () => {
const db = await makeDB()
describe('Actors', () => {
test('fetch non-existant user by id', async () => {
const db = await makeDB()
const res = await ap_users.handleRequest(domain, db, 'nonexisting')
assert.equal(res.status, 404)
})
const res = await ap_users.handleRequest(domain, db, 'nonexisting')
assert.equal(res.status, 404)
})
test('fetch user by id', async () => {
const db = await makeDB()
const properties = {
summary: 'test summary',
inbox: 'https://example.com/inbox',
outbox: 'https://example.com/outbox',
following: 'https://example.com/following',
followers: 'https://example.com/followers',
}
const pubKey =
'-----BEGIN PUBLIC KEY-----MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApnI8FHJQXqqAdM87YwVseRUqbNLiw8nQ0zHBUyLylzaORhI4LfW4ozguiw8cWYgMbCufXMoITVmdyeTMGbQ3Q1sfQEcEjOZZXEeCCocmnYjK6MFSspjFyNw6GP0a5A/tt1tAcSlgALv8sg1RqMhSE5Kv+6lSblAYXcIzff7T2jh9EASnimaoAAJMaRH37+HqSNrouCxEArcOFhmFETadXsv+bHZMozEFmwYSTugadr4WD3tZd+ONNeimX7XZ3+QinMzFGOW19ioVHyjt3yCDU1cPvZIDR17dyEjByNvx/4N4Zly7puwBn6Ixy/GkIh5BWtL5VOFDJm/S+zcf1G1WsOAXMwKL4Nc5UWKfTB7Wd6voId7vF7nI1QYcOnoyh0GqXWhTPMQrzie4nVnUrBedxW0s/0vRXeR63vTnh5JrTVu06JGiU2pq2kvwqoui5VU6rtdImITybJ8xRkAQ2jo4FbbkS6t49PORIuivxjS9wPl7vWYazZtDVa5g/5eL7PnxOG3HsdIJWbGEh1CsG83TU9burHIepxXuQ+JqaSiKdCVc8CUiO++acUqKp7lmbYR9E/wRmvxXDFkxCZzA0UL2mRoLLLOe4aHvRSTsqiHC5Wwxyew5bb+eseJz3wovid9ZSt/tfeMAkCDmaCxEK+LGEbJ9Ik8ihis8Esm21N0A54sCAwEAAQ==-----END PUBLIC KEY-----'
await db
.prepare('INSERT INTO actors (id, email, type, properties, pubkey) VALUES (?, ?, ?, ?, ?)')
.bind(`https://${domain}/ap/users/sven`, 'sven@cloudflare.com', 'Person', JSON.stringify(properties), pubKey)
.run()
test('fetch user by id', async () => {
const db = await makeDB()
const properties = {
summary: 'test summary',
inbox: 'https://example.com/inbox',
outbox: 'https://example.com/outbox',
following: 'https://example.com/following',
followers: 'https://example.com/followers',
}
const pubKey =
'-----BEGIN PUBLIC KEY-----MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApnI8FHJQXqqAdM87YwVseRUqbNLiw8nQ0zHBUyLylzaORhI4LfW4ozguiw8cWYgMbCufXMoITVmdyeTMGbQ3Q1sfQEcEjOZZXEeCCocmnYjK6MFSspjFyNw6GP0a5A/tt1tAcSlgALv8sg1RqMhSE5Kv+6lSblAYXcIzff7T2jh9EASnimaoAAJMaRH37+HqSNrouCxEArcOFhmFETadXsv+bHZMozEFmwYSTugadr4WD3tZd+ONNeimX7XZ3+QinMzFGOW19ioVHyjt3yCDU1cPvZIDR17dyEjByNvx/4N4Zly7puwBn6Ixy/GkIh5BWtL5VOFDJm/S+zcf1G1WsOAXMwKL4Nc5UWKfTB7Wd6voId7vF7nI1QYcOnoyh0GqXWhTPMQrzie4nVnUrBedxW0s/0vRXeR63vTnh5JrTVu06JGiU2pq2kvwqoui5VU6rtdImITybJ8xRkAQ2jo4FbbkS6t49PORIuivxjS9wPl7vWYazZtDVa5g/5eL7PnxOG3HsdIJWbGEh1CsG83TU9burHIepxXuQ+JqaSiKdCVc8CUiO++acUqKp7lmbYR9E/wRmvxXDFkxCZzA0UL2mRoLLLOe4aHvRSTsqiHC5Wwxyew5bb+eseJz3wovid9ZSt/tfeMAkCDmaCxEK+LGEbJ9Ik8ihis8Esm21N0A54sCAwEAAQ==-----END PUBLIC KEY-----'
await db
.prepare('INSERT INTO actors (id, email, type, properties, pubkey) VALUES (?, ?, ?, ?, ?)')
.bind(`https://${domain}/ap/users/sven`, 'sven@cloudflare.com', 'Person', JSON.stringify(properties), pubKey)
.run()
const res = await ap_users.handleRequest(domain, db, 'sven')
assert.equal(res.status, 200)
const res = await ap_users.handleRequest(domain, db, 'sven')
assert.equal(res.status, 200)
const data = await res.json<any>()
assert.equal(data.summary, 'test summary')
assert(data.discoverable)
assert(data['@context'])
assert(isUrlValid(data.id))
assert(isUrlValid(data.url))
assert(isUrlValid(data.inbox))
assert(isUrlValid(data.outbox))
assert(isUrlValid(data.following))
assert(isUrlValid(data.followers))
assert.equal(data.publicKey.publicKeyPem, pubKey)
const data = await res.json<any>()
assert.equal(data.summary, 'test summary')
assert(data.discoverable)
assert(data['@context'])
assert(isUrlValid(data.id))
assert(isUrlValid(data.url))
assert(isUrlValid(data.inbox))
assert(isUrlValid(data.outbox))
assert(isUrlValid(data.following))
assert(isUrlValid(data.followers))
assert.equal(data.publicKey.publicKeyPem, pubKey)
})
test('sanitize Actor properties', async () => {
globalThis.fetch = async (input: RequestInfo) => {
if (input === 'https://example.com/actor') {
return new Response(
JSON.stringify({
id: 'https://example.com/actor',
type: 'Person',
summary: "it's me, Mario. <script>alert(1)</script>",
name: 'hi<br />hey',
preferredUsername: 'sven <script>alert(1)</script>',
})
)
}
throw new Error(`unexpected request to "${input}"`)
}
const actor = await actors.get('https://example.com/actor')
assert.equal(actor.summary, "it's me, Mario. <p>alert(1)</p>")
assert.equal(actor.name, 'hi hey')
assert.equal(actor.preferredUsername, 'sven alert(1)')
})
test('Actor properties limits', async () => {
globalThis.fetch = async (input: RequestInfo) => {
if (input === 'https://example.com/actor') {
return new Response(
JSON.stringify({
id: 'https://example.com/actor',
type: 'Person',
summary: 'a'.repeat(612),
name: 'b'.repeat(50),
preferredUsername: 'c'.repeat(50),
})
)
}
throw new Error(`unexpected request to "${input}"`)
}
const actor = await actors.get('https://example.com/actor')
assert.equal(actor.summary, 'a'.repeat(500))
assert.equal(actor.name, 'b'.repeat(30))
assert.equal(actor.preferredUsername, 'c'.repeat(30))
})
})
describe('Outbox', () => {
@ -100,7 +146,7 @@ describe('ActivityPub', () => {
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
const note = await createPrivateNote(domain, db, 'DM', actorA, actorB)
const note = await createDirectNote(domain, db, 'DM', actorA, [actorB])
await addObjectInOutbox(db, actorA, note, undefined, actorB.id.toString())
{
@ -125,7 +171,7 @@ describe('ActivityPub', () => {
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'target@cloudflare.com')
const note = await createPrivateNote(domain, db, 'DM', actorA, actorB)
const note = await createDirectNote(domain, db, 'DM', actorA, [actorB])
await addObjectInOutbox(db, actorA, note)
const res = await ap_outbox_page.handleRequest(domain, db, 'target')

Wyświetl plik

@ -976,24 +976,24 @@ describe('Mastodon APIs', () => {
{
rel: 'self',
type: 'application/activity+json',
href: 'https://social.com/sven',
href: `https://${domain}/ap/users/actor`,
},
],
})
)
}
if (request.url === 'https://social.com/sven') {
if (request.url === `https://${domain}/ap/users/actor`) {
return new Response(
JSON.stringify({
id: `https://${domain}/ap/users/actor`,
type: 'Person',
inbox: 'https://example.com/inbox',
inbox: `https://${domain}/ap/users/actor/inbox`,
})
)
}
if (request.url === 'https://example.com/inbox') {
if (request.url === `https://${domain}/ap/users/actor/inbox`) {
assert.equal(request.method, 'POST')
receivedActivity = await request.json()
return new Response('')
@ -1040,7 +1040,7 @@ describe('Mastodon APIs', () => {
const connectedActor = actor
const req = new Request('https://example.com', { method: 'POST' })
const req = new Request('https://' + domain, { method: 'POST' })
const res = await accounts_unfollow.handleRequest(req, db, 'actor@' + domain, connectedActor, userKEK)
assert.equal(res.status, 200)
assertCORS(res)

Wyświetl plik

@ -18,6 +18,7 @@ import { MessageType } from 'wildebeest/backend/src/types/queue'
import { MastodonStatus } from 'wildebeest/backend/src/types'
import { mastodonIdSymbol, getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
import * as timelines from 'wildebeest/backend/src/mastodon/timeline'
const userKEK = 'test_kek4'
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
@ -202,6 +203,7 @@ describe('Mastodon APIs', () => {
return new Response(
JSON.stringify({
id: 'https://social.com/users/sven',
type: 'Person',
inbox: 'https://social.com/sven/inbox',
})
)
@ -404,6 +406,7 @@ describe('Mastodon APIs', () => {
})
test('get mentions from status', async () => {
const db = await makeDB()
globalThis.fetch = async (input: RequestInfo) => {
if (input.toString() === 'https://instance.horse/.well-known/webfinger?resource=acct%3Asven%40instance.horse') {
return new Response(
@ -467,6 +470,7 @@ describe('Mastodon APIs', () => {
return new Response(
JSON.stringify({
id: 'https://instance.horse/users/sven',
type: 'Person',
})
)
}
@ -474,6 +478,7 @@ describe('Mastodon APIs', () => {
return new Response(
JSON.stringify({
id: 'https://cloudflare.com/users/sven',
type: 'Person',
})
)
}
@ -481,6 +486,7 @@ describe('Mastodon APIs', () => {
return new Response(
JSON.stringify({
id: 'https://cloudflare.com/users/a',
type: 'Person',
})
)
}
@ -488,6 +494,7 @@ describe('Mastodon APIs', () => {
return new Response(
JSON.stringify({
id: 'https://cloudflare.com/users/b',
type: 'Person',
})
)
}
@ -496,42 +503,42 @@ describe('Mastodon APIs', () => {
}
{
const mentions = await getMentions('test status', domain)
const mentions = await getMentions('test status', domain, db)
assert.equal(mentions.length, 0)
}
{
const mentions = await getMentions('no-json@actor.com', domain)
const mentions = await getMentions('no-json@actor.com', domain, db)
assert.equal(mentions.length, 0)
}
{
const mentions = await getMentions('@sven@instance.horse test status', domain)
const mentions = await getMentions('@sven@instance.horse test status', domain, db)
assert.equal(mentions.length, 1)
assert.equal(mentions[0].id.toString(), 'https://instance.horse/users/sven')
}
{
const mentions = await getMentions('@sven test status', domain)
const mentions = await getMentions('@sven test status', domain, db)
assert.equal(mentions.length, 1)
assert.equal(mentions[0].id.toString(), 'https://' + domain + '/users/sven')
}
{
const mentions = await getMentions('@a @b', domain)
const mentions = await getMentions('@a @b', domain, db)
assert.equal(mentions.length, 2)
assert.equal(mentions[0].id.toString(), 'https://' + domain + '/users/a')
assert.equal(mentions[1].id.toString(), 'https://' + domain + '/users/b')
}
{
const mentions = await getMentions('<p>@sven</p>', domain)
const mentions = await getMentions('<p>@sven</p>', domain, db)
assert.equal(mentions.length, 1)
assert.equal(mentions[0].id.toString(), 'https://' + domain + '/users/sven')
}
{
const mentions = await getMentions('<p>@unknown</p>', domain)
const mentions = await getMentions('<p>@unknown</p>', domain, db)
assert.equal(mentions.length, 0)
}
})
@ -1010,5 +1017,150 @@ describe('Mastodon APIs', () => {
assert.equal(results![0].object_id, note.id.toString())
assert.equal(results![1].object_id, note.id.toString())
})
test('reject statuses exceeding limits', async () => {
const db = await makeDB()
const queue = makeQueue()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const body = {
status: 'a'.repeat(501),
visibility: 'public',
}
const req = new Request('https://example.com', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
})
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
assert.equal(res.status, 422)
assertJSON(res)
})
test('create status with direct visibility', async () => {
const db = await makeDB()
const queue = makeQueue()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const actor1 = await createPerson(domain, db, userKEK, 'actor1@cloudflare.com')
const actor2 = await createPerson(domain, db, userKEK, 'actor2@cloudflare.com')
let deliveredActivity1: any = null
let deliveredActivity2: any = null
globalThis.fetch = async (input: RequestInfo | Request) => {
if (
input.toString() === 'https://cloudflare.com/.well-known/webfinger?resource=acct%3Aactor1%40cloudflare.com'
) {
return new Response(
JSON.stringify({
links: [
{
rel: 'self',
type: 'application/activity+json',
href: actor1.id,
},
],
})
)
}
if (
input.toString() === 'https://cloudflare.com/.well-known/webfinger?resource=acct%3Aactor2%40cloudflare.com'
) {
return new Response(
JSON.stringify({
links: [
{
rel: 'self',
type: 'application/activity+json',
href: actor2.id,
},
],
})
)
}
// @ts-ignore
if (input.url === actor1.inbox.toString()) {
deliveredActivity1 = await (input as Request).json()
return new Response()
}
// @ts-ignore
if (input.url === actor2.inbox.toString()) {
deliveredActivity2 = await (input as Request).json()
return new Response()
}
throw new Error('unexpected request to ' + input)
}
const body = {
status: '@actor1 @actor2 hey',
visibility: 'direct',
}
const req = new Request('https://' + domain, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
})
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
assert.equal(res.status, 200)
assert(deliveredActivity1)
assert(deliveredActivity2)
delete deliveredActivity1.id
delete deliveredActivity2.id
assert.deepEqual(deliveredActivity1, deliveredActivity2)
assert.equal(deliveredActivity1.to.length, 2)
assert.equal(deliveredActivity1.to[0], actor1.id.toString())
assert.equal(deliveredActivity1.to[1], actor2.id.toString())
assert.equal(deliveredActivity1.cc.length, 0)
// ensure that the private note doesn't show up in public timeline
const timeline = await timelines.getPublicTimeline(domain, db, timelines.LocalPreference.NotSet)
assert.equal(timeline.length, 0)
})
test('create status with unlisted visibility', async () => {
const db = await makeDB()
const queue = makeQueue()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const body = {
status: 'something nice',
visibility: 'unlisted',
}
const req = new Request('https://' + domain, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
})
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
assert.equal(res.status, 422)
assertJSON(res)
})
test('create status with private visibility', async () => {
const db = await makeDB()
const queue = makeQueue()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const body = {
status: 'something nice',
visibility: 'private',
}
const req = new Request('https://' + domain, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
})
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
assert.equal(res.status, 422)
assertJSON(res)
})
})
})

Wyświetl plik

@ -2,7 +2,7 @@ import { strict as assert } from 'node:assert/strict'
import { createReply } from 'wildebeest/backend/test/shared.utils'
import { createImage } from 'wildebeest/backend/src/activitypub/objects/image'
import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow'
import { createPublicNote, createPrivateNote } from 'wildebeest/backend/src/activitypub/objects/note'
import { createPublicNote, createDirectNote } from 'wildebeest/backend/src/activitypub/objects/note'
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
import { makeDB, assertCORS, assertJSON, makeCache } from '../utils'
@ -67,7 +67,7 @@ describe('Mastodon APIs', () => {
await acceptFollowing(db, actor3, actor2)
// actor2 sends a DM to actor1
const note = await createPrivateNote(domain, db, 'DM', actor2, actor1)
const note = await createDirectNote(domain, db, 'DM', actor2, [actor1])
await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString())
// actor3 shouldn't see the private note
@ -100,7 +100,7 @@ describe('Mastodon APIs', () => {
const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com')
// actor2 sends a DM to actor1
const note = await createPrivateNote(domain, db, 'DM', actor2, actor1)
const note = await createDirectNote(domain, db, 'DM', actor2, [actor1])
await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString())
const data = await timelines.getPublicTimeline(domain, db, timelines.LocalPreference.NotSet)

Wyświetl plik

@ -4,6 +4,7 @@
* building.
*/
import { type Database } from 'wildebeest/backend/src/database'
import type { Actor } from '../src/activitypub/actors'
import { addObjectInOutbox } from '../src/activitypub/actors/outbox'
import { type Note, createPublicNote } from '../src/activitypub/objects/note'
@ -13,14 +14,14 @@ import { insertReply } from '../src/mastodon/reply'
* Creates a reply and inserts it in the reply author's outbox
*
* @param domain the domain to use
* @param db D1Database
* @param db Database
* @param actor Author of the reply
* @param originalNote The original note
* @param replyContent content of the reply
*/
export async function createReply(
domain: string,
db: D1Database,
db: Database,
actor: Actor,
originalNote: Note,
replyContent: string

Wyświetl plik

@ -7,7 +7,8 @@ import type { Client } from 'wildebeest/backend/src/mastodon/client'
import { promises as fs } from 'fs'
import * as path from 'path'
import { BetaDatabase } from '@miniflare/d1'
import * as Database from 'better-sqlite3'
import * as SQLiteDatabase from 'better-sqlite3'
import { type Database } from 'wildebeest/backend/src/database'
export function isUrlValid(s: string) {
let url
@ -19,8 +20,8 @@ export function isUrlValid(s: string) {
return url.protocol === 'https:'
}
export async function makeDB(): Promise<D1Database> {
const db = new Database(':memory:')
export async function makeDB(): Promise<Database> {
const db = new SQLiteDatabase(':memory:')
const db2 = new BetaDatabase(db)!
// Manually run our migrations since @miniflare/d1 doesn't support it (yet).
@ -31,7 +32,7 @@ export async function makeDB(): Promise<D1Database> {
db.exec(content)
}
return db2 as unknown as D1Database
return db2 as unknown as Database
}
export function assertCORS(response: Response) {
@ -66,7 +67,7 @@ export async function streamToArrayBuffer(stream: ReadableStream) {
}
export async function createTestClient(
db: D1Database,
db: Database,
redirectUri: string = 'https://localhost',
scopes: string = 'read follow'
): Promise<Client> {

Wyświetl plik

@ -31,6 +31,7 @@ describe('Wildebeest', () => {
return new Response(
JSON.stringify({
id: 'https://social.com/someone',
type: 'Person',
})
)
}

Wyświetl plik

@ -1,4 +1,5 @@
import type { DeliverMessageBody } from 'wildebeest/backend/src/types/queue'
import { getDatabase } from 'wildebeest/backend/src/database'
import { getSigningKey } from 'wildebeest/backend/src/mastodon/account'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
@ -7,12 +8,12 @@ import { deliverToActor } from 'wildebeest/backend/src/activitypub/deliver'
export async function handleDeliverMessage(env: Env, actor: Actor, message: DeliverMessageBody) {
const toActorId = new URL(message.toActorId)
const targetActor = await actors.getAndCache(toActorId, env.DATABASE)
const targetActor = await actors.getAndCache(toActorId, getDatabase(env as any))
if (targetActor === null) {
console.warn(`actor ${toActorId} not found`)
return
}
const signingKey = await getSigningKey(message.userKEK, env.DATABASE, actor)
const signingKey = await getSigningKey(message.userKEK, getDatabase(env as any), actor)
await deliverToActor(signingKey, actor, targetActor, message.activity, env.DOMAIN)
}

Wyświetl plik

@ -1,4 +1,5 @@
import type { InboxMessageBody } from 'wildebeest/backend/src/types/queue'
import { getDatabase } from 'wildebeest/backend/src/database'
import * as activityHandler from 'wildebeest/backend/src/activitypub/activities/handle'
import * as notification from 'wildebeest/backend/src/mastodon/notification'
import * as timeline from 'wildebeest/backend/src/mastodon/timeline'
@ -8,7 +9,7 @@ import type { Env } from './'
export async function handleInboxMessage(env: Env, actor: Actor, message: InboxMessageBody) {
const domain = env.DOMAIN
const db = env.DATABASE
const db = getDatabase(env as any)
const adminEmail = env.ADMIN_EMAIL
const cache = cacheFromEnv(env)
const activity = message.activity

Wyświetl plik

@ -1,4 +1,5 @@
import type { MessageBody, InboxMessageBody, DeliverMessageBody } from 'wildebeest/backend/src/types/queue'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
import { MessageType } from 'wildebeest/backend/src/types/queue'
import { initSentryQueue } from './sentry'
@ -6,7 +7,7 @@ import { handleInboxMessage } from './inbox'
import { handleDeliverMessage } from './deliver'
export type Env = {
DATABASE: D1Database
DATABASE: Database
DOMAIN: string
ADMIN_EMAIL: string
DO_CACHE: DurableObjectNamespace
@ -19,10 +20,11 @@ export type Env = {
export default {
async queue(batch: MessageBatch<MessageBody>, env: Env, ctx: ExecutionContext) {
const sentry = initSentryQueue(env, ctx)
const db = getDatabase(env as any)
try {
for (const message of batch.messages) {
const actor = await actors.getActorById(env.DATABASE, new URL(message.body.actorId))
const actor = await actors.getActorById(db, new URL(message.body.actorId))
if (actor === null) {
console.warn(`actor ${message.body.actorId} is missing`)
return

Wyświetl plik

@ -6,11 +6,12 @@ import { createReblog } from 'wildebeest/backend/src/mastodon/reblog'
import { createReply as createReplyInBackend } from 'wildebeest/backend/test/shared.utils'
import { createStatus } from 'wildebeest/backend/src/mastodon/status'
import type { APObject } from 'wildebeest/backend/src/activitypub/objects'
import { type Database } from 'wildebeest/backend/src/database'
/**
* Run helper commands to initialize the database with actors, statuses, etc.
*/
export async function init(domain: string, db: D1Database) {
export async function init(domain: string, db: Database) {
const loadedStatuses: { status: MastodonStatus; note: Note }[] = []
for (const status of statuses) {
const actor = await getOrCreatePerson(domain, db, status.account)
@ -47,7 +48,7 @@ export async function init(domain: string, db: D1Database) {
*/
async function createReply(
domain: string,
db: D1Database,
db: Database,
reply: MastodonStatus,
loadedStatuses: { status: MastodonStatus; note: Note }[]
) {
@ -70,7 +71,7 @@ async function createReply(
async function getOrCreatePerson(
domain: string,
db: D1Database,
db: Database,
{ username, avatar, display_name }: Account
): Promise<Person> {
const person = await getPersonByEmail(db, username)

Wyświetl plik

@ -1,7 +1,8 @@
import { init } from './init'
import { type Database } from 'wildebeest/backend/src/database'
interface Env {
DATABASE: D1Database
DATABASE: Database
}
/**

Wyświetl plik

@ -26,6 +26,7 @@
"eslint": "8.30.0",
"eslint-plugin-qwik": "0.16.1",
"jest": "^29.3.1",
"lorem-ipsum": "^2.0.8",
"node-fetch": "3.3.0",
"postcss": "^8.4.16",
"prettier": "2.8.1",

Wyświetl plik

@ -13,18 +13,13 @@ export const AccountCard = component$<{
const accountUrl = useAccountUrl(account)
return (
<Link
href={accountUrl}
class="inline-grid grid-cols-[repeat(2,_max-content)] grid-rows-[1fr,1fr] items-center no-underline"
>
<div class="row-span-2">
<Link href={accountUrl} class="inline-flex items-center no-underline flex-wrap gap-2">
<div class="flex-grow flex-shrink-0 flex justify-center">
<Avatar primary={account} secondary={secondaryAvatar ?? null} />
</div>
<div data-testid="account-display-name" class="ml-2 col-start-2 row-start-1">
{getDisplayNameElement(account)}
</div>
<div class="ml-2 text-wildebeest-400 col-start-2 row-start-2">
@{subText === 'username' ? account.username : account.acct}
<div>
<div data-testid="account-display-name">{getDisplayNameElement(account)}</div>
<div class="text-wildebeest-400">@{subText === 'username' ? account.username : account.acct}</div>
</div>
</Link>
)

Wyświetl plik

@ -32,9 +32,9 @@ export default component$((props: Props) => {
return (
<article class="p-4 border-t border-wildebeest-700 break-words">
<RebloggerLink account={reblogger}></RebloggerLink>
<div class="flex justify-between mb-3">
<div class="flex justify-between mb-3 flex-wrap">
<AccountCard account={status.account} subText={props.accountSubText} secondaryAvatar={reblogger} />
<Link class="no-underline" href={statusUrl}>
<Link class="no-underline ml-auto" href={statusUrl}>
<div class="text-wildebeest-500 flex items-baseline">
<i style={{ height: '0.75rem', width: '0.75rem' }} class="fa fa-xs fa-globe w-3 h-3" />
<span class="ml-2 text-sm hover:underline min-w-max">{formatTimeAgo(new Date(status.created_at))}</span>

Wyświetl plik

@ -38,7 +38,7 @@ export default component$(() => {
// const aboutLink = { iconName: 'fa-ellipsis', linkText: 'About', linkTarget: '/about', linkActiveRegex: /^\/about/ }
return (
<div class="bg-wildebeest-600 xl:bg-transparent flex flex-col justify-between right-column-wrapper text-wildebeest-200 flex-1">
<div class="bg-wildebeest-600 xl:bg-transparent flex flex-col justify-between right-column-wrapper text-wildebeest-200 flex-1 z-10">
<div class="sticky top-[3.9rem] xl:top-0">
<div class="xl:p-4">
<Link class="no-underline hidden xl:flex items-center" aria-label="Wildebeest Home" href={'/'}>

Wyświetl plik

@ -1,6 +1,7 @@
import type { MediaAttachment, MastodonStatus } from '~/types'
import { generateDummyStatus } from './generateDummyStatus'
import { ben, george, penny, rafael, zak } from './accounts'
import { loremIpsum } from 'lorem-ipsum'
// Raw statuses which follow the precise structure found mastodon does
const mastodonRawStatuses: MastodonStatus[] = [
@ -38,6 +39,11 @@ const mastodonRawStatuses: MastodonStatus[] = [
.fill(null)
.map((_, idx) => generateDummyMediaImage(`https:/loremflickr.com/640/480/abstract?lock=${100 + idx}`)),
}),
generateDummyStatus({
content:
loremIpsum({ count: 2, format: 'html', units: 'paragraphs' }) +
'<p>#テスト投稿\n長いURLを投稿してみる\nついでに改行も複数いれてみる\n\n\n良いプログラマになるには | プログラマが知るべき97のこと\n<a href="https://xn--97-273ae6a4irb6e2hsoiozc2g4b8082p.com/%E3%82%A8%E3%83%83%E3%82%BB%E3%82%A4/%E8%89%AF%E3%81%84%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%81%AB%E3%81%AA%E3%82%8B%E3%81%AB%E3%81%AF/">xn--97-273ae6a4irb6e2hsoiozc2g4b8082p.com/%E3%82%A8%E3%83%83%E3%82%BB%E3%82%A4/%E8%89%AF%E3%81%84%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%81%AB%E3%81%AA%E3%82%8B%E3%81%AB%E3%81%AF/</a></p>',
}),
]
export const statuses: MastodonStatus[] = mastodonRawStatuses.map((rawStatus) => ({

Wyświetl plik

@ -14,7 +14,9 @@ export const clientLoader = loader$<Promise<Client>, { DATABASE: D1Database }>(a
let client: Client | null = null
try {
client = await getClientById(platform.DATABASE, client_id)
} catch {
} catch (e: unknown) {
const error = e as { stack: string; cause: string }
console.warn(error.stack, error.cause)
throw html(500, getErrorHtml('An error occurred while trying to fetch the client data, please try again later'))
}
if (client === null) {
@ -36,8 +38,9 @@ export const userLoader = loader$<
// TODO: eventually, verify the JWT with Access, however this
// is not critical.
payload = access.getPayload(jwt.value)
} catch (err: unknown) {
console.warn((err as { stack: unknown }).stack)
} catch (e: unknown) {
const error = e as { stack: string; cause: string }
console.warn(error.stack, error.cause)
throw html(500, getErrorHtml('Failed to validate Access JWT'))
}

Wyświetl plik

@ -19,7 +19,9 @@ export const statusLoader = loader$<
try {
const statusResponse = await statusAPI.handleRequestGet(platform.DATABASE, params.statusId, domain, {} as Person)
statusText = await statusResponse.text()
} catch {
} catch (e: unknown) {
const error = e as { stack: string; cause: string }
console.warn(error.stack, error.cause)
throw html(500, getErrorHtml('An error occurred whilst retrieving the status data, please try again later'))
}
if (!statusText) {
@ -36,7 +38,9 @@ export const statusLoader = loader$<
throw new Error(`No context present for status with ${params.statusId}`)
}
return { status, statusTextContent, context }
} catch {
} catch (e: unknown) {
const error = e as { stack: string; cause: string }
console.warn(error.stack, error.cause)
throw html(500, getErrorHtml('No context for the status has been found, please try again later'))
}
})

Wyświetl plik

@ -9,6 +9,7 @@ import { WildebeestLogo } from '~/components/MastodonLogo'
import { getCommitHash } from '~/utils/getCommitHash'
import { InstanceConfigContext } from '~/utils/instanceConfig'
import { getDocumentHead } from '~/utils/getDocumentHead'
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
export const instanceLoader = loader$<
Promise<InstanceConfig>,
@ -24,8 +25,10 @@ export const instanceLoader = loader$<
const results = await response.text()
const json = JSON.parse(results) as InstanceConfig
return json
} catch {
throw html(500, 'An error occurred whilst retrieving the instance details')
} catch (e: unknown) {
const error = e as { stack: string; cause: string }
console.warn(error.stack, error.cause)
throw html(500, getErrorHtml('An error occurred whilst retrieving the instance details'))
}
})
@ -40,13 +43,13 @@ export default component$(() => {
<WildebeestLogo size="small" />
</Link>
</header>
<main class="flex-1 flex justify-center top-[3.9rem]">
<main class="flex-1 flex justify-center top-[3.9rem] max-w-screen">
<div class="w-fit md:w-72 hidden xl:block mx-2.5">
<div class="sticky top-2.5">
<LeftColumn />
</div>
</div>
<div class="w-full xl:max-w-xl bg-wildebeest-600 xl:bg-transparent flex flex-col break-all">
<div class="w-0 xl:max-w-xl bg-wildebeest-600 xl:bg-transparent flex flex-col flex-1">
<div class="bg-wildebeest-600 rounded flex flex-1 flex-col">
<Slot />
</div>

Wyświetl plik

@ -5,6 +5,7 @@ import { DocumentHead, loader$ } from '@builder.io/qwik-city'
import StickyHeader from '~/components/StickyHeader/StickyHeader'
import { getDocumentHead } from '~/utils/getDocumentHead'
import { StatusesPanel } from '~/components/StatusesPanel/StatusesPanel'
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
export const statusesLoader = loader$<Promise<MastodonStatus[]>, { DATABASE: D1Database; domain: string }>(
async ({ platform, html }) => {
@ -14,8 +15,10 @@ export const statusesLoader = loader$<Promise<MastodonStatus[]>, { DATABASE: D1D
const results = await response.text()
// Manually parse the JSON to ensure that Qwik finds the resulting objects serializable.
return JSON.parse(results) as MastodonStatus[]
} catch {
throw html(500, 'The public timeline is unavailable')
} catch (e: unknown) {
const error = e as { stack: string; cause: string }
console.warn(error.stack, error.cause)
throw html(500, getErrorHtml('The public timeline is unavailable'))
}
}
)

Wyświetl plik

@ -5,6 +5,7 @@ import { DocumentHead, loader$ } from '@builder.io/qwik-city'
import StickyHeader from '~/components/StickyHeader/StickyHeader'
import { getDocumentHead } from '~/utils/getDocumentHead'
import { StatusesPanel } from '~/components/StatusesPanel/StatusesPanel'
import { getErrorHtml } from '~/utils/getErrorHtml/getErrorHtml'
export const statusesLoader = loader$<Promise<MastodonStatus[]>, { DATABASE: D1Database; domain: string }>(
async ({ platform, html }) => {
@ -14,8 +15,10 @@ export const statusesLoader = loader$<Promise<MastodonStatus[]>, { DATABASE: D1D
const results = await response.text()
// Manually parse the JSON to ensure that Qwik finds the resulting objects serializable.
return JSON.parse(results) as MastodonStatus[]
} catch {
throw html(500, 'The local timeline is unavailable')
} catch (e: unknown) {
const error = e as { stack: string; cause: string }
console.warn(error.stack, error.cause)
throw html(500, getErrorHtml('The local timeline is unavailable'))
}
}
)

Wyświetl plik

@ -1441,6 +1441,11 @@ commander@^4.0.0:
resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
commander@^9.3.0:
version "9.5.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30"
integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
@ -2814,6 +2819,13 @@ longest-streak@^3.0.0:
resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz"
integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
lorem-ipsum@^2.0.8:
version "2.0.8"
resolved "https://registry.yarnpkg.com/lorem-ipsum/-/lorem-ipsum-2.0.8.tgz#f969a089f2ac6f19cf01b854b61beabb0e6f3cbc"
integrity sha512-5RIwHuCb979RASgCJH0VKERn9cQo/+NcAi2BMe9ddj+gp7hujl6BI+qdOG4nVsLDpwWEJwTVYXNKP6BGgbcoGA==
dependencies:
commander "^9.3.0"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"

Wyświetl plik

@ -4,9 +4,10 @@ import { parseHandle } from '../../backend/src/utils/parse'
import { getActorById, actorURL } from 'wildebeest/backend/src/activitypub/actors'
import type { Env } from '../../backend/src/types/env'
import type { WebFingerResponse } from '../../backend/src/webfinger'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
export const onRequest: PagesFunction<Env, any> = async ({ request, env }) => {
return handleRequest(request, env.DATABASE)
return handleRequest(request, getDatabase(env))
}
const headers = {
@ -14,7 +15,7 @@ const headers = {
'cache-control': 'max-age=3600, public',
}
export async function handleRequest(request: Request, db: D1Database): Promise<Response> {
export async function handleRequest(request: Request, db: Database): Promise<Response> {
const url = new URL(request.url)
const domain = url.hostname
const resource = url.searchParams.get('resource')

Wyświetl plik

@ -1,10 +1,11 @@
import { cors } from 'wildebeest/backend/src/utils/cors'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import type { Env } from 'wildebeest/backend/src/types/env'
import * as objects from 'wildebeest/backend/src/activitypub/objects'
export const onRequest: PagesFunction<Env, any> = async ({ params, request, env }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, params.id as string)
return handleRequest(domain, getDatabase(env), params.id as string)
}
const headers = {
@ -12,7 +13,7 @@ const headers = {
'content-type': 'application/activity+json; charset=utf-8',
}
export async function handleRequest(domain: string, db: D1Database, id: string): Promise<Response> {
export async function handleRequest(domain: string, db: Database, id: string): Promise<Response> {
const obj = await objects.getObjectById(db, objects.uri(domain, id))
if (obj === null) {
return new Response('', { status: 404 })

Wyświetl plik

@ -1,4 +1,5 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { cors } from 'wildebeest/backend/src/utils/cors'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
import type { Env } from 'wildebeest/backend/src/types/env'
@ -6,7 +7,7 @@ import * as actors from 'wildebeest/backend/src/activitypub/actors'
export const onRequest: PagesFunction<Env, any> = async ({ params, request, env }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, params.id as string)
return handleRequest(domain, getDatabase(env), params.id as string)
}
const headers = {
@ -15,7 +16,7 @@ const headers = {
'Cache-Control': 'max-age=180, public',
}
export async function handleRequest(domain: string, db: D1Database, id: string): Promise<Response> {
export async function handleRequest(domain: string, db: Database, id: string): Promise<Response> {
const handle = parseHandle(id)
if (handle.domain !== null && handle.domain !== domain) {

Wyświetl plik

@ -1,4 +1,5 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import type { Env } from 'wildebeest/backend/src/types/env'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
@ -10,10 +11,10 @@ const headers = {
export const onRequest: PagesFunction<Env, any> = async ({ params, request, env }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, params.id as string)
return handleRequest(domain, getDatabase(env), params.id as string)
}
export async function handleRequest(domain: string, db: D1Database, id: string): Promise<Response> {
export async function handleRequest(domain: string, db: Database, id: string): Promise<Response> {
const handle = parseHandle(id)
if (handle.domain !== null) {

Wyświetl plik

@ -1,4 +1,5 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { getFollowers } from 'wildebeest/backend/src/mastodon/follow'
import { getActorById } from 'wildebeest/backend/src/activitypub/actors'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
@ -7,14 +8,14 @@ import type { Env } from 'wildebeest/backend/src/types/env'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, params.id as string)
return handleRequest(domain, getDatabase(env), params.id as string)
}
const headers = {
'content-type': 'application/json; charset=utf-8',
}
export async function handleRequest(domain: string, db: D1Database, id: string): Promise<Response> {
export async function handleRequest(domain: string, db: Database, id: string): Promise<Response> {
const handle = parseHandle(id)
if (handle.domain !== null) {

Wyświetl plik

@ -1,4 +1,5 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import type { Env } from 'wildebeest/backend/src/types/env'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
@ -10,10 +11,10 @@ const headers = {
export const onRequest: PagesFunction<Env, any> = async ({ params, request, env }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, params.id as string)
return handleRequest(domain, getDatabase(env), params.id as string)
}
export async function handleRequest(domain: string, db: D1Database, id: string): Promise<Response> {
export async function handleRequest(domain: string, db: Database, id: string): Promise<Response> {
const handle = parseHandle(id)
if (handle.domain !== null) {

Wyświetl plik

@ -1,4 +1,5 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { getFollowingId } from 'wildebeest/backend/src/mastodon/follow'
import { getActorById } from 'wildebeest/backend/src/activitypub/actors'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
@ -7,14 +8,14 @@ import type { Env } from 'wildebeest/backend/src/types/env'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, params.id as string)
return handleRequest(domain, getDatabase(env), params.id as string)
}
const headers = {
'content-type': 'application/json; charset=utf-8',
}
export async function handleRequest(domain: string, db: D1Database, id: string): Promise<Response> {
export async function handleRequest(domain: string, db: Database, id: string): Promise<Response> {
const handle = parseHandle(id)
if (handle.domain !== null) {

Wyświetl plik

@ -1,4 +1,5 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { getVAPIDKeys } from 'wildebeest/backend/src/config'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
@ -38,12 +39,20 @@ export const onRequest: PagesFunction<Env, any> = async ({ params, request, env
const activity: Activity = JSON.parse(body)
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, params.id as string, activity, env.QUEUE, env.userKEK, getVAPIDKeys(env))
return handleRequest(
domain,
getDatabase(env),
params.id as string,
activity,
env.QUEUE,
env.userKEK,
getVAPIDKeys(env)
)
}
export async function handleRequest(
domain: string,
db: D1Database,
db: Database,
id: string,
activity: Activity,
queue: Queue<InboxMessageBody>,

Wyświetl plik

@ -1,4 +1,5 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { getActorById } from 'wildebeest/backend/src/activitypub/actors'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
import type { ContextData } from 'wildebeest/backend/src/types/context'
@ -6,7 +7,7 @@ import type { Env } from 'wildebeest/backend/src/types/env'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, params.id as string, env.userKEK)
return handleRequest(domain, getDatabase(env), params.id as string, env.userKEK)
}
const headers = {
@ -14,7 +15,7 @@ const headers = {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: use userKEK
export async function handleRequest(domain: string, db: D1Database, id: string, userKEK: string): Promise<Response> {
export async function handleRequest(domain: string, db: Database, id: string, userKEK: string): Promise<Response> {
const handle = parseHandle(id)
if (handle.domain !== null) {

Wyświetl plik

@ -1,4 +1,5 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { cors } from 'wildebeest/backend/src/utils/cors'
import type { Activity } from 'wildebeest/backend/src/activitypub/activities'
import { getActorById } from 'wildebeest/backend/src/activitypub/actors'
@ -11,7 +12,7 @@ import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, params.id as string)
return handleRequest(domain, getDatabase(env), params.id as string)
}
const headers = {
@ -21,7 +22,7 @@ const headers = {
const DEFAULT_LIMIT = 20
export async function handleRequest(domain: string, db: D1Database, id: string): Promise<Response> {
export async function handleRequest(domain: string, db: Database, id: string): Promise<Response> {
const handle = parseHandle(id)
if (handle.domain !== null) {

Wyświetl plik

@ -1,5 +1,6 @@
// https://docs.joinmastodon.org/methods/accounts/#get
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { cors } from 'wildebeest/backend/src/utils/cors'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { Env } from 'wildebeest/backend/src/types/env'
@ -12,10 +13,10 @@ const headers = {
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, params.id as string, env.DATABASE)
return handleRequest(domain, params.id as string, getDatabase(env))
}
export async function handleRequest(domain: string, id: string, db: D1Database): Promise<Response> {
export async function handleRequest(domain: string, id: string, db: Database): Promise<Response> {
const account = await getAccount(domain, id, db)
if (account) {

Wyświetl plik

@ -1,4 +1,5 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { cors } from 'wildebeest/backend/src/utils/cors'
import * as actors from 'wildebeest/backend/src/activitypub/actors'
import { deliverToActor } from 'wildebeest/backend/src/activitypub/deliver'
@ -12,12 +13,12 @@ import type { Relationship } from 'wildebeest/backend/src/types/account'
import { addFollowing } from 'wildebeest/backend/src/mastodon/follow'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params, data }) => {
return handleRequest(request, env.DATABASE, params.id as string, data.connectedActor, env.userKEK)
return handleRequest(request, getDatabase(env), params.id as string, data.connectedActor, env.userKEK)
}
export async function handleRequest(
request: Request,
db: D1Database,
db: Database,
id: string,
connectedActor: Person,
userKEK: string

Wyświetl plik

@ -1,5 +1,6 @@
// https://docs.joinmastodon.org/methods/accounts/#followers
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import type { Handle } from 'wildebeest/backend/src/utils/parse'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
import { cors } from 'wildebeest/backend/src/utils/cors'
@ -15,10 +16,10 @@ import { getFollowers, loadActors } from 'wildebeest/backend/src/activitypub/act
import * as localFollow from 'wildebeest/backend/src/mastodon/follow'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ params, request, env }) => {
return handleRequest(request, env.DATABASE, params.id as string)
return handleRequest(request, getDatabase(env), params.id as string)
}
export async function handleRequest(request: Request, db: D1Database, id: string): Promise<Response> {
export async function handleRequest(request: Request, db: Database, id: string): Promise<Response> {
const handle = parseHandle(id)
const domain = new URL(request.url).hostname
@ -33,7 +34,7 @@ export async function handleRequest(request: Request, db: D1Database, id: string
}
}
async function getRemoteFollowers(request: Request, handle: Handle, db: D1Database): Promise<Response> {
async function getRemoteFollowers(request: Request, handle: Handle, db: Database): Promise<Response> {
const acct = `${handle.localPart}@${handle.domain}`
const link = await webfinger.queryAcctLink(handle.domain!, acct)
if (link === null) {
@ -57,7 +58,7 @@ async function getRemoteFollowers(request: Request, handle: Handle, db: D1Databa
return new Response(JSON.stringify(out), { headers })
}
async function getLocalFollowers(request: Request, handle: Handle, db: D1Database): Promise<Response> {
async function getLocalFollowers(request: Request, handle: Handle, db: Database): Promise<Response> {
const domain = new URL(request.url).hostname
const actorId = actorURL(domain, handle.localPart)
const actor = await actors.getAndCache(actorId, db)

Wyświetl plik

@ -1,5 +1,6 @@
// https://docs.joinmastodon.org/methods/accounts/#following
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import type { Handle } from 'wildebeest/backend/src/utils/parse'
import { actorURL } from 'wildebeest/backend/src/activitypub/actors'
import { cors } from 'wildebeest/backend/src/utils/cors'
@ -15,10 +16,10 @@ import * as webfinger from 'wildebeest/backend/src/webfinger'
import { getFollowing, loadActors } from 'wildebeest/backend/src/activitypub/actors/follow'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ params, request, env }) => {
return handleRequest(request, env.DATABASE, params.id as string)
return handleRequest(request, getDatabase(env), params.id as string)
}
export async function handleRequest(request: Request, db: D1Database, id: string): Promise<Response> {
export async function handleRequest(request: Request, db: Database, id: string): Promise<Response> {
const handle = parseHandle(id)
const domain = new URL(request.url).hostname
@ -33,7 +34,7 @@ export async function handleRequest(request: Request, db: D1Database, id: string
}
}
async function getRemoteFollowing(request: Request, handle: Handle, db: D1Database): Promise<Response> {
async function getRemoteFollowing(request: Request, handle: Handle, db: Database): Promise<Response> {
const acct = `${handle.localPart}@${handle.domain}`
const link = await webfinger.queryAcctLink(handle.domain!, acct)
if (link === null) {
@ -57,7 +58,7 @@ async function getRemoteFollowing(request: Request, handle: Handle, db: D1Databa
return new Response(JSON.stringify(out), { headers })
}
async function getLocalFollowing(request: Request, handle: Handle, db: D1Database): Promise<Response> {
async function getLocalFollowing(request: Request, handle: Handle, db: Database): Promise<Response> {
const domain = new URL(request.url).hostname
const actorId = actorURL(domain, handle.localPart)
const actor = await actors.getAndCache(actorId, db)

Wyświetl plik

@ -1,4 +1,5 @@
import type { Env } from 'wildebeest/backend/src/types/env'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities'
import * as errors from 'wildebeest/backend/src/errors'
import { cors } from 'wildebeest/backend/src/utils/cors'
@ -25,10 +26,10 @@ const headers = {
}
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
return handleRequest(request, env.DATABASE, params.id as string)
return handleRequest(request, getDatabase(env), params.id as string)
}
export async function handleRequest(request: Request, db: D1Database, id: string): Promise<Response> {
export async function handleRequest(request: Request, db: Database, id: string): Promise<Response> {
const handle = parseHandle(id)
const url = new URL(request.url)
const domain = url.hostname
@ -46,7 +47,7 @@ export async function handleRequest(request: Request, db: D1Database, id: string
}
}
async function getRemoteStatuses(request: Request, handle: Handle, db: D1Database): Promise<Response> {
async function getRemoteStatuses(request: Request, handle: Handle, db: Database): Promise<Response> {
const url = new URL(request.url)
const domain = url.hostname
const isPinned = url.searchParams.get('pinned') === 'true'
@ -118,7 +119,7 @@ async function getRemoteStatuses(request: Request, handle: Handle, db: D1Databas
export async function getLocalStatuses(
request: Request,
db: D1Database,
db: Database,
handle: Handle,
offset: number,
withReplies: boolean

Wyświetl plik

@ -1,4 +1,5 @@
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { cors } from 'wildebeest/backend/src/utils/cors'
import { deliverToActor } from 'wildebeest/backend/src/activitypub/deliver'
import { getSigningKey } from 'wildebeest/backend/src/mastodon/account'
@ -11,12 +12,12 @@ import type { Relationship } from 'wildebeest/backend/src/types/account'
import { removeFollowing } from 'wildebeest/backend/src/mastodon/follow'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params, data }) => {
return handleRequest(request, env.DATABASE, params.id as string, data.connectedActor, env.userKEK)
return handleRequest(request, getDatabase(env), params.id as string, data.connectedActor, env.userKEK)
}
export async function handleRequest(
request: Request,
db: D1Database,
db: Database,
id: string,
connectedActor: Person,
userKEK: string
@ -34,7 +35,7 @@ export async function handleRequest(
}
const acct = `${handle.localPart}@${handle.domain}`
const targetActor = await webfinger.queryAcct(handle.domain!, acct)
const targetActor = await webfinger.queryAcct(handle.domain!, db, acct)
if (targetActor === null) {
return new Response('', { status: 404 })
}

Wyświetl plik

@ -1,5 +1,6 @@
// https://docs.joinmastodon.org/methods/accounts/#relationships
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { cors } from 'wildebeest/backend/src/utils/cors'
import type { Person } from 'wildebeest/backend/src/activitypub/actors'
import type { Env } from 'wildebeest/backend/src/types/env'
@ -7,10 +8,10 @@ import type { ContextData } from 'wildebeest/backend/src/types/context'
import { getFollowingAcct, getFollowingRequestedAcct } from 'wildebeest/backend/src/mastodon/follow'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
return handleRequest(request, env.DATABASE, data.connectedActor)
return handleRequest(request, getDatabase(env), data.connectedActor)
}
export async function handleRequest(req: Request, db: D1Database, connectedActor: Person): Promise<Response> {
export async function handleRequest(req: Request, db: Database, connectedActor: Person): Promise<Response> {
const url = new URL(req.url)
let ids = []

Wyświetl plik

@ -1,6 +1,7 @@
// https://docs.joinmastodon.org/methods/accounts/#update_credentials
import { cors } from 'wildebeest/backend/src/utils/cors'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import type { Queue, DeliverMessageBody } from 'wildebeest/backend/src/types/queue'
import * as errors from 'wildebeest/backend/src/errors'
import * as activities from 'wildebeest/backend/src/activitypub/activities/update'
@ -21,7 +22,7 @@ const headers = {
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, data, env }) => {
return handleRequest(
env.DATABASE,
getDatabase(env),
request,
data.connectedActor,
env.CF_ACCOUNT_ID,
@ -32,7 +33,7 @@ export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request,
}
export async function handleRequest(
db: D1Database,
db: Database,
request: Request,
connectedActor: Actor,

Wyświetl plik

@ -6,12 +6,13 @@ import type { Env } from 'wildebeest/backend/src/types/env'
import * as errors from 'wildebeest/backend/src/errors'
import type { CredentialAccount } from 'wildebeest/backend/src/types/account'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import { getDatabase } from 'wildebeest/backend/src/database'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ data, env }) => {
if (!data.connectedActor) {
return errors.notAuthorized('no connected user')
}
const user = await loadLocalMastodonAccount(env.DATABASE, data.connectedActor)
const user = await loadLocalMastodonAccount(getDatabase(env), data.connectedActor)
const res: CredentialAccount = {
...user,

Wyświetl plik

@ -7,6 +7,7 @@ import { createClient } from 'wildebeest/backend/src/mastodon/client'
import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
import { getVAPIDKeys } from 'wildebeest/backend/src/config'
import { readBody } from 'wildebeest/backend/src/utils/body'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
type AppsPost = {
redirect_uris: string
@ -16,10 +17,10 @@ type AppsPost = {
}
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env }) => {
return handleRequest(env.DATABASE, request, getVAPIDKeys(env))
return handleRequest(getDatabase(env), request, getVAPIDKeys(env))
}
export async function handleRequest(db: D1Database, request: Request, vapidKeys: JWK) {
export async function handleRequest(db: Database, request: Request, vapidKeys: JWK) {
if (request.method !== 'POST') {
return errors.methodNotAllowed()
}

Wyświetl plik

@ -1,12 +1,13 @@
import { cors } from 'wildebeest/backend/src/utils/cors'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import type { Env } from 'wildebeest/backend/src/types/env'
import { getPeers } from 'wildebeest/backend/src/activitypub/peers'
export const onRequest: PagesFunction<Env, any> = async ({ env }) => {
return handleRequest(env.DATABASE)
return handleRequest(getDatabase(env))
}
export async function handleRequest(db: D1Database): Promise<Response> {
export async function handleRequest(db: Database): Promise<Response> {
const headers = {
...cors(),
'content-type': 'application/json; charset=utf-8',

Wyświetl plik

@ -1,5 +1,6 @@
// https://docs.joinmastodon.org/methods/notifications/#get-one
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import type { Notification, NotificationsQueryResult } from 'wildebeest/backend/src/types/notification'
import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
import { getActorById } from 'wildebeest/backend/src/activitypub/actors'
@ -14,13 +15,13 @@ const headers = {
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ data, request, env, params }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, params.id as string, env.DATABASE, data.connectedActor)
return handleRequest(domain, params.id as string, getDatabase(env), data.connectedActor)
}
export async function handleRequest(
domain: string,
id: string,
db: D1Database,
db: Database,
connectedActor: Person
): Promise<Response> {
const query = `

Wyświetl plik

@ -9,13 +9,14 @@ import { ContextData } from 'wildebeest/backend/src/types/context'
import type { Env } from 'wildebeest/backend/src/types/env'
import * as errors from 'wildebeest/backend/src/errors'
import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
export const onRequestGet: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
return handleGetRequest(env.DATABASE, request, data.connectedActor, data.clientId, getVAPIDKeys(env))
return handleGetRequest(getDatabase(env), request, data.connectedActor, data.clientId, getVAPIDKeys(env))
}
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
return handlePostRequest(env.DATABASE, request, data.connectedActor, data.clientId, getVAPIDKeys(env))
return handlePostRequest(getDatabase(env), request, data.connectedActor, data.clientId, getVAPIDKeys(env))
}
const headers = {
@ -24,7 +25,7 @@ const headers = {
}
export async function handleGetRequest(
db: D1Database,
db: Database,
request: Request,
connectedActor: Actor,
clientId: string,
@ -55,7 +56,7 @@ export async function handleGetRequest(
}
export async function handlePostRequest(
db: D1Database,
db: Database,
request: Request,
connectedActor: Actor,
clientId: string,

Wyświetl plik

@ -8,7 +8,7 @@ import * as timeline from 'wildebeest/backend/src/mastodon/timeline'
import type { Queue, DeliverMessageBody } from 'wildebeest/backend/src/types/queue'
import type { Document } from 'wildebeest/backend/src/activitypub/objects'
import { getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
import { createStatus, getMentions } from 'wildebeest/backend/src/mastodon/status'
import { getMentions } from 'wildebeest/backend/src/mastodon/status'
import { getHashtags, insertHashtags } from 'wildebeest/backend/src/mastodon/hashtag'
import * as activities from 'wildebeest/backend/src/activitypub/activities/create'
import type { Env } from 'wildebeest/backend/src/types/env'
@ -26,6 +26,9 @@ import { enrichStatus } from 'wildebeest/backend/src/mastodon/microformats'
import * as idempotency from 'wildebeest/backend/src/mastodon/idempotency'
import { newMention } from 'wildebeest/backend/src/activitypub/objects/mention'
import { originalObjectIdSymbol } from 'wildebeest/backend/src/activitypub/objects'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { createPublicNote, createDirectNote } from 'wildebeest/backend/src/activitypub/objects/note'
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
type StatusCreate = {
status: string
@ -36,13 +39,13 @@ type StatusCreate = {
}
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
return handleRequest(request, env.DATABASE, data.connectedActor, env.userKEK, env.QUEUE, cacheFromEnv(env))
return handleRequest(request, getDatabase(env), data.connectedActor, env.userKEK, env.QUEUE, cacheFromEnv(env))
}
// FIXME: add tests for delivery to followers and mentions to a specific Actor.
export async function handleRequest(
request: Request,
db: D1Database,
db: Database,
connectedActor: Person,
userKEK: string,
queue: Queue<DeliverMessageBody>,
@ -74,6 +77,10 @@ export async function handleRequest(
return new Response('', { status: 400 })
}
if (body.status.length > 500) {
return errors.validationError('text character limit of 500 exceeded')
}
const mediaAttachments: Array<Document> = []
if (body.media_ids && body.media_ids.length > 0) {
if (body.media_ids.length > 4) {
@ -107,13 +114,22 @@ export async function handleRequest(
const hashtags = getHashtags(body.status)
const mentions = await getMentions(body.status, domain)
const mentions = await getMentions(body.status, domain, db)
if (mentions.length > 0) {
extraProperties.tag = mentions.map(newMention)
}
const content = enrichStatus(body.status, mentions)
const note = await createStatus(domain, db, connectedActor, content, mediaAttachments, extraProperties)
let note
if (body.visibility === 'public') {
note = await createPublicNote(domain, db, content, connectedActor, mediaAttachments, extraProperties)
} else if (body.visibility === 'direct') {
note = await createDirectNote(domain, db, content, connectedActor, mentions, mediaAttachments, extraProperties)
} else {
return errors.validationError(`status with visibility: ${body.visibility}`)
}
if (hashtags.length > 0) {
await insertHashtags(db, note, hashtags)
@ -127,11 +143,27 @@ export async function handleRequest(
const activity = activities.create(domain, connectedActor, note)
await deliverFollowers(db, userKEK, connectedActor, activity, queue)
if (body.visibility === 'public') {
await addObjectInOutbox(db, connectedActor, note)
// A public note is sent to the public group URL and cc'ed any mentioned
// actors.
for (let i = 0, len = mentions.length; i < len; i++) {
const targetActor = mentions[i]
note.cc.push(targetActor.id.toString())
}
} else if (body.visibility === 'direct') {
// A direct note is sent to mentioned people only
for (let i = 0, len = mentions.length; i < len; i++) {
const targetActor = mentions[i]
await addObjectInOutbox(db, connectedActor, note, undefined, targetActor.id.toString())
}
}
{
// If the status is mentioning other persons, we need to delivery it to them.
for (let i = 0, len = mentions.length; i < len; i++) {
const targetActor = mentions[i]
note.cc.push(targetActor.id.toString())
const activity = activities.create(domain, connectedActor, note)
const signingKey = await getSigningKey(userKEK, db, connectedActor)
await deliverToActor(signingKey, connectedActor, targetActor, activity, domain)

Wyświetl plik

@ -16,16 +16,17 @@ import { deliverFollowers } from 'wildebeest/backend/src/activitypub/deliver'
import type { Queue, DeliverMessageBody } from 'wildebeest/backend/src/types/queue'
import * as timeline from 'wildebeest/backend/src/mastodon/timeline'
import { cacheFromEnv } from 'wildebeest/backend/src/cache'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
export const onRequestGet: PagesFunction<Env, any, ContextData> = async ({ params, env, request, data }) => {
const domain = new URL(request.url).hostname
return handleRequestGet(env.DATABASE, params.id as UUID, domain, data.connectedActor)
return handleRequestGet(getDatabase(env), params.id as UUID, domain, data.connectedActor)
}
export const onRequestDelete: PagesFunction<Env, any, ContextData> = async ({ params, env, request, data }) => {
const domain = new URL(request.url).hostname
return handleRequestDelete(
env.DATABASE,
getDatabase(env),
params.id as UUID,
data.connectedActor,
domain,
@ -36,7 +37,7 @@ export const onRequestDelete: PagesFunction<Env, any, ContextData> = async ({ pa
}
export async function handleRequestGet(
db: D1Database,
db: Database,
id: UUID,
domain: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- To be used when we implement private statuses
@ -62,7 +63,7 @@ export async function handleRequestGet(
}
export async function handleRequestDelete(
db: D1Database,
db: Database,
id: UUID,
connectedActor: Person,
domain: string,

Wyświetl plik

@ -6,10 +6,11 @@ import type { Env } from 'wildebeest/backend/src/types/env'
import { getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
import { getReplies } from 'wildebeest/backend/src/mastodon/reply'
import type { Context } from 'wildebeest/backend/src/types/status'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, params.id as string)
return handleRequest(domain, getDatabase(env), params.id as string)
}
const headers = {
@ -17,7 +18,7 @@ const headers = {
'content-type': 'application/json; charset=utf-8',
}
export async function handleRequest(domain: string, db: D1Database, id: string): Promise<Response> {
export async function handleRequest(domain: string, db: Database, id: string): Promise<Response> {
const obj = await getObjectByMastodonId(db, id)
if (obj === null) {
return new Response('', { status: 404 })

Wyświetl plik

@ -13,14 +13,15 @@ import type { Note } from 'wildebeest/backend/src/activitypub/objects/note'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import { toMastodonStatusFromObject } from 'wildebeest/backend/src/mastodon/status'
import { originalObjectIdSymbol, originalActorIdSymbol } from 'wildebeest/backend/src/activitypub/objects'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ env, data, params, request }) => {
const domain = new URL(request.url).hostname
return handleRequest(env.DATABASE, params.id as string, data.connectedActor, env.userKEK, domain)
return handleRequest(getDatabase(env), params.id as string, data.connectedActor, env.userKEK, domain)
}
export async function handleRequest(
db: D1Database,
db: Database,
id: string,
connectedActor: Person,
userKEK: string,

Wyświetl plik

@ -13,14 +13,15 @@ import type { Note } from 'wildebeest/backend/src/activitypub/objects/note'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import { toMastodonStatusFromObject } from 'wildebeest/backend/src/mastodon/status'
import { originalActorIdSymbol, originalObjectIdSymbol } from 'wildebeest/backend/src/activitypub/objects'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ env, data, params, request }) => {
const domain = new URL(request.url).hostname
return handleRequest(env.DATABASE, params.id as string, data.connectedActor, env.userKEK, env.QUEUE, domain)
return handleRequest(getDatabase(env), params.id as string, data.connectedActor, env.userKEK, env.QUEUE, domain)
}
export async function handleRequest(
db: D1Database,
db: Database,
id: string,
connectedActor: Person,
userKEK: string,

Wyświetl plik

@ -5,6 +5,7 @@ import type { Env } from 'wildebeest/backend/src/types/env'
import { getTag } from 'wildebeest/backend/src/mastodon/hashtag'
import * as errors from 'wildebeest/backend/src/errors'
import { cors } from 'wildebeest/backend/src/utils/cors'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
const headers = {
...cors(),
@ -13,10 +14,10 @@ const headers = {
export const onRequestGet: PagesFunction<Env, any, ContextData> = async ({ params, env, request }) => {
const domain = new URL(request.url).hostname
return handleRequestGet(env.DATABASE, domain, params.tag as string)
return handleRequestGet(getDatabase(env), domain, params.tag as string)
}
export async function handleRequestGet(db: D1Database, domain: string, value: string): Promise<Response> {
export async function handleRequestGet(db: Database, domain: string, value: string): Promise<Response> {
const tag = await getTag(db, domain, value)
if (tag === null) {
return errors.tagNotFound(value)

Wyświetl plik

@ -2,6 +2,7 @@ import type { Env } from 'wildebeest/backend/src/types/env'
import { cors } from 'wildebeest/backend/src/utils/cors'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import { getPublicTimeline, LocalPreference } from 'wildebeest/backend/src/mastodon/timeline'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
const headers = {
...cors(),
@ -15,12 +16,12 @@ export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request,
const only_media = searchParams.get('only_media') === 'true'
const offset = Number.parseInt(searchParams.get('offset') ?? '0')
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, { local, remote, only_media, offset })
return handleRequest(domain, getDatabase(env), { local, remote, only_media, offset })
}
export async function handleRequest(
domain: string,
db: D1Database,
db: Database,
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: use only_media
{ local = false, remote = false, only_media = false, offset = 0 } = {}
): Promise<Response> {

Wyświetl plik

@ -2,6 +2,7 @@ import type { Env } from 'wildebeest/backend/src/types/env'
import { cors } from 'wildebeest/backend/src/utils/cors'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import * as timelines from 'wildebeest/backend/src/mastodon/timeline'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
const headers = {
...cors(),
@ -10,10 +11,10 @@ const headers = {
export const onRequest: PagesFunction<Env, any, ContextData> = async ({ request, env, params }) => {
const domain = new URL(request.url).hostname
return handleRequest(env.DATABASE, request, domain, params.tag as string)
return handleRequest(getDatabase(env), request, domain, params.tag as string)
}
export async function handleRequest(db: D1Database, request: Request, domain: string, tag: string): Promise<Response> {
export async function handleRequest(db: Database, request: Request, domain: string, tag: string): Promise<Response> {
const url = new URL(request.url)
if (url.searchParams.has('max_id')) {
return new Response(JSON.stringify([]), { headers })

Wyświetl plik

@ -3,13 +3,14 @@ import { cors } from 'wildebeest/backend/src/utils/cors'
import { DEFAULT_THUMBNAIL } from 'wildebeest/backend/src/config'
import type { InstanceConfigV2 } from 'wildebeest/backend/src/types/configs'
import { getVersion } from 'wildebeest/config/versions'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
export const onRequest: PagesFunction<Env, any> = async ({ env, request }) => {
const domain = new URL(request.url).hostname
return handleRequest(domain, env.DATABASE, env)
return handleRequest(domain, getDatabase(env), env)
}
export async function handleRequest(domain: string, db: D1Database, env: Env) {
export async function handleRequest(domain: string, db: Database, env: Env) {
const headers = {
...cors(),
'content-type': 'application/json; charset=utf-8',

Wyświetl plik

@ -6,14 +6,15 @@ import type { ContextData } from 'wildebeest/backend/src/types/context'
import type { MediaAttachment } from 'wildebeest/backend/src/types/media'
import type { Person } from 'wildebeest/backend/src/activitypub/actors'
import { mastodonIdSymbol } from 'wildebeest/backend/src/activitypub/objects'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ request, env, data }) => {
return handleRequestPost(request, env.DATABASE, data.connectedActor, env.CF_ACCOUNT_ID, env.CF_API_TOKEN)
return handleRequestPost(request, getDatabase(env), data.connectedActor, env.CF_ACCOUNT_ID, env.CF_API_TOKEN)
}
export async function handleRequestPost(
request: Request,
db: D1Database,
db: Database,
connectedActor: Person,
accountId: string,

Wyświetl plik

@ -11,16 +11,17 @@ import type { Env } from 'wildebeest/backend/src/types/env'
import type { ContextData } from 'wildebeest/backend/src/types/context'
import * as errors from 'wildebeest/backend/src/errors'
import { updateObjectProperty } from 'wildebeest/backend/src/activitypub/objects'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
export const onRequestPut: PagesFunction<Env, any, ContextData> = async ({ params, env, request }) => {
return handleRequestPut(env.DATABASE, params.id as UUID, request)
return handleRequestPut(getDatabase(env), params.id as UUID, request)
}
type UpdateMedia = {
description?: string
}
export async function handleRequestPut(db: D1Database, id: UUID, request: Request): Promise<Response> {
export async function handleRequestPut(db: Database, id: UUID, request: Request): Promise<Response> {
// Update the image properties
{
const image = (await getObjectByMastodonId(db, id)) as Image

Wyświetl plik

@ -8,6 +8,7 @@ import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { loadExternalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'
import { personFromRow } from 'wildebeest/backend/src/activitypub/actors'
import type { Handle } from 'wildebeest/backend/src/utils/parse'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
const headers = {
...cors(),
@ -21,10 +22,10 @@ type SearchResult = {
}
export const onRequest: PagesFunction<Env, any> = async ({ request, env }) => {
return handleRequest(env.DATABASE, request)
return handleRequest(getDatabase(env), request)
}
export async function handleRequest(db: D1Database, request: Request): Promise<Response> {
export async function handleRequest(db: Database, request: Request): Promise<Response> {
const url = new URL(request.url)
if (!url.searchParams.has('q')) {
@ -49,7 +50,7 @@ export async function handleRequest(db: D1Database, request: Request): Promise<R
if (useWebFinger && query.domain !== null) {
const acct = `${query.localPart}@${query.domain}`
const res = await queryAcct(query.domain, acct)
const res = await queryAcct(query.domain, db, acct)
if (res !== null) {
out.accounts.push(await loadExternalMastodonAccount(acct, res))
}

Wyświetl plik

@ -5,16 +5,17 @@ import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import { parseHandle } from 'wildebeest/backend/src/utils/parse'
import { queryAcct } from 'wildebeest/backend/src/webfinger'
import * as errors from 'wildebeest/backend/src/errors'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ env, request, data }) => {
return handleRequestPost(env.DATABASE, request, data.connectedActor)
return handleRequestPost(getDatabase(env), request, data.connectedActor)
}
type AddAliasRequest = {
alias: string
}
export async function handleRequestPost(db: D1Database, request: Request, connectedActor: Actor): Promise<Response> {
export async function handleRequestPost(db: Database, request: Request, connectedActor: Actor): Promise<Response> {
const body = await request.json<AddAliasRequest>()
const handle = parseHandle(body.alias)
@ -23,7 +24,7 @@ export async function handleRequestPost(db: D1Database, request: Request, connec
console.warn("account migration within an instance isn't supported")
return new Response('', { status: 400 })
}
const actor = await queryAcct(handle.domain, acct)
const actor = await queryAcct(handle.domain, db, acct)
if (actor === null) {
return errors.resourceNotFound('actor', acct)
}

Wyświetl plik

@ -6,14 +6,15 @@ import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
import { parse } from 'cookie'
import * as errors from 'wildebeest/backend/src/errors'
import * as access from 'wildebeest/backend/src/access'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ request, env }) => {
return handlePostRequest(request, env.DATABASE, env.userKEK, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD)
return handlePostRequest(request, getDatabase(env), env.userKEK, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD)
}
export async function handlePostRequest(
request: Request,
db: D1Database,
db: Database,
userKEK: string,
accessDomain: string,
accessAud: string

Wyświetl plik

@ -7,16 +7,17 @@ import * as errors from 'wildebeest/backend/src/errors'
import { getClientById } from 'wildebeest/backend/src/mastodon/client'
import * as access from 'wildebeest/backend/src/access'
import { getPersonByEmail } from 'wildebeest/backend/src/activitypub/actors'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
// Extract the JWT token sent by Access (running before us).
const extractJWTFromRequest = (request: Request) => request.headers.get('Cf-Access-Jwt-Assertion') || ''
export const onRequestPost: PagesFunction<Env, any, ContextData> = async ({ request, env }) => {
return handleRequestPost(request, env.DATABASE, env.userKEK, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD)
return handleRequestPost(request, getDatabase(env), env.userKEK, env.ACCESS_AUTH_DOMAIN, env.ACCESS_AUD)
}
export async function buildRedirect(
db: D1Database,
db: Database,
request: Request,
isFirstLogin: boolean,
jwt: string
@ -64,7 +65,7 @@ export async function buildRedirect(
export async function handleRequestPost(
request: Request,
db: D1Database,
db: Database,
userKEK: string,
accessDomain: string,
accessAud: string

Wyświetl plik

@ -3,6 +3,7 @@
import { cors } from 'wildebeest/backend/src/utils/cors'
import * as errors from 'wildebeest/backend/src/errors'
import type { Env } from 'wildebeest/backend/src/types/env'
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
import { readBody } from 'wildebeest/backend/src/utils/body'
import { getClientById } from 'wildebeest/backend/src/mastodon/client'
@ -11,10 +12,10 @@ type Body = {
}
export const onRequest: PagesFunction<Env, any> = async ({ request, env }) => {
return handleRequest(env.DATABASE, request)
return handleRequest(getDatabase(env), request)
}
export async function handleRequest(db: D1Database, request: Request): Promise<Response> {
export async function handleRequest(db: Database, request: Request): Promise<Response> {
const headers = {
...cors(),
'content-type': 'application/json; charset=utf-8',

Wyświetl plik

@ -66,18 +66,18 @@ const config: PlaywrightTestConfig = {
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: {
// ...devices['Pixel 5'],
// },
// },
// {
// name: 'Mobile Safari',
// use: {
// ...devices['iPhone 12'],
// },
// },
{
name: 'Mobile Chrome',
use: {
...devices['Pixel 5'],
},
},
{
name: 'Mobile Safari',
use: {
...devices['iPhone 12'],
},
},
/* Test against branded browsers. */
// {