aboutsummaryrefslogtreecommitdiff
path: root/vnext/server/middleware
diff options
context:
space:
mode:
Diffstat (limited to 'vnext/server/middleware')
-rw-r--r--vnext/server/middleware/android.spec.js12
-rw-r--r--vnext/server/middleware/event.js4
-rw-r--r--vnext/server/middleware/host-meta.js42
-rw-r--r--vnext/server/middleware/mastodon.js23
-rw-r--r--vnext/server/middleware/mastodon.spec.js13
-rw-r--r--vnext/server/middleware/rememberme.js50
-rw-r--r--vnext/server/middleware/urlexpand.js3
-rw-r--r--vnext/server/middleware/webfinger.js59
-rw-r--r--vnext/server/middleware/webfinger.spec.js28
9 files changed, 223 insertions, 11 deletions
diff --git a/vnext/server/middleware/android.spec.js b/vnext/server/middleware/android.spec.js
index 448714ac..19d380d7 100644
--- a/vnext/server/middleware/android.spec.js
+++ b/vnext/server/middleware/android.spec.js
@@ -1,15 +1,11 @@
import request from 'supertest'
-import express from 'express'
-import releases from './android'
-
-const app = express()
-app.get('/releases', releases)
+import { app } from '../app'
describe('Releases helper', () => {
it('Should respond with empty array to unknown user agents', async () => {
return request(app)
- .get('/releases')
+ .get('/api/apps/android/releases')
.expect(200)
.then(response => {
expect(response.body).toStrictEqual([])
@@ -17,7 +13,7 @@ describe('Releases helper', () => {
})
it('Should respond with a single legacy version data to old Android app', async () => {
return request(app)
- .get('/releases')
+ .get('/api/apps/android/releases')
.set('User-Agent', 'Juick/100 okhttp/3.12 Android/19')
.expect(200)
.then(response => {
@@ -27,7 +23,7 @@ describe('Releases helper', () => {
})
it('Should redirect to Github when Android version is ok', async () => {
return request(app)
- .get('/releases')
+ .get('/api/apps/android/releases')
.set('User-Agent', 'Juick/100 okhttp/3.12 Android/24')
.expect(302)
.then(response => {
diff --git a/vnext/server/middleware/event.js b/vnext/server/middleware/event.js
index c80f2249..623ee9a9 100644
--- a/vnext/server/middleware/event.js
+++ b/vnext/server/middleware/event.js
@@ -1,3 +1,5 @@
+import config from 'config'
+
import { simpleParser } from 'mailparser'
import { isPM, isReply, isService } from '../common/MessageUtils'
import { sendTelegramNotification } from '../durov'
@@ -8,7 +10,7 @@ import { send } from '../hms'
var log = debug('event')
/** @type {number[]} */
-const allSandboxIds = []
+const allSandboxIds = config.get('service.sandboxIds')
/**
* handle message event
diff --git a/vnext/server/middleware/host-meta.js b/vnext/server/middleware/host-meta.js
new file mode 100644
index 00000000..0bca925a
--- /dev/null
+++ b/vnext/server/middleware/host-meta.js
@@ -0,0 +1,42 @@
+import config from 'config'
+
+const baseURL = config.get('service.baseURL')
+
+/**
+ * host-meta endpoint
+ * @type {import('express').RequestParamHandler}
+ */
+export const xmlMeta = async (_req, res) => {
+ return res.set('Content-Type', 'text/xml')
+ .send(`<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="${baseURL}/.well-known/webfinger?resource={uri}"/></XRD>`)
+}
+
+/**
+ * host-meta.json endpoint
+ * @type {import('express').RequestParamHandler}
+ */
+export const jsonMeta = async (_req, res) => {
+ return res.json({
+ 'links': [
+ {
+ 'rel': 'lrdd',
+ 'template': `${baseURL}/.well-known/webfinger?resource={uri}`
+ }
+ ]
+ })
+}
+
+/**
+ * nodeinfo endpoint
+ * @type {import('express').RequestParamHandler}
+ */
+export const nodeinfo = async (_req, res) => {
+ return res.json({
+ 'links': [
+ {
+ 'rel': 'https://nodeinfo.diaspora.software/ns/schema/2.0',
+ 'href': `${baseURL}/.well-known/nodeinfo/2.0`
+ }
+ ]
+ })
+}
diff --git a/vnext/server/middleware/mastodon.js b/vnext/server/middleware/mastodon.js
new file mode 100644
index 00000000..e29fd736
--- /dev/null
+++ b/vnext/server/middleware/mastodon.js
@@ -0,0 +1,23 @@
+import { getMonthlyActiveUsers } from '../db/Users'
+
+/**
+ * Return content for embedding
+ * @type {import('express').RequestParamHandler}
+ */
+export const instance = async (req, res) => {
+ const activeUsers = await getMonthlyActiveUsers()
+ res.json({
+ 'domain': 'juick.com',
+ 'title': 'Microblogging service',
+ 'description': 'Juick',
+ 'version': '2.x',
+ 'contact': {
+ 'email': 'support@juick.com'
+ },
+ 'usage': {
+ 'users': {
+ 'active_month': activeUsers
+ }
+ }
+ })
+}
diff --git a/vnext/server/middleware/mastodon.spec.js b/vnext/server/middleware/mastodon.spec.js
new file mode 100644
index 00000000..561303bc
--- /dev/null
+++ b/vnext/server/middleware/mastodon.spec.js
@@ -0,0 +1,13 @@
+import request from 'supertest'
+import { app } from '../app'
+
+describe('Mastodon API middleware', () => {
+ it('Inactive users should not be included in Instance response', async () => {
+ return request(app)
+ .get('/api/v2/instance')
+ .expect(200)
+ .then(response => {
+ expect(response.body.usage.users.active_month).toStrictEqual(1)
+ })
+ })
+})
diff --git a/vnext/server/middleware/rememberme.js b/vnext/server/middleware/rememberme.js
new file mode 100644
index 00000000..1c0a2ee4
--- /dev/null
+++ b/vnext/server/middleware/rememberme.js
@@ -0,0 +1,50 @@
+import config from 'config'
+import { createHash } from 'node:crypto'
+import debug from 'debug'
+import { getUserByName } from '../db/Users'
+const log = debug('auth')
+
+const auth_key = config.get('service.auth.key')
+const JUICK_COOKIE_NAME = 'juick-remember-me'
+
+export const rememberMeParser = (req, _res, next) => {
+ if (req.cookies && !!req.cookies[JUICK_COOKIE_NAME]) {
+ validate_cookie(req.cookies[JUICK_COOKIE_NAME]).then(visitor => {
+ req.visitor = visitor
+ setImmediate(next)
+ }).catch(() => {
+ setImmediate(next)
+ })
+ } else {
+ setImmediate(next)
+ }
+}
+
+const validate_cookie = async (cookie) => {
+ const [ username, expiry_time, , signature ] = Buffer.from(cookie, 'base64').toString('ascii').split(':', 4)
+ if (is_token_expired(expiry_time)) {
+ log(`Token expired at: ${new Date(expiry_time)}`)
+ return ''
+ }
+ const user = await getUserByName(username)
+ if (!user) {
+ log(`User not found: ${username}`)
+ return ''
+ }
+ const expected_signature = make_token_signature(expiry_time, username, user['passw'])
+ if (expected_signature === signature) {
+ log(`Signature verified: ${username}`)
+ return username
+ }
+ log(`Invalid token signature: ${username}`)
+ return ''
+}
+
+const make_token_signature = (expiry_time, username, password) => {
+ const data = `${username}:${expiry_time}:{noop}${password}:${auth_key}`
+ return createHash('sha256').update(data).digest('hex')
+}
+
+const is_token_expired = (expiry_time) => {
+ return new Date(expiry_time) < new Date()
+}
diff --git a/vnext/server/middleware/urlexpand.js b/vnext/server/middleware/urlexpand.js
index a99f80a7..fd7ab3bb 100644
--- a/vnext/server/middleware/urlexpand.js
+++ b/vnext/server/middleware/urlexpand.js
@@ -2,8 +2,7 @@ import { expandShortenedLink } from '../../src/api'
/**
* Expand URLs
- * @param {import("next").NextApiRequest} req
- * @param {import("next").NextApiResponse} res
+ * @type {import('express').RequestParamHandler}
*/
export default function urlExpand(req, res) {
let url = (req.query.url || '').toString()
diff --git a/vnext/server/middleware/webfinger.js b/vnext/server/middleware/webfinger.js
new file mode 100644
index 00000000..873387b3
--- /dev/null
+++ b/vnext/server/middleware/webfinger.js
@@ -0,0 +1,59 @@
+import config from 'config'
+import addrparser from 'address-rfc2822'
+import debug from 'debug'
+var log = debug('webfinger')
+
+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 name = addresses[0].user()
+ const user = await getUserByName(name)
+ if (user) {
+ return res.json({
+ subject: resource,
+ links: [
+ {
+ rel: 'self',
+ type: 'application/activity+json',
+ href: `${baseUrl}/u/${user.nick}`
+ }
+ ]
+ })
+ } else {
+ log(`User not found: ${name}`)
+ return res.status(404).end()
+ }
+ } else {
+ log(`Address not found: ${address.host()}`)
+ return res.status(404).end()
+ }
+ } else {
+ log(`Invalid resource: ${resource}`)
+ return res.status(400).end()
+ }
+ }
+ log('Missing `resource` param')
+ res.status(400)
+}
+
+const parseAddress = (address = '') => {
+ try {
+ return addrparser.parse(address, { startAt: 'address' })
+ } catch {
+ return undefined
+ }
+}
diff --git a/vnext/server/middleware/webfinger.spec.js b/vnext/server/middleware/webfinger.spec.js
new file mode 100644
index 00000000..d1b198e6
--- /dev/null
+++ b/vnext/server/middleware/webfinger.spec.js
@@ -0,0 +1,28 @@
+import request from 'supertest'
+
+import { app } from '../app'
+
+describe('WebFinger middleware', () => {
+ it('Existing user response should have a subject and links', async () => {
+ const resource = 'acct:ugnich@example.lan'
+ const response = await request(app)
+ .get(`/.well-known/webfinger?resource=${resource}`)
+ expect(response.status).toStrictEqual(200)
+ expect(response.body.subject).toStrictEqual(resource)
+ expect(response.body.links.length).toStrictEqual(1)
+ expect(response.body.links[0].href).toStrictEqual('https://example.lan/u/ugnich')
+ })
+ it('Unknown user should return 404', async () => {
+ const resource = 'acct:durov@example.lan'
+ const response = await request(app)
+ .get(`/.well-known/webfinger?resource=${resource}`)
+ expect(response.status).toStrictEqual(404)
+ })
+ it('Invalid input should return 400', async () => {
+ const resource = ';DROP TABLE users'
+ const response = await request(app)
+ .get(`/.well-known/webfinger?resource=${resource}`)
+ expect(response.status).toStrictEqual(400)
+ })
+})
+