openvibers/openvibe.community Commits
openvibe-shared v1.5.1 (Track R release manifest), openvibe-contracts v0.33.0
Changes
@@@ -124,6 +124,7 @@ API and machine endpoints:| `/api/v1/relay/*` | Discord relay administration (staff) — see [Discord relay](#discord-relay) || `GET /api/health`, `GET /api/ready` | Liveness / readiness (see below) || `GET /metrics` | Prometheus text for direct loopback callers only (404 through nginx) |++| `GET /release.json`, `POST /release-metrics` | What this server runs (ADR-016 release manifest, `metrics_url: /release-metrics`); open tabs' update reports (release-watch's same-origin beacon, no auth) || `GET /auth/login` | → Network `/oauth/authorize`. `?next=` (same-site path, this origin, or `https://openvibe.network/…`), `?silent=1` adds `prompt=none` || `GET /auth/callback` | Code exchange; sets cookies. `error=login_required` → `next` + `?sso=none` || `GET /auth/logout` | Clears session, sets `ov_sso_hint=guest`, honours `?next=` |@@@ -142,7 +143,9 @@ Network signing key; without it nobody can sign in or write as a signed-in vieweUntil Track O this route returned `{ "ready": true }` unconditionally. `GET /metrics` serves HTTPgolden signals by route template (`http_requests_total{method,route,status_class}`,`http_request_duration_seconds`, `http_requests_in_flight`), process metrics and--`release_info`; content counts (pastes, comments, threads) are deliberately not metrics.++`release_info`, and `release_client_updates_total{outcome,reason}` from the tabs' reports to++`POST /release-metrics` (openvibe-shared `release.mount`); content counts (pastes, comments,++threads) are deliberately not metrics.Cookies are host-only for `openvibe.community`: `ov_token` (24 h access JWT, JS-readable sothe shared navbar can use it), `ov_refresh` (httpOnly, `/auth`), `ov_sso_hint`@@@ -430,7 +433,7 @@ Community checks service tokens against these capabilities (manifests in| `community.pulse.write` | active in contracts (v0.7.0) | publishing to Pulse || `community.post.create` | active in contracts (v0.7.0) | forum writes |--This repository pins `openvibe-contracts` v0.11.0, which knows every id above, so they all go++This repository pins `openvibe-contracts` v0.33.0, which knows every id above, so they all gothrough the library's `capabilities.check`. `server/identity/capabilities.js` still decides an idthe installed contracts do not know locally, with the library's own matching rule (the exact idor a `prefix.*` grant).@@@ -550,7 +553,7 @@ server/render/highlight.js highlight.js wrapper, language list, download extensionsseo.js robots, sitemap, RSS, JSON-LD builderspublic/ css/community.css, js/community.js, favicon.svg, og-default.png--(openvibe-shared is the pinned OpenVibe.Shared v1.0.0 release, installed by npm)++(openvibe-shared is the pinned OpenVibe.Shared v1.5.1 release, installed by npm)deploy/ systemd unit, nginx vhostscripts/import-pastes.js Media paste bundle importerscripts/import-live-comments.js Live's VOD/clip comments → Community threads (dry run by default)@@@ -17,8 +17,8 @@"helmet": "^8.1.0","highlight.js": "^11.11.1","multer": "^2.4.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.11.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0"++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"},"engines": {"node": ">=20.0.0"@@@ -1056,9 +1056,9 @@}},"node_modules/openvibe-contracts": {-- "version": "0.11.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.11.0",-- "integrity": "sha512-M1UIDUF1nK2v94S++Sqw+d1G9mj3UKaPB8vse6UWIKZy9IAdA7Yu6DsTK0mJ0UXKeOVLLWuxeQX4G+/HtV9REg==",++ "version": "0.33.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",++ "integrity": "sha512-7q8NUhcPxgb8lxrVdC3pFbiPdjzliJM1mMP/1G5a2PmOr2tJVAXwYa8+a8YgCJvYeMSeMHWijLkIEiZ9IhllOg==","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -1072,8 +1072,9 @@}},"node_modules/openvibe-shared": {-- "version": "1.3.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0",++ "version": "1.5.1",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",++ "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -21,8 +21,8 @@"helmet": "^8.1.0","highlight.js": "^11.11.1","multer": "^2.4.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.11.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0"++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"},"engines": {"node": ">=20.0.0"@@@ -14,7 +14,7 @@* GET /p/:slug/download GET /api/health, /api/ready, /release.json, /metrics (loopback)* GET|POST /new create GET /robots.txt, /sitemap.xml, /feed.xml, /s/feed.xml* GET /my signed-in user's pastes /auth/login|callback|logout|me|refresh-- * GET /s … spaces, threads, posts (forum/routes.js)++ * GET /s … spaces, threads, posts (forum/routes.js) POST /release-metrics (open tabs' update reports)* GET /pulse the network's public activity* GET|POST /c/:accessId one comment thread's own page (comments/routes.js)*@@@ -170,7 +170,9 @@ function createApp(opts = {}) {app.use('/api/v1/relay', createRelayApi({ relay, db, viewers }));app.get('/api/health', (_req, res) => res.json({ status: 'ok', service: 'openvibe-community', version: VERSION }));-- app.get('/release.json', release.handler);++ // GET /release.json (ADR-016) and POST /release-metrics: open tabs' update reports (a same-origin++ // sendBeacon, no auth) into /metrics as release_client_updates_total.++ release.mount(app, { registry: metrics.registry });// Readiness reports what is actually served: 503 only without the database; Network key,// Live (live mode) and Media (community mode) failures degrade (server/observability.js).const readiness = require('./observability').createCommunityReadiness({ db, auth, config, relay, release: release.release, fetchImpl: opts.fetchImpl });@@@ -50,6 +50,20 @@ function raw(base, path, headers = {}) {assert.ok(!/pastes_total|comments_total|threads_total/.test(m.body), 'no content counts');});++ await check('/release.json is a valid release manifest (Track R) and POST /release-metrics feeds /metrics', async () => {++ const r = await t.get('/release.json');++ assert.strictEqual(r.status, 200, r.text);++ const rel = r.json();++ assert.strictEqual(rel.service, 'community');++ assert.deepStrictEqual(require('openvibe-contracts').validate('registry.release-manifest@1', rel).errors, []);++ assert.strictEqual(rel.metrics_url, '/release-metrics');++ // release-watch's beacon: same-origin, text/plain, no auth.++ const b = await t.get('/release-metrics', { method: 'POST', headers: { 'content-type': 'text/plain;charset=UTF-8' }, body: JSON.stringify({ counts: { reloaded: { user: 2 } } }) });++ assert.strictEqual(b.status, 204, b.text);++ const m = await raw(t.base, '/metrics');++ assert.ok(/release_client_updates_total\{outcome="reloaded",reason="user"\} 2/.test(m.body), m.body.split('\n').filter((l) => l.includes('release_client')).join('\n'));++ });++await check('Live down: still ready (comments, forum and Pulse are served), live degraded', async () => {await t.live.close();await new Promise((r) => setTimeout(r, 20));@@@ -25,8 +25,10 @@ const { boot, check, done } = require('./helpers/app');const samJwt = net.sign({ id: 9, subject_id: sam.subject_id, username: 'sam', display_name: 'Sam', role: 'user' });const CREATE = 'community.paste.create', WRITE = 'community.paste.write', POST = 'community.post.create';const APP = 'app:app_01HZX3K5V7Q9M2N4P6R8T0W2Y4';++ // An app token carries its developer project and env, as Network's do (identity.service-token-claims 1.2.0).++ const PROJECT = 'prj_01J8ZQ4Y7N3M2K1H0G9F8E7D6C';const appToken = ({ cap = [CREATE, POST], env = 'production', onBehalfOf = null, sub = APP, actorType = 'app' } = {}) => net.signService({-- sub, actorType, cap, extra: { env, project_id: 'prj_test', ns: ['prj_test'], ...(onBehalfOf ? { on_behalf_of: onBehalfOf } : {}) },++ sub, actorType, cap, extra: { env, project_id: PROJECT, ns: [PROJECT], ...(onBehalfOf ? { on_behalf_of: onBehalfOf } : {}) },});const call = (path, { method = 'GET', token, cookie, headers = {}, json, ip } = {}) => {@@@ -79,6 +81,7 @@ const { boot, check, done } = require('./helpers/app');await check('sandbox app tokens are refused', async () => {const r = await call('/api/pastes', { method: 'POST', token: appToken({ env: 'sandbox' }), json: { content: 'sandbox' } });assert.strictEqual(r.status, 401, r.text);++ assert.strictEqual(r.json().code, 'token.sandbox_refused', r.text);});// ── burn after read ──────────────────────────────────────
@@ -124,6 +124,7 @@ API and machine endpoints:
@@ -124,6 +124,7 @@ API and machine endpoints:
| `/api/v1/relay/*` | Discord relay administration (staff) — see [Discord relay](#discord-relay) |
| `/api/v1/relay/*` | Discord relay administration (staff) — see [Discord relay](#discord-relay) |
| `GET /api/health`, `GET /api/ready` | Liveness / readiness (see below) |
| `GET /api/health`, `GET /api/ready` | Liveness / readiness (see below) |
| `GET /metrics` | Prometheus text for direct loopback callers only (404 through nginx) |
| `GET /metrics` | Prometheus text for direct loopback callers only (404 through nginx) |
+| `GET /release.json`, `POST /release-metrics` | What this server runs (ADR-016 release manifest, `metrics_url: /release-metrics`); open tabs' update reports (release-watch's same-origin beacon, no auth) |
| `GET /auth/login` | → Network `/oauth/authorize`. `?next=` (same-site path, this origin, or `https://openvibe.network/…`), `?silent=1` adds `prompt=none` |
| `GET /auth/login` | → Network `/oauth/authorize`. `?next=` (same-site path, this origin, or `https://openvibe.network/…`), `?silent=1` adds `prompt=none` |
| `GET /auth/callback` | Code exchange; sets cookies. `error=login_required` → `next` + `?sso=none` |
| `GET /auth/callback` | Code exchange; sets cookies. `error=login_required` → `next` + `?sso=none` |
| `GET /auth/logout` | Clears session, sets `ov_sso_hint=guest`, honours `?next=` |
| `GET /auth/logout` | Clears session, sets `ov_sso_hint=guest`, honours `?next=` |
@@ -142,7 +143,9 @@ Network signing key; without it nobody can sign in or write as a signed-in viewe
@@ -142,7 +143,9 @@ Network signing key; without it nobody can sign in or write as a signed-in viewe
Until Track O this route returned `{ "ready": true }` unconditionally. `GET /metrics` serves HTTP
Until Track O this route returned `{ "ready": true }` unconditionally. `GET /metrics` serves HTTP
golden signals by route template (`http_requests_total{method,route,status_class}`,
golden signals by route template (`http_requests_total{method,route,status_class}`,
`http_request_duration_seconds`, `http_requests_in_flight`), process metrics and
`http_request_duration_seconds`, `http_requests_in_flight`), process metrics and
-`release_info`; content counts (pastes, comments, threads) are deliberately not metrics.
+`release_info`, and `release_client_updates_total{outcome,reason}` from the tabs' reports to
+`POST /release-metrics` (openvibe-shared `release.mount`); content counts (pastes, comments,
+threads) are deliberately not metrics.
Cookies are host-only for `openvibe.community`: `ov_token` (24 h access JWT, JS-readable so
Cookies are host-only for `openvibe.community`: `ov_token` (24 h access JWT, JS-readable so
the shared navbar can use it), `ov_refresh` (httpOnly, `/auth`), `ov_sso_hint`
the shared navbar can use it), `ov_refresh` (httpOnly, `/auth`), `ov_sso_hint`
@@ -430,7 +433,7 @@ Community checks service tokens against these capabilities (manifests in
@@ -430,7 +433,7 @@ Community checks service tokens against these capabilities (manifests in
| `community.pulse.write` | active in contracts (v0.7.0) | publishing to Pulse |
| `community.pulse.write` | active in contracts (v0.7.0) | publishing to Pulse |
| `community.post.create` | active in contracts (v0.7.0) | forum writes |
| `community.post.create` | active in contracts (v0.7.0) | forum writes |
-This repository pins `openvibe-contracts` v0.11.0, which knows every id above, so they all go
+This repository pins `openvibe-contracts` v0.33.0, which knows every id above, so they all go
through the library's `capabilities.check`. `server/identity/capabilities.js` still decides an id
through the library's `capabilities.check`. `server/identity/capabilities.js` still decides an id
the installed contracts do not know locally, with the library's own matching rule (the exact id
the installed contracts do not know locally, with the library's own matching rule (the exact id
or a `prefix.*` grant).
or a `prefix.*` grant).
@@ -550,7 +553,7 @@ server/
@@ -550,7 +553,7 @@ server/
render/highlight.js highlight.js wrapper, language list, download extensions
render/highlight.js highlight.js wrapper, language list, download extensions
seo.js robots, sitemap, RSS, JSON-LD builders
seo.js robots, sitemap, RSS, JSON-LD builders
public/ css/community.css, js/community.js, favicon.svg, og-default.png
public/ css/community.css, js/community.js, favicon.svg, og-default.png
-(openvibe-shared is the pinned OpenVibe.Shared v1.0.0 release, installed by npm)
+(openvibe-shared is the pinned OpenVibe.Shared v1.5.1 release, installed by npm)
deploy/ systemd unit, nginx vhost
deploy/ systemd unit, nginx vhost
scripts/import-pastes.js Media paste bundle importer
scripts/import-pastes.js Media paste bundle importer
scripts/import-live-comments.js Live's VOD/clip comments → Community threads (dry run by default)
scripts/import-live-comments.js Live's VOD/clip comments → Community threads (dry run by default)
@@ -17,8 +17,8 @@
@@ -17,8 +17,8 @@
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"highlight.js": "^11.11.1",
"highlight.js": "^11.11.1",
"multer": "^2.4.0",
"multer": "^2.4.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.11.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0"
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
},
},
"engines": {
"engines": {
"node": ">=20.0.0"
"node": ">=20.0.0"
@@ -1056,9 +1056,9 @@
@@ -1056,9 +1056,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.11.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.11.0",
- "integrity": "sha512-M1UIDUF1nK2v94S++Sqw+d1G9mj3UKaPB8vse6UWIKZy9IAdA7Yu6DsTK0mJ0UXKeOVLLWuxeQX4G+/HtV9REg==",
+ "version": "0.33.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
+ "integrity": "sha512-7q8NUhcPxgb8lxrVdC3pFbiPdjzliJM1mMP/1G5a2PmOr2tJVAXwYa8+a8YgCJvYeMSeMHWijLkIEiZ9IhllOg==",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -1072,8 +1072,9 @@
@@ -1072,8 +1072,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "1.3.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0",
+ "version": "1.5.1",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
+ "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -21,8 +21,8 @@
@@ -21,8 +21,8 @@
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"highlight.js": "^11.11.1",
"highlight.js": "^11.11.1",
"multer": "^2.4.0",
"multer": "^2.4.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.11.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0"
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
},
},
"engines": {
"engines": {
"node": ">=20.0.0"
"node": ">=20.0.0"
@@ -14,7 +14,7 @@
@@ -14,7 +14,7 @@
* GET /p/:slug/download GET /api/health, /api/ready, /release.json, /metrics (loopback)
* GET /p/:slug/download GET /api/health, /api/ready, /release.json, /metrics (loopback)
* GET|POST /new create GET /robots.txt, /sitemap.xml, /feed.xml, /s/feed.xml
* GET|POST /new create GET /robots.txt, /sitemap.xml, /feed.xml, /s/feed.xml
* GET /my signed-in user's pastes /auth/login|callback|logout|me|refresh
* GET /my signed-in user's pastes /auth/login|callback|logout|me|refresh
- * GET /s … spaces, threads, posts (forum/routes.js)
+ * GET /s … spaces, threads, posts (forum/routes.js) POST /release-metrics (open tabs' update reports)
* GET /pulse the network's public activity
* GET /pulse the network's public activity
* GET|POST /c/:accessId one comment thread's own page (comments/routes.js)
* GET|POST /c/:accessId one comment thread's own page (comments/routes.js)
*
*
@@ -170,7 +170,9 @@ function createApp(opts = {}) {
@@ -170,7 +170,9 @@ function createApp(opts = {}) {
app.use('/api/v1/relay', createRelayApi({ relay, db, viewers }));
app.use('/api/v1/relay', createRelayApi({ relay, db, viewers }));
app.get('/api/health', (_req, res) => res.json({ status: 'ok', service: 'openvibe-community', version: VERSION }));
app.get('/api/health', (_req, res) => res.json({ status: 'ok', service: 'openvibe-community', version: VERSION }));
- app.get('/release.json', release.handler);
+ // GET /release.json (ADR-016) and POST /release-metrics: open tabs' update reports (a same-origin
+ // sendBeacon, no auth) into /metrics as release_client_updates_total.
+ release.mount(app, { registry: metrics.registry });
// Readiness reports what is actually served: 503 only without the database; Network key,
// Readiness reports what is actually served: 503 only without the database; Network key,
// Live (live mode) and Media (community mode) failures degrade (server/observability.js).
// Live (live mode) and Media (community mode) failures degrade (server/observability.js).
const readiness = require('./observability').createCommunityReadiness({ db, auth, config, relay, release: release.release, fetchImpl: opts.fetchImpl });
const readiness = require('./observability').createCommunityReadiness({ db, auth, config, relay, release: release.release, fetchImpl: opts.fetchImpl });
@@ -50,6 +50,20 @@ function raw(base, path, headers = {}) {
@@ -50,6 +50,20 @@ function raw(base, path, headers = {}) {
assert.ok(!/pastes_total|comments_total|threads_total/.test(m.body), 'no content counts');
assert.ok(!/pastes_total|comments_total|threads_total/.test(m.body), 'no content counts');
});
});
+ await check('/release.json is a valid release manifest (Track R) and POST /release-metrics feeds /metrics', async () => {
+ const r = await t.get('/release.json');
+ assert.strictEqual(r.status, 200, r.text);
+ const rel = r.json();
+ assert.strictEqual(rel.service, 'community');
+ assert.deepStrictEqual(require('openvibe-contracts').validate('registry.release-manifest@1', rel).errors, []);
+ assert.strictEqual(rel.metrics_url, '/release-metrics');
+ // release-watch's beacon: same-origin, text/plain, no auth.
+ const b = await t.get('/release-metrics', { method: 'POST', headers: { 'content-type': 'text/plain;charset=UTF-8' }, body: JSON.stringify({ counts: { reloaded: { user: 2 } } }) });
+ assert.strictEqual(b.status, 204, b.text);
+ const m = await raw(t.base, '/metrics');
+ assert.ok(/release_client_updates_total\{outcome="reloaded",reason="user"\} 2/.test(m.body), m.body.split('\n').filter((l) => l.includes('release_client')).join('\n'));
+ });
+
await check('Live down: still ready (comments, forum and Pulse are served), live degraded', async () => {
await check('Live down: still ready (comments, forum and Pulse are served), live degraded', async () => {
await t.live.close();
await t.live.close();
await new Promise((r) => setTimeout(r, 20));
await new Promise((r) => setTimeout(r, 20));
@@ -25,8 +25,10 @@ const { boot, check, done } = require('./helpers/app');
@@ -25,8 +25,10 @@ const { boot, check, done } = require('./helpers/app');
const samJwt = net.sign({ id: 9, subject_id: sam.subject_id, username: 'sam', display_name: 'Sam', role: 'user' });
const samJwt = net.sign({ id: 9, subject_id: sam.subject_id, username: 'sam', display_name: 'Sam', role: 'user' });
const CREATE = 'community.paste.create', WRITE = 'community.paste.write', POST = 'community.post.create';
const CREATE = 'community.paste.create', WRITE = 'community.paste.write', POST = 'community.post.create';
const APP = 'app:app_01HZX3K5V7Q9M2N4P6R8T0W2Y4';
const APP = 'app:app_01HZX3K5V7Q9M2N4P6R8T0W2Y4';
+ // An app token carries its developer project and env, as Network's do (identity.service-token-claims 1.2.0).
+ const PROJECT = 'prj_01J8ZQ4Y7N3M2K1H0G9F8E7D6C';
const appToken = ({ cap = [CREATE, POST], env = 'production', onBehalfOf = null, sub = APP, actorType = 'app' } = {}) => net.signService({
const appToken = ({ cap = [CREATE, POST], env = 'production', onBehalfOf = null, sub = APP, actorType = 'app' } = {}) => net.signService({
- sub, actorType, cap, extra: { env, project_id: 'prj_test', ns: ['prj_test'], ...(onBehalfOf ? { on_behalf_of: onBehalfOf } : {}) },
+ sub, actorType, cap, extra: { env, project_id: PROJECT, ns: [PROJECT], ...(onBehalfOf ? { on_behalf_of: onBehalfOf } : {}) },
});
});
const call = (path, { method = 'GET', token, cookie, headers = {}, json, ip } = {}) => {
const call = (path, { method = 'GET', token, cookie, headers = {}, json, ip } = {}) => {
@@ -79,6 +81,7 @@ const { boot, check, done } = require('./helpers/app');
@@ -79,6 +81,7 @@ const { boot, check, done } = require('./helpers/app');
await check('sandbox app tokens are refused', async () => {
await check('sandbox app tokens are refused', async () => {
const r = await call('/api/pastes', { method: 'POST', token: appToken({ env: 'sandbox' }), json: { content: 'sandbox' } });
const r = await call('/api/pastes', { method: 'POST', token: appToken({ env: 'sandbox' }), json: { content: 'sandbox' } });
assert.strictEqual(r.status, 401, r.text);
assert.strictEqual(r.status, 401, r.text);
+ assert.strictEqual(r.json().code, 'token.sandbox_refused', r.text);
});
});
// ── burn after read ──────────────────────────────────────
// ── burn after read ──────────────────────────────────────