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
}