Firefish v1.0.5-dev22

This commit is contained in:
naskya 2023-11-27 19:32:26 +09:00
parent 74f7ef6b98
commit 9fcd3e496f
Signed by: naskya
GPG key ID: 164DFF24E2D40139
71 changed files with 359 additions and 694 deletions

View file

@ -1,195 +0,0 @@
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Firefish configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌─────┐
#───┘ URL └─────────────────────────────────────────────────────
# Final accessible URL seen by a user.
url: https://example.tld/
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# URL SETTINGS AFTER THAT!
# ┌───────────────────────┐
#───┘ Port and TLS settings └───────────────────────────────────
#
# Misskey requires a reverse proxy to support HTTPS connections.
#
# +----- https://example.tld/ ------------+
# +------+ |+-------------+ +----------------+|
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
# +------+ |+-------------+ +----------------+|
# +---------------------------------------+
#
# You need to set up a reverse proxy. (e.g. nginx)
# An encrypted connection with HTTPS is highly recommended
# because tokens may be transferred in GET requests.
# The port that your Misskey server should listen on.
port: 3000
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────
db:
host: postgres
port: 5432
# Database name
db: postgres
# Auth
user: postgres
pass: test
# Whether disable Caching queries
#disableCache: true
# Extra Connection options
#extra:
# ssl: true
# ┌─────────────────────┐
#───┘ Redis configuration └─────────────────────────────────────
redis:
host: redis
port: 6379
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
#pass: example-pass
#prefix: example-prefix
#db: 1
# ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └─────────────────────────────
#elasticsearch:
# host: localhost
# port: 9200
# ssl: false
# user:
# pass:
# ┌───────────────┐
#───┘ ID generation └───────────────────────────────────────────
# You can select the ID generation method.
# You don't usually need to change this setting, but you can
# change it according to your preferences.
# Available methods:
# aid ... Short, Millisecond accuracy
# meid ... Similar to ObjectID, Millisecond accuracy
# ulid ... Millisecond accuracy
# objectid ... This is left for backward compatibility
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
# ID SETTINGS AFTER THAT!
id: 'aid'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Max note length, should be < 8000.
#maxNoteLength: 3000
# Whether disable HSTS
#disableHsts: true
# Number of worker processes
#clusterLimit: 1
# Job concurrency per worker
# deliverJobConcurrency: 128
# inboxJobConcurrency: 16
# Job rate limiter
# deliverJobPerSec: 128
# inboxJobPerSec: 16
# Job attempts
# deliverJobMaxAttempts: 12
# inboxJobMaxAttempts: 8
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4
# Syslog option
#syslog:
# host: localhost
# port: 514
# Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128
#proxyBypassHosts: [
# 'example.com',
# '192.0.2.8'
#]
# Proxy for SMTP/SMTPS
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
# Media Proxy
#mediaProxy: https://example.com/proxy
# Proxy remote files (default: false)
#proxyRemoteFiles: true
#allowedPrivateNetworks: [
# '127.0.0.1/32'
#]
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
# Managed hosting settings
# !!!!!!!!!!
# >>>>>> NORMAL SELF-HOSTERS, STAY AWAY! <<<<<<
# >>>>>> YOU DON'T NEED THIS! <<<<<<
# !!!!!!!!!!
# Each category is optional, but if each item in each category is mandatory!
# If you mess this up, that's on you, you've been warned...
#maxUserSignups: 100
#isManagedHosting: true
#deepl:
# managed: true
# authKey: ''
# isPro: false
#
#email:
# managed: true
# address: 'example@email.com'
# host: 'email.com'
# port: 587
# user: 'example@email.com'
# pass: ''
# useImplicitSslTls: false
#
#objectStorage:
# managed: true
# baseUrl: ''
# bucket: ''
# prefix: ''
# endpoint: ''
# region: ''
# accessKey: ''
# secretKey: ''
# useSsl: true
# connnectOverProxy: false
# setPublicReadOnUpload: true
# s3ForcePathStyle: true
# !!!!!!!!!!
# >>>>>> AGAIN, NORMAL SELF-HOSTERS, STAY AWAY! <<<<<<
# >>>>>> YOU DON'T NEED THIS, ABOVE SETTINGS ARE FOR MANAGED HOSTING ONLY! <<<<<<
# !!!!!!!!!!
# Seriously. Do NOT fill out the above settings if you're self-hosting.
# They're much better off being set from the control panel.

View file

@ -1,38 +0,0 @@
url: http://localhost:3000
port: 3000
db:
host: 127.0.0.1
port: 5432
db: firefish
user: firefish
pass: firefish
redis:
host: localhost
port: 6379
family: 4
#sonic:
# host: localhost
# port: 1491
# auth: SecretPassword
# collection: notes
# bucket: default
#elasticsearch:
# host: localhost
# port: 9200
# ssl: false
# user:
# pass:
id: 'aid'
reservedUsernames:
- root
- admin
- administrator
- me
- system

View file

@ -1,4 +0,0 @@
# db settings
POSTGRES_PASSWORD=test
POSTGRES_USER=postgres
POSTGRES_DB=postgres

View file

@ -1,82 +0,0 @@
replicaCount: 1
resources:
requests:
cpu: 0.5
memory: 512Mi
limits:
cpu: 1
memory: 1Gi
firefish:
domain: example.tld
smtp:
from_address: noreply@example.tld
port: 587
server: smtp.gmail.com
useImplicitSslTls: false
login: me@example.tld
password: CHANGEME
objectStorage:
baseUrl: https://example-bucket.nyc3.cdn.digitaloceanspaces.com
access_key: CHANGEME
access_secret: CHANGEME
bucket: example-bucket
endpoint: nyc3.digitaloceanspaces.com:443
region: nyc3
allowedPrivateNetworks: []
ingress:
enabled: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt
hosts:
- host: example.tld
paths:
- path: /
pathType: ImplementationSpecific
tls:
- secretName: example-tld-certificate
hosts:
- example.tld
elasticsearch:
enabled: false
postgresql:
auth:
password: CHANGEME
postgresPassword: CHANGEME
primary:
persistence:
enabled: true
storageClass: vultr-block-storage
size: 25Gi
resources:
requests:
cpu: 0.25
memory: 256Mi
limits:
cpu: 0.5
memory: 512Mi
metrics:
enabled: true
redis:
auth:
password: CHANGEME
master:
resources:
requests:
cpu: 0.25
memory: 256Mi
limits:
cpu: 0.5
memory: 256Mi
persistence:
storageclass: vultr-block-storage
size: 10Gi
replica:
replicaCount: 0
metrics:
enabled: true

View file

@ -52,7 +52,7 @@ gulp.task("build:backend:script", () => {
"./packages/backend/src/server/web/bios.js", "./packages/backend/src/server/web/bios.js",
"./packages/backend/src/server/web/cli.js", "./packages/backend/src/server/web/cli.js",
]) ])
.pipe(replace("LANGS", JSON.stringify(Object.keys(locales)))) .pipe(replace("SUPPORTED_LANGS", JSON.stringify(Object.keys(locales))))
.pipe( .pipe(
terser({ terser({
toplevel: true, toplevel: true,

View file

@ -112,7 +112,7 @@ you: "Tu"
clickToShow: "Fes clic per a mostrar" clickToShow: "Fes clic per a mostrar"
sensitive: "NSFW" sensitive: "NSFW"
add: "Afegeix" add: "Afegeix"
reaction: "Reaccions" reaction: "Reacció"
reactionSetting: "Reaccions a mostrar al selector de reaccions" reactionSetting: "Reaccions a mostrar al selector de reaccions"
reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem
\"+\" per afegir." \"+\" per afegir."
@ -792,11 +792,11 @@ customEmojis: Emojis personalitzats
cacheRemoteFilesDescription: Quan aquesta opció està desactivada, els fitxers remots cacheRemoteFilesDescription: Quan aquesta opció està desactivada, els fitxers remots
es carreguen directament del servidor remot. Desactivar-la farà que baixi l'ús d'emmagatzematge, es carreguen directament del servidor remot. Desactivar-la farà que baixi l'ús d'emmagatzematge,
però incrementa el tràfic, perquè les miniatures no es generaran. però incrementa el tràfic, perquè les miniatures no es generaran.
flagAsBot: Marca aquest compte com a bot flagAsBot: Marca aquest compte com automatitzat
flagAsBotDescription: Activa aquesta opció si aquest compte és controlat per un programa. flagAsBotDescription: Activa aquesta opció si aquest compte és controlat per un programa.
Si s'activa, això actuarà com una bandera per a altres desenvolupadors i ajuda a Si s'activa, això actuarà com una bandera per a altres desenvolupadors i ajuda a
prevenir cadenes de interaccions infinites amb altres bots a més d'ajustar els sistemes prevenir cadenes de interaccions infinites amb altres comptes automatitzats a més
interns de Firefish per tractar aquest compte com un bot. d'ajustar els sistemes interns de Firefish per tractar aquest compte com automatitzat.
flagAsCat: Ets un gat? 🐱 flagAsCat: Ets un gat? 🐱
flagShowTimelineReplies: Mostra respostes a la línia de temps flagShowTimelineReplies: Mostra respostes a la línia de temps
flagAsCatDescription: Guanyaràs unes orelles de gat i parlares com un gat! flagAsCatDescription: Guanyaràs unes orelles de gat i parlares com un gat!
@ -2127,7 +2127,7 @@ clipsDesc: Els clips són com marcadors categoritzats que es poden compartir. Po
selectChannel: Selecciona un canal selectChannel: Selecciona un canal
isLocked: Aquest compte té les següents aprovacions isLocked: Aquest compte té les següents aprovacions
isPatron: Mecenes de Firefish isPatron: Mecenes de Firefish
isBot: Aquest compte és un bot isBot: Aquest es un compte automatitzat
isModerator: Moderador isModerator: Moderador
isAdmin: Administrador isAdmin: Administrador
_filters: _filters:
@ -2199,3 +2199,16 @@ openServerInfo: Mostra la informació del servidor fent clic al símbol del serv
en un missatge en un missatge
vibrate: Activar vibracions vibrate: Activar vibracions
clickToShowPatterns: Fes clic per veure patrons de mòduls clickToShowPatterns: Fes clic per veure patrons de mòduls
iconSet: Conjunt d'Icones
_iconSets:
fill: Omplerts
regular: Normals
bold: Negreta
duotone: Bitó
light: Prims
showAttachedNotes: Mostrar publicacions que contenen aquest fitxer
reactions: Reaccions
attachedToNotes: Publicacions que contenen aquest fitxer
replies: Respostes
quotes: Cites
renotes: Impulsos

View file

@ -2218,3 +2218,4 @@ openServerInfo: Anzeigen von Serverinformationen durch Anklicken des Server-Tick
in einem Beitrag in einem Beitrag
vibrate: Vibrationen abspielen vibrate: Vibrationen abspielen
clickToShowPatterns: Klicken um Modul-Muster anzuzeigen clickToShowPatterns: Klicken um Modul-Muster anzuzeigen
replies: Antworten

View file

@ -312,7 +312,7 @@ agreeTo: "I agree to {0}"
tos: "Terms of Service" tos: "Terms of Service"
start: "Begin" start: "Begin"
home: "Home" home: "Home"
remoteUserCaution: "Information from remote users may be incomplete." remoteUserCaution: "Information from remote users are incomplete."
activity: "Activity" activity: "Activity"
images: "Images" images: "Images"
birthday: "Birthday" birthday: "Birthday"
@ -2188,5 +2188,5 @@ _iconSets:
regular: "Regular" regular: "Regular"
fill: "Filled" fill: "Filled"
duotone: "Duotone" duotone: "Duotone"
moreUrls: "pinned pages" moreUrls: "Pinned pages"
moreUrlsDescription: "Enter the pages you want to pin to the help menu in the lower left corner using this notation:\n\"Display name\": https://example.com/" moreUrlsDescription: "Enter the pages you want to pin to the help menu in the lower left corner using this notation:\n\"Display name\": https://example.com/"

View file

@ -278,8 +278,7 @@ agreeTo: "Saya setuju kepada {0}"
tos: "Syarat dan ketentuan" tos: "Syarat dan ketentuan"
start: "Mulai" start: "Mulai"
home: "Beranda" home: "Beranda"
remoteUserCaution: "Informasi ini mungkin tidak mutakhir, karena pengguna ini berasal remoteUserCaution: "Informasi dari pengguna luar tidak lengkap."
dari instansi luar."
activity: "Aktivitas" activity: "Aktivitas"
images: "Gambar" images: "Gambar"
birthday: "Tanggal lahir" birthday: "Tanggal lahir"
@ -2192,3 +2191,5 @@ reactions: Reaksi
replies: Balasan replies: Balasan
quotes: Kutipan quotes: Kutipan
renotes: Postingan ulang renotes: Postingan ulang
showAttachedNotes: Tampilkan postingan dengan berkas ini
attachedToNotes: Posting dengan berkas ini

View file

@ -268,7 +268,7 @@ agreeTo: "Sono d'accordo con {0}"
tos: "Termini d'uso" tos: "Termini d'uso"
start: "Inizia" start: "Inizia"
home: "Home" home: "Home"
remoteUserCaution: "Le informazioni degli utenti remoti possono essere incomplete." remoteUserCaution: "Le informazioni degli utenti remoti sono incomplete."
activity: "Attività" activity: "Attività"
images: "Immagini" images: "Immagini"
birthday: "Compleanno" birthday: "Compleanno"
@ -2180,3 +2180,5 @@ reactions: Reazioni
replies: Risposte replies: Risposte
quotes: Citazioni quotes: Citazioni
renotes: Boost renotes: Boost
showAttachedNotes: Mostra i post con questo allegato
attachedToNotes: Post con questo allegato

View file

@ -1 +1 @@
465a585f47037ce26834587429f0bd341e35f76f a6aa7d2b74020dfc9303995918e63727fb3cb2c0

View file

@ -139,7 +139,7 @@ importers:
specifier: 6.0.1 specifier: 6.0.1
version: 6.0.1 version: 6.0.1
argon2: argon2:
specifier: 0.31.2 specifier: ^0.31.2
version: 0.31.2 version: 0.31.2
aws-sdk: aws-sdk:
specifier: 2.1498.0 specifier: 2.1498.0
@ -624,7 +624,7 @@ importers:
specifier: 0.16.0 specifier: 0.16.0
version: 0.16.0 version: 0.16.0
'@types/autosize': '@types/autosize':
specifier: 4.0.3 specifier: ^4.0.3
version: 4.0.3 version: 4.0.3
'@types/glob': '@types/glob':
specifier: 8.1.0 specifier: 8.1.0
@ -717,7 +717,7 @@ importers:
specifier: 2.30.0 specifier: 2.30.0
version: 2.30.0 version: 2.30.0
emojilib: emojilib:
specifier: 3.0.11 specifier: ^3.0.11
version: 3.0.11 version: 3.0.11
eslint-config-prettier: eslint-config-prettier:
specifier: 9.0.0 specifier: 9.0.0
@ -810,7 +810,7 @@ importers:
specifier: 1.6.0 specifier: 1.6.0
version: 1.6.0 version: 1.6.0
tinyld: tinyld:
specifier: 1.3.4 specifier: ^1.3.4
version: 1.3.4 version: 1.3.4
typescript: typescript:
specifier: 5.2.2 specifier: 5.2.2
@ -831,10 +831,10 @@ importers:
specifier: 3.3.8 specifier: 3.3.8
version: 3.3.8(typescript@5.2.2) version: 3.3.8(typescript@5.2.2)
vue-draggable-plus: vue-draggable-plus:
specifier: 0.2.7 specifier: ^0.2.7
version: 0.2.7(@types/sortablejs@1.15.5) version: 0.2.7(@types/sortablejs@1.15.5)
vue-plyr: vue-plyr:
specifier: 7.0.0 specifier: ^7.0.0
version: 7.0.0 version: 7.0.0
vue-prism-editor: vue-prism-editor:
specifier: 2.0.0-alpha.2 specifier: 2.0.0-alpha.2

View file

@ -1,6 +1,6 @@
{ {
"name": "firefish", "name": "firefish",
"version": "1.0.5-dev21", "version": "1.0.5-dev22",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -40,7 +40,7 @@
"adm-zip": "0.5.10", "adm-zip": "0.5.10",
"ajv": "8.12.0", "ajv": "8.12.0",
"archiver": "6.0.1", "archiver": "6.0.1",
"argon2": "0.31.2", "argon2": "^0.31.2",
"aws-sdk": "2.1498.0", "aws-sdk": "2.1498.0",
"axios": "1.6.2", "axios": "1.6.2",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",

View file

@ -1,12 +1,12 @@
import * as Acct from "@/misc/acct.js"; import * as Acct from "@/misc/acct.js";
import { Cache } from "@/misc/cache.js"; import { Cache } from "@/misc/cache.js";
import { getWordHardMute } from "@/misc/check-word-mute.js"; import { getWordHardMute } from "@/misc/check-word-mute.js";
import { getFullApAccount } from "@/misc/convert-host.js";
import type { Packed } from "@/misc/schema.js"; import type { Packed } from "@/misc/schema.js";
import type { Antenna } from "@/models/entities/antenna.js"; import type { Antenna } from "@/models/entities/antenna.js";
import type { Note } from "@/models/entities/note.js"; import type { Note } from "@/models/entities/note.js";
import type { User } from "@/models/entities/user.js"; import type { User } from "@/models/entities/user.js";
import { Blockings, Followings, UserProfiles } from "@/models/index.js"; import { Blockings, Followings, UserProfiles } from "@/models/index.js";
import { getFullApAccount } from "./convert-host.js";
const blockingCache = new Cache<User["id"][]>("blocking", 60 * 5); const blockingCache = new Cache<User["id"][]>("blocking", 60 * 5);
const mutedWordsCache = new Cache<string[][] | undefined>("mutedWords", 60 * 5); const mutedWordsCache = new Cache<string[][] | undefined>("mutedWords", 60 * 5);

View file

@ -1,25 +0,0 @@
// AID
// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ2の[ノイズ文字列]
import * as crypto from "node:crypto";
const TIME2000 = 946684800000;
let counter = crypto.randomBytes(2).readUInt16LE(0);
function getTime(time: number) {
time = time - TIME2000;
if (time < 0) time = 0;
return time.toString(36).padStart(8, "0");
}
function getNoise() {
return counter.toString(36).padStart(2, "0").slice(-2);
}
export function genAid(date: Date): string {
const t = date.getTime();
if (Number.isNaN(t)) throw "Failed to create AID: Invalid Date";
counter++;
return getTime(t) + getNoise();
}

View file

@ -1,26 +0,0 @@
const CHARS = "0123456789abcdef";
function getTime(time: number) {
if (time < 0) time = 0;
if (time === 0) {
return CHARS[0];
}
time += 0x800000000000;
return time.toString(16).padStart(12, CHARS[0]);
}
function getRandom() {
let str = "";
for (let i = 0; i < 12; i++) {
str += CHARS[Math.floor(Math.random() * CHARS.length)];
}
return str;
}
export function genMeid(date: Date): string {
return getTime(date.getTime()) + getRandom();
}

View file

@ -1,28 +0,0 @@
const CHARS = "0123456789abcdef";
// 4bit Fixed hex value 'g'
// 44bit UNIX Time ms in Hex
// 48bit Random value in Hex
function getTime(time: number) {
if (time < 0) time = 0;
if (time === 0) {
return CHARS[0];
}
return time.toString(16).padStart(11, CHARS[0]);
}
function getRandom() {
let str = "";
for (let i = 0; i < 12; i++) {
str += CHARS[Math.floor(Math.random() * CHARS.length)];
}
return str;
}
export function genMeidg(date: Date): string {
return `g${getTime(date.getTime())}${getRandom()}`;
}

View file

@ -1,26 +0,0 @@
const CHARS = "0123456789abcdef";
function getTime(time: number) {
if (time < 0) time = 0;
if (time === 0) {
return CHARS[0];
}
time = Math.floor(time / 1000);
return time.toString(16).padStart(8, CHARS[0]);
}
function getRandom() {
let str = "";
for (let i = 0; i < 16; i++) {
str += CHARS[Math.floor(Math.random() * CHARS.length)];
}
return str;
}
export function genObjectId(date: Date): string {
return getTime(date.getTime()) + getRandom();
}

View file

@ -6,6 +6,7 @@ import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import type { UserPublickey } from "@/models/entities/user-publickey.js"; import type { UserPublickey } from "@/models/entities/user-publickey.js";
import type { CacheableRemoteUser } from "@/models/entities/user.js"; import type { CacheableRemoteUser } from "@/models/entities/user.js";
import { Instances } from "@/models/index.js"; import { Instances } from "@/models/index.js";
import { verifySignature } from "@/remote/activitypub/check-fetch.js";
import DbResolver from "@/remote/activitypub/db-resolver.js"; import DbResolver from "@/remote/activitypub/db-resolver.js";
import { LdSignature } from "@/remote/activitypub/misc/ld-signature.js"; import { LdSignature } from "@/remote/activitypub/misc/ld-signature.js";
import { resolvePerson } from "@/remote/activitypub/models/person.js"; import { resolvePerson } from "@/remote/activitypub/models/person.js";
@ -109,6 +110,12 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
); );
} }
if (httpSignatureValidated) {
if (!verifySignature(signature, authUser.key)) {
return "skip: Invalid HTTP signature";
}
}
// また、signatureのsignerは、activity.actorと一致する必要がある // また、signatureのsignerは、activity.actorと一致する必要がある
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) { if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る // 一致しなくても、でもLD-Signatureがありそうならそっちも見る

View file

@ -1,7 +1,7 @@
import type Bull from "bull"; import type Bull from "bull";
import { activeUsersChart } from "@/services/chart/index.js"; import { activeUsersChart } from "@/services/chart/index.js";
import { queueLogger } from "../../logger.js"; import { queueLogger } from "@/queue/logger.js";
const logger = queueLogger.createSubLogger("clean-charts"); const logger = queueLogger.createSubLogger("clean-charts");

View file

@ -3,6 +3,8 @@ import { checkExpiredMutings } from "./check-expired-mutings.js";
import { cleanCharts } from "./clean-charts.js"; import { cleanCharts } from "./clean-charts.js";
import { clean } from "./clean.js"; import { clean } from "./clean.js";
import { setLocalEmojiSizes } from "./local-emoji-size.js"; import { setLocalEmojiSizes } from "./local-emoji-size.js";
import { resyncCharts } from "./resync-charts.js";
import { tickCharts } from "./tick-charts.js";
import { verifyLinks } from "./verify-links.js"; import { verifyLinks } from "./verify-links.js";
const jobs = { const jobs = {

View file

@ -1,4 +1,6 @@
import type { IncomingMessage } from "http"; import type { IncomingMessage } from "http";
import { verify } from "node:crypto";
import { createHash } from "node:crypto";
import { URL } from "url"; import { URL } from "url";
import config from "@/config/index.js"; import config from "@/config/index.js";
import { toPuny } from "@/misc/convert-host.js"; import { toPuny } from "@/misc/convert-host.js";
@ -6,9 +8,10 @@ import { fetchMeta } from "@/misc/fetch-meta.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js"; import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import type { UserPublickey } from "@/models/entities/user-publickey.js"; import type { UserPublickey } from "@/models/entities/user-publickey.js";
import type { CacheableRemoteUser } from "@/models/entities/user.js"; import type { CacheableRemoteUser } from "@/models/entities/user.js";
import { toSingle } from "@/prelude/array.js";
import DbResolver from "@/remote/activitypub/db-resolver.js"; import DbResolver from "@/remote/activitypub/db-resolver.js";
import { getApId } from "@/remote/activitypub/type.js"; import { getApId } from "@/remote/activitypub/type.js";
import httpSignature from "@peertube/http-signature"; import httpSignature, { IParsedSignature } from "@peertube/http-signature";
export async function hasSignature(req: IncomingMessage): Promise<string> { export async function hasSignature(req: IncomingMessage): Promise<string> {
const meta = await fetchMeta(); const meta = await fetchMeta();
@ -28,10 +31,14 @@ export async function hasSignature(req: IncomingMessage): Promise<string> {
export async function checkFetch(req: IncomingMessage): Promise<number> { export async function checkFetch(req: IncomingMessage): Promise<number> {
const meta = await fetchMeta(); const meta = await fetchMeta();
if (meta.secureMode || meta.privateMode) { if (meta.secureMode || meta.privateMode) {
if (req.headers.host !== config.host) return 400;
let signature; let signature;
try { try {
signature = httpSignature.parseRequest(req, { headers: [] }); signature = httpSignature.parseRequest(req, {
headers: ["(request-target)", "host", "date"],
});
} catch (e) { } catch (e) {
return 401; return 401;
} }
@ -108,6 +115,8 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
if (!httpSignatureValidated) { if (!httpSignatureValidated) {
return 403; return 403;
} }
return verifySignature(signature, authUser.key) ? 200 : 401;
} }
return 200; return 200;
} }
@ -130,3 +139,39 @@ export async function getSignatureUser(req: IncomingMessage): Promise<{
keyId.hash = ""; keyId.hash = "";
return await dbResolver.getAuthUserFromApId(getApId(keyId.toString())); return await dbResolver.getAuthUserFromApId(getApId(keyId.toString()));
} }
export function verifySignature(
sig: IParsedSignature,
key: UserPublickey,
): boolean {
if (!["hs2019", "rsa-sha256"].includes(sig.algorithm.toLowerCase()))
return false;
try {
return verify(
"rsa-sha256",
Buffer.from(sig.signingString, "utf8"),
key.keyPem,
Buffer.from(sig.params.signature, "base64"),
);
} catch {
// Algo not supported
return false;
}
}
export function verifyDigest(
body: string,
digest: string | string[] | undefined,
): boolean {
digest = toSingle(digest);
if (
body == null ||
digest == null ||
!digest.toLowerCase().startsWith("sha-256=")
)
return false;
return (
createHash("sha256").update(body).digest("base64") === digest.substring(8)
);
}

View file

@ -1,4 +1,5 @@
import config from "@/config/index.js"; import config from "@/config/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import type { NoteReaction } from "@/models/entities/note-reaction.js"; import type { NoteReaction } from "@/models/entities/note-reaction.js";
import type { Note } from "@/models/entities/note.js"; import type { Note } from "@/models/entities/note.js";
import { Emojis } from "@/models/index.js"; import { Emojis } from "@/models/index.js";

View file

@ -1,7 +1,8 @@
import Router from "@koa/router"; import Router from "@koa/router";
import httpSignature from "@peertube/http-signature"; import httpSignature from "@peertube/http-signature";
import json from "koa-json-body"; import bodyParser from "koa-bodyparser";
import config from "@/config/index.js";
import { isSelfHost } from "@/misc/convert-host.js"; import { isSelfHost } from "@/misc/convert-host.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { getUserKeypair } from "@/misc/keypair-store.js"; import { getUserKeypair } from "@/misc/keypair-store.js";
@ -17,6 +18,7 @@ import { inbox as processInbox } from "@/queue/index.js";
import { import {
checkFetch, checkFetch,
getSignatureUser, getSignatureUser,
verifyDigest,
} from "@/remote/activitypub/check-fetch.js"; } from "@/remote/activitypub/check-fetch.js";
import renderEmoji from "@/remote/activitypub/renderer/emoji.js"; import renderEmoji from "@/remote/activitypub/renderer/emoji.js";
import renderFollow from "@/remote/activitypub/renderer/follow.js"; import renderFollow from "@/remote/activitypub/renderer/follow.js";
@ -26,6 +28,7 @@ import { renderLike } from "@/remote/activitypub/renderer/like.js";
import renderNote from "@/remote/activitypub/renderer/note.js"; import renderNote from "@/remote/activitypub/renderer/note.js";
import { renderPerson } from "@/remote/activitypub/renderer/person.js"; import { renderPerson } from "@/remote/activitypub/renderer/person.js";
import { getInstanceActor } from "@/services/instance-actor.js"; import { getInstanceActor } from "@/services/instance-actor.js";
import Koa from "koa";
import { In, IsNull, Not } from "typeorm"; import { In, IsNull, Not } from "typeorm";
import Featured from "./activitypub/featured.js"; import Featured from "./activitypub/featured.js";
import Followers from "./activitypub/followers.js"; import Followers from "./activitypub/followers.js";
@ -39,15 +42,27 @@ const router = new Router();
//#region Routing //#region Routing
function inbox(ctx: Router.RouterContext) { function inbox(ctx: Router.RouterContext) {
if (ctx.req.headers.host !== config.host) {
ctx.status = 400;
return;
}
let signature; let signature;
try { try {
signature = httpSignature.parseRequest(ctx.req, { headers: [] }); signature = httpSignature.parseRequest(ctx.req, {
headers: ["(request-target)", "digest", "host", "date"],
});
} catch (e) { } catch (e) {
ctx.status = 401; ctx.status = 401;
return; return;
} }
if (!verifyDigest(ctx.request.rawBody, ctx.headers.digest)) {
ctx.status = 401;
return;
}
processInbox(ctx.request.body, signature); processInbox(ctx.request.body, signature);
ctx.status = 202; ctx.status = 202;
@ -72,9 +87,23 @@ export function setResponseType(ctx: Router.RouterContext) {
} }
} }
async function parseJsonBodyOrFail(ctx: Router.RouterContext, next: Koa.Next) {
const koaBodyParser = bodyParser({
enableTypes: ["json"],
detectJSON: () => true,
});
try {
await koaBodyParser(ctx, next);
} catch {
ctx.status = 400;
return;
}
}
// inbox // inbox
router.post("/inbox", json(), inbox); router.post("/inbox", parseJsonBodyOrFail, inbox);
router.post("/users/:user/inbox", json(), inbox); router.post("/users/:user/inbox", parseJsonBodyOrFail, inbox);
// note // note
router.get("/notes/:note", async (ctx, next) => { router.get("/notes/:note", async (ctx, next) => {

View file

@ -1,6 +1,6 @@
import { createImportCustomEmojisJob } from "@/queue/index.js"; import { createImportCustomEmojisJob } from "@/queue/index.js";
import define from "@/server/api/define.js"; import define from "@/server/api/define.js";
import { ApiError } from "../../../error.js"; import { ApiError } from "@/server/api/error.js";
export const meta = { export const meta = {
tags: ["admin", "emoji"], tags: ["admin", "emoji"],

View file

@ -3,7 +3,7 @@ import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
import { Emojis } from "@/models/index.js"; import { Emojis } from "@/models/index.js";
import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js"; import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js";
import define from "@/server/api/define.js"; import define from "@/server/api/define.js";
import { ApiError } from "../../../error.js"; import { ApiError } from "@/server/api/error.js";
export const meta = { export const meta = {
tags: ["admin", "emoji"], tags: ["admin", "emoji"],

View file

@ -1,4 +1,4 @@
import define from "../../define.js"; import define from "@/server/api/define.js";
export const meta = { export const meta = {
tags: ["charts"], tags: ["charts"],

View file

@ -1,4 +1,4 @@
import define from "../../define.js"; import define from "@/server/api/define.js";
export const meta = { export const meta = {
tags: ["charts", "drive"], tags: ["charts", "drive"],

View file

@ -1,4 +1,4 @@
import define from "../../define.js"; import define from "@/server/api/define.js";
export const meta = { export const meta = {
tags: ["charts"], tags: ["charts"],

View file

@ -1,4 +1,4 @@
import define from "../../define.js"; import define from "@/server/api/define.js";
export const meta = { export const meta = {
tags: ["charts", "hashtags"], tags: ["charts", "hashtags"],

View file

@ -1,4 +1,4 @@
import define from "../../define.js"; import define from "@/server/api/define.js";
export const meta = { export const meta = {
tags: ["charts"], tags: ["charts"],

View file

@ -1,4 +1,4 @@
import define from "../../define.js"; import define from "@/server/api/define.js";
export const meta = { export const meta = {
tags: ["charts", "notes"], tags: ["charts", "notes"],

View file

@ -1,4 +1,4 @@
import define from "../../../define.js"; import define from "@/server/api/define.js";
export const meta = { export const meta = {
tags: ["charts", "drive", "users"], tags: ["charts", "drive", "users"],

View file

@ -1,4 +1,4 @@
import define from "../../../define.js"; import define from "@/server/api/define.js";
export const meta = { export const meta = {
tags: ["charts", "users", "notes"], tags: ["charts", "users", "notes"],

View file

@ -1,4 +1,4 @@
import define from "../../../define.js"; import define from "@/server/api/define.js";
export const meta = { export const meta = {
tags: ["charts", "users", "reactions"], tags: ["charts", "users", "reactions"],

View file

@ -1,4 +1,4 @@
import define from "../../define.js"; import define from "@/server/api/define.js";
export const meta = { export const meta = {
tags: ["charts", "users"], tags: ["charts", "users"],

View file

@ -1,15 +1,10 @@
import { sqlLikeEscape } from "@/misc/sql-like-escape.js"; import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
import { Note } from "@/models/entities/note.js"; import { Note } from "@/models/entities/note.js";
// import { FindManyOptions, In } from "typeorm";
import { Notes } from "@/models/index.js"; import { Notes } from "@/models/index.js";
import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js"; import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js";
import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js"; import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js";
import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js"; import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js";
import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js"; import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js";
// import config from "@/config/index.js";
// import es from "@/db/elasticsearch.js";
// import sonic from "@/db/sonic.js";
// import meilisearch, { MeilisearchNote } from "@/db/meilisearch.js";
import define from "@/server/api/define.js"; import define from "@/server/api/define.js";
export const meta = { export const meta = {
@ -255,7 +250,7 @@ export default define(meta, paramDef, async (ps, me) => {
while (found.length < ps.limit && start < noteIDs.length) { while (found.length < ps.limit && start < noteIDs.length) {
const chunk = noteIDs.slice(start, start + chunkSize); const chunk = noteIDs.slice(start, start + chunkSize);
let query: FindManyOptions = { const query: FindManyOptions = {
where: { where: {
id: In(chunk), id: In(chunk),
}, },

View file

@ -25,8 +25,7 @@ const assets = `${_dirname}/../../server/file/assets/`;
const MAX_BYTE_RANGES = 10; const MAX_BYTE_RANGES = 10;
const commonReadableHandlerGenerator = const commonReadableHandlerGenerator =
(ctx: Koa.Context) => (ctx: Koa.Context) => (e: Error): void => {
(e: Error): void => {
serverLogger.error(e); serverLogger.error(e);
ctx.status = 500; ctx.status = 500;
ctx.set("Cache-Control", "max-age=300"); ctx.set("Cache-Control", "max-age=300");

View file

@ -25,7 +25,12 @@
//#region Detect language & fetch translations //#region Detect language & fetch translations
const v = localStorage.getItem("v") || VERSION; const v = localStorage.getItem("v") || VERSION;
const supportedLangs = LANGS; let supportedLangs = [];
try {
supportedLangs = SUPPORTED_LANGS;
} catch {
console.warn("LANGS not found");
}
let lang = localStorage.getItem("lang"); let lang = localStorage.getItem("lang");
if (lang == null || !supportedLangs.includes(lang)) { if (lang == null || !supportedLangs.includes(lang)) {
if (supportedLangs.includes(navigator.language)) { if (supportedLangs.includes(navigator.language)) {

View file

@ -31,7 +31,6 @@ import type { ILocalUser, IRemoteUser, User } from "@/models/entities/user.js";
import { import {
ChannelFollowings, ChannelFollowings,
Channels, Channels,
Followings,
Instances, Instances,
MutedNotes, MutedNotes,
Mutings, Mutings,

View file

@ -18,7 +18,7 @@
"@rollup/plugin-json": "6.0.1", "@rollup/plugin-json": "6.0.1",
"@rollup/pluginutils": "5.0.5", "@rollup/pluginutils": "5.0.5",
"@syuilo/aiscript": "0.16.0", "@syuilo/aiscript": "0.16.0",
"@types/autosize": "4.0.3", "@types/autosize": "^4.0.3",
"@types/glob": "8.1.0", "@types/glob": "8.1.0",
"@types/gulp": "4.0.17", "@types/gulp": "4.0.17",
"@types/gulp-rename": "2.0.5", "@types/gulp-rename": "2.0.5",
@ -49,7 +49,7 @@
"compare-versions": "6.1.0", "compare-versions": "6.1.0",
"cropperjs": "2.0.0-beta.4", "cropperjs": "2.0.0-beta.4",
"date-fns": "2.30.0", "date-fns": "2.30.0",
"emojilib": "3.0.11", "emojilib": "^3.0.11",
"eslint-config-prettier": "9.0.0", "eslint-config-prettier": "9.0.0",
"eslint-plugin-file-progress": "1.3.0", "eslint-plugin-file-progress": "1.3.0",
"eventemitter3": "5.0.1", "eventemitter3": "5.0.1",
@ -80,15 +80,15 @@
"three": "0.158.0", "three": "0.158.0",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tinyld": "1.3.4", "tinyld": "^1.3.4",
"typescript": "5.2.2", "typescript": "5.2.2",
"unicode-emoji-json": "0.4.0", "unicode-emoji-json": "0.4.0",
"uuid": "9.0.1", "uuid": "9.0.1",
"vite": "5.0.0", "vite": "5.0.0",
"vite-plugin-compression": "0.5.1", "vite-plugin-compression": "0.5.1",
"vue": "3.3.8", "vue": "3.3.8",
"vue-draggable-plus": "0.2.7", "vue-draggable-plus": "^0.2.7",
"vue-plyr": "7.0.0", "vue-plyr": "^7.0.0",
"vue-prism-editor": "2.0.0-alpha.2" "vue-prism-editor": "2.0.0-alpha.2"
} }
} }

View file

@ -360,7 +360,6 @@ const visibleUsers = ref([]);
if (props.initialVisibleUsers) { if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser); props.initialVisibleUsers.forEach(pushVisibleUser);
} }
const autocomplete = ref(null);
const draghover = ref(false); const draghover = ref(false);
const quoteId = ref(null); const quoteId = ref(null);
const hasNotSpecifiedMentions = ref(false); const hasNotSpecifiedMentions = ref(false);
@ -492,11 +491,11 @@ if (props.reply && props.reply.text != null) {
? `@${x.username}` ? `@${x.username}`
: `@${x.username}@${toASCII(otherHost)}`; : `@${x.username}@${toASCII(otherHost)}`;
// // exclude me
if ($i.username === x.username && (x.host == null || x.host === host)) if ($i.username === x.username && (x.host == null || x.host === host))
continue; continue;
// // remove duplicates
if (text.value.includes(`${mention} `)) continue; if (text.value.includes(`${mention} `)) continue;
text.value += `${mention} `; text.value += `${mention} `;
@ -505,10 +504,10 @@ if (props.reply && props.reply.text != null) {
if (props.channel) { if (props.channel) {
visibility.value = "public"; visibility.value = "public";
localOnly.value = true; // TODO: localOnly.value = true; // TODO: Delete this once channels get federated
} }
// // Inherit the original visibility
if ( if (
props.reply && props.reply &&
["home", "followers", "specified"].includes(props.reply.visibility) ["home", "followers", "specified"].includes(props.reply.visibility)
@ -629,10 +628,6 @@ function togglePoll() {
} }
} }
function addTag(tag: string) {
insertTextAtCursor(textareaEl.value, ` #${tag} `);
}
function focus() { function focus() {
if (textareaEl.value) { if (textareaEl.value) {
textareaEl.value.focus(); textareaEl.value.focus();
@ -736,6 +731,8 @@ function clear() {
quoteId.value = null; quoteId.value = null;
} }
// FIXME: ev.which is deprecated
// https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/which
function onKeydown(ev: KeyboardEvent) { function onKeydown(ev: KeyboardEvent) {
if ( if (
(ev.which === 10 || ev.which === 13) && (ev.which === 10 || ev.which === 13) &&
@ -772,7 +769,7 @@ async function onPaste(ev: ClipboardEvent) {
} }
} }
const paste = ev.clipboardData.getData("text"); const paste = ev.clipboardData?.getData("text") ?? "";
if (!props.renote && !quoteId.value && paste.startsWith(url + "/notes/")) { if (!props.renote && !quoteId.value && paste.startsWith(url + "/notes/")) {
ev.preventDefault(); ev.preventDefault();
@ -787,7 +784,7 @@ async function onPaste(ev: ClipboardEvent) {
} }
quoteId.value = paste quoteId.value = paste
.substr(url.length) .substring(url.length)
.match(/^\/notes\/(.+?)\/?$/)[1]; .match(/^\/notes\/(.+?)\/?$/)[1];
}); });
} }

View file

@ -229,14 +229,7 @@ function edit() {
watch(() => props.postId, fetchPost, { immediate: true }); watch(() => props.postId, fetchPost, { immediate: true });
const headerActions = computed(() => [ const headerActions = computed(() => []);
{
icon: `${icon("ph-pencil")}`,
text: i18n.ts.edit,
handler: edit,
},
]);
const headerTabs = computed(() => []); const headerTabs = computed(() => []);
definePageMetadata( definePageMetadata(

View file

@ -108,11 +108,11 @@
>{{ i18n.ts._theme.manage >{{ i18n.ts._theme.manage
}}<template #suffix>{{ themesCount }}</template></FormLink }}<template #suffix>{{ themesCount }}</template></FormLink
> >
<FormLink to="https://assets.firefish.io/theme/list" external <!-- <FormLink to="https://assets.misskey.io/theme/list" external
><template #icon ><template #icon
><i :class="icon('ph-planet')"></i></template ><i :class="icon('ph-planet')"></i></template
>{{ i18n.ts._theme.explore }}</FormLink >{{ i18n.ts._theme.explore }}</FormLink
> > -->
<FormLink to="/settings/theme/install" <FormLink to="/settings/theme/install"
><template #icon ><template #icon
><i :class="icon('ph-download-simple')"></i></template ><i :class="icon('ph-download-simple')"></i></template

View file

@ -1,11 +1,11 @@
import XCheatSheet from "@/components/MkCheatSheetDialog.vue"; import XCheatSheet from "@/components/MkCheatSheetDialog.vue";
import XTutorial from "@/components/MkTutorialDialog.vue"; import XTutorial from "@/components/MkTutorialDialog.vue";
import { defaultStore } from "@/store";
import { host } from "@/config"; import { host } from "@/config";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { instance } from "@/instance"; import { instance } from "@/instance";
import * as os from "@/os"; import * as os from "@/os";
import icon from "@/scripts/icon"; import icon from "@/scripts/icon";
import { defaultStore } from "@/store";
import type { MenuItem } from "@/types/menu"; import type { MenuItem } from "@/types/menu";
const instanceSpecificItems: MenuItem[] = []; const instanceSpecificItems: MenuItem[] = [];

View file

@ -1,17 +1,17 @@
import Response from "./response"; import { RequestCanceledError, isCancel } from "./cancel";
import OAuth from "./oauth"; import Converter from "./converter";
import { isCancel, RequestCanceledError } from "./cancel"; import Entity from "./entity";
import { ProxyConfig } from "./proxy_config"; import FilterContext from "./filter_context";
import generator, { import generator, {
detector,
MegalodonInterface, MegalodonInterface,
WebSocketInterface, WebSocketInterface,
detector,
} from "./megalodon"; } from "./megalodon";
import Misskey from "./misskey"; import Misskey from "./misskey";
import Entity from "./entity";
import NotificationType from "./notification"; import NotificationType from "./notification";
import FilterContext from "./filter_context"; import OAuth from "./oauth";
import Converter from "./converter"; import { ProxyConfig } from "./proxy_config";
import Response from "./response";
export { export {
Response, Response,

View file

@ -1,10 +1,10 @@
import Response from "./response"; import axios, { AxiosRequestConfig } from "axios";
import { DEFAULT_UA } from "./default";
import Entity from "./entity";
import Misskey from "./misskey";
import OAuth from "./oauth"; import OAuth from "./oauth";
import proxyAgent, { ProxyConfig } from "./proxy_config"; import proxyAgent, { ProxyConfig } from "./proxy_config";
import Entity from "./entity"; import Response from "./response";
import axios, { AxiosRequestConfig } from "axios";
import Misskey from "./misskey";
import { DEFAULT_UA } from "./default";
export interface WebSocketInterface { export interface WebSocketInterface {
start(): void; start(): void;

View file

@ -1,22 +1,22 @@
import FormData from "form-data";
import AsyncLock from "async-lock"; import AsyncLock from "async-lock";
import FormData from "form-data";
import MisskeyAPI from "./misskey/api_client"; import fs from "node:fs";
import MegalodonEntity from "@/entity";
import { DEFAULT_UA } from "./default"; import { DEFAULT_UA } from "./default";
import { ProxyConfig } from "./proxy_config";
import OAuth from "./oauth";
import Response from "./response";
import Entity from "./entity"; import Entity from "./entity";
import { import {
MegalodonInterface,
WebSocketInterface,
NoImplementedError,
ArgumentError, ArgumentError,
MegalodonInterface,
NoImplementedError,
UnexpectedError, UnexpectedError,
WebSocketInterface,
} from "./megalodon"; } from "./megalodon";
import MegalodonEntity from "@/entity"; import MisskeyAPI from "./misskey/api_client";
import fs from "node:fs";
import MisskeyNotificationType from "./misskey/notification"; import MisskeyNotificationType from "./misskey/notification";
import OAuth from "./oauth";
import { ProxyConfig } from "./proxy_config";
import Response from "./response";
type AccountCache = { type AccountCache = {
locks: AsyncLock; locks: AsyncLock;