Removed NodeInfo and WebFinger endpoints
This commit is contained in:
parent
86c4804f9b
commit
ddf7e07481
|
@ -1,10 +1,7 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
|
|
|
@ -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 { envOption } from "@/env.js";
|
||||
import activityPub from "./activitypub.js";
|
||||
import nodeinfo from "./nodeinfo.js";
|
||||
import wellKnown from "./well-known.js";
|
||||
import apiServer from "./api/index.js";
|
||||
import fileServer from "./file/index.js";
|
||||
|
@ -36,30 +35,30 @@ const app = new Koa();
|
|||
app.proxy = true;
|
||||
|
||||
if (!["production", "test"].includes(process.env.NODE_ENV || "")) {
|
||||
// Logger
|
||||
app.use(
|
||||
koaLogger((str) => {
|
||||
serverLogger.info(str);
|
||||
}),
|
||||
);
|
||||
// Logger
|
||||
app.use(
|
||||
koaLogger((str) => {
|
||||
serverLogger.info(str);
|
||||
})
|
||||
);
|
||||
|
||||
// Delay
|
||||
if (envOption.slow) {
|
||||
app.use(
|
||||
slow({
|
||||
delay: 3000,
|
||||
}),
|
||||
);
|
||||
}
|
||||
// Delay
|
||||
if (envOption.slow) {
|
||||
app.use(
|
||||
slow({
|
||||
delay: 3000,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// HSTS
|
||||
// 6months (15552000sec)
|
||||
if (config.url.startsWith("https") && !config.disableHsts) {
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.set("strict-transport-security", "max-age=15552000; preload");
|
||||
await next();
|
||||
});
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.set("strict-transport-security", "max-age=15552000; preload");
|
||||
await next();
|
||||
});
|
||||
}
|
||||
|
||||
app.use(mount("/api", apiServer));
|
||||
|
@ -71,66 +70,65 @@ const router = new Router();
|
|||
|
||||
// Routing
|
||||
router.use(activityPub.routes());
|
||||
router.use(nodeinfo.routes());
|
||||
router.use(wellKnown.routes());
|
||||
|
||||
router.get("/avatar/@:acct", async (ctx) => {
|
||||
const { username, host } = Acct.parse(ctx.params.acct);
|
||||
const user = await Users.findOne({
|
||||
where: {
|
||||
usernameLower: username.toLowerCase(),
|
||||
host: host == null || host === config.host ? IsNull() : host,
|
||||
isSuspended: false,
|
||||
},
|
||||
relations: ["avatar"],
|
||||
});
|
||||
const { username, host } = Acct.parse(ctx.params.acct);
|
||||
const user = await Users.findOne({
|
||||
where: {
|
||||
usernameLower: username.toLowerCase(),
|
||||
host: host == null || host === config.host ? IsNull() : host,
|
||||
isSuspended: false,
|
||||
},
|
||||
relations: ["avatar"],
|
||||
});
|
||||
|
||||
if (user) {
|
||||
ctx.redirect(Users.getAvatarUrlSync(user));
|
||||
} else {
|
||||
ctx.redirect("/static-assets/user-unknown.png");
|
||||
}
|
||||
if (user) {
|
||||
ctx.redirect(Users.getAvatarUrlSync(user));
|
||||
} else {
|
||||
ctx.redirect("/static-assets/user-unknown.png");
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/identicon/:x", async (ctx) => {
|
||||
const [temp, cleanup] = await createTemp();
|
||||
await genIdenticon(ctx.params.x, fs.createWriteStream(temp));
|
||||
ctx.set("Content-Type", "image/png");
|
||||
ctx.body = fs.createReadStream(temp).on("close", () => cleanup());
|
||||
const [temp, cleanup] = await createTemp();
|
||||
await genIdenticon(ctx.params.x, fs.createWriteStream(temp));
|
||||
ctx.set("Content-Type", "image/png");
|
||||
ctx.body = fs.createReadStream(temp).on("close", () => cleanup());
|
||||
});
|
||||
|
||||
router.get("/verify-email/:code", async (ctx) => {
|
||||
const profile = await UserProfiles.findOneBy({
|
||||
emailVerifyCode: ctx.params.code,
|
||||
});
|
||||
const profile = await UserProfiles.findOneBy({
|
||||
emailVerifyCode: ctx.params.code,
|
||||
});
|
||||
|
||||
if (profile != null) {
|
||||
ctx.body = "Verify succeeded!";
|
||||
ctx.status = 200;
|
||||
if (profile != null) {
|
||||
ctx.body = "Verify succeeded!";
|
||||
ctx.status = 200;
|
||||
|
||||
await UserProfiles.update(
|
||||
{ userId: profile.userId },
|
||||
{
|
||||
emailVerified: true,
|
||||
emailVerifyCode: null,
|
||||
},
|
||||
);
|
||||
await UserProfiles.update(
|
||||
{ userId: profile.userId },
|
||||
{
|
||||
emailVerified: true,
|
||||
emailVerifyCode: null,
|
||||
}
|
||||
);
|
||||
|
||||
publishMainStream(
|
||||
profile.userId,
|
||||
"meUpdated",
|
||||
await Users.pack(
|
||||
profile.userId,
|
||||
{ id: profile.userId },
|
||||
{
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ctx.status = 404;
|
||||
}
|
||||
publishMainStream(
|
||||
profile.userId,
|
||||
"meUpdated",
|
||||
await Users.pack(
|
||||
profile.userId,
|
||||
{ id: profile.userId },
|
||||
{
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
ctx.status = 404;
|
||||
}
|
||||
});
|
||||
|
||||
// Register router
|
||||
|
@ -139,51 +137,51 @@ app.use(router.routes());
|
|||
app.use(mount(webServer));
|
||||
|
||||
function createServer() {
|
||||
return http.createServer(app.callback());
|
||||
return http.createServer(app.callback());
|
||||
}
|
||||
|
||||
// For testing
|
||||
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 () =>
|
||||
new Promise((resolve) => {
|
||||
const server = createServer();
|
||||
new Promise((resolve) => {
|
||||
const server = createServer();
|
||||
|
||||
initializeStreamingServer(server);
|
||||
initializeStreamingServer(server);
|
||||
|
||||
server.on("error", (e) => {
|
||||
switch ((e as any).code) {
|
||||
case "EACCES":
|
||||
serverLogger.error(
|
||||
`You do not have permission to listen on port ${config.port}.`,
|
||||
);
|
||||
break;
|
||||
case "EADDRINUSE":
|
||||
serverLogger.error(
|
||||
`Port ${config.port} is already in use by another process.`,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
serverLogger.error(e);
|
||||
break;
|
||||
}
|
||||
server.on("error", (e) => {
|
||||
switch ((e as any).code) {
|
||||
case "EACCES":
|
||||
serverLogger.error(
|
||||
`You do not have permission to listen on port ${config.port}.`
|
||||
);
|
||||
break;
|
||||
case "EADDRINUSE":
|
||||
serverLogger.error(
|
||||
`Port ${config.port} is already in use by another process.`
|
||||
);
|
||||
break;
|
||||
default:
|
||||
serverLogger.error(e);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cluster.isWorker) {
|
||||
process.send!("listenFailed");
|
||||
} else {
|
||||
// disableClustering
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
if (cluster.isWorker) {
|
||||
process.send!("listenFailed");
|
||||
} else {
|
||||
// disableClustering
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
server.listen(config.port, resolve);
|
||||
});
|
||||
// @ts-ignore
|
||||
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 config from "@/config/index.js";
|
||||
import * as Acct from "@/misc/acct.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";
|
||||
import {escapeAttribute, escapeValue} from "@/prelude/xml.js";
|
||||
|
||||
// Init router
|
||||
const router = new Router();
|
||||
|
||||
const XRD = (
|
||||
...x: {
|
||||
element: string;
|
||||
value?: string;
|
||||
attributes?: Record<string, string>;
|
||||
}[]
|
||||
...x: {
|
||||
element: string;
|
||||
value?: 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
|
||||
.map(
|
||||
({ element, value, attributes }) =>
|
||||
`<${Object.entries(
|
||||
(typeof attributes === "object" && attributes) || {},
|
||||
).reduce((a, [k, v]) => `${a} ${k}="${escapeAttribute(v)}"`, element)}${
|
||||
typeof value === "string" ? `>${escapeValue(value)}</${element}` : "/"
|
||||
}>`,
|
||||
)
|
||||
.reduce((a, c) => a + c, "")}</XRD>`;
|
||||
`<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">${x
|
||||
.map(
|
||||
({ element, value, attributes }) =>
|
||||
`<${Object.entries(
|
||||
(typeof attributes === "object" && attributes) || {}
|
||||
).reduce(
|
||||
(a, [k, v]) => `${a} ${k}="${escapeAttribute(v)}"`,
|
||||
element
|
||||
)}${
|
||||
typeof value === "string"
|
||||
? `>${escapeValue(value)}</${element}`
|
||||
: "/"
|
||||
}>`
|
||||
)
|
||||
.reduce((a, c) => a + c, "")}</XRD>`;
|
||||
|
||||
const allPath = "/.well-known/(.*)";
|
||||
const webFingerPath = "/.well-known/webfinger";
|
||||
|
@ -36,156 +35,68 @@ const jrd = "application/jrd+json";
|
|||
const xrd = "application/xrd+xml";
|
||||
|
||||
router.use(allPath, async (ctx, next) => {
|
||||
ctx.set({
|
||||
"Access-Control-Allow-Headers": "Accept",
|
||||
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Expose-Headers": "Vary",
|
||||
});
|
||||
await next();
|
||||
ctx.set({
|
||||
"Access-Control-Allow-Headers": "Accept",
|
||||
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Expose-Headers": "Vary",
|
||||
});
|
||||
await next();
|
||||
});
|
||||
|
||||
router.options(allPath, async (ctx) => {
|
||||
ctx.status = 204;
|
||||
ctx.status = 204;
|
||||
});
|
||||
|
||||
router.get("/.well-known/host-meta", async (ctx) => {
|
||||
ctx.set("Content-Type", xrd);
|
||||
ctx.body = XRD({
|
||||
element: "Link",
|
||||
attributes: {
|
||||
rel: "lrdd",
|
||||
type: xrd,
|
||||
template: `${config.url}${webFingerPath}?resource={uri}`,
|
||||
},
|
||||
});
|
||||
ctx.set("Content-Type", xrd);
|
||||
ctx.body = XRD({
|
||||
element: "Link",
|
||||
attributes: {
|
||||
rel: "lrdd",
|
||||
type: xrd,
|
||||
template: `${config.url}${webFingerPath}?resource={uri}`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/.well-known/host-meta.json", async (ctx) => {
|
||||
ctx.set("Content-Type", jrd);
|
||||
ctx.body = {
|
||||
links: [
|
||||
{
|
||||
rel: "lrdd",
|
||||
type: jrd,
|
||||
template: `${config.url}${webFingerPath}?resource={uri}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
ctx.set("Content-Type", jrd);
|
||||
ctx.body = {
|
||||
links: [
|
||||
{
|
||||
rel: "lrdd",
|
||||
type: jrd,
|
||||
template: `${config.url}${webFingerPath}?resource={uri}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
if (config.twa != null) {
|
||||
router.get("/.well-known/assetlinks.json", async (ctx) => {
|
||||
ctx.set("Content-Type", "application/json");
|
||||
ctx.body = [
|
||||
{
|
||||
relation: ["delegate_permission/common.handle_all_urls"],
|
||||
target: {
|
||||
namespace: config.twa.nameSpace,
|
||||
package_name: config.twa.packageName,
|
||||
sha256_cert_fingerprints: config.twa.sha256CertFingerprints,
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
router.get("/.well-known/assetlinks.json", async (ctx) => {
|
||||
ctx.set("Content-Type", "application/json");
|
||||
ctx.body = [
|
||||
{
|
||||
relation: ["delegate_permission/common.handle_all_urls"],
|
||||
target: {
|
||||
namespace: config.twa.nameSpace,
|
||||
package_name: config.twa.packageName,
|
||||
sha256_cert_fingerprints: config.twa.sha256CertFingerprints,
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
router.get("/.well-known/nodeinfo", async (ctx) => {
|
||||
ctx.body = { links };
|
||||
});
|
||||
|
||||
/* TODO
|
||||
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
|
||||
router.all(allPath, async (ctx) => {
|
||||
ctx.status = 404;
|
||||
ctx.status = 404;
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
Loading…
Reference in New Issue