Compare commits
No commits in common. "dab22cd63151d95c238db731e64200d741f825cf" and "1b79c99459c018aaccf7ca6e79e3be5f3d15ce26" have entirely different histories.
dab22cd631
...
1b79c99459
37 changed files with 194 additions and 327 deletions
55
.pnp.cjs
generated
55
.pnp.cjs
generated
|
|
@ -7295,7 +7295,7 @@ const RAW_RUNTIME_STATE =
|
||||||
["mfm-js", "npm:0.23.3"],\
|
["mfm-js", "npm:0.23.3"],\
|
||||||
["mime-types", "npm:2.1.35"],\
|
["mime-types", "npm:2.1.35"],\
|
||||||
["mocha", "npm:10.2.0"],\
|
["mocha", "npm:10.2.0"],\
|
||||||
["msgpackr", "npm:1.11.2"],\
|
["msgpackr", "npm:1.9.5"],\
|
||||||
["multer", "npm:1.4.4-lts.1"],\
|
["multer", "npm:1.4.4-lts.1"],\
|
||||||
["nested-property", "npm:4.0.0"],\
|
["nested-property", "npm:4.0.0"],\
|
||||||
["node-fetch", "npm:3.3.2"],\
|
["node-fetch", "npm:3.3.2"],\
|
||||||
|
|
@ -7317,7 +7317,7 @@ const RAW_RUNTIME_STATE =
|
||||||
["qs", "npm:6.11.2"],\
|
["qs", "npm:6.11.2"],\
|
||||||
["random-seed", "npm:0.3.0"],\
|
["random-seed", "npm:0.3.0"],\
|
||||||
["ratelimiter", "npm:3.4.1"],\
|
["ratelimiter", "npm:3.4.1"],\
|
||||||
["re2", "npm:1.21.4"],\
|
["re2", "npm:1.20.11"],\
|
||||||
["redis-lock", "npm:0.1.4"],\
|
["redis-lock", "npm:0.1.4"],\
|
||||||
["redis-semaphore", "virtual:aa59773ac87791c4813d53447077fcf8a847d6de5a301d34dc31286584b1dbb26d30d3adb5b4c41c1e8aea04371e926fda05c09c6253647c432e11d872a304ba#npm:5.3.1"],\
|
["redis-semaphore", "virtual:aa59773ac87791c4813d53447077fcf8a847d6de5a301d34dc31286584b1dbb26d30d3adb5b4c41c1e8aea04371e926fda05c09c6253647c432e11d872a304ba#npm:5.3.1"],\
|
||||||
["reflect-metadata", "npm:0.1.13"],\
|
["reflect-metadata", "npm:0.1.13"],\
|
||||||
|
|
@ -18152,10 +18152,10 @@ const RAW_RUNTIME_STATE =
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
}],\
|
}],\
|
||||||
["npm:1.11.2", {\
|
["npm:1.9.5", {\
|
||||||
"packageLocation": "./.yarn/cache/msgpackr-npm-1.11.2-a21c5db6f8-7602f1e91e.zip/node_modules/msgpackr/",\
|
"packageLocation": "./.yarn/cache/msgpackr-npm-1.9.5-69f0e8f5b8-d95fbee39b.zip/node_modules/msgpackr/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["msgpackr", "npm:1.11.2"],\
|
["msgpackr", "npm:1.9.5"],\
|
||||||
["msgpackr-extract", "npm:3.0.2"]\
|
["msgpackr-extract", "npm:3.0.2"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
|
@ -18225,10 +18225,10 @@ const RAW_RUNTIME_STATE =
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["nan", [\
|
["nan", [\
|
||||||
["npm:2.22.0", {\
|
["npm:2.19.0", {\
|
||||||
"packageLocation": "./.yarn/unplugged/nan-npm-2.22.0-3750ad85d9/node_modules/nan/",\
|
"packageLocation": "./.yarn/unplugged/nan-npm-2.19.0-2f5da4a528/node_modules/nan/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["nan", "npm:2.22.0"],\
|
["nan", "npm:2.19.0"],\
|
||||||
["node-gyp", "npm:9.4.0"]\
|
["node-gyp", "npm:9.4.0"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
|
@ -18406,19 +18406,19 @@ const RAW_RUNTIME_STATE =
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["node-gyp", [\
|
["node-gyp", [\
|
||||||
["npm:10.2.0", {\
|
["npm:10.0.1", {\
|
||||||
"packageLocation": "./.yarn/unplugged/node-gyp-npm-10.2.0-cad1109948/node_modules/node-gyp/",\
|
"packageLocation": "./.yarn/unplugged/node-gyp-npm-10.0.1-48708ce70b/node_modules/node-gyp/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["node-gyp", "npm:10.2.0"],\
|
["node-gyp", "npm:10.0.1"],\
|
||||||
["env-paths", "npm:2.2.1"],\
|
["env-paths", "npm:2.2.1"],\
|
||||||
["exponential-backoff", "npm:3.1.1"],\
|
["exponential-backoff", "npm:3.1.1"],\
|
||||||
["glob", "npm:10.3.10"],\
|
["glob", "npm:10.3.10"],\
|
||||||
["graceful-fs", "npm:4.2.11"],\
|
["graceful-fs", "npm:4.2.11"],\
|
||||||
["make-fetch-happen", "npm:13.0.0"],\
|
["make-fetch-happen", "npm:13.0.0"],\
|
||||||
["nopt", "npm:7.2.0"],\
|
["nopt", "npm:7.2.0"],\
|
||||||
["proc-log", "npm:4.2.0"],\
|
["proc-log", "npm:3.0.0"],\
|
||||||
["semver", "npm:7.5.4"],\
|
["semver", "npm:7.5.4"],\
|
||||||
["tar", "npm:6.2.1"],\
|
["tar", "npm:6.1.15"],\
|
||||||
["which", "npm:4.0.0"]\
|
["which", "npm:4.0.0"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
|
@ -20445,10 +20445,10 @@ const RAW_RUNTIME_STATE =
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["proc-log", [\
|
["proc-log", [\
|
||||||
["npm:4.2.0", {\
|
["npm:3.0.0", {\
|
||||||
"packageLocation": "./.yarn/cache/proc-log-npm-4.2.0-4d65296a9d-4e1394491b.zip/node_modules/proc-log/",\
|
"packageLocation": "./.yarn/cache/proc-log-npm-3.0.0-a8c21c2f0f-02b64e1b39.zip/node_modules/proc-log/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["proc-log", "npm:4.2.0"]\
|
["proc-log", "npm:3.0.0"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
|
|
@ -20975,13 +20975,13 @@ const RAW_RUNTIME_STATE =
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["re2", [\
|
["re2", [\
|
||||||
["npm:1.21.4", {\
|
["npm:1.20.11", {\
|
||||||
"packageLocation": "./.yarn/unplugged/re2-npm-1.21.4-315af4327e/node_modules/re2/",\
|
"packageLocation": "./.yarn/unplugged/re2-npm-1.20.11-ab65de125e/node_modules/re2/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["re2", "npm:1.21.4"],\
|
["re2", "npm:1.20.11"],\
|
||||||
["install-artifact-from-github", "npm:1.3.5"],\
|
["install-artifact-from-github", "npm:1.3.5"],\
|
||||||
["nan", "npm:2.22.0"],\
|
["nan", "npm:2.19.0"],\
|
||||||
["node-gyp", "npm:10.2.0"]\
|
["node-gyp", "npm:10.0.1"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
|
|
@ -23165,19 +23165,6 @@ const RAW_RUNTIME_STATE =
|
||||||
["yallist", "npm:4.0.0"]\
|
["yallist", "npm:4.0.0"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
}],\
|
|
||||||
["npm:6.2.1", {\
|
|
||||||
"packageLocation": "./.yarn/cache/tar-npm-6.2.1-237800bb20-bfbfbb2861.zip/node_modules/tar/",\
|
|
||||||
"packageDependencies": [\
|
|
||||||
["tar", "npm:6.2.1"],\
|
|
||||||
["chownr", "npm:2.0.0"],\
|
|
||||||
["fs-minipass", "npm:2.1.0"],\
|
|
||||||
["minipass", "npm:5.0.0"],\
|
|
||||||
["minizlib", "npm:2.1.2"],\
|
|
||||||
["mkdirp", "npm:1.0.4"],\
|
|
||||||
["yallist", "npm:4.0.0"]\
|
|
||||||
],\
|
|
||||||
"linkType": "HARD"\
|
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["tar-stream", [\
|
["tar-stream", [\
|
||||||
|
|
|
||||||
BIN
.yarn/cache/msgpackr-npm-1.11.2-a21c5db6f8-7602f1e91e.zip
(Stored with Git LFS)
vendored
BIN
.yarn/cache/msgpackr-npm-1.11.2-a21c5db6f8-7602f1e91e.zip
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
.yarn/cache/msgpackr-npm-1.9.5-69f0e8f5b8-d95fbee39b.zip
(Stored with Git LFS)
vendored
Normal file
BIN
.yarn/cache/msgpackr-npm-1.9.5-69f0e8f5b8-d95fbee39b.zip
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/nan-npm-2.19.0-2f5da4a528-b97f680753.zip
(Stored with Git LFS)
vendored
Normal file
BIN
.yarn/cache/nan-npm-2.19.0-2f5da4a528-b97f680753.zip
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/nan-npm-2.22.0-3750ad85d9-ab165ba910.zip
(Stored with Git LFS)
vendored
BIN
.yarn/cache/nan-npm-2.22.0-3750ad85d9-ab165ba910.zip
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
.yarn/cache/node-gyp-npm-10.0.1-48708ce70b-578cf0c821.zip
(Stored with Git LFS)
vendored
Normal file
BIN
.yarn/cache/node-gyp-npm-10.0.1-48708ce70b-578cf0c821.zip
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/node-gyp-npm-10.2.0-cad1109948-41773093b1.zip
(Stored with Git LFS)
vendored
BIN
.yarn/cache/node-gyp-npm-10.2.0-cad1109948-41773093b1.zip
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
.yarn/cache/proc-log-npm-3.0.0-a8c21c2f0f-02b64e1b39.zip
(Stored with Git LFS)
vendored
Normal file
BIN
.yarn/cache/proc-log-npm-3.0.0-a8c21c2f0f-02b64e1b39.zip
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/proc-log-npm-4.2.0-4d65296a9d-4e1394491b.zip
(Stored with Git LFS)
vendored
BIN
.yarn/cache/proc-log-npm-4.2.0-4d65296a9d-4e1394491b.zip
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
.yarn/cache/re2-npm-1.20.11-ab65de125e-a8665c861c.zip
(Stored with Git LFS)
vendored
Normal file
BIN
.yarn/cache/re2-npm-1.20.11-ab65de125e-a8665c861c.zip
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/re2-npm-1.21.4-315af4327e-926871cc84.zip
(Stored with Git LFS)
vendored
BIN
.yarn/cache/re2-npm-1.21.4-315af4327e-926871cc84.zip
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
.yarn/cache/tar-npm-6.2.1-237800bb20-bfbfbb2861.zip
(Stored with Git LFS)
vendored
BIN
.yarn/cache/tar-npm-6.2.1-237800bb20-bfbfbb2861.zip
(Stored with Git LFS)
vendored
Binary file not shown.
20
CHANGELOG.md
20
CHANGELOG.md
|
|
@ -1,23 +1,3 @@
|
||||||
## v2023.12.11
|
|
||||||
This release contains several critical security patches, as well as minor fixes and improvements. Upgrading is strongly recommended for all server operators.
|
|
||||||
|
|
||||||
### Highlights
|
|
||||||
- Several DoS, impersonation, data leakage & click jacking vulnerabilities have been patched
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- Various issues related to AP object validation have been resolved
|
|
||||||
- The ap/get API endpoint is now only available to administrators
|
|
||||||
- Blocks are now enforced in NoteRepository.isVisibleForMe
|
|
||||||
- Audience parsing no longer bypasses the AP recursion limit
|
|
||||||
- Edits of local-only notes are no longer federated out
|
|
||||||
- AP object URIs now get canonicalized before comparing them for consistency
|
|
||||||
- SSRF prevention now applies to all code paths
|
|
||||||
|
|
||||||
### Attribution
|
|
||||||
This release was made possible by project contributors: Kopper & Laura Hausmann
|
|
||||||
|
|
||||||
Furthermore, I want to give special thanks to Hazel Koehler for the vulnerability disclosure.
|
|
||||||
|
|
||||||
## v2023.12.10
|
## v2023.12.10
|
||||||
This release contains a critical security patch, as well as minor fixes and improvements. Upgrading is strongly recommended for all server operators.
|
This release contains a critical security patch, as well as minor fixes and improvements. Upgrading is strongly recommended for all server operators.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1086,7 +1086,7 @@ _registry:
|
||||||
domain: "Domain"
|
domain: "Domain"
|
||||||
createKey: "Schlüssel erstellen"
|
createKey: "Schlüssel erstellen"
|
||||||
_aboutIceshrimp:
|
_aboutIceshrimp:
|
||||||
about: "Iceshrimp ist ein Fork von Firefish, der seit 2022 von zotan
|
about: "Iceshrimp ist ein Fork von Iceshrimp, der seit 2022 von ThatOneCalculator
|
||||||
entwickelt wird."
|
entwickelt wird."
|
||||||
contributors: "Hauptmitwirkende"
|
contributors: "Hauptmitwirkende"
|
||||||
allContributors: "Alle Mitwirkenden"
|
allContributors: "Alle Mitwirkenden"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "iceshrimp",
|
"name": "iceshrimp",
|
||||||
"version": "2023.12.11",
|
"version": "2023.12.10",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://iceshrimp.dev/iceshrimp/iceshrimp.git"
|
"url": "https://iceshrimp.dev/iceshrimp/iceshrimp.git"
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
"koa-views": "7.0.2",
|
"koa-views": "7.0.2",
|
||||||
"mfm-js": "0.23.3",
|
"mfm-js": "0.23.3",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"msgpackr": "1.11.2",
|
"msgpackr": "1.9.5",
|
||||||
"multer": "1.4.4-lts.1",
|
"multer": "1.4.4-lts.1",
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
|
|
@ -111,7 +111,7 @@
|
||||||
"qs": "6.11.2",
|
"qs": "6.11.2",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
"re2": "^1.21.4",
|
"re2": "^1.20.11",
|
||||||
"redis-lock": "0.1.4",
|
"redis-lock": "0.1.4",
|
||||||
"redis-semaphore": "5.3.1",
|
"redis-semaphore": "5.3.1",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import * as http from "node:http";
|
|
||||||
import * as https from "node:https";
|
|
||||||
import net from "node:net";
|
|
||||||
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
|
|
||||||
import config from "@/config/index.js";
|
|
||||||
import IPCIDR from "ip-cidr";
|
|
||||||
import PrivateIp from "private-ip";
|
|
||||||
|
|
||||||
declare module 'node:http' {
|
|
||||||
interface Agent {
|
|
||||||
createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPrivateIp(ip: string): boolean {
|
|
||||||
for (const net of config.allowedPrivateNetworks || []) {
|
|
||||||
const cidr = new IPCIDR(net);
|
|
||||||
if (cidr.contains(ip)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return PrivateIp(ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkConnection(socket: net.Socket) {
|
|
||||||
const address = socket.remoteAddress;
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
|
||||||
if (address && IPCIDR.isValidAddress(address) && isPrivateIp(address)) {
|
|
||||||
socket.destroy(new Error(`Blocked address: ${address}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CheckedHttpAgent extends http.Agent {
|
|
||||||
createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket {
|
|
||||||
const socket = super.createConnection(options, callback).on('connect', () => { checkConnection(socket) });
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CheckedHttpsAgent extends https.Agent {
|
|
||||||
createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket {
|
|
||||||
const socket = super.createConnection(options, callback).on('connect', () => { checkConnection(socket) });
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class CheckedHttpProxyAgent extends HttpProxyAgent {
|
|
||||||
createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket {
|
|
||||||
const socket = super.createConnection(options, callback).on('connect', () => { checkConnection(socket) });
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CheckedHttpsProxyAgent extends HttpsProxyAgent {
|
|
||||||
createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket {
|
|
||||||
const socket = super.createConnection(options, callback).on('connect', () => { checkConnection(socket) });
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,6 +6,8 @@ import { httpAgent, httpsAgent, StatusError } from "./fetch.js";
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import Logger from "@/services/logger.js";
|
import Logger from "@/services/logger.js";
|
||||||
|
import IPCIDR from "ip-cidr";
|
||||||
|
import PrivateIp from "private-ip";
|
||||||
|
|
||||||
const pipeline = util.promisify(stream.pipeline);
|
const pipeline = util.promisify(stream.pipeline);
|
||||||
|
|
||||||
|
|
@ -43,6 +45,18 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.on("response", (res: Got.Response) => {
|
.on("response", (res: Got.Response) => {
|
||||||
|
if (
|
||||||
|
(process.env.NODE_ENV === "production" ||
|
||||||
|
process.env.NODE_ENV === "test") &&
|
||||||
|
!config.proxy &&
|
||||||
|
res.ip
|
||||||
|
) {
|
||||||
|
if (isPrivateIp(res.ip)) {
|
||||||
|
logger.warn(`Blocked address: ${res.ip}`);
|
||||||
|
req.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const contentLength = res.headers["content-length"];
|
const contentLength = res.headers["content-length"];
|
||||||
if (contentLength != null) {
|
if (contentLength != null) {
|
||||||
const size = Number(contentLength);
|
const size = Number(contentLength);
|
||||||
|
|
@ -78,3 +92,13 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
|
||||||
logger.succ(`Download finished: ${chalk.cyan(url)}`);
|
logger.succ(`Download finished: ${chalk.cyan(url)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPrivateIp(ip: string): boolean {
|
||||||
|
for (const net of config.allowedPrivateNetworks || []) {
|
||||||
|
const cidr = new IPCIDR(net);
|
||||||
|
if (cidr.contains(ip)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PrivateIp(ip);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ import * as https from "node:https";
|
||||||
import type { URL } from "node:url";
|
import type { URL } from "node:url";
|
||||||
import CacheableLookup from "cacheable-lookup";
|
import CacheableLookup from "cacheable-lookup";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
|
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
import net from "node:net";
|
|
||||||
import {CheckedHttpAgent, CheckedHttpProxyAgent, CheckedHttpsAgent, CheckedHttpsProxyAgent} from "@/misc/checked-fetch.js";
|
|
||||||
|
|
||||||
export async function getJson(
|
export async function getJson(
|
||||||
url: string,
|
url: string,
|
||||||
|
|
@ -133,7 +132,7 @@ const cache = new CacheableLookup({
|
||||||
/**
|
/**
|
||||||
* Get http non-proxy agent
|
* Get http non-proxy agent
|
||||||
*/
|
*/
|
||||||
const _http = new CheckedHttpAgent({
|
const _http = new http.Agent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * 1000,
|
keepAliveMsecs: 30 * 1000,
|
||||||
lookup: cache.lookup,
|
lookup: cache.lookup,
|
||||||
|
|
@ -142,7 +141,7 @@ const _http = new CheckedHttpAgent({
|
||||||
/**
|
/**
|
||||||
* Get https non-proxy agent
|
* Get https non-proxy agent
|
||||||
*/
|
*/
|
||||||
const _https = new CheckedHttpsAgent({
|
const _https = new https.Agent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * 1000,
|
keepAliveMsecs: 30 * 1000,
|
||||||
lookup: cache.lookup,
|
lookup: cache.lookup,
|
||||||
|
|
@ -154,7 +153,7 @@ const maxSockets = Math.max(256, config.deliverJobConcurrency || 128);
|
||||||
* Get http proxy or non-proxy agent
|
* Get http proxy or non-proxy agent
|
||||||
*/
|
*/
|
||||||
export const httpAgent = config.proxy
|
export const httpAgent = config.proxy
|
||||||
? new CheckedHttpProxyAgent({
|
? new HttpProxyAgent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * 1000,
|
keepAliveMsecs: 30 * 1000,
|
||||||
maxSockets,
|
maxSockets,
|
||||||
|
|
@ -168,7 +167,7 @@ export const httpAgent = config.proxy
|
||||||
* Get https proxy or non-proxy agent
|
* Get https proxy or non-proxy agent
|
||||||
*/
|
*/
|
||||||
export const httpsAgent = config.proxy
|
export const httpsAgent = config.proxy
|
||||||
? new CheckedHttpsProxyAgent({
|
? new HttpsProxyAgent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
keepAliveMsecs: 30 * 1000,
|
keepAliveMsecs: 30 * 1000,
|
||||||
maxSockets,
|
maxSockets,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
Followings,
|
Followings,
|
||||||
Polls,
|
Polls,
|
||||||
Channels,
|
Channels,
|
||||||
Notes, UserProfiles, Blockings,
|
Notes, UserProfiles,
|
||||||
} from "../index.js";
|
} from "../index.js";
|
||||||
import type { Packed } from "@/misc/schema.js";
|
import type { Packed } from "@/misc/schema.js";
|
||||||
import { nyaize } from "@/misc/nyaize.js";
|
import { nyaize } from "@/misc/nyaize.js";
|
||||||
|
|
@ -113,20 +113,6 @@ async function populateIsRenoted(
|
||||||
|
|
||||||
export const NoteRepository = db.getRepository(Note).extend({
|
export const NoteRepository = db.getRepository(Note).extend({
|
||||||
async isVisibleForMe(note: Note, meId: User["id"] | null): Promise<boolean> {
|
async isVisibleForMe(note: Note, meId: User["id"] | null): Promise<boolean> {
|
||||||
if (meId != null && meId !== note.userId) {
|
|
||||||
const blocked = await Blockings.count({
|
|
||||||
where: {
|
|
||||||
blockeeId: meId,
|
|
||||||
blockerId: note.userId
|
|
||||||
},
|
|
||||||
take: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
if (blocked !== 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This code must always be synchronized with the checks in generateVisibilityQuery.
|
// This code must always be synchronized with the checks in generateVisibilityQuery.
|
||||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||||
if (note.visibility === "specified") {
|
if (note.visibility === "specified") {
|
||||||
|
|
|
||||||
|
|
@ -178,14 +178,12 @@ async function process(job: Job<InboxJobData>): Promise<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// activity.idがあればホストが署名者のホストであることを確認する
|
// activity.idがあればホストが署名者のホストであることを確認する
|
||||||
if (typeof activity.id !== "string") {
|
if (typeof activity.id === "string") {
|
||||||
return 'skip: activity.id is not a string';
|
const signerHost = extractDbHost(authUser.user.uri!);
|
||||||
}
|
const activityIdHost = extractDbHost(activity.id);
|
||||||
|
if (signerHost !== activityIdHost) {
|
||||||
const signerHost = extractDbHost(authUser.user.uri!);
|
return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`;
|
||||||
const activityIdHost = extractDbHost(activity.id);
|
}
|
||||||
if (signerHost !== activityIdHost) {
|
|
||||||
return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update stats
|
// Update stats
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { ApObject } from "./type.js";
|
import type { ApObject } from "./type.js";
|
||||||
import { getApIds } from "./type.js";
|
import { getApIds } from "./type.js";
|
||||||
import Resolver from "./resolver.js";
|
import type Resolver from "./resolver.js";
|
||||||
import { resolvePerson } from "./models/person.js";
|
import { resolvePerson } from "./models/person.js";
|
||||||
import { unique, concat } from "@/prelude/array.js";
|
import { unique, concat } from "@/prelude/array.js";
|
||||||
import promiseLimit from "promise-limit";
|
import promiseLimit from "promise-limit";
|
||||||
|
|
@ -31,7 +31,6 @@ export async function parseAudience(
|
||||||
|
|
||||||
const others = unique(concat([toGroups.other, ccGroups.other]));
|
const others = unique(concat([toGroups.other, ccGroups.other]));
|
||||||
|
|
||||||
resolver ??= new Resolver();
|
|
||||||
const limit = promiseLimit<CacheableUser | null>(2);
|
const limit = promiseLimit<CacheableUser | null>(2);
|
||||||
const mentionedUsers = (
|
const mentionedUsers = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
|
||||||
let signature;
|
let signature;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
signature = httpSignature.parseRequest(req, { headers: ["(request-target)", "host", "date"], authorizationHeaderName: 'signature' });
|
signature = httpSignature.parseRequest(req, { headers: ["(request-target)", "host", "date"] });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 401;
|
return 401;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import type { IObject } from "./type.js";
|
||||||
import { getApId } from "./type.js";
|
import { getApId } from "./type.js";
|
||||||
import { resolvePerson, updatePerson } from "./models/person.js";
|
import { resolvePerson, updatePerson } from "./models/person.js";
|
||||||
import {redisClient, subscriber} from "@/db/redis.js";
|
import {redisClient, subscriber} from "@/db/redis.js";
|
||||||
import { extractDbHost, toPuny } from "@/misc/convert-host.js";
|
|
||||||
|
|
||||||
const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30);
|
const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30);
|
||||||
const publicKeyByUserIdCache = new Cache<UserPublickey | null>(
|
const publicKeyByUserIdCache = new Cache<UserPublickey | null>(
|
||||||
|
|
@ -47,15 +46,15 @@ export type UriParseResult =
|
||||||
|
|
||||||
export function parseUri(value: string | IObject): UriParseResult {
|
export function parseUri(value: string | IObject): UriParseResult {
|
||||||
const uri = getApId(value);
|
const uri = getApId(value);
|
||||||
const parsed = new URL(uri);
|
|
||||||
|
|
||||||
if (toPuny(parsed.host) === toPuny(config.host)) {
|
// the host part of a URL is case insensitive, so use the 'i' flag.
|
||||||
const localRegex = new RegExp(`^.*?/(\\w+)/(\\w+)(?:/(.+))?`);
|
const localRegex = new RegExp(
|
||||||
const matchLocal = uri.match(localRegex);
|
`^${escapeRegexp(config.url)}/(\\w+)/(\\w+)(?:/(.+))?`,
|
||||||
if (matchLocal == null) {
|
"i",
|
||||||
throw new Error(`Failed to parse local URI: ${uri}`);
|
);
|
||||||
}
|
const matchLocal = uri.match(localRegex);
|
||||||
|
|
||||||
|
if (matchLocal) {
|
||||||
return {
|
return {
|
||||||
local: true,
|
local: true,
|
||||||
type: matchLocal[1],
|
type: matchLocal[1],
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,6 @@ export default async function (
|
||||||
return "skip: host in actor.uri !== note.id";
|
return "skip: host in actor.uri !== note.id";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return "skip: note.id is not a string";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const unlock = await getApLock(uri);
|
const unlock = await getApLock(uri);
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,19 @@ export async function performActivity(
|
||||||
activity: IObject,
|
activity: IObject,
|
||||||
) {
|
) {
|
||||||
if (isCollectionOrOrderedCollection(activity)) {
|
if (isCollectionOrOrderedCollection(activity)) {
|
||||||
apLogger.debug('Refusing to ingest collection as activity');
|
const resolver = new Resolver();
|
||||||
return;
|
for (const item of toArray(
|
||||||
|
isCollection(activity) ? activity.items : activity.orderedItems,
|
||||||
|
)) {
|
||||||
|
const act = await resolver.resolve(item);
|
||||||
|
try {
|
||||||
|
await performOneActivity(actor, act);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error || typeof err === "string") {
|
||||||
|
apLogger.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await performOneActivity(actor, activity);
|
await performOneActivity(actor, activity);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { CacheableRemoteUser } from "@/models/entities/user.js";
|
import type { CacheableRemoteUser } from "@/models/entities/user.js";
|
||||||
import { getApId, IUpdate } from "../../type.js";
|
import type { IUpdate } from "../../type.js";
|
||||||
import { getApType, isActor } from "../../type.js";
|
import { getApType, isActor } from "../../type.js";
|
||||||
import { apLogger } from "../../logger.js";
|
import { apLogger } from "../../logger.js";
|
||||||
import { updateNote } from "../../models/note.js";
|
import { updateNote } from "../../models/note.js";
|
||||||
|
|
@ -13,7 +13,7 @@ export default async (
|
||||||
actor: CacheableRemoteUser,
|
actor: CacheableRemoteUser,
|
||||||
activity: IUpdate,
|
activity: IUpdate,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
if (actor.uri == null || actor.uri !== getApId(activity.actor)) {
|
if ("actor" in activity && actor.uri !== activity.actor) {
|
||||||
return "skip: invalid actor";
|
return "skip: invalid actor";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,10 +27,6 @@ export default async (
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isActor(object)) {
|
if (isActor(object)) {
|
||||||
if (actor.uri !== object.id) {
|
|
||||||
return "skip: actor id mismatch";
|
|
||||||
}
|
|
||||||
|
|
||||||
await updatePerson(actor.uri!, resolver, object);
|
await updatePerson(actor.uri!, resolver, object);
|
||||||
return "ok: Person updated";
|
return "ok: Person updated";
|
||||||
}
|
}
|
||||||
|
|
@ -43,7 +39,7 @@ export default async (
|
||||||
case "Document":
|
case "Document":
|
||||||
case "Page":
|
case "Page":
|
||||||
let failed = false;
|
let failed = false;
|
||||||
await updateNote(object, actor, resolver).catch((e: Error) => {
|
await updateNote(object, resolver).catch((e: Error) => {
|
||||||
failed = true;
|
failed = true;
|
||||||
});
|
});
|
||||||
return failed ? "skip: Note update failed" : "ok: Note updated";
|
return failed ? "skip: Note update failed" : "ok: Note updated";
|
||||||
|
|
|
||||||
|
|
@ -131,20 +131,13 @@ export async function createNote(
|
||||||
|
|
||||||
const note: IPost = object;
|
const note: IPost = object;
|
||||||
|
|
||||||
if (note.id == null) {
|
if (note.id && !note.id.startsWith("https://")) {
|
||||||
throw new Error('Note must have an id');
|
|
||||||
}
|
|
||||||
|
|
||||||
const idUrl = new URL(note.id);
|
|
||||||
|
|
||||||
if (idUrl.protocol != 'https:') {
|
|
||||||
throw new Error(`unexpected schema of note.id: ${note.id}`);
|
throw new Error(`unexpected schema of note.id: ${note.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = getOneApHrefNullable(note.url);
|
const url = getOneApHrefNullable(note.url);
|
||||||
const urlUrl = url != null ? new URL(url) : null;
|
|
||||||
|
|
||||||
if (urlUrl != null && urlUrl.protocol != 'https:') {
|
if (url && !url.startsWith("https://")) {
|
||||||
throw new Error(`unexpected schema of note url: ${url}`);
|
throw new Error(`unexpected schema of note url: ${url}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,22 +169,6 @@ export async function createNote(
|
||||||
limiter
|
limiter
|
||||||
)) as CacheableRemoteUser;
|
)) as CacheableRemoteUser;
|
||||||
|
|
||||||
if (actor.uri == null) {
|
|
||||||
logger.warn('Note actor uri is null, discarding');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actorUri = new URL(actor.uri);
|
|
||||||
if (idUrl.host != actorUri.host) {
|
|
||||||
logger.warn("Note id host doesn't match actor host, discarding");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (urlUrl != null && urlUrl.host != actorUri.host) {
|
|
||||||
logger.debug("Note url host doesn't match actor host, clearing variable");
|
|
||||||
url = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if author is suspended.
|
// Skip if author is suspended.
|
||||||
if (actor.isSuspended) {
|
if (actor.isSuspended) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|
@ -455,7 +432,7 @@ export async function resolveNote(
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
if (extractDbHost(uri) === toPuny(config.host)) {
|
if (uri.startsWith(config.url)) {
|
||||||
throw new StatusError(
|
throw new StatusError(
|
||||||
"cannot resolve local note",
|
"cannot resolve local note",
|
||||||
400,
|
400,
|
||||||
|
|
@ -567,12 +544,12 @@ function notEmpty(partial: Partial<any>) {
|
||||||
return Object.keys(partial).length > 0;
|
return Object.keys(partial).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateNote(value: string | IObject, actor: CacheableRemoteUser, resolver?: Resolver) {
|
export async function updateNote(value: string | IObject, resolver?: Resolver) {
|
||||||
const uri = typeof value === "string" ? value : value.id;
|
const uri = typeof value === "string" ? value : value.id;
|
||||||
if (!uri) throw new Error("Missing note uri");
|
if (!uri) throw new Error("Missing note uri");
|
||||||
|
|
||||||
// Skip if URI points to this server
|
// Skip if URI points to this server
|
||||||
if (extractDbHost(uri) === toPuny(config.host)) throw new Error("uri points local");
|
if (uri.startsWith(`${config.url}/`)) throw new Error("uri points local");
|
||||||
|
|
||||||
// A new resolver is created if not specified
|
// A new resolver is created if not specified
|
||||||
if (resolver == null) resolver = new Resolver();
|
if (resolver == null) resolver = new Resolver();
|
||||||
|
|
@ -580,18 +557,16 @@ export async function updateNote(value: string | IObject, actor: CacheableRemote
|
||||||
// Resolve the updated Note object
|
// Resolve the updated Note object
|
||||||
const post = (await resolver.resolve(value)) as IPost;
|
const post = (await resolver.resolve(value)) as IPost;
|
||||||
|
|
||||||
if (getOneApId(post.attributedTo) !== actor.uri || actor.uri == null) {
|
const actor = (await resolvePerson(
|
||||||
throw new Error('Refusing to ingest update for note with mismatching actor');
|
getOneApId(post.attributedTo),
|
||||||
}
|
resolver,
|
||||||
|
)) as CacheableRemoteUser;
|
||||||
|
|
||||||
// Already registered with this server?
|
// Already registered with this server?
|
||||||
const note = await Notes.findOneBy({ uri });
|
const note = await Notes.findOneBy({ uri });
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
return await createNote(post, resolver);
|
return await createNote(post, resolver);
|
||||||
}
|
}
|
||||||
if (note.userId !== actor.id) {
|
|
||||||
throw new Error('Refusing to ingest update for note of different user');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether to tell clients the note has been updated and requires refresh.
|
// Whether to tell clients the note has been updated and requires refresh.
|
||||||
let updating = false;
|
let updating = false;
|
||||||
|
|
@ -724,10 +699,6 @@ export async function updateNote(value: string | IObject, actor: CacheableRemote
|
||||||
|
|
||||||
if (poll) {
|
if (poll) {
|
||||||
const dbPoll = await Polls.findOneBy({ noteId: note.id });
|
const dbPoll = await Polls.findOneBy({ noteId: note.id });
|
||||||
if (poll?.votes != null && poll.votes.find(p => !Number.isInteger(p) || p < 0) !== undefined) {
|
|
||||||
throw new Error('Refusing to ingest poll with non-integer or negative vote count');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dbPoll == null) {
|
if (dbPoll == null) {
|
||||||
await Polls.insert({
|
await Polls.insert({
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import { genId } from "@/misc/gen-id.js";
|
||||||
import { instanceChart, usersChart } from "@/services/chart/index.js";
|
import { instanceChart, usersChart } from "@/services/chart/index.js";
|
||||||
import { UserPublickey } from "@/models/entities/user-publickey.js";
|
import { UserPublickey } from "@/models/entities/user-publickey.js";
|
||||||
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
|
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
|
||||||
import { extractDbHost, toPuny } from "@/misc/convert-host.js";
|
import { toPuny } from "@/misc/convert-host.js";
|
||||||
import { UserProfile } from "@/models/entities/user-profile.js";
|
import { UserProfile } from "@/models/entities/user-profile.js";
|
||||||
import { toArray } from "@/prelude/array.js";
|
import { toArray } from "@/prelude/array.js";
|
||||||
import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js";
|
import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js";
|
||||||
|
|
@ -69,7 +69,7 @@ const summaryLength = 2048;
|
||||||
* @param uri Fetch target URI
|
* @param uri Fetch target URI
|
||||||
*/
|
*/
|
||||||
function validateActor(x: IObject, uri: string): IActor {
|
function validateActor(x: IObject, uri: string): IActor {
|
||||||
const expectHost = extractDbHost(uri);
|
const expectHost = toPuny(new URL(uri).hostname);
|
||||||
|
|
||||||
if (x == null) {
|
if (x == null) {
|
||||||
throw new Error("invalid Actor: object is null");
|
throw new Error("invalid Actor: object is null");
|
||||||
|
|
@ -83,36 +83,10 @@ function validateActor(x: IObject, uri: string): IActor {
|
||||||
throw new Error("invalid Actor: wrong id");
|
throw new Error("invalid Actor: wrong id");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(typeof x.inbox === "string" && x.inbox.length > 0 && extractDbHost(x.inbox) === expectHost)) {
|
if (!(typeof x.inbox === "string" && x.inbox.length > 0)) {
|
||||||
throw new Error("invalid Actor: wrong inbox");
|
throw new Error("invalid Actor: wrong inbox");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(typeof x.outbox === "string" && x.outbox.length > 0 && extractDbHost(getApId(x.outbox)) === expectHost)) {
|
|
||||||
throw new Error("invalid Actor: wrong outbox");
|
|
||||||
}
|
|
||||||
|
|
||||||
const sharedInboxObject = x.sharedInbox ?? (x.endpoints ? x.endpoints.sharedInbox : undefined);
|
|
||||||
if (sharedInboxObject != null) {
|
|
||||||
const sharedInbox = getApId(sharedInboxObject);
|
|
||||||
if (!(typeof sharedInbox === "string" && sharedInbox.length > 0 && extractDbHost(sharedInbox) === expectHost)) {
|
|
||||||
throw new Error("invalid Actor: wrong shared inbox");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x.followers != null) {
|
|
||||||
x.followers = getApId(x.followers);
|
|
||||||
if (!(typeof x.followers === "string" && x.followers.length > 0 && extractDbHost(x.followers) === expectHost)) {
|
|
||||||
throw new Error("invalid Actor: wrong followers");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x.following != null) {
|
|
||||||
x.following = getApId(x.following);
|
|
||||||
if (!(typeof x.following === "string" && x.following.length > 0 && extractDbHost(x.following) === expectHost)) {
|
|
||||||
throw new Error("invalid Actor: wrong following");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
typeof x.preferredUsername === "string" &&
|
typeof x.preferredUsername === "string" &&
|
||||||
|
|
@ -140,7 +114,7 @@ function validateActor(x: IObject, uri: string): IActor {
|
||||||
x.summary = truncate(x.summary, summaryLength);
|
x.summary = truncate(x.summary, summaryLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
const idHost = toPuny(new URL(x.id!).host);
|
const idHost = toPuny(new URL(x.id!).hostname);
|
||||||
if (idHost !== expectHost) {
|
if (idHost !== expectHost) {
|
||||||
throw new Error("invalid Actor: id has different host");
|
throw new Error("invalid Actor: id has different host");
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +124,7 @@ function validateActor(x: IObject, uri: string): IActor {
|
||||||
throw new Error("invalid Actor: publicKey.id is not a string");
|
throw new Error("invalid Actor: publicKey.id is not a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicKeyIdHost = toPuny(new URL(x.publicKey.id).host);
|
const publicKeyIdHost = toPuny(new URL(x.publicKey.id).hostname);
|
||||||
if (publicKeyIdHost !== expectHost) {
|
if (publicKeyIdHost !== expectHost) {
|
||||||
throw new Error("invalid Actor: publicKey.id has different host");
|
throw new Error("invalid Actor: publicKey.id has different host");
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +148,7 @@ export async function fetchPerson(
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
// Fetch from the database if the URI points to this server
|
// Fetch from the database if the URI points to this server
|
||||||
if (extractDbHost(uri) === toPuny(config.host)) {
|
if (uri.startsWith(`${config.url}/`)) {
|
||||||
const id = uri.split("/").pop();
|
const id = uri.split("/").pop();
|
||||||
const u = await Users.findOneBy({ id });
|
const u = await Users.findOneBy({ id });
|
||||||
if (u) await uriPersonCache.set(uri, u);
|
if (u) await uriPersonCache.set(uri, u);
|
||||||
|
|
@ -204,7 +178,7 @@ export async function createPerson(
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
if (typeof uri !== "string") throw new Error("uri is not string");
|
if (typeof uri !== "string") throw new Error("uri is not string");
|
||||||
|
|
||||||
if (extractDbHost(uri) === toPuny(config.host)) {
|
if (uri.startsWith(config.url)) {
|
||||||
throw new StatusError(
|
throw new StatusError(
|
||||||
"cannot resolve local user",
|
"cannot resolve local user",
|
||||||
400,
|
400,
|
||||||
|
|
@ -221,10 +195,10 @@ export async function createPerson(
|
||||||
person = validateActor(object, uri);
|
person = validateActor(object, uri);
|
||||||
}
|
}
|
||||||
catch (e: any) {
|
catch (e: any) {
|
||||||
// Work around GoToSocial issue #1186 (ref: https://github.com/superseriousbusiness/gotosocial/issues/1186)
|
if (typeof object.publicKey?.owner !== 'string')
|
||||||
if (typeof object.publicKey?.owner !== 'string' || object.inbox != null)
|
|
||||||
throw e;
|
throw e;
|
||||||
|
|
||||||
|
// Work around GoToSocial issue #1186 (ref: https://github.com/superseriousbusiness/gotosocial/issues/1186)
|
||||||
logger.info(`Received stub actor, re-resolving with key owner uri: ${object.publicKey.owner}`);
|
logger.info(`Received stub actor, re-resolving with key owner uri: ${object.publicKey.owner}`);
|
||||||
object = (await resolver.resolve(object.publicKey.owner)) as any;
|
object = (await resolver.resolve(object.publicKey.owner)) as any;
|
||||||
person = validateActor(object, uri);
|
person = validateActor(object, uri);
|
||||||
|
|
@ -287,19 +261,12 @@ export async function createPerson(
|
||||||
|
|
||||||
const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/);
|
const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/);
|
||||||
|
|
||||||
let url = getOneApHrefNullable(person.url);
|
const url = getOneApHrefNullable(person.url);
|
||||||
const urlUrl = url != null ? new URL(url) : null;
|
|
||||||
const uriUrl = new URL(uri);
|
|
||||||
|
|
||||||
if (urlUrl != null && urlUrl.protocol != 'https:') {
|
if (url && !url.startsWith("https://")) {
|
||||||
throw new Error(`unexpected schema of person url: ${url}`);
|
throw new Error(`unexpected schema of person url: ${url}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlUrl != null && urlUrl.host != uriUrl.host) {
|
|
||||||
logger.debug("Person url host doesn't match person uri host, clearing variable");
|
|
||||||
url = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let followersCount: number | undefined;
|
let followersCount: number | undefined;
|
||||||
|
|
||||||
if (typeof person.followers === "string") {
|
if (typeof person.followers === "string") {
|
||||||
|
|
@ -507,7 +474,7 @@ export async function updatePerson(
|
||||||
if (typeof uri !== "string") throw new Error("uri is not string");
|
if (typeof uri !== "string") throw new Error("uri is not string");
|
||||||
|
|
||||||
// Skip if the URI points to this server
|
// Skip if the URI points to this server
|
||||||
if (extractDbHost(uri) === toPuny(config.host)) {
|
if (uri.startsWith(`${config.url}/`)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { getApId, isQuestion } from "../type.js";
|
||||||
import { apLogger } from "../logger.js";
|
import { apLogger } from "../logger.js";
|
||||||
import { Notes, Polls } from "@/models/index.js";
|
import { Notes, Polls } from "@/models/index.js";
|
||||||
import type { IPoll } from "@/models/entities/poll.js";
|
import type { IPoll } from "@/models/entities/poll.js";
|
||||||
import { extractDbHost, toPuny } from "@/misc/convert-host.js";
|
|
||||||
|
|
||||||
export async function extractPollFromQuestion(
|
export async function extractPollFromQuestion(
|
||||||
source: string | IObject,
|
source: string | IObject,
|
||||||
|
|
@ -56,7 +55,7 @@ export async function updateQuestion(
|
||||||
const uri = typeof value === "string" ? value : getApId(value);
|
const uri = typeof value === "string" ? value : getApId(value);
|
||||||
|
|
||||||
// Skip if URI points to this server
|
// Skip if URI points to this server
|
||||||
if (extractDbHost(uri) === toPuny(config.host)) throw new Error("uri points local");
|
if (uri.startsWith(`${config.url}/`)) throw new Error("uri points local");
|
||||||
|
|
||||||
//#region Already registered with this server?
|
//#region Already registered with this server?
|
||||||
const note = await Notes.findOneBy({ uri });
|
const note = await Notes.findOneBy({ uri });
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ function inbox(ctx: Router.RouterContext) {
|
||||||
let signature;
|
let signature;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
signature = httpSignature.parseRequest(ctx.req, { headers: ['(request-target)', 'digest', 'host', 'date'], authorizationHeaderName: 'signature' });
|
signature = httpSignature.parseRequest(ctx.req, { headers: ['(request-target)', 'digest', 'host', 'date'] });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ctx.status = 401;
|
ctx.status = 401;
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ export const meta = {
|
||||||
tags: ["federation"],
|
tags: ["federation"],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
requireAdmin: true,
|
|
||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
duration: HOUR,
|
duration: HOUR,
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,40 @@ export async function proxyMedia(ctx: Koa.Context) {
|
||||||
|
|
||||||
if (ctx.status == 429) return;
|
if (ctx.status == 429) return;
|
||||||
|
|
||||||
|
const { hostname } = new URL(url);
|
||||||
|
let resolvedIps;
|
||||||
|
try {
|
||||||
|
resolvedIps = await promises.resolve(hostname);
|
||||||
|
} catch (error) {
|
||||||
|
ctx.status = 400;
|
||||||
|
ctx.body = { message: "Invalid URL" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSSRF = resolvedIps.some((ip) => {
|
||||||
|
if (net.isIPv4(ip)) {
|
||||||
|
const parts = ip.split(".").map(Number);
|
||||||
|
return (
|
||||||
|
parts[0] === 10 ||
|
||||||
|
(parts[0] === 172 && parts[1] >= 16 && parts[1] < 32) ||
|
||||||
|
(parts[0] === 192 && parts[1] === 168) ||
|
||||||
|
parts[0] === 127 ||
|
||||||
|
parts[0] === 0
|
||||||
|
);
|
||||||
|
} else if (net.isIPv6(ip)) {
|
||||||
|
return (
|
||||||
|
ip.startsWith("::") || ip.startsWith("fc00:") || ip.startsWith("fe80:")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isSSRF) {
|
||||||
|
ctx.status = 400;
|
||||||
|
ctx.body = { message: "Access to this URL is not allowed" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const [path, cleanup] = await createTemp();
|
const [path, cleanup] = await createTemp();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@
|
||||||
{{ i18n.ts.updateRemoteUser }}</FormButton
|
{{ i18n.ts.updateRemoteUser }}</FormButton
|
||||||
>
|
>
|
||||||
|
|
||||||
<FormFolder class="_formBlock" v-if="iAmAdmin">
|
<FormFolder class="_formBlock">
|
||||||
<template #label>Raw</template>
|
<template #label>Raw</template>
|
||||||
|
|
||||||
<MkObjectView v-if="ap" tall :value="ap">
|
<MkObjectView v-if="ap" tall :value="ap">
|
||||||
|
|
@ -577,15 +577,13 @@ watch(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (iAmAdmin) {
|
watch($$(user), () => {
|
||||||
watch($$(user), () => {
|
os.api("ap/get", {
|
||||||
os.api("ap/get", {
|
uri: user.uri ?? `${url}/users/${user.id}`,
|
||||||
uri: user.uri ?? `${url}/users/${user.id}`,
|
}).then((res) => {
|
||||||
}).then((res) => {
|
ap = res;
|
||||||
ap = res;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
const headerActions = $computed(() => []);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
/>
|
/>
|
||||||
<MkRemoteCaution
|
<MkRemoteCaution
|
||||||
v-if="user.host != null"
|
v-if="user.host != null"
|
||||||
:href="user.url ?? user.uri"
|
:href="user.url"
|
||||||
class="warn"
|
class="warn"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -293,7 +293,7 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||||
type: "a",
|
type: "a",
|
||||||
icon: "ph-arrow-square-out ph-bold ph-lg",
|
icon: "ph-arrow-square-out ph-bold ph-lg",
|
||||||
text: i18n.ts.showOnRemote,
|
text: i18n.ts.showOnRemote,
|
||||||
href: user.url ?? user.uri,
|
href: user.url,
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
|
||||||
66
yarn.lock
66
yarn.lock
|
|
@ -5593,7 +5593,7 @@ __metadata:
|
||||||
mfm-js: "npm:0.23.3"
|
mfm-js: "npm:0.23.3"
|
||||||
mime-types: "npm:2.1.35"
|
mime-types: "npm:2.1.35"
|
||||||
mocha: "npm:10.2.0"
|
mocha: "npm:10.2.0"
|
||||||
msgpackr: "npm:1.11.2"
|
msgpackr: "npm:1.9.5"
|
||||||
multer: "npm:1.4.4-lts.1"
|
multer: "npm:1.4.4-lts.1"
|
||||||
nested-property: "npm:4.0.0"
|
nested-property: "npm:4.0.0"
|
||||||
node-fetch: "npm:3.3.2"
|
node-fetch: "npm:3.3.2"
|
||||||
|
|
@ -5615,7 +5615,7 @@ __metadata:
|
||||||
qs: "npm:6.11.2"
|
qs: "npm:6.11.2"
|
||||||
random-seed: "npm:0.3.0"
|
random-seed: "npm:0.3.0"
|
||||||
ratelimiter: "npm:3.4.1"
|
ratelimiter: "npm:3.4.1"
|
||||||
re2: "npm:^1.21.4"
|
re2: "npm:^1.20.11"
|
||||||
redis-lock: "npm:0.1.4"
|
redis-lock: "npm:0.1.4"
|
||||||
redis-semaphore: "npm:5.3.1"
|
redis-semaphore: "npm:5.3.1"
|
||||||
reflect-metadata: "npm:0.1.13"
|
reflect-metadata: "npm:0.1.13"
|
||||||
|
|
@ -15035,15 +15035,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"msgpackr@npm:1.11.2":
|
"msgpackr@npm:1.9.5":
|
||||||
version: 1.11.2
|
version: 1.9.5
|
||||||
resolution: "msgpackr@npm:1.11.2"
|
resolution: "msgpackr@npm:1.9.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
msgpackr-extract: "npm:^3.0.2"
|
msgpackr-extract: "npm:^3.0.2"
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
msgpackr-extract:
|
msgpackr-extract:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 10/7602f1e91e5ba13f4289ec9cab0d3f3db87d4ed323bebcb40a0c43ba2f6153192bffb63a5bb4755faacb6e0985f307c35084f40eaba1c325b7035da91381f01a
|
checksum: 10/d95fbee39b6046bdee06c59af43efda7068c6d1b0406d82b345b2ffd31f917b58829f925ceb56bbd686374ae52c952d3106747c41097021a8218c023715c948a
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -15099,12 +15099,12 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"nan@npm:^2.20.0":
|
"nan@npm:^2.19.0":
|
||||||
version: 2.22.0
|
version: 2.19.0
|
||||||
resolution: "nan@npm:2.22.0"
|
resolution: "nan@npm:2.19.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
node-gyp: "npm:latest"
|
node-gyp: "npm:latest"
|
||||||
checksum: 10/ab165ba910e549fcc21fd561a33f534d86e81ae36c97b1019dcfe506b09692ff867c97794a54b49c9a83b8b485f529f0f58d24966c3a11863c97dc70814f4d50
|
checksum: 10/b97f680753113bcd803cb174e40baa01e04aa4cb95ee62b48841336d9c48b278a2eeff71a4a0d7315b8f639fb1e38049925d3be1c6e266c158dc8f7d95d67eaa
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -15277,9 +15277,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"node-gyp@npm:^10.2.0":
|
"node-gyp@npm:^10.0.1":
|
||||||
version: 10.2.0
|
version: 10.0.1
|
||||||
resolution: "node-gyp@npm:10.2.0"
|
resolution: "node-gyp@npm:10.0.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
env-paths: "npm:^2.2.0"
|
env-paths: "npm:^2.2.0"
|
||||||
exponential-backoff: "npm:^3.1.1"
|
exponential-backoff: "npm:^3.1.1"
|
||||||
|
|
@ -15287,13 +15287,13 @@ __metadata:
|
||||||
graceful-fs: "npm:^4.2.6"
|
graceful-fs: "npm:^4.2.6"
|
||||||
make-fetch-happen: "npm:^13.0.0"
|
make-fetch-happen: "npm:^13.0.0"
|
||||||
nopt: "npm:^7.0.0"
|
nopt: "npm:^7.0.0"
|
||||||
proc-log: "npm:^4.1.0"
|
proc-log: "npm:^3.0.0"
|
||||||
semver: "npm:^7.3.5"
|
semver: "npm:^7.3.5"
|
||||||
tar: "npm:^6.2.1"
|
tar: "npm:^6.1.2"
|
||||||
which: "npm:^4.0.0"
|
which: "npm:^4.0.0"
|
||||||
bin:
|
bin:
|
||||||
node-gyp: bin/node-gyp.js
|
node-gyp: bin/node-gyp.js
|
||||||
checksum: 10/41773093b1275751dec942b985982fd4e7a69b88cae719b868babcef3880ee6168aaec8dcaa8cd0b9fa7c84873e36cc549c6cac6a124ee65ba4ce1f1cc108cfe
|
checksum: 10/578cf0c821f258ce4b6ebce4461eca4c991a4df2dee163c0624f2fe09c7d6d37240be4942285a0048d307230248ee0b18382d6623b9a0136ce9533486deddfa8
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -17073,10 +17073,10 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"proc-log@npm:^4.1.0":
|
"proc-log@npm:^3.0.0":
|
||||||
version: 4.2.0
|
version: 3.0.0
|
||||||
resolution: "proc-log@npm:4.2.0"
|
resolution: "proc-log@npm:3.0.0"
|
||||||
checksum: 10/4e1394491b717f6c1ade15c570ecd4c2b681698474d3ae2d303c1e4b6ab9455bd5a81566211e82890d5a5ae9859718cc6954d5150bb18b09b72ecb297beae90a
|
checksum: 10/02b64e1b3919e63df06f836b98d3af002b5cd92655cab18b5746e37374bfb73e03b84fe305454614b34c25b485cc687a9eebdccf0242cda8fda2475dd2c97e02
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -17541,14 +17541,14 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"re2@npm:^1.21.4":
|
"re2@npm:^1.20.11":
|
||||||
version: 1.21.4
|
version: 1.20.11
|
||||||
resolution: "re2@npm:1.21.4"
|
resolution: "re2@npm:1.20.11"
|
||||||
dependencies:
|
dependencies:
|
||||||
install-artifact-from-github: "npm:^1.3.5"
|
install-artifact-from-github: "npm:^1.3.5"
|
||||||
nan: "npm:^2.20.0"
|
nan: "npm:^2.19.0"
|
||||||
node-gyp: "npm:^10.2.0"
|
node-gyp: "npm:^10.0.1"
|
||||||
checksum: 10/926871cc84dab656afc035118c79d121211f21f4154084d7f6c05a05f746b5355f04e80a773db9ca817718dde03c561421b0a962300698c9f2eeafa4f70fd364
|
checksum: 10/a8665c861c632c67db448832a5a6a0092a1a29b8b6b731d6ce10f0017ba2871620780a745a8b2cbdd77e57ecf9e7bc8983c7ec5e10e6da6c06079a98146db443
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -19655,20 +19655,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tar@npm:^6.2.1":
|
|
||||||
version: 6.2.1
|
|
||||||
resolution: "tar@npm:6.2.1"
|
|
||||||
dependencies:
|
|
||||||
chownr: "npm:^2.0.0"
|
|
||||||
fs-minipass: "npm:^2.0.0"
|
|
||||||
minipass: "npm:^5.0.0"
|
|
||||||
minizlib: "npm:^2.1.1"
|
|
||||||
mkdirp: "npm:^1.0.3"
|
|
||||||
yallist: "npm:^4.0.0"
|
|
||||||
checksum: 10/bfbfbb2861888077fc1130b84029cdc2721efb93d1d1fb80f22a7ac3a98ec6f8972f29e564103bbebf5e97be67ebc356d37fa48dbc4960600a1eb7230fbd1ea0
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"tdigest@npm:^0.1.1":
|
"tdigest@npm:^0.1.1":
|
||||||
version: 0.1.2
|
version: 0.1.2
|
||||||
resolution: "tdigest@npm:0.1.2"
|
resolution: "tdigest@npm:0.1.2"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue