Removed NodeInfo and WebFinger endpoints
This commit is contained in:
parent
86c4804f9b
commit
ddf7e07481
|
@ -1,10 +1,7 @@
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
indent_style = tab
|
|
||||||
indent_size = 2
|
|
||||||
charset = utf-8
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.yml]
|
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
charset = utf-8
|
||||||
|
insert_final_newline = true
|
|
@ -1,72 +0,0 @@
|
||||||
# Replace example.tld with your domain
|
|
||||||
|
|
||||||
# For WebSocket
|
|
||||||
map $http_upgrade $connection_upgrade {
|
|
||||||
default upgrade;
|
|
||||||
'' close;
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=cache1:16m max_size=1g inactive=720m use_temp_path=off;
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
server_name example.tld;
|
|
||||||
|
|
||||||
# For SSL domain validation
|
|
||||||
root /var/www/html;
|
|
||||||
location /.well-known/acme-challenge/ { allow all; }
|
|
||||||
location /.well-known/pki-validation/ { allow all; }
|
|
||||||
location / { return 301 https://$server_name$request_uri; }
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
listen [::]:443 ssl http2;
|
|
||||||
server_name example.tld;
|
|
||||||
|
|
||||||
ssl_session_timeout 1d;
|
|
||||||
ssl_session_cache shared:ssl_session_cache:10m;
|
|
||||||
ssl_session_tickets off;
|
|
||||||
|
|
||||||
# To use Let's Encrypt certificate
|
|
||||||
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
|
|
||||||
|
|
||||||
# To use Debian/Ubuntu's self-signed certificate (For testing or before issuing a certificate)
|
|
||||||
#ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
|
|
||||||
#ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
|
|
||||||
|
|
||||||
# SSL protocol settings
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
|
||||||
ssl_prefer_server_ciphers off;
|
|
||||||
ssl_stapling on;
|
|
||||||
ssl_stapling_verify on;
|
|
||||||
|
|
||||||
# Change to your upload limit
|
|
||||||
client_max_body_size 80m;
|
|
||||||
|
|
||||||
# Proxy to Node
|
|
||||||
location / {
|
|
||||||
proxy_pass http://127.0.0.1:3000;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_redirect off;
|
|
||||||
|
|
||||||
# If it's behind another reverse proxy or CDN, remove the following.
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
|
||||||
|
|
||||||
# For WebSocket
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection $connection_upgrade;
|
|
||||||
|
|
||||||
# Cache settings
|
|
||||||
proxy_cache cache1;
|
|
||||||
proxy_cache_lock on;
|
|
||||||
proxy_cache_use_stale updating;
|
|
||||||
add_header X-Cache $upstream_cache_status;
|
|
||||||
}
|
|
||||||
}
|
|
103
cliff.toml
103
cliff.toml
|
@ -1,103 +0,0 @@
|
||||||
# configuration file for git-cliff (0.1.0)
|
|
||||||
|
|
||||||
[changelog]
|
|
||||||
# changelog header
|
|
||||||
header = """
|
|
||||||
# Changelog\n
|
|
||||||
All changes from v13.0.0 onwards, for a full list of differences read CALCKEY.md\n
|
|
||||||
"""
|
|
||||||
# template for the changelog body
|
|
||||||
# https://tera.netlify.app/docs/#introduction
|
|
||||||
body = """
|
|
||||||
{% if version %}\
|
|
||||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
|
||||||
{% else %}\
|
|
||||||
## [unreleased]
|
|
||||||
{% endif %}\
|
|
||||||
{% for group, commits in commits | group_by(attribute="group") %}
|
|
||||||
### {{ group | upper_first }}
|
|
||||||
{% for commit in commits %}
|
|
||||||
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}\n
|
|
||||||
"""
|
|
||||||
# remove the leading and trailing whitespace from the template
|
|
||||||
trim = true
|
|
||||||
# changelog footer
|
|
||||||
footer = """
|
|
||||||
<!-- generated by git-cliff -->
|
|
||||||
"""
|
|
||||||
|
|
||||||
[git]
|
|
||||||
# parse the commits based on https://www.conventionalcommits.org
|
|
||||||
conventional_commits = false
|
|
||||||
# filter out the commits that are not conventional
|
|
||||||
filter_unconventional = true
|
|
||||||
# process each line of a commit as an individual commit
|
|
||||||
split_commits = false
|
|
||||||
# regex for preprocessing the commit messages
|
|
||||||
commit_preprocessors = [
|
|
||||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"},
|
|
||||||
]
|
|
||||||
# regex for parsing and grouping commits
|
|
||||||
commit_parsers = [
|
|
||||||
{ message = "^feat", group = "Features"},
|
|
||||||
{ message = "^add", group = "Features"},
|
|
||||||
{ message = "^fix", group = "Bug Fixes"},
|
|
||||||
{ message = "^prevent", group = "Bug Fixes"},
|
|
||||||
{ message = "^doc", group = "Documentation"},
|
|
||||||
{ message = "^perf", group = "Performance"},
|
|
||||||
{ message = "^🎨", group = "Refactor"},
|
|
||||||
{ message = "^enhance", group = "Refactor"},
|
|
||||||
{ message = "^⚡️", group = "Refactor"},
|
|
||||||
{ message = "^🔥", group = "Features"},
|
|
||||||
{ message = "^🐛", group = "Bug Fixes"},
|
|
||||||
{ message = "^🚑️", group = "Bug Fixes"},
|
|
||||||
{ message = "^block", group = "Bug Fixes"},
|
|
||||||
{ message = "^✨", group = "Features"},
|
|
||||||
{ message = "^📝", group = "Documentation"},
|
|
||||||
{ message = "^🚀", group = "Features"},
|
|
||||||
{ message = "^💄", group = "Styling"},
|
|
||||||
{ message = "^✅", group = "Testing"},
|
|
||||||
{ message = "^🔒️", group = "Security"},
|
|
||||||
{ message = "^🚨", group = "Testing"},
|
|
||||||
{ message = "^💚", group = "CI"},
|
|
||||||
{ message = "^👷", group = "CI"},
|
|
||||||
{ message = "^⬇️", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^⬆️", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^📌", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^➕", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^➖", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^♻️", group = "Refactor"},
|
|
||||||
{ message = "^🔧", group = "CI"},
|
|
||||||
{ message = "^🔨", group = "CI"},
|
|
||||||
{ message = "^🌐", group = "Localization"},
|
|
||||||
{ message = "^✏️", group = "Localization"},
|
|
||||||
{ message = "^👽️", group = "Bug Fixes"},
|
|
||||||
{ message = "^🍱", group = "Styling"},
|
|
||||||
{ message = "^♿️", group = "Styling"},
|
|
||||||
{ message = "^🩹", group = "Bug Fixes"},
|
|
||||||
{ message = "^refactor", group = "Refactor"},
|
|
||||||
{ message = "^style", group = "Styling"},
|
|
||||||
{ message = "^test", group = "Testing"},
|
|
||||||
{ message = "^chore\\(release\\): prepare for", skip = true},
|
|
||||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
|
||||||
{ message = "^update", group = "Miscellaneous Tasks"},
|
|
||||||
{ body = ".*security", group = "Security"},
|
|
||||||
]
|
|
||||||
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
|
||||||
protect_breaking_commits = false
|
|
||||||
# filter out the commits that are not matched by commit parsers
|
|
||||||
filter_commits = false
|
|
||||||
# glob pattern for matching git tags
|
|
||||||
tag_pattern = "v[0-9]*"
|
|
||||||
# regex for skipping tags
|
|
||||||
skip_tags = "v0.1.0-beta.1"
|
|
||||||
# regex for ignoring tags
|
|
||||||
ignore_tags = ""
|
|
||||||
# sort the tags chronologically
|
|
||||||
date_order = false
|
|
||||||
# sort the commits inside sections by oldest/newest order
|
|
||||||
sort_commits = "oldest"
|
|
||||||
# limit the number of commits included in the changelog.
|
|
||||||
# limit_commits = 42
|
|
|
@ -21,7 +21,6 @@ import { publishMainStream } from "@/services/stream.js";
|
||||||
import * as Acct from "@/misc/acct.js";
|
import * as Acct from "@/misc/acct.js";
|
||||||
import { envOption } from "@/env.js";
|
import { envOption } from "@/env.js";
|
||||||
import activityPub from "./activitypub.js";
|
import activityPub from "./activitypub.js";
|
||||||
import nodeinfo from "./nodeinfo.js";
|
|
||||||
import wellKnown from "./well-known.js";
|
import wellKnown from "./well-known.js";
|
||||||
import apiServer from "./api/index.js";
|
import apiServer from "./api/index.js";
|
||||||
import fileServer from "./file/index.js";
|
import fileServer from "./file/index.js";
|
||||||
|
@ -36,30 +35,30 @@ const app = new Koa();
|
||||||
app.proxy = true;
|
app.proxy = true;
|
||||||
|
|
||||||
if (!["production", "test"].includes(process.env.NODE_ENV || "")) {
|
if (!["production", "test"].includes(process.env.NODE_ENV || "")) {
|
||||||
// Logger
|
// Logger
|
||||||
app.use(
|
app.use(
|
||||||
koaLogger((str) => {
|
koaLogger((str) => {
|
||||||
serverLogger.info(str);
|
serverLogger.info(str);
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Delay
|
// Delay
|
||||||
if (envOption.slow) {
|
if (envOption.slow) {
|
||||||
app.use(
|
app.use(
|
||||||
slow({
|
slow({
|
||||||
delay: 3000,
|
delay: 3000,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HSTS
|
// HSTS
|
||||||
// 6months (15552000sec)
|
// 6months (15552000sec)
|
||||||
if (config.url.startsWith("https") && !config.disableHsts) {
|
if (config.url.startsWith("https") && !config.disableHsts) {
|
||||||
app.use(async (ctx, next) => {
|
app.use(async (ctx, next) => {
|
||||||
ctx.set("strict-transport-security", "max-age=15552000; preload");
|
ctx.set("strict-transport-security", "max-age=15552000; preload");
|
||||||
await next();
|
await next();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(mount("/api", apiServer));
|
app.use(mount("/api", apiServer));
|
||||||
|
@ -71,66 +70,65 @@ const router = new Router();
|
||||||
|
|
||||||
// Routing
|
// Routing
|
||||||
router.use(activityPub.routes());
|
router.use(activityPub.routes());
|
||||||
router.use(nodeinfo.routes());
|
|
||||||
router.use(wellKnown.routes());
|
router.use(wellKnown.routes());
|
||||||
|
|
||||||
router.get("/avatar/@:acct", async (ctx) => {
|
router.get("/avatar/@:acct", async (ctx) => {
|
||||||
const { username, host } = Acct.parse(ctx.params.acct);
|
const { username, host } = Acct.parse(ctx.params.acct);
|
||||||
const user = await Users.findOne({
|
const user = await Users.findOne({
|
||||||
where: {
|
where: {
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: host == null || host === config.host ? IsNull() : host,
|
host: host == null || host === config.host ? IsNull() : host,
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
},
|
},
|
||||||
relations: ["avatar"],
|
relations: ["avatar"],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
ctx.redirect(Users.getAvatarUrlSync(user));
|
ctx.redirect(Users.getAvatarUrlSync(user));
|
||||||
} else {
|
} else {
|
||||||
ctx.redirect("/static-assets/user-unknown.png");
|
ctx.redirect("/static-assets/user-unknown.png");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/identicon/:x", async (ctx) => {
|
router.get("/identicon/:x", async (ctx) => {
|
||||||
const [temp, cleanup] = await createTemp();
|
const [temp, cleanup] = await createTemp();
|
||||||
await genIdenticon(ctx.params.x, fs.createWriteStream(temp));
|
await genIdenticon(ctx.params.x, fs.createWriteStream(temp));
|
||||||
ctx.set("Content-Type", "image/png");
|
ctx.set("Content-Type", "image/png");
|
||||||
ctx.body = fs.createReadStream(temp).on("close", () => cleanup());
|
ctx.body = fs.createReadStream(temp).on("close", () => cleanup());
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/verify-email/:code", async (ctx) => {
|
router.get("/verify-email/:code", async (ctx) => {
|
||||||
const profile = await UserProfiles.findOneBy({
|
const profile = await UserProfiles.findOneBy({
|
||||||
emailVerifyCode: ctx.params.code,
|
emailVerifyCode: ctx.params.code,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
ctx.body = "Verify succeeded!";
|
ctx.body = "Verify succeeded!";
|
||||||
ctx.status = 200;
|
ctx.status = 200;
|
||||||
|
|
||||||
await UserProfiles.update(
|
await UserProfiles.update(
|
||||||
{ userId: profile.userId },
|
{ userId: profile.userId },
|
||||||
{
|
{
|
||||||
emailVerified: true,
|
emailVerified: true,
|
||||||
emailVerifyCode: null,
|
emailVerifyCode: null,
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
publishMainStream(
|
publishMainStream(
|
||||||
profile.userId,
|
profile.userId,
|
||||||
"meUpdated",
|
"meUpdated",
|
||||||
await Users.pack(
|
await Users.pack(
|
||||||
profile.userId,
|
profile.userId,
|
||||||
{ id: profile.userId },
|
{ id: profile.userId },
|
||||||
{
|
{
|
||||||
detail: true,
|
detail: true,
|
||||||
includeSecrets: true,
|
includeSecrets: true,
|
||||||
},
|
}
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ctx.status = 404;
|
ctx.status = 404;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register router
|
// Register router
|
||||||
|
@ -139,51 +137,51 @@ app.use(router.routes());
|
||||||
app.use(mount(webServer));
|
app.use(mount(webServer));
|
||||||
|
|
||||||
function createServer() {
|
function createServer() {
|
||||||
return http.createServer(app.callback());
|
return http.createServer(app.callback());
|
||||||
}
|
}
|
||||||
|
|
||||||
// For testing
|
// For testing
|
||||||
export const startServer = () => {
|
export const startServer = () => {
|
||||||
const server = createServer();
|
const server = createServer();
|
||||||
|
|
||||||
initializeStreamingServer(server);
|
initializeStreamingServer(server);
|
||||||
|
|
||||||
server.listen(config.port);
|
server.listen(config.port);
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default () =>
|
export default () =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
const server = createServer();
|
const server = createServer();
|
||||||
|
|
||||||
initializeStreamingServer(server);
|
initializeStreamingServer(server);
|
||||||
|
|
||||||
server.on("error", (e) => {
|
server.on("error", (e) => {
|
||||||
switch ((e as any).code) {
|
switch ((e as any).code) {
|
||||||
case "EACCES":
|
case "EACCES":
|
||||||
serverLogger.error(
|
serverLogger.error(
|
||||||
`You do not have permission to listen on port ${config.port}.`,
|
`You do not have permission to listen on port ${config.port}.`
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "EADDRINUSE":
|
case "EADDRINUSE":
|
||||||
serverLogger.error(
|
serverLogger.error(
|
||||||
`Port ${config.port} is already in use by another process.`,
|
`Port ${config.port} is already in use by another process.`
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
serverLogger.error(e);
|
serverLogger.error(e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cluster.isWorker) {
|
if (cluster.isWorker) {
|
||||||
process.send!("listenFailed");
|
process.send!("listenFailed");
|
||||||
} else {
|
} else {
|
||||||
// disableClustering
|
// disableClustering
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
server.listen(config.port, resolve);
|
server.listen(config.port, resolve);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
import Router from "@koa/router";
|
|
||||||
import config from "@/config/index.js";
|
|
||||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
|
||||||
import { Users, Notes } from "@/models/index.js";
|
|
||||||
import { IsNull, MoreThan } from "typeorm";
|
|
||||||
import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js";
|
|
||||||
import { Cache } from "@/misc/cache.js";
|
|
||||||
|
|
||||||
const router = new Router();
|
|
||||||
|
|
||||||
const nodeinfo2_1path = "/nodeinfo/2.1";
|
|
||||||
const nodeinfo2_0path = "/nodeinfo/2.0";
|
|
||||||
|
|
||||||
// to cleo: leave this http or bonks
|
|
||||||
export const links = [
|
|
||||||
{
|
|
||||||
rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
|
|
||||||
href: config.url + nodeinfo2_1path,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
|
|
||||||
href: config.url + nodeinfo2_0path,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const nodeinfo2 = async () => {
|
|
||||||
const now = Date.now();
|
|
||||||
const [meta, total, activeHalfyear, activeMonth, localPosts] =
|
|
||||||
await Promise.all([
|
|
||||||
fetchMeta(true),
|
|
||||||
Users.count({ where: { host: IsNull() } }),
|
|
||||||
Users.count({
|
|
||||||
where: {
|
|
||||||
host: IsNull(),
|
|
||||||
lastActiveDate: MoreThan(new Date(now - 15552000000)),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
Users.count({
|
|
||||||
where: {
|
|
||||||
host: IsNull(),
|
|
||||||
lastActiveDate: MoreThan(new Date(now - 2592000000)),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
Notes.count({ where: { userHost: IsNull() } }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const proxyAccount = meta.proxyAccountId
|
|
||||||
? await Users.pack(meta.proxyAccountId).catch(() => null)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
software: {
|
|
||||||
name: "calckey",
|
|
||||||
version: config.version,
|
|
||||||
repository: meta.repositoryUrl,
|
|
||||||
homepage: "https://calckey.cloud",
|
|
||||||
},
|
|
||||||
protocols: ["activitypub"],
|
|
||||||
services: {
|
|
||||||
inbound: [] as string[],
|
|
||||||
outbound: ["atom1.0", "rss2.0"],
|
|
||||||
},
|
|
||||||
openRegistrations: !meta.disableRegistration,
|
|
||||||
usage: {
|
|
||||||
users: { total, activeHalfyear, activeMonth },
|
|
||||||
localPosts,
|
|
||||||
localComments: 0,
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
nodeName: meta.name,
|
|
||||||
nodeDescription: meta.description,
|
|
||||||
maintainer: {
|
|
||||||
name: meta.maintainerName,
|
|
||||||
email: meta.maintainerEmail,
|
|
||||||
},
|
|
||||||
langs: meta.langs,
|
|
||||||
tosUrl: meta.ToSUrl,
|
|
||||||
repositoryUrl: meta.repositoryUrl,
|
|
||||||
feedbackUrl: meta.feedbackUrl,
|
|
||||||
disableRegistration: meta.disableRegistration,
|
|
||||||
disableLocalTimeline: meta.disableLocalTimeline,
|
|
||||||
disableRecommendedTimeline: meta.disableRecommendedTimeline,
|
|
||||||
disableGlobalTimeline: meta.disableGlobalTimeline,
|
|
||||||
emailRequiredForSignup: meta.emailRequiredForSignup,
|
|
||||||
enableHcaptcha: meta.enableHcaptcha,
|
|
||||||
enableRecaptcha: meta.enableRecaptcha,
|
|
||||||
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
|
|
||||||
maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH,
|
|
||||||
enableTwitterIntegration: meta.enableTwitterIntegration,
|
|
||||||
enableGithubIntegration: meta.enableGithubIntegration,
|
|
||||||
enableDiscordIntegration: meta.enableDiscordIntegration,
|
|
||||||
enableEmail: meta.enableEmail,
|
|
||||||
enableServiceWorker: meta.enableServiceWorker,
|
|
||||||
proxyAccountName: proxyAccount ? proxyAccount.username : null,
|
|
||||||
themeColor: meta.themeColor || "#31748f",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const cache = new Cache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
|
|
||||||
|
|
||||||
router.get(nodeinfo2_1path, async (ctx) => {
|
|
||||||
const base = await cache.fetch(null, () => nodeinfo2());
|
|
||||||
|
|
||||||
ctx.body = { version: "2.1", ...base };
|
|
||||||
ctx.set("Cache-Control", "public, max-age=600");
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get(nodeinfo2_0path, async (ctx) => {
|
|
||||||
const base = await cache.fetch(null, () => nodeinfo2());
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
base.software.repository = undefined;
|
|
||||||
|
|
||||||
ctx.body = { version: "2.0", ...base };
|
|
||||||
ctx.set("Cache-Control", "public, max-age=600");
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
|
@ -1,34 +1,33 @@
|
||||||
import Router from "@koa/router";
|
import Router from "@koa/router";
|
||||||
|
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
import * as Acct from "@/misc/acct.js";
|
import {escapeAttribute, escapeValue} from "@/prelude/xml.js";
|
||||||
import { links } from "./nodeinfo.js";
|
|
||||||
import { escapeAttribute, escapeValue } from "@/prelude/xml.js";
|
|
||||||
import { Users } from "@/models/index.js";
|
|
||||||
import type { User } from "@/models/entities/user.js";
|
|
||||||
import type { FindOptionsWhere } from "typeorm";
|
|
||||||
import { IsNull } from "typeorm";
|
|
||||||
|
|
||||||
// Init router
|
// Init router
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
const XRD = (
|
const XRD = (
|
||||||
...x: {
|
...x: {
|
||||||
element: string;
|
element: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
attributes?: Record<string, string>;
|
attributes?: Record<string, string>;
|
||||||
}[]
|
}[]
|
||||||
) =>
|
) =>
|
||||||
`<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">${x
|
`<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">${x
|
||||||
.map(
|
.map(
|
||||||
({ element, value, attributes }) =>
|
({ element, value, attributes }) =>
|
||||||
`<${Object.entries(
|
`<${Object.entries(
|
||||||
(typeof attributes === "object" && attributes) || {},
|
(typeof attributes === "object" && attributes) || {}
|
||||||
).reduce((a, [k, v]) => `${a} ${k}="${escapeAttribute(v)}"`, element)}${
|
).reduce(
|
||||||
typeof value === "string" ? `>${escapeValue(value)}</${element}` : "/"
|
(a, [k, v]) => `${a} ${k}="${escapeAttribute(v)}"`,
|
||||||
}>`,
|
element
|
||||||
)
|
)}${
|
||||||
.reduce((a, c) => a + c, "")}</XRD>`;
|
typeof value === "string"
|
||||||
|
? `>${escapeValue(value)}</${element}`
|
||||||
|
: "/"
|
||||||
|
}>`
|
||||||
|
)
|
||||||
|
.reduce((a, c) => a + c, "")}</XRD>`;
|
||||||
|
|
||||||
const allPath = "/.well-known/(.*)";
|
const allPath = "/.well-known/(.*)";
|
||||||
const webFingerPath = "/.well-known/webfinger";
|
const webFingerPath = "/.well-known/webfinger";
|
||||||
|
@ -36,156 +35,68 @@ const jrd = "application/jrd+json";
|
||||||
const xrd = "application/xrd+xml";
|
const xrd = "application/xrd+xml";
|
||||||
|
|
||||||
router.use(allPath, async (ctx, next) => {
|
router.use(allPath, async (ctx, next) => {
|
||||||
ctx.set({
|
ctx.set({
|
||||||
"Access-Control-Allow-Headers": "Accept",
|
"Access-Control-Allow-Headers": "Accept",
|
||||||
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
"Access-Control-Expose-Headers": "Vary",
|
"Access-Control-Expose-Headers": "Vary",
|
||||||
});
|
});
|
||||||
await next();
|
await next();
|
||||||
});
|
});
|
||||||
|
|
||||||
router.options(allPath, async (ctx) => {
|
router.options(allPath, async (ctx) => {
|
||||||
ctx.status = 204;
|
ctx.status = 204;
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/.well-known/host-meta", async (ctx) => {
|
router.get("/.well-known/host-meta", async (ctx) => {
|
||||||
ctx.set("Content-Type", xrd);
|
ctx.set("Content-Type", xrd);
|
||||||
ctx.body = XRD({
|
ctx.body = XRD({
|
||||||
element: "Link",
|
element: "Link",
|
||||||
attributes: {
|
attributes: {
|
||||||
rel: "lrdd",
|
rel: "lrdd",
|
||||||
type: xrd,
|
type: xrd,
|
||||||
template: `${config.url}${webFingerPath}?resource={uri}`,
|
template: `${config.url}${webFingerPath}?resource={uri}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/.well-known/host-meta.json", async (ctx) => {
|
router.get("/.well-known/host-meta.json", async (ctx) => {
|
||||||
ctx.set("Content-Type", jrd);
|
ctx.set("Content-Type", jrd);
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
rel: "lrdd",
|
rel: "lrdd",
|
||||||
type: jrd,
|
type: jrd,
|
||||||
template: `${config.url}${webFingerPath}?resource={uri}`,
|
template: `${config.url}${webFingerPath}?resource={uri}`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (config.twa != null) {
|
if (config.twa != null) {
|
||||||
router.get("/.well-known/assetlinks.json", async (ctx) => {
|
router.get("/.well-known/assetlinks.json", async (ctx) => {
|
||||||
ctx.set("Content-Type", "application/json");
|
ctx.set("Content-Type", "application/json");
|
||||||
ctx.body = [
|
ctx.body = [
|
||||||
{
|
{
|
||||||
relation: ["delegate_permission/common.handle_all_urls"],
|
relation: ["delegate_permission/common.handle_all_urls"],
|
||||||
target: {
|
target: {
|
||||||
namespace: config.twa.nameSpace,
|
namespace: config.twa.nameSpace,
|
||||||
package_name: config.twa.packageName,
|
package_name: config.twa.packageName,
|
||||||
sha256_cert_fingerprints: config.twa.sha256CertFingerprints,
|
sha256_cert_fingerprints: config.twa.sha256CertFingerprints,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get("/.well-known/nodeinfo", async (ctx) => {
|
|
||||||
ctx.body = { links };
|
|
||||||
});
|
|
||||||
|
|
||||||
/* TODO
|
/* TODO
|
||||||
router.get('/.well-known/change-password', async ctx => {
|
router.get('/.well-known/change-password', async ctx => {
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
router.get(webFingerPath, async (ctx) => {
|
|
||||||
const fromId = (id: User["id"]): FindOptionsWhere<User> => ({
|
|
||||||
id,
|
|
||||||
host: IsNull(),
|
|
||||||
isSuspended: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const generateQuery = (resource: string): FindOptionsWhere<User> | number =>
|
|
||||||
resource.startsWith(`${config.url.toLowerCase()}/users/`)
|
|
||||||
? fromId(resource.split("/").pop()!)
|
|
||||||
: fromAcct(
|
|
||||||
Acct.parse(
|
|
||||||
resource.startsWith(`${config.url.toLowerCase()}/@`)
|
|
||||||
? resource.split("/").pop()!
|
|
||||||
: resource.startsWith("acct:")
|
|
||||||
? resource.slice("acct:".length)
|
|
||||||
: resource,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const fromAcct = (acct: Acct.Acct): FindOptionsWhere<User> | number =>
|
|
||||||
!acct.host || acct.host === config.host.toLowerCase()
|
|
||||||
? {
|
|
||||||
usernameLower: acct.username,
|
|
||||||
host: IsNull(),
|
|
||||||
isSuspended: false,
|
|
||||||
}
|
|
||||||
: 422;
|
|
||||||
|
|
||||||
if (typeof ctx.query.resource !== "string") {
|
|
||||||
ctx.status = 400;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = generateQuery(ctx.query.resource.toLowerCase());
|
|
||||||
|
|
||||||
if (typeof query === "number") {
|
|
||||||
ctx.status = query;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await Users.findOneBy(query);
|
|
||||||
|
|
||||||
if (user == null) {
|
|
||||||
ctx.status = 404;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subject = `acct:${user.username}@${config.host}`;
|
|
||||||
const self = {
|
|
||||||
rel: "self",
|
|
||||||
type: "application/activity+json",
|
|
||||||
href: `${config.url}/users/${user.id}`,
|
|
||||||
};
|
|
||||||
const profilePage = {
|
|
||||||
rel: "http://webfinger.net/rel/profile-page",
|
|
||||||
type: "text/html",
|
|
||||||
href: `${config.url}/@${user.username}`,
|
|
||||||
};
|
|
||||||
const subscribe = {
|
|
||||||
rel: "http://ostatus.org/schema/1.0/subscribe",
|
|
||||||
template: `${config.url}/authorize-follow?acct={uri}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ctx.accepts(jrd, xrd) === xrd) {
|
|
||||||
ctx.body = XRD(
|
|
||||||
{ element: "Subject", value: subject },
|
|
||||||
{ element: "Link", attributes: self },
|
|
||||||
{ element: "Link", attributes: profilePage },
|
|
||||||
{ element: "Link", attributes: subscribe },
|
|
||||||
);
|
|
||||||
ctx.type = xrd;
|
|
||||||
} else {
|
|
||||||
ctx.body = {
|
|
||||||
subject,
|
|
||||||
links: [self, profilePage, subscribe],
|
|
||||||
};
|
|
||||||
ctx.type = jrd;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.vary("Accept");
|
|
||||||
ctx.set("Cache-Control", "public, max-age=180");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return 404 for other .well-known
|
// Return 404 for other .well-known
|
||||||
router.all(allPath, async (ctx) => {
|
router.all(allPath, async (ctx) => {
|
||||||
ctx.status = 404;
|
ctx.status = 404;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
Loading…
Reference in New Issue