From 6f7c755dca704ccf880538b7884473c9c829c2b7 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Mon, 21 Oct 2024 05:53:23 +0300 Subject: vnext: `webfinger` middleware --- package-lock.json | 14 ++++++++++ package.json | 1 + vnext/server/db/Users.js | 14 +++++++++- vnext/server/index.js | 5 ++++ vnext/server/middleware/webfinger.js | 52 ++++++++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 vnext/server/middleware/webfinger.js diff --git a/package-lock.json b/package-lock.json index 69a8222e..7447cba6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "name": "juick", "license": "AGPL-3.0-or-later", "dependencies": { + "address-rfc2822": "^2.2.2", "axios": "^1.7.7", "axios-cache-interceptor": "^1.6.0", "body-parser": "^1.20.3", @@ -5805,6 +5806,14 @@ "node": ">=0.4.0" } }, + "node_modules/address-rfc2822": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/address-rfc2822/-/address-rfc2822-2.2.2.tgz", + "integrity": "sha512-Kkl42jmfpSjkAtuGYnfD4cwGpGtNvrYaCdDFesb8z9GxlWNAaB/SLkDfmGxhh/qH/GfvKmMxt6Nt6Ek4qjFC7w==", + "dependencies": { + "email-addresses": "^5.0.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -8500,6 +8509,11 @@ "node": ">=0.12.0" } }, + "node_modules/email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==" + }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", diff --git a/package.json b/package.json index c397d0b3..b31416ab 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "webpack-node-externals": "^3.0.0" }, "dependencies": { + "address-rfc2822": "^2.2.2", "axios": "^1.7.7", "axios-cache-interceptor": "^1.6.0", "body-parser": "^1.20.3", diff --git a/vnext/server/db/Users.js b/vnext/server/db/Users.js index 3db3eb5b..a6148497 100644 --- a/vnext/server/db/Users.js +++ b/vnext/server/db/Users.js @@ -1,7 +1,7 @@ import { DataTypes, Op } from 'sequelize' import db from './index' -export const User = db.define('user', { +const User = db.define('user', { id: { type: DataTypes.INTEGER, primaryKey: true @@ -9,6 +9,8 @@ export const User = db.define('user', { nick: DataTypes.STRING, banned: DataTypes.INTEGER, last_seen: DataTypes.DATE +}, { + timestamps: false }) export const getMonthlyActiveUsers = async () => { @@ -25,3 +27,13 @@ export const getMonthlyActiveUsers = async () => { } }) } + +export const getUserByName = async (name = '') => { + return User.findOne({ + where: { + nick: { + [Op.eq]: name + } + } + }) +} diff --git a/vnext/server/index.js b/vnext/server/index.js index f27f0908..75b92270 100644 --- a/vnext/server/index.js +++ b/vnext/server/index.js @@ -15,6 +15,7 @@ import { instance } from './middleware/mastodon' const PORT = process.env.LISTEN_PORT || 8081 import path from 'path' import { webhook, webhookPath } from './durov' +import { webfinger } from './middleware/webfinger' // initialize the application and create the routes const app = express() @@ -27,6 +28,10 @@ router.get('/api/v2/oembed', oembed) router.get('/api/v2/urlexpand', urlExpand) router.get('/api/apps/android/releases', releases) +// WebFinger + +router.get('/.well-known/webfinger', webfinger) + // Mastodon API router.get('/api/v2/instance', instance) diff --git a/vnext/server/middleware/webfinger.js b/vnext/server/middleware/webfinger.js new file mode 100644 index 00000000..9800fc01 --- /dev/null +++ b/vnext/server/middleware/webfinger.js @@ -0,0 +1,52 @@ +import config from 'config' +import addrparser from 'address-rfc2822' + +import { getUserByName } from '../db/Users' + +const baseUrl = config.get('service.baseURL') + +/** + * WebFinger endpoint + * @type {import('express').RequestParamHandler} + */ +export const webfinger = async (req, res) => { + const resource = req.query.resource + if (resource) { + const acct = resource.substring(5) // drop "acct:" + const addresses = parseAddress(acct) + if (addresses && addresses.length == 1) { + const address = addresses[0] + const ourAddress = new URL(baseUrl) + if (address.host() === ourAddress.hostname) { + const user = await getUserByName(addresses[0].user()) + if (user) { + return res.json({ + subject: resource, + links: [ + { + rel: 'self', + type: 'application/activity+json', + href: `${baseUrl}/u/${user.nick}` + } + ] + }) + } else { + return res.status(404).send('User not found') + } + } else { + return res.status(404).send('Address not found') + } + } else { + return res.status(400).send('Invalid resource') + } + } + res.status(400).send('Missing `resource` param') +} + +const parseAddress = (address = '') => { + try { + return addrparser.parse(address, { startAt: 'address' }) + } catch { + return undefined + } +} -- cgit v1.2.3