improve be (+functions) linting:

- enable no-unsafe-call for be/functions linting
 - enable backend await-thenable eslint rule
 - enable backend no-misused-promises eslint rule
pull/75/head
Dario Piotrowicz 2023-01-11 15:45:07 +00:00
rodzic c15f6bec5e
commit 3738c7dda1
22 zmienionych plików z 230 dodań i 132 usunięć

Wyświetl plik

@ -18,6 +18,10 @@ module.exports = {
'@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-unused-vars': 'error',
'no-console': 'off', 'no-console': 'off',
'no-constant-condition': 'off', 'no-constant-condition': 'off',
'@typescript-eslint/require-await': 'off',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/no-misused-promises': 'error',
/* /*
Note: the following rules have been set to off so that linting Note: the following rules have been set to off so that linting
can pass with the current code, but we need to gradually can pass with the current code, but we need to gradually
@ -25,13 +29,9 @@ module.exports = {
*/ */
'@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-argument': 'off', '@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/restrict-plus-operands': 'off', '@typescript-eslint/restrict-plus-operands': 'off',
'@typescript-eslint/await-thenable': 'off',
'@typescript-eslint/require-await': 'off',
'@typescript-eslint/restrict-template-expressions': 'off', '@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/no-unnecessary-type-assertion': 'off', '@typescript-eslint/no-unnecessary-type-assertion': 'off',
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-inferrable-types': 'off', '@typescript-eslint/no-inferrable-types': 'off',

Wyświetl plik

@ -121,7 +121,7 @@ export const generateValidator =
const unroundedSecondsSinceEpoch = Date.now() / 1000 const unroundedSecondsSinceEpoch = Date.now() / 1000
const payloadObj = JSON.parse(textDecoder.decode(base64URLDecode(payload))) const payloadObj = JSON.parse(textDecoder.decode(base64URLDecode(payload))) as JWTPayload
// For testing disable JWT checks. // For testing disable JWT checks.
// Ideally we match the production behavior in testing but that // Ideally we match the production behavior in testing but that

Wyświetl plik

@ -29,7 +29,7 @@ function extractID(domain: string, s: string | URL): string {
return s.toString().replace(`https://${domain}/ap/users/`, '') return s.toString().replace(`https://${domain}/ap/users/`, '')
} }
export function makeGetObjectAsId(activity: Activity): Function { export function makeGetObjectAsId(activity: Activity) {
return () => { return () => {
let url: any = null let url: any = null
if (activity.object.id !== undefined) { if (activity.object.id !== undefined) {
@ -55,7 +55,7 @@ export function makeGetObjectAsId(activity: Activity): Function {
} }
} }
export function makeGetActorAsId(activity: Activity): Function { export function makeGetActorAsId(activity: Activity) {
return () => { return () => {
let url: any = null let url: any = null
if (activity.actor.id !== undefined) { if (activity.actor.id !== undefined) {

Wyświetl plik

@ -154,7 +154,7 @@ export async function getObjectByMastodonId(db: D1Database, id: UUID): Promise<O
return getObjectBy(db, 'mastodon_id', id) return getObjectBy(db, 'mastodon_id', id)
} }
export async function getObjectBy(db: D1Database, key: string, value: string): Promise<Object | null> { export async function getObjectBy(db: D1Database, key: string, value: string) {
const query = ` const query = `
SELECT * SELECT *
FROM objects FROM objects

Wyświetl plik

@ -1,3 +1,5 @@
import type { Env } from 'wildebeest/consumer/src'
const CACHE_DO_NAME = 'cachev1' const CACHE_DO_NAME = 'cachev1'
export interface Cache { export interface Cache {
@ -5,7 +7,7 @@ export interface Cache {
put<T>(key: string, value: T): Promise<void> put<T>(key: string, value: T): Promise<void>
} }
export function cacheFromEnv(env: any): Cache { export function cacheFromEnv(env: Env): Cache {
return { return {
async get<T>(key: string): Promise<T | null> { async get<T>(key: string): Promise<T | null> {
const id = env.DO_CACHE.idFromName(CACHE_DO_NAME) const id = env.DO_CACHE.idFromName(CACHE_DO_NAME)

Wyświetl plik

@ -8,7 +8,11 @@ import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
import type { WebPushInfos, WebPushMessage } from 'wildebeest/backend/src/webpush/webpushinfos' import type { WebPushInfos, WebPushMessage } from 'wildebeest/backend/src/webpush/webpushinfos'
import { WebPushResult } from 'wildebeest/backend/src/webpush/webpushinfos' import { WebPushResult } from 'wildebeest/backend/src/webpush/webpushinfos'
import type { Actor } from 'wildebeest/backend/src/activitypub/actors' import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import type { NotificationType, Notification } from 'wildebeest/backend/src/types/notification' import type {
NotificationType,
Notification,
NotificationsQueryResult,
} from 'wildebeest/backend/src/types/notification'
import { getSubscriptionForAllClients } from 'wildebeest/backend/src/mastodon/subscription' import { getSubscriptionForAllClients } from 'wildebeest/backend/src/mastodon/subscription'
import type { Cache } from 'wildebeest/backend/src/cache' import type { Cache } from 'wildebeest/backend/src/cache'
@ -24,10 +28,10 @@ export async function createNotification(
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
RETURNING id RETURNING id
` `
const row: { id: string } = await db const row = await db
.prepare(query) .prepare(query)
.bind(type, actor.id.toString(), fromActor.id.toString(), obj.id.toString()) .bind(type, actor.id.toString(), fromActor.id.toString(), obj.id.toString())
.first() .first<{ id: string }>()
return row.id return row.id
} }
@ -39,7 +43,7 @@ export async function insertFollowNotification(db: D1Database, actor: Actor, fro
VALUES (?, ?, ?) VALUES (?, ?, ?)
RETURNING id RETURNING id
` `
const row: { id: string } = await db.prepare(query).bind(type, actor.id.toString(), fromActor.id.toString()).first() const row = await db.prepare(query).bind(type, actor.id.toString(), fromActor.id.toString()).first<{ id: string }>()
return row.id return row.id
} }
@ -187,7 +191,7 @@ export async function getNotifications(db: D1Database, actor: Actor, domain: str
` `
const stmt = db.prepare(query).bind(actor.id.toString()) const stmt = db.prepare(query).bind(actor.id.toString())
const { results, success, error } = await stmt.all() const { results, success, error } = await stmt.all<NotificationsQueryResult>()
if (!success) { if (!success) {
throw new Error('SQL error: ' + error) throw new Error('SQL error: ' + error)
} }
@ -198,7 +202,7 @@ export async function getNotifications(db: D1Database, actor: Actor, domain: str
} }
for (let i = 0, len = results.length; i < len; i++) { for (let i = 0, len = results.length; i < len; i++) {
const result = results[i] as any const result = results[i]
const properties = JSON.parse(result.properties) const properties = JSON.parse(result.properties)
const notifFromActorId = new URL(result.notif_from_actor_id) const notifFromActorId = new URL(result.notif_from_actor_id)

Wyświetl plik

@ -1,5 +1,6 @@
import type { MastodonAccount } from 'wildebeest/backend/src/types/account' import type { MastodonAccount } from 'wildebeest/backend/src/types/account'
import type { MastodonStatus } from 'wildebeest/backend/src/types/status' import type { MastodonStatus } from 'wildebeest/backend/src/types/status'
import type { ObjectsRow } from './objects'
export type NotificationType = export type NotificationType =
| 'mention' | 'mention'
@ -20,3 +21,12 @@ export type Notification = {
account: MastodonAccount account: MastodonAccount
status?: MastodonStatus status?: MastodonStatus
} }
export interface NotificationsQueryResult extends ObjectsRow {
type: NotificationType
original_actor_id: URL
notif_from_actor_id: URL
notif_cdate: string
notif_id: URL
from_actor_id: string
}

Wyświetl plik

@ -0,0 +1,6 @@
export interface ObjectsRow {
properties: string
mastodon_id: string
id: URL
cdate: string
}

Wyświetl plik

@ -26,10 +26,8 @@ export async function readBody<T>(request: Request): Promise<T> {
// The `key[]` notiation is used when sending an array of values. // The `key[]` notiation is used when sending an array of values.
const key2 = key.replace('[]', '') const key2 = key.replace('[]', '')
if (out[key2] === undefined) { const outArr: unknown[] = (out[key2] ??= [])
out[key2] = [] outArr.push(value)
}
out[key2].push(value)
} else { } else {
out[key] = value out[key] = value
} }

Wyświetl plik

@ -1,4 +1,3 @@
// @ts-nocheck
// Copyright 2012 Joyent, Inc. All rights reserved. // Copyright 2012 Joyent, Inc. All rights reserved.
import { HEADER, HttpSignatureError, InvalidAlgorithmError, validateAlgorithm } from './utils' import { HEADER, HttpSignatureError, InvalidAlgorithmError, validateAlgorithm } from './utils'
@ -61,6 +60,9 @@ export type ParsedSignature = {
keyId: string keyId: string
signingString: string signingString: string
algorithm: string algorithm: string
scheme: string
params: Record<string, string | string[] | number>
opaque: string
} }
///--- Exported API ///--- Exported API
@ -141,10 +143,14 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
let tmpName = '' let tmpName = ''
let tmpValue = '' let tmpValue = ''
const parsed = { const parsed: ParsedSignature = {
scheme: authz === request.headers.get(HEADER.SIG) ? 'Signature' : '', scheme: authz === request.headers.get(HEADER.SIG) ? 'Signature' : '',
params: {}, params: {},
signingString: '', signingString: '',
signature: '',
keyId: '',
algorithm: '',
opaque: '',
} }
for (i = 0; i < authz.length; i++) { for (i = 0; i < authz.length; i++) {
@ -234,14 +240,16 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
} }
} }
let parsedHeaders: string[] = []
if (!parsed.params.headers || parsed.params.headers === '') { if (!parsed.params.headers || parsed.params.headers === '') {
if (request.headers.has('x-date')) { if (request.headers.has('x-date')) {
parsed.params.headers = ['x-date'] parsedHeaders = ['x-date']
} else { } else {
parsed.params.headers = ['date'] parsedHeaders = ['date']
} }
} else { } else if (typeof parsed.params.headers === 'string') {
parsed.params.headers = parsed.params.headers.split(' ') parsedHeaders = parsed.params.headers.split(' ')
} }
// Minimally validate the parsed object // Minimally validate the parsed object
@ -253,13 +261,13 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
if (!parsed.params.signature) throw new InvalidHeaderError('signature was not specified') if (!parsed.params.signature) throw new InvalidHeaderError('signature was not specified')
if (['date', 'x-date', '(created)'].every((hdr) => parsed.params.headers.indexOf(hdr) < 0)) { if (['date', 'x-date', '(created)'].every((hdr) => parsedHeaders.indexOf(hdr) < 0)) {
throw new MissingHeaderError('no signed date header') throw new MissingHeaderError('no signed date header')
} }
// Check the algorithm against the official list // Check the algorithm against the official list
try { try {
validateAlgorithm(parsed.params.algorithm, 'rsa') validateAlgorithm(parsed.params.algorithm as string, 'rsa')
} catch (e) { } catch (e) {
if (e instanceof InvalidAlgorithmError) if (e instanceof InvalidAlgorithmError)
throw new InvalidParamsError(parsed.params.algorithm + ' is not ' + 'supported') throw new InvalidParamsError(parsed.params.algorithm + ' is not ' + 'supported')
@ -267,9 +275,9 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
} }
// Build the signingString // Build the signingString
for (i = 0; i < parsed.params.headers.length; i++) { for (i = 0; i < parsedHeaders.length; i++) {
const h = parsed.params.headers[i].toLowerCase() const h = parsedHeaders[i].toLowerCase()
parsed.params.headers[i] = h parsedHeaders[i] = h
if (h === 'request-line') { if (h === 'request-line') {
if (!options.strict) { if (!options.strict) {
@ -292,6 +300,7 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
} else if (h === '(opaque)') { } else if (h === '(opaque)') {
const opaque = parsed.params.opaque const opaque = parsed.params.opaque
if (opaque === undefined) { if (opaque === undefined) {
//@ts-expect-error -- authzHeaderName doesn't exist TOFIX
throw new MissingHeaderError('opaque param was not in the ' + authzHeaderName + ' header') throw new MissingHeaderError('opaque param was not in the ' + authzHeaderName + ' header')
} }
parsed.signingString += '(opaque): ' + opaque parsed.signingString += '(opaque): ' + opaque
@ -305,17 +314,17 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
parsed.signingString += h + ': ' + value parsed.signingString += h + ': ' + value
} }
if (i + 1 < parsed.params.headers.length) parsed.signingString += '\n' if (i + 1 < parsedHeaders.length) parsed.signingString += '\n'
} }
// Check against the constraints // Check against the constraints
let date let date
let skew let skew
if (request.headers.date || request.headers.has('x-date')) { if (request.headers.get('date') || request.headers.has('x-date')) {
if (request.headers.has('x-date')) { if (request.headers.has('x-date')) {
date = new Date(request.headers.get('x-date') as string) date = new Date(request.headers.get('x-date') as string)
} else { } else {
date = new Date(request.headers.date) date = new Date(request.headers.get('date') as string)
} }
const now = new Date() const now = new Date()
skew = Math.abs(now.getTime() - date.getTime()) skew = Math.abs(now.getTime() - date.getTime())
@ -325,7 +334,7 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
} }
} }
if (parsed.params.created) { if (parsed.params.created && typeof parsed.params.created === 'number') {
skew = parsed.params.created - Math.floor(Date.now() / 1000) skew = parsed.params.created - Math.floor(Date.now() / 1000)
if (skew > options.clockSkew) { if (skew > options.clockSkew) {
throw new ExpiredRequestError( throw new ExpiredRequestError(
@ -340,7 +349,7 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
} }
} }
if (parsed.params.expires) { if (parsed.params.expires && typeof parsed.params.expires === 'number') {
const expiredSince = Math.floor(Date.now() / 1000) - parsed.params.expires const expiredSince = Math.floor(Date.now() / 1000) - parsed.params.expires
if (expiredSince > options.clockSkew) { if (expiredSince > options.clockSkew) {
throw new ExpiredRequestError( throw new ExpiredRequestError(
@ -352,14 +361,17 @@ export function parseRequest(request: Request, options?: Options): ParsedSignatu
headers.forEach(function (hdr) { headers.forEach(function (hdr) {
// Remember that we already checked any headers in the params // Remember that we already checked any headers in the params
// were in the request, so if this passes we're good. // were in the request, so if this passes we're good.
if (parsed.params.headers.indexOf(hdr.toLowerCase()) < 0) if (parsedHeaders.indexOf(hdr.toLowerCase()) < 0) {
throw new MissingHeaderError(hdr + ' was not a signed header') throw new MissingHeaderError(hdr + ' was not a signed header')
}
}) })
parsed.params.algorithm = parsed.params.algorithm.toLowerCase() const algorithm = parsed.params.algorithm as string
parsed.algorithm = parsed.params.algorithm.toUpperCase() parsed.params.algorithm = algorithm.toLowerCase()
parsed.keyId = parsed.params.keyId parsed.algorithm = algorithm.toUpperCase()
parsed.opaque = parsed.params.opaque parsed.keyId = parsed.params.keyId as string
parsed.signature = parsed.params.signature parsed.opaque = parsed.params.opaque as string
parsed.signature = parsed.params.signature as string
parsed.params.headers = parsedHeaders
return parsed return parsed
} }

Wyświetl plik

@ -21,16 +21,17 @@ describe('ActivityPub', () => {
beforeEach(() => { beforeEach(() => {
receivedActivity = null receivedActivity = null
globalThis.fetch = async (input: any) => { globalThis.fetch = async (input: RequestInfo) => {
if (input.url === `https://${domain}/ap/users/sven2/inbox`) { const request = new Request(input)
assert.equal(input.method, 'POST') if (request.url === `https://${domain}/ap/users/sven2/inbox`) {
const data = await input.json() assert.equal(request.method, 'POST')
const data = await request.json()
receivedActivity = data receivedActivity = data
console.log({ receivedActivity }) console.log({ receivedActivity })
return new Response('') return new Response('')
} }
throw new Error('unexpected request to ' + input.url) throw new Error('unexpected request to ' + request.url)
} }
}) })
@ -51,14 +52,17 @@ describe('ActivityPub', () => {
const row = await db const row = await db
.prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`) .prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`)
.bind(actor2.id.toString()) .bind(actor2.id.toString())
.first() .first<{
target_actor_id: object
state: string
}>()
assert(row) assert(row)
assert.equal(row.target_actor_id.toString(), actor.id.toString()) assert.equal(row.target_actor_id.toString(), actor.id.toString())
assert.equal(row.state, 'accepted') assert.equal(row.state, 'accepted')
assert(receivedActivity) assert(receivedActivity)
assert.equal(receivedActivity.type, 'Accept') assert.equal(receivedActivity.type, 'Accept')
assert.equal(receivedActivity.actor.toString(), actor.id.toString()) assert.equal((receivedActivity.actor as object).toString(), actor.id.toString())
assert.equal(receivedActivity.object.actor, activity.actor) assert.equal(receivedActivity.object.actor, activity.actor)
assert.equal(receivedActivity.object.type, activity.type) assert.equal(receivedActivity.object.type, activity.type)
}) })
@ -144,7 +148,11 @@ describe('ActivityPub', () => {
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_notifications').first() const entry = await db.prepare('SELECT * FROM actor_notifications').first<{
type: string
actor_id: object
from_actor_id: object
}>()
assert.equal(entry.type, 'follow') assert.equal(entry.type, 'follow')
assert.equal(entry.actor_id.toString(), actor.id.toString()) assert.equal(entry.actor_id.toString(), actor.id.toString())
assert.equal(entry.from_actor_id.toString(), actor2.id.toString()) assert.equal(entry.from_actor_id.toString(), actor2.id.toString())
@ -167,7 +175,7 @@ describe('ActivityPub', () => {
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
// Even if we followed multiple times, only one row should be present. // Even if we followed multiple times, only one row should be present.
const { count } = await db.prepare(`SELECT count(*) as count FROM actor_following`).first() const { count } = await db.prepare(`SELECT count(*) as count FROM actor_following`).first<{ count: number }>()
assert.equal(count, 1) assert.equal(count, 1)
}) })
}) })

Wyświetl plik

@ -6,6 +6,7 @@ import { cacheObject, getObjectById } from 'wildebeest/backend/src/activitypub/o
import { addFollowing } from 'wildebeest/backend/src/mastodon/follow' import { addFollowing } from 'wildebeest/backend/src/mastodon/follow'
import * as activityHandler from 'wildebeest/backend/src/activitypub/activities/handle' import * as activityHandler from 'wildebeest/backend/src/activitypub/activities/handle'
import { createPerson } from 'wildebeest/backend/src/activitypub/actors' import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
import { ObjectsRow } from 'wildebeest/backend/src/types/objects'
const adminEmail = 'admin@example.com' const adminEmail = 'admin@example.com'
const domain = 'cloudflare.com' const domain = 'cloudflare.com'
@ -29,7 +30,10 @@ describe('ActivityPub', () => {
} }
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_reblogs').first() const entry = await db.prepare('SELECT * FROM actor_reblogs').first<{
actor_id: URL
object_id: URL
}>()
assert.equal(entry.actor_id.toString(), actorB.id.toString()) assert.equal(entry.actor_id.toString(), actorB.id.toString())
assert.equal(entry.object_id.toString(), note.id.toString()) assert.equal(entry.object_id.toString(), note.id.toString())
}) })
@ -48,7 +52,11 @@ describe('ActivityPub', () => {
} }
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_notifications').first() const entry = await db.prepare('SELECT * FROM actor_notifications').first<{
type: string
actor_id: URL
from_actor_id: URL
}>()
assert(entry) assert(entry)
assert.equal(entry.type, 'reblog') assert.equal(entry.type, 'reblog')
assert.equal(entry.actor_id.toString(), actorA.id.toString()) assert.equal(entry.actor_id.toString(), actorA.id.toString())
@ -71,7 +79,7 @@ describe('ActivityPub', () => {
} }
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_favourites').first() const entry = await db.prepare('SELECT * FROM actor_favourites').first<{ actor_id: URL; object_id: URL }>()
assert.equal(entry.actor_id.toString(), actorB.id.toString()) assert.equal(entry.actor_id.toString(), actorB.id.toString())
assert.equal(entry.object_id.toString(), note.id.toString()) assert.equal(entry.object_id.toString(), note.id.toString())
}) })
@ -90,7 +98,11 @@ describe('ActivityPub', () => {
} }
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_notifications').first() const entry = await db.prepare('SELECT * FROM actor_notifications').first<{
type: string
actor_id: URL
from_actor_id: URL
}>()
assert.equal(entry.type, 'favourite') assert.equal(entry.type, 'favourite')
assert.equal(entry.actor_id.toString(), actorA.id.toString()) assert.equal(entry.actor_id.toString(), actorA.id.toString())
assert.equal(entry.from_actor_id.toString(), actorB.id.toString()) assert.equal(entry.from_actor_id.toString(), actorB.id.toString())
@ -110,7 +122,10 @@ describe('ActivityPub', () => {
} }
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_favourites').first() const entry = await db.prepare('SELECT * FROM actor_favourites').first<{
actor_id: URL
object_id: URL
}>()
assert.equal(entry.actor_id.toString(), actorB.id.toString()) assert.equal(entry.actor_id.toString(), actorB.id.toString())
assert.equal(entry.object_id.toString(), note.id.toString()) assert.equal(entry.object_id.toString(), note.id.toString())
}) })
@ -145,7 +160,10 @@ describe('ActivityPub', () => {
const row = await db const row = await db
.prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`) .prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`)
.bind(actor.id.toString()) .bind(actor.id.toString())
.first() .first<{
target_actor_id: string
state: string
}>()
assert(row) assert(row)
assert.equal(row.target_actor_id, 'https://' + domain + '/ap/users/sven2') assert.equal(row.target_actor_id, 'https://' + domain + '/ap/users/sven2')
assert.equal(row.state, 'accepted') assert.equal(row.state, 'accepted')
@ -204,7 +222,7 @@ describe('ActivityPub', () => {
const entry = await db const entry = await db
.prepare('SELECT objects.* FROM inbox_objects INNER JOIN objects ON objects.id=inbox_objects.object_id') .prepare('SELECT objects.* FROM inbox_objects INNER JOIN objects ON objects.id=inbox_objects.object_id')
.first() .first<ObjectsRow>()
const properties = JSON.parse(entry.properties) const properties = JSON.parse(entry.properties)
assert.equal(properties.content, 'test note') assert.equal(properties.content, 'test note')
}) })
@ -241,7 +259,10 @@ describe('ActivityPub', () => {
} }
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM outbox_objects WHERE actor_id=?').bind(remoteActorId).first() const entry = await db
.prepare('SELECT * FROM outbox_objects WHERE actor_id=?')
.bind(remoteActorId)
.first<{ actor_id: string }>()
assert.equal(entry.actor_id, remoteActorId) assert.equal(entry.actor_id, remoteActorId)
}) })
@ -263,7 +284,11 @@ describe('ActivityPub', () => {
} }
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_notifications').first() const entry = await db.prepare('SELECT * FROM actor_notifications').first<{
type: string
actor_id: URL
from_actor_id: URL
}>()
assert(entry) assert(entry)
assert.equal(entry.type, 'mention') assert.equal(entry.type, 'mention')
assert.equal(entry.actor_id.toString(), actorA.id.toString()) assert.equal(entry.actor_id.toString(), actorA.id.toString())
@ -303,7 +328,11 @@ describe('ActivityPub', () => {
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
} }
const entry = await db.prepare('SELECT * FROM actor_replies').first() const entry = await db.prepare('SELECT * FROM actor_replies').first<{
actor_id: string
object_id: string
in_reply_to_object_id: string
}>()
assert.equal(entry.actor_id, actor.id.toString().toString()) assert.equal(entry.actor_id, actor.id.toString().toString())
const obj: any = await getObjectById(db, entry.object_id) const obj: any = await getObjectById(db, entry.object_id)
@ -319,7 +348,7 @@ describe('ActivityPub', () => {
const db = await makeDB() const db = await makeDB()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const activity: any = { const activity = {
type: 'Create', type: 'Create',
actor: actor.id.toString(), actor: actor.id.toString(),
to: ['some actor'], to: ['some actor'],
@ -332,7 +361,7 @@ describe('ActivityPub', () => {
} }
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const row = await db.prepare('SELECT * FROM outbox_objects').first() const row = await db.prepare('SELECT * FROM outbox_objects').first<{ target: string }>()
assert.equal(row.target, 'some actor') assert.equal(row.target, 'some actor')
}) })
@ -355,7 +384,7 @@ describe('ActivityPub', () => {
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const row = await db.prepare(`SELECT * from objects`).first() const row = await db.prepare(`SELECT * from objects`).first<ObjectsRow>()
const { content, name } = JSON.parse(row.properties) const { content, name } = JSON.parse(row.properties)
assert.equal( assert.equal(
content, content,
@ -454,7 +483,7 @@ describe('ActivityPub', () => {
const updatedObject = await db const updatedObject = await db
.prepare('SELECT * FROM objects WHERE original_object_id=?') .prepare('SELECT * FROM objects WHERE original_object_id=?')
.bind(object.id) .bind(object.id)
.first() .first<ObjectsRow>()
assert(updatedObject) assert(updatedObject)
assert.equal(JSON.parse(updatedObject.properties).content, newObject.content) assert.equal(JSON.parse(updatedObject.properties).content, newObject.content)
}) })
@ -500,7 +529,10 @@ describe('ActivityPub', () => {
} }
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const object = await db.prepare('SELECT * FROM objects').first() const object = await db.prepare('SELECT * FROM objects').first<{
type: string
original_actor_id: string
}>()
assert(object) assert(object)
assert.equal(object.type, 'Note') assert.equal(object.type, 'Note')
assert.equal(object.original_actor_id, remoteActorId) assert.equal(object.original_actor_id, remoteActorId)
@ -508,7 +540,7 @@ describe('ActivityPub', () => {
const outbox_object = await db const outbox_object = await db
.prepare('SELECT * FROM outbox_objects WHERE actor_id=?') .prepare('SELECT * FROM outbox_objects WHERE actor_id=?')
.bind(remoteActorId) .bind(remoteActorId)
.first() .first<{ actor_id: string }>()
assert(outbox_object) assert(outbox_object)
assert.equal(outbox_object.actor_id, remoteActorId) assert.equal(outbox_object.actor_id, remoteActorId)
}) })

Wyświetl plik

@ -26,6 +26,17 @@ async function generateVAPIDKeys(): Promise<JWK> {
describe('Mastodon APIs', () => { describe('Mastodon APIs', () => {
describe('instance', () => { describe('instance', () => {
type Data = {
rules: unknown[]
uri: string
title: string
email: string
description: string
version: string
domain: string
contact: { email: string }
}
test('return the instance infos v1', async () => { test('return the instance infos v1', async () => {
const env = { const env = {
INSTANCE_TITLE: 'a', INSTANCE_TITLE: 'a',
@ -39,7 +50,7 @@ describe('Mastodon APIs', () => {
assertJSON(res) assertJSON(res)
{ {
const data = await res.json<any>() const data = await res.json<Data>()
assert.equal(data.rules.length, 0) assert.equal(data.rules.length, 0)
assert.equal(data.uri, domain) assert.equal(data.uri, domain)
assert.equal(data.title, 'a') assert.equal(data.title, 'a')
@ -77,7 +88,7 @@ describe('Mastodon APIs', () => {
assertJSON(res) assertJSON(res)
{ {
const data = await res.json<any>() const data = await res.json<Data>()
assert.equal(data.rules.length, 0) assert.equal(data.rules.length, 0)
assert.equal(data.domain, domain) assert.equal(data.domain, domain)
assert.equal(data.title, 'a') assert.equal(data.title, 'a')
@ -248,7 +259,7 @@ describe('Mastodon APIs', () => {
const res = await subscription.handlePostRequest(db, req, connectedActor, client.id, vapidKeys) const res = await subscription.handlePostRequest(db, req, connectedActor, client.id, vapidKeys)
assert.equal(res.status, 200) assert.equal(res.status, 200)
const { count } = await db.prepare('SELECT count(*) as count FROM subscriptions').first() const { count } = await db.prepare('SELECT count(*) as count FROM subscriptions').first<{ count: number }>()
assert.equal(count, 1) assert.equal(count, 1)
}) })
}) })

Wyświetl plik

@ -169,7 +169,7 @@ describe('Mastodon APIs', () => {
globalThis.fetch = async (input: RequestInfo, data: any) => { globalThis.fetch = async (input: RequestInfo, data: any) => {
if (input === 'https://api.cloudflare.com/client/v4/accounts/CF_ACCOUNT_ID/images/v1') { if (input === 'https://api.cloudflare.com/client/v4/accounts/CF_ACCOUNT_ID/images/v1') {
assert.equal(data.method, 'POST') assert.equal(data.method, 'POST')
const file: any = data.body.get('file') const file: any = (data.body as { get: (str: string) => any }).get('file')
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
@ -333,7 +333,7 @@ describe('Mastodon APIs', () => {
assert.equal(data.following_count, 2) assert.equal(data.following_count, 2)
assert.equal(data.statuses_count, 1) assert.equal(data.statuses_count, 1)
assert(isUrlValid(data.url)) assert(isUrlValid(data.url))
assert(data.url.includes(domain)) assert((data.url as string).includes(domain))
}) })
test('get local actor statuses', async () => { test('get local actor statuses', async () => {
@ -561,7 +561,7 @@ describe('Mastodon APIs', () => {
// Statuses were imported locally and once was a reblog of an already // Statuses were imported locally and once was a reblog of an already
// existing local object. // existing local object.
const row = await db.prepare(`SELECT count(*) as count FROM objects`).first() const row: { count: number } = await db.prepare(`SELECT count(*) as count FROM objects`).first()
assert.equal(row.count, 2) assert.equal(row.count, 2)
}) })
@ -640,7 +640,7 @@ describe('Mastodon APIs', () => {
const db = await makeDB() const db = await makeDB()
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com') const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
globalThis.fetch = async (input: any) => { globalThis.fetch = async (input: RequestInfo) => {
if (input.toString() === 'https://example.com/.well-known/webfinger?resource=acct%3Asven%40example.com') { if (input.toString() === 'https://example.com/.well-known/webfinger?resource=acct%3Asven%40example.com') {
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
@ -718,7 +718,7 @@ describe('Mastodon APIs', () => {
test('get local actor followers', async () => { test('get local actor followers', async () => {
globalThis.fetch = async (input: any) => { globalThis.fetch = async (input: any) => {
if (input.toString() === 'https://' + domain + '/ap/users/sven2') { if ((input as object).toString() === 'https://' + domain + '/ap/users/sven2') {
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
id: 'https://example.com/actor', id: 'https://example.com/actor',
@ -746,7 +746,7 @@ describe('Mastodon APIs', () => {
test('get local actor following', async () => { test('get local actor following', async () => {
globalThis.fetch = async (input: any) => { globalThis.fetch = async (input: any) => {
if (input.toString() === 'https://' + domain + '/ap/users/sven2') { if ((input as object).toString() === 'https://' + domain + '/ap/users/sven2') {
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
id: 'https://example.com/foo', id: 'https://example.com/foo',
@ -776,7 +776,7 @@ describe('Mastodon APIs', () => {
const db = await makeDB() const db = await makeDB()
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com') const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
globalThis.fetch = async (input: any) => { globalThis.fetch = async (input: RequestInfo) => {
if (input.toString() === 'https://example.com/.well-known/webfinger?resource=acct%3Asven%40example.com') { if (input.toString() === 'https://example.com/.well-known/webfinger?resource=acct%3Asven%40example.com') {
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
@ -952,11 +952,9 @@ describe('Mastodon APIs', () => {
beforeEach(() => { beforeEach(() => {
receivedActivity = null receivedActivity = null
globalThis.fetch = async (input: any) => { globalThis.fetch = async (input: RequestInfo) => {
if ( const request = new Request(input)
input.toString() === if (request.url === 'https://' + domain + '/.well-known/webfinger?resource=acct%3Aactor%40' + domain + '') {
'https://' + domain + '/.well-known/webfinger?resource=acct%3Aactor%40' + domain + ''
) {
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
links: [ links: [
@ -970,7 +968,7 @@ describe('Mastodon APIs', () => {
) )
} }
if (input.toString() === 'https://social.com/sven') { if (request.url === 'https://social.com/sven') {
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
id: `https://${domain}/ap/users/actor`, id: `https://${domain}/ap/users/actor`,
@ -980,13 +978,13 @@ describe('Mastodon APIs', () => {
) )
} }
if (input.url === 'https://example.com/inbox') { if (request.url === 'https://example.com/inbox') {
assert.equal(input.method, 'POST') assert.equal(request.method, 'POST')
receivedActivity = await input.json() receivedActivity = await request.json()
return new Response('') return new Response('')
} }
throw new Error('unexpected request to ' + input) throw new Error('unexpected request to ' + request.url)
} }
}) })
@ -1005,7 +1003,11 @@ describe('Mastodon APIs', () => {
assert(receivedActivity) assert(receivedActivity)
assert.equal(receivedActivity.type, 'Follow') assert.equal(receivedActivity.type, 'Follow')
const row = await db const row: {
target_actor_acct: string
target_actor_id: string
state: string
} = await db
.prepare(`SELECT target_actor_acct, target_actor_id, state FROM actor_following WHERE actor_id=?`) .prepare(`SELECT target_actor_acct, target_actor_id, state FROM actor_following WHERE actor_id=?`)
.bind(actor.id.toString()) .bind(actor.id.toString())
.first() .first()
@ -1036,7 +1038,7 @@ describe('Mastodon APIs', () => {
const row = await db const row = await db
.prepare(`SELECT count(*) as count FROM actor_following WHERE actor_id=?`) .prepare(`SELECT count(*) as count FROM actor_following WHERE actor_id=?`)
.bind(actor.id.toString()) .bind(actor.id.toString())
.first() .first<{ count: number }>()
assert(row) assert(row)
assert.equal(row.count, 0) assert.equal(row.count, 0)
}) })

Wyświetl plik

@ -12,8 +12,9 @@ const domain = 'cloudflare.com'
describe('Mastodon APIs', () => { describe('Mastodon APIs', () => {
describe('media', () => { describe('media', () => {
test('upload image creates object', async () => { test('upload image creates object', async () => {
globalThis.fetch = async (input: any) => { globalThis.fetch = async (input: RequestInfo) => {
if (input.url.toString() === 'https://api.cloudflare.com/client/v4/accounts/testaccountid/images/v1') { const request = new Request(input)
if (request.url.toString() === 'https://api.cloudflare.com/client/v4/accounts/testaccountid/images/v1') {
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
@ -24,7 +25,7 @@ describe('Mastodon APIs', () => {
}) })
) )
} }
throw new Error('unexpected request to ' + input.url) throw new Error('unexpected request to ' + request.url)
} }
const db = await makeDB() const db = await makeDB()

Wyświetl plik

@ -117,7 +117,7 @@ describe('Mastodon APIs', () => {
globalThis.fetch = async (input: RequestInfo, data: any) => { globalThis.fetch = async (input: RequestInfo, data: any) => {
if (input === 'https://push.com') { if (input === 'https://push.com') {
assert(data.headers['Authorization'].includes('WebPush')) assert((data.headers['Authorization'] as string).includes('WebPush'))
const cryptoKeyHeader = parseCryptoKey(data.headers['Crypto-Key']) const cryptoKeyHeader = parseCryptoKey(data.headers['Crypto-Key'])
assert(cryptoKeyHeader.dh) assert(cryptoKeyHeader.dh)

Wyświetl plik

@ -5,6 +5,7 @@ import * as oauth_token from 'wildebeest/functions/oauth/token'
import { isUrlValid, makeDB, assertCORS, assertJSON, createTestClient } from '../utils' import { isUrlValid, makeDB, assertCORS, assertJSON, createTestClient } from '../utils'
import { TEST_JWT, ACCESS_CERTS } from '../test-data' import { TEST_JWT, ACCESS_CERTS } from '../test-data'
import { strict as assert } from 'node:assert/strict' import { strict as assert } from 'node:assert/strict'
import { Actor } from 'wildebeest/backend/src/activitypub/actors'
const userKEK = 'test_kek3' const userKEK = 'test_kek3'
const accessDomain = 'access.com' const accessDomain = 'access.com'
@ -100,7 +101,7 @@ describe('Mastodon APIs', () => {
) )
// actor isn't created yet // actor isn't created yet
const { count } = await db.prepare('SELECT count(*) as count FROM actors').first() const { count } = await db.prepare('SELECT count(*) as count FROM actors').first<{ count: number }>()
assert.equal(count, 0) assert.equal(count, 0)
}) })
@ -126,7 +127,9 @@ describe('Mastodon APIs', () => {
const location = res.headers.get('location') const location = res.headers.get('location')
assert.equal(location, 'https://redirect.com/a') assert.equal(location, 'https://redirect.com/a')
const actor = await db.prepare('SELECT * FROM actors').first() const actor = await db
.prepare('SELECT * FROM actors')
.first<{ properties: string; email: string; id: string } & Actor>()
const properties = JSON.parse(actor.properties) const properties = JSON.parse(actor.properties)
assert.equal(actor.email, 'a@cloudflare.com') assert.equal(actor.email, 'a@cloudflare.com')
@ -134,7 +137,7 @@ describe('Mastodon APIs', () => {
assert.equal(properties.name, 'name') assert.equal(properties.name, 'name')
assert(isUrlValid(actor.id)) assert(isUrlValid(actor.id))
// ensure that we generate a correct key pairs for the user // ensure that we generate a correct key pairs for the user
assert((await getSigningKey(userKEK, db, actor)) instanceof CryptoKey) assert((await getSigningKey(userKEK, db, actor as Actor)) instanceof CryptoKey)
}) })
test('token error on unknown client', async () => { test('token error on unknown client', async () => {

Wyświetl plik

@ -2,7 +2,7 @@ import { strict as assert } from 'node:assert/strict'
import { insertReply } from 'wildebeest/backend/src/mastodon/reply' import { insertReply } from 'wildebeest/backend/src/mastodon/reply'
import { getMentions } from 'wildebeest/backend/src/mastodon/status' import { getMentions } from 'wildebeest/backend/src/mastodon/status'
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox' import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note' import { createPublicNote, Note } from 'wildebeest/backend/src/activitypub/objects/note'
import { createImage } from 'wildebeest/backend/src/activitypub/objects/image' import { createImage } from 'wildebeest/backend/src/activitypub/objects/image'
import * as statuses from 'wildebeest/functions/api/v1/statuses' import * as statuses from 'wildebeest/functions/api/v1/statuses'
import * as statuses_get from 'wildebeest/functions/api/v1/statuses/[id]' import * as statuses_get from 'wildebeest/functions/api/v1/statuses/[id]'
@ -16,6 +16,7 @@ import { isUrlValid, makeDB, assertJSON, streamToArrayBuffer, makeQueue, makeCac
import * as activities from 'wildebeest/backend/src/activitypub/activities' import * as activities from 'wildebeest/backend/src/activitypub/activities'
import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow' import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow'
import { MessageType } from 'wildebeest/backend/src/types/queue' import { MessageType } from 'wildebeest/backend/src/types/queue'
import { MastodonStatus } from 'wildebeest/backend/src/types'
const userKEK = 'test_kek4' const userKEK = 'test_kek4'
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)) const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
@ -60,9 +61,9 @@ describe('Mastodon APIs', () => {
assert.equal(res.status, 200) assert.equal(res.status, 200)
assertJSON(res) assertJSON(res)
const data = await res.json<any>() const data = await res.json<MastodonStatus>()
assert(data.uri.includes('example.com')) assert((data.uri as unknown as string).includes('example.com'))
assert(data.uri.includes(data.id)) assert((data.uri as unknown as string).includes(data.id))
// Required fields from https://github.com/mastodon/mastodon-android/blob/master/mastodon/src/main/java/org/joinmastodon/android/model/Status.java // Required fields from https://github.com/mastodon/mastodon-android/blob/master/mastodon/src/main/java/org/joinmastodon/android/model/Status.java
assert(data.created_at !== undefined) assert(data.created_at !== undefined)
assert(data.account !== undefined) assert(data.account !== undefined)
@ -84,7 +85,7 @@ describe('Mastodon APIs', () => {
FROM objects FROM objects
` `
) )
.first() .first<{ content: string; original_actor_id: URL; original_object_id: unknown }>()
assert.equal(row.content, 'my status <p>evil</p>') // note the sanitization assert.equal(row.content, 'my status <p>evil</p>') // note the sanitization
assert.equal(row.original_actor_id.toString(), actor.id.toString()) assert.equal(row.original_actor_id.toString(), actor.id.toString())
assert.equal(row.original_object_id, null) assert.equal(row.original_object_id, null)
@ -138,7 +139,7 @@ describe('Mastodon APIs', () => {
const res = await statuses.handleRequest(req, db, connectedActor, userKEK, queue, cache) const res = await statuses.handleRequest(req, db, connectedActor, userKEK, queue, cache)
assert.equal(res.status, 200) assert.equal(res.status, 200)
const row = await db.prepare(`SELECT count(*) as count FROM outbox_objects`).first() const row = await db.prepare(`SELECT count(*) as count FROM outbox_objects`).first<{ count: number }>()
assert.equal(row.count, 1) assert.equal(row.count, 1)
}) })
@ -180,7 +181,7 @@ describe('Mastodon APIs', () => {
}) })
test('create new status with mention delivers ActivityPub Note', async () => { test('create new status with mention delivers ActivityPub Note', async () => {
let deliveredNote: any = null let deliveredNote: Note | null = null
globalThis.fetch = async (input: RequestInfo, data: any) => { globalThis.fetch = async (input: RequestInfo, data: any) => {
if (input.toString() === 'https://remote.com/.well-known/webfinger?resource=acct%3Asven%40remote.com') { if (input.toString() === 'https://remote.com/.well-known/webfinger?resource=acct%3Asven%40remote.com') {
@ -246,12 +247,15 @@ describe('Mastodon APIs', () => {
assert.equal(res.status, 200) assert.equal(res.status, 200)
assert(deliveredNote) assert(deliveredNote)
assert.equal(deliveredNote.type, 'Create') assert.equal((deliveredNote as { type: string }).type, 'Create')
assert.equal(deliveredNote.actor, `https://${domain}/ap/users/sven`) assert.equal((deliveredNote as { actor: string }).actor, `https://${domain}/ap/users/sven`)
assert.equal(deliveredNote.object.attributedTo, `https://${domain}/ap/users/sven`) assert.equal(
assert.equal(deliveredNote.object.type, 'Note') (deliveredNote as { object: { attributedTo: string } }).object.attributedTo,
assert(deliveredNote.object.to.includes(activities.PUBLIC_GROUP)) `https://${domain}/ap/users/sven`
assert.equal(deliveredNote.object.cc.length, 1) )
assert.equal((deliveredNote as { object: { type: string } }).object.type, 'Note')
assert((deliveredNote as { object: { to: string[] } }).object.to.includes(activities.PUBLIC_GROUP))
assert.equal((deliveredNote as { object: { cc: string[] } }).object.cc.length, 1)
}) })
test('create new status with image', async () => { test('create new status with image', async () => {
@ -302,15 +306,16 @@ describe('Mastodon APIs', () => {
) )
.run() .run()
globalThis.fetch = async (input: any) => { globalThis.fetch = async (input: RequestInfo) => {
if (input.url === actor.id.toString() + '/inbox') { const request = new Request(input)
assert.equal(input.method, 'POST') if (request.url === actor.id.toString() + '/inbox') {
const body = await input.json() assert.equal(request.method, 'POST')
const body = await request.json()
deliveredActivity = body deliveredActivity = body
return new Response() return new Response()
} }
throw new Error('unexpected request to ' + JSON.stringify(input)) throw new Error('unexpected request to ' + request.url)
} }
const connectedActor: any = actor const connectedActor: any = actor
@ -336,7 +341,7 @@ describe('Mastodon APIs', () => {
const data = await res.json<any>() const data = await res.json<any>()
assert.equal(data.favourited, true) assert.equal(data.favourited, true)
const row = await db.prepare(`SELECT * FROM actor_favourites`).first() const row = await db.prepare(`SELECT * FROM actor_favourites`).first<{ actor_id: string; object_id: string }>()
assert.equal(row.actor_id, actor.id.toString()) assert.equal(row.actor_id, actor.id.toString())
assert.equal(row.object_id, note.id.toString()) assert.equal(row.object_id, note.id.toString())
}) })
@ -469,7 +474,7 @@ describe('Mastodon APIs', () => {
const data = await res.json<any>() const data = await res.json<any>()
assert.equal(data.reblogged, true) assert.equal(data.reblogged, true)
const row = await db.prepare(`SELECT * FROM actor_reblogs`).first() const row = await db.prepare(`SELECT * FROM actor_reblogs`).first<{ actor_id: string; object_id: string }>()
assert.equal(row.actor_id, actor.id.toString()) assert.equal(row.actor_id, actor.id.toString())
assert.equal(row.object_id, note.id.toString()) assert.equal(row.object_id, note.id.toString())
}) })
@ -486,7 +491,7 @@ describe('Mastodon APIs', () => {
const res = await statuses_reblog.handleRequest(db, note.mastodonId!, connectedActor, userKEK, queue, domain) const res = await statuses_reblog.handleRequest(db, note.mastodonId!, connectedActor, userKEK, queue, domain)
assert.equal(res.status, 200) assert.equal(res.status, 200)
const row = await db.prepare(`SELECT * FROM outbox_objects`).first() const row = await db.prepare(`SELECT * FROM outbox_objects`).first<{ actor_id: string; object_id: string }>()
assert.equal(row.actor_id, actor.id.toString()) assert.equal(row.actor_id, actor.id.toString())
assert.equal(row.object_id, note.id.toString()) assert.equal(row.object_id, note.id.toString())
}) })
@ -513,15 +518,16 @@ describe('Mastodon APIs', () => {
) )
.run() .run()
globalThis.fetch = async (input: any) => { globalThis.fetch = async (input: RequestInfo) => {
if (input.url === 'https://cloudflare.com/ap/users/sven/inbox') { const request = new Request(input)
assert.equal(input.method, 'POST') if (request.url === 'https://cloudflare.com/ap/users/sven/inbox') {
const body = await input.json() assert.equal(request.method, 'POST')
const body = await request.json()
deliveredActivity = body deliveredActivity = body
return new Response() return new Response()
} }
throw new Error('unexpected request to ' + JSON.stringify(input)) throw new Error('unexpected request to ' + request.url)
} }
const connectedActor: any = actor const connectedActor: any = actor
@ -588,13 +594,16 @@ describe('Mastodon APIs', () => {
` `
) )
.bind(data.id) .bind(data.id)
.first() .first<{ inReplyTo: string }>()
assert(row !== undefined) assert(row !== undefined)
assert.equal(row.inReplyTo, note.id.toString()) assert.equal(row.inReplyTo, note.id.toString())
} }
{ {
const row = await db.prepare('select * from actor_replies').first() const row = await db.prepare('select * from actor_replies').first<{
actor_id: string
in_reply_to_object_id: string
}>()
assert(row !== undefined) assert(row !== undefined)
assert.equal(row.actor_id, actor.id.toString()) assert.equal(row.actor_id, actor.id.toString())
assert.equal(row.in_reply_to_object_id, note.id.toString()) assert.equal(row.in_reply_to_object_id, note.id.toString())
@ -619,7 +628,7 @@ describe('Mastodon APIs', () => {
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache) const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
assert.equal(res.status, 400) assert.equal(res.status, 400)
const data = await res.json<any>() const data = await res.json<{ error: string }>()
assert(data.error.includes('Limit exceeded')) assert(data.error.includes('Limit exceeded'))
}) })
@ -644,7 +653,7 @@ describe('Mastodon APIs', () => {
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache) const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
assert.equal(res.status, 400) assert.equal(res.status, 400)
const data = await res.json<any>() const data = await res.json<{ error: string }>()
assert(data.error.includes('Limit exceeded')) assert(data.error.includes('Limit exceeded'))
}) })
}) })

Wyświetl plik

@ -18,7 +18,7 @@ export function isUrlValid(s: string) {
return url.protocol === 'https:' return url.protocol === 'https:'
} }
export async function makeDB(): Promise<any> { export async function makeDB(): Promise<D1Database> {
const db = new Database(':memory:') const db = new Database(':memory:')
const db2 = new BetaDatabase(db)! const db2 = new BetaDatabase(db)!
@ -27,10 +27,10 @@ export async function makeDB(): Promise<any> {
for (let i = 0, len = migrations.length; i < len; i++) { for (let i = 0, len = migrations.length; i < len; i++) {
const content = await fs.readFile(path.join('migrations', migrations[i]), 'utf-8') const content = await fs.readFile(path.join('migrations', migrations[i]), 'utf-8')
await db.exec(content) db.exec(content)
} }
return db2 return db2 as unknown as D1Database
} }
export function assertCORS(response: Response) { export function assertCORS(response: Response) {

Wyświetl plik

@ -1,7 +1,7 @@
import { ContextData } from 'wildebeest/backend/src/types/context' import { ContextData } from 'wildebeest/backend/src/types/context'
import { cors } from 'wildebeest/backend/src/utils/cors' import { cors } from 'wildebeest/backend/src/utils/cors'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk' import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import { Env } from 'wildebeest/backend/src/types/env' import type { Env } from 'wildebeest/backend/src/types/env'
import { createClient } from 'wildebeest/backend/src/mastodon/client' import { createClient } from 'wildebeest/backend/src/mastodon/client'
import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription' import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'
import { getVAPIDKeys } from 'wildebeest/backend/src/config' import { getVAPIDKeys } from 'wildebeest/backend/src/config'

Wyświetl plik

@ -1,6 +1,6 @@
// https://docs.joinmastodon.org/methods/notifications/#get-one // https://docs.joinmastodon.org/methods/notifications/#get-one
import type { Notification } from 'wildebeest/backend/src/types/notification' import type { Notification, NotificationsQueryResult } from 'wildebeest/backend/src/types/notification'
import { urlToHandle } from 'wildebeest/backend/src/utils/handle' import { urlToHandle } from 'wildebeest/backend/src/utils/handle'
import { getPersonById } from 'wildebeest/backend/src/activitypub/actors' import { getPersonById } from 'wildebeest/backend/src/activitypub/actors'
import { loadExternalMastodonAccount } from 'wildebeest/backend/src/mastodon/account' import { loadExternalMastodonAccount } from 'wildebeest/backend/src/mastodon/account'
@ -36,7 +36,7 @@ export async function handleRequest(
WHERE actor_notifications.id=? AND actor_notifications.actor_id=? WHERE actor_notifications.id=? AND actor_notifications.actor_id=?
` `
const row: any = await db.prepare(query).bind(id, connectedActor.id.toString()).first() const row = await db.prepare(query).bind(id, connectedActor.id.toString()).first<NotificationsQueryResult>()
const from_actor_id = new URL(row.from_actor_id) const from_actor_id = new URL(row.from_actor_id)
const fromActor = await getPersonById(db, from_actor_id) const fromActor = await getPersonById(db, from_actor_id)

Wyświetl plik

@ -6,7 +6,7 @@ import type { Actor } from 'wildebeest/backend/src/activitypub/actors'
import { createSubscription, getSubscription } from 'wildebeest/backend/src/mastodon/subscription' import { createSubscription, getSubscription } from 'wildebeest/backend/src/mastodon/subscription'
import type { CreateRequest } from 'wildebeest/backend/src/mastodon/subscription' import type { CreateRequest } from 'wildebeest/backend/src/mastodon/subscription'
import { ContextData } from 'wildebeest/backend/src/types/context' import { ContextData } from 'wildebeest/backend/src/types/context'
import { Env } from 'wildebeest/backend/src/types/env' import type { Env } from 'wildebeest/backend/src/types/env'
import * as errors from 'wildebeest/backend/src/errors' import * as errors from 'wildebeest/backend/src/errors'
import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription' import { VAPIDPublicKey } from 'wildebeest/backend/src/mastodon/subscription'