From 70863878b45ebb13d5e6f9119734bf307cbd53a8 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Fri, 17 Mar 2023 11:37:31 -0500 Subject: [PATCH] admin api key and user profile attachments --- CHANGELOG.md | 6 ++++++ README.md | 2 ++ data/attachments.json | 29 +++++++++++++++++++++++++++++ data/context.json | 5 +++++ index.js | 31 ++++++++++++++++++++++++------- 5 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 data/attachments.json create mode 100644 data/context.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d742e82..377021c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## Unreleased +### Added +* Optional `ADMIN_SECRET` env var enables C2S API access with the given bearer token +* Optional `USE_ATTACHMENTS` env var to add Mastodon-style user attributes to groups + +## v1.3.0 (2022-12-29) + * Change production swarm setup to use nginx for ssl-terminating reverse proxy due to renewal issues with @small-tech/auto-encrypt in in swarm mode * Change swarm node labeling scheme to allow consolidation of all services on one machine * Update activitypub-express to fix [a spec compliance issue](https://github.com/immers-space/activitypub-express/pull/83) diff --git a/README.md b/README.md index 653e91b..25a24b8 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,9 @@ Additional values can be set in `.env` file | Setting | Description | | --- | --- | +| ADMIN_SECRET | Sets a bearer token with full C2S API access for all guppe actors | PROXY_MODE | Enable use behind an SSL-terminating proxy or load balancer, serves over http instead of https and sets Express `trust proxy` setting to the value of `PROXY_MODE` (e.g. `1`, [other options](https://expressjs.com/en/guide/behind-proxies.html)) See note. | +| USE_ATTACHMENTS | Add Mastodon-style user attributes to groups based on data in `./data/attachments.json` **Notes on use with a reverse proxy**: When setting proxyMode, you must ensure your reverse proxy sets the following headers: X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto (example for nginx below). diff --git a/data/attachments.json b/data/attachments.json new file mode 100644 index 0000000..0c31b3f --- /dev/null +++ b/data/attachments.json @@ -0,0 +1,29 @@ +[ + { + "type": "PropertyValue", + "name": [ + "Support Guppe" + ], + "value": [ + "https://opencollective.com/guppe-groups" + ] + }, + { + "type": "PropertyValue", + "name": [ + "Get support" + ], + "value": [ + "@GuppeGroups@a.gup.pe" + ] + }, + { + "type": "PropertyValue", + "name": [ + "Admin contact" + ], + "value": [ + "@datatitian@social.coop" + ] + } +] diff --git a/data/context.json b/data/context.json new file mode 100644 index 0000000..c41bb8e --- /dev/null +++ b/data/context.json @@ -0,0 +1,5 @@ +{ + "@context": { + "PropertyValue" : "http://schema.org/#PropertyValue" + } +} diff --git a/index.js b/index.js index 7f71700..8edb0c1 100644 --- a/index.js +++ b/index.js @@ -11,7 +11,7 @@ const { onShutdown } = require('node-graceful-shutdown') const ActivitypubExpress = require('activitypub-express') const { version } = require('./package.json') -const { DOMAIN, KEY_PATH, CERT_PATH, CA_PATH, PORT_HTTPS, DB_URL, DB_NAME, PROXY_MODE } = process.env +const { DOMAIN, KEY_PATH, CERT_PATH, CA_PATH, PORT_HTTPS, DB_URL, DB_NAME, PROXY_MODE, ADMIN_SECRET, USE_ATTACHMENTS } = process.env const app = express() const client = new MongoClient(DB_URL) @@ -51,11 +51,29 @@ const apex = ActivitypubExpress({ itemsPerPage: 100, // delivery done in workers only in production offlineMode: process.env.NODE_ENV === 'production', + context: require('./data/context.json'), routes }) -app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status Accepts ":req[accept]" ":referrer" ":user-agent"')) -app.use(express.json({ type: apex.consts.jsonldTypes }), apex) +app.use( + morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status Accepts ":req[accept]" ":referrer" ":user-agent"'), + express.json({ type: apex.consts.jsonldTypes }), + apex, + function checkAdminKey (req, res, next) { + if (ADMIN_SECRET && req.get('authorization') === `Bearer ${ADMIN_SECRET}`) { + res.locals.apex.authorized = true + } + next() + } +) + +async function createGuppeActor (...args) { + const actor = await apex.createActor(...args) + if (USE_ATTACHMENTS) { + actor.attachment = require('./data/attachments.json') + } + return actor +} // Create new groups on demand whenever someone tries to access one async function actorOnDemand (req, res, next) { @@ -68,7 +86,7 @@ async function actorOnDemand (req, res, next) { if (!(await apex.store.getObject(actorIRI)) && actor.length <= 255) { console.log(`Creating group: ${actor}`) const summary = `I'm a group about ${actor}. Follow me to get all the group posts. Tag me to share with the group. Create other groups by searching for or tagging @yourGroupName@${DOMAIN}` - const actorObj = await apex.createActor(actor, `${actor} group`, summary, icon, 'Group') + const actorObj = await createGuppeActor(actor, `${actor} group`, summary, icon, 'Group') await apex.store.saveObject(actorObj) } } catch (err) { return next(err) } @@ -102,8 +120,7 @@ app.route(routes.inbox) .get(actorOnDemand, apex.net.inbox.get) app.route(routes.outbox) .get(actorOnDemand, apex.net.outbox.get) - // no C2S at present - // .post(apex.net.outbox.post) + .post(apex.net.outbox.post) // replace apex's target actor validator with our create on demand method app.get(routes.actor, actorOnDemand, apex.net.actor.get) @@ -228,7 +245,7 @@ client.connect() }) apex.systemUser = await apex.store.getObject(apex.utils.usernameToIRI('system_service'), true) if (!apex.systemUser) { - const systemUser = await apex.createActor('system_service', `${DOMAIN} system service`, `${DOMAIN} system service`, icon, 'Service') + const systemUser = await createGuppeActor('system_service', `${DOMAIN} system service`, `${DOMAIN} system service`, icon, 'Service') await apex.store.saveObject(systemUser) apex.systemUser = systemUser }