openvibers/openvibe.host Commits
Open Host messages in Dock Chats (#340)
Changes
@@@ -47,6 +47,10 @@ server {limit_req zone=ovhost_api burst=30 nodelay;proxy_pass http://127.0.0.1:4910;}++ location = /auth/navbar-token {++ limit_req zone=ovhost_api burst=30 nodelay;++ proxy_pass http://127.0.0.1:4910;++ }location /auth/ {limit_req zone=ovhost_auth burst=10 nodelay;proxy_pass http://127.0.0.1:4910;@@@ -2,6 +2,7 @@/** Host-only, HttpOnly session and OAuth flow cookies around openvibe-sdk/sso. */const crypto = require('crypto');++const { ids } = require('openvibe-contracts');const { COOKIES, decodeJwtPayload, parseCookies } = require('openvibe-sdk/sso');const SESSION_MAX_AGE = 30 * 24 * 3600e3; // the SDK's ov_refresh lifetime: a refresh always finds the session it renews@@@ -84,6 +85,17 @@ function createHostSession({ auth, config }) {function router(express) {const r = express.Router();r.use(guard);++ r.get('/navbar-token', async (req, res) => {++ res.set('Cache-Control', 'private, no-store');++ const token = sessionToken(config, req);++ let claims = null;++ try { if (token) claims = await auth.verify(token); } catch { /* an invalid session */ }++ if (!claims || !ids.isSubjectId('user', claims.subject_id) || /^(svc|app|mod):/.test(String(claims.sub || ''))++ || claims.typ !== undefined || claims.nonce !== undefined || claims.actor_type !== undefined) {++ return res.status(401).json({ error: 'Not signed in' });++ }++ return res.json({ token });++ });r.use(auth.router(express));return r;}@@@ -74,6 +74,7 @@ function renderPage(o) {history: { type: 'page', title: o.title || SITE_NAME },silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,sessionUrl: '/auth/me',++ tokenUrl: '/auth/navbar-token',loginUrl: '/auth/login?next={path}', // filled from the current page (boost moves between pages)logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session toonotificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)nnew file mode 100644@@@ -0,0 +1,34 @@++'use strict';++const assert = require('assert');++const express = require('express');++const { createHostSession } = require('../server/auth/sso');++++const person = { sub: 'usr_01JABCDEFGHJKMNPQRSTVWXYZ0', subject_id: 'usr_01JABCDEFGHJKMNPQRSTVWXYZ0' };++const auth = {++ async verify(token) {++ if (token === 'person.jwt.sig') return person;++ if (token === 'service.jwt.sig') return { ...person, sub: 'svc:host' };++ if (token === 'assertion.jwt.sig') return { ...person, typ: 'fedcm' };++ return null;++ },++ router: () => express.Router(),++};++const app = express();++app.use('/auth', createHostSession({ auth, config: { cookies: { secure: false } } }).router(express));++++(async () => {++ const server = app.listen(0, '127.0.0.1');++ try {++ await new Promise((resolve) => server.once('listening', resolve));++ const base = `http://127.0.0.1:${server.address().port}`;++ const get = (cookie) => fetch(`${base}/auth/navbar-token`, { headers: cookie ? { Cookie: `ov_host_session=${cookie}` } : {} });++ const ok = await get('person.jwt.sig');++ assert.strictEqual(ok.status, 200);++ assert.strictEqual((await ok.json()).token, 'person.jwt.sig');++ assert.strictEqual(ok.headers.get('cache-control'), 'private, no-store');++ for (const cookie of [null, 'invalid', 'service.jwt.sig', 'assertion.jwt.sig']) {++ assert.strictEqual((await get(cookie)).status, 401, `refuse ${cookie || 'a guest'}`);++ }++ console.log('navbar token: verified person only, uncached');++ } finally { await new Promise((resolve) => server.close(resolve)); }++})().catch((error) => { console.error(error); process.exitCode = 1; });@@@ -25,5 +25,6 @@ runTests([const cfg = JSON.parse(page().match(/window\.__OV_PAGE = (.+);/)[1]);assert.strictEqual(cfg.navbar.loginUrl, '/auth/login?next={path}');assert.strictEqual(cfg.navbar.logoutUrl, '/auth/logout?next={path}');++ assert.strictEqual(cfg.navbar.tokenUrl, '/auth/navbar-token');}),]);
@@ -47,6 +47,10 @@ server {
@@ -47,6 +47,10 @@ server {
limit_req zone=ovhost_api burst=30 nodelay;
limit_req zone=ovhost_api burst=30 nodelay;
proxy_pass http://127.0.0.1:4910;
proxy_pass http://127.0.0.1:4910;
}
}
+ location = /auth/navbar-token {
+ limit_req zone=ovhost_api burst=30 nodelay;
+ proxy_pass http://127.0.0.1:4910;
+ }
location /auth/ {
location /auth/ {
limit_req zone=ovhost_auth burst=10 nodelay;
limit_req zone=ovhost_auth burst=10 nodelay;
proxy_pass http://127.0.0.1:4910;
proxy_pass http://127.0.0.1:4910;
@@ -2,6 +2,7 @@
@@ -2,6 +2,7 @@
/** Host-only, HttpOnly session and OAuth flow cookies around openvibe-sdk/sso. */
/** Host-only, HttpOnly session and OAuth flow cookies around openvibe-sdk/sso. */
const crypto = require('crypto');
const crypto = require('crypto');
+const { ids } = require('openvibe-contracts');
const { COOKIES, decodeJwtPayload, parseCookies } = require('openvibe-sdk/sso');
const { COOKIES, decodeJwtPayload, parseCookies } = require('openvibe-sdk/sso');
const SESSION_MAX_AGE = 30 * 24 * 3600e3; // the SDK's ov_refresh lifetime: a refresh always finds the session it renews
const SESSION_MAX_AGE = 30 * 24 * 3600e3; // the SDK's ov_refresh lifetime: a refresh always finds the session it renews
@@ -84,6 +85,17 @@ function createHostSession({ auth, config }) {
@@ -84,6 +85,17 @@ function createHostSession({ auth, config }) {
function router(express) {
function router(express) {
const r = express.Router();
const r = express.Router();
r.use(guard);
r.use(guard);
+ r.get('/navbar-token', async (req, res) => {
+ res.set('Cache-Control', 'private, no-store');
+ const token = sessionToken(config, req);
+ let claims = null;
+ try { if (token) claims = await auth.verify(token); } catch { /* an invalid session */ }
+ if (!claims || !ids.isSubjectId('user', claims.subject_id) || /^(svc|app|mod):/.test(String(claims.sub || ''))
+ || claims.typ !== undefined || claims.nonce !== undefined || claims.actor_type !== undefined) {
+ return res.status(401).json({ error: 'Not signed in' });
+ }
+ return res.json({ token });
+ });
r.use(auth.router(express));
r.use(auth.router(express));
return r;
return r;
}
}
@@ -74,6 +74,7 @@ function renderPage(o) {
@@ -74,6 +74,7 @@ function renderPage(o) {
history: { type: 'page', title: o.title || SITE_NAME },
history: { type: 'page', title: o.title || SITE_NAME },
silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,
silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,
sessionUrl: '/auth/me',
sessionUrl: '/auth/me',
+ tokenUrl: '/auth/navbar-token',
loginUrl: '/auth/login?next={path}', // filled from the current page (boost moves between pages)
loginUrl: '/auth/login?next={path}', // filled from the current page (boost moves between pages)
logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session too
logoutUrl: '/auth/logout?next={path}', // Sign out in the shared navbar ends this site's session too
notificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)
notificationsRealtime: true, // the bell hears new notifications over OpenVibe.Events (Shared 1.22.0)
new file mode 100644
new file mode 100644
@@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
+'use strict';
+const assert = require('assert');
+const express = require('express');
+const { createHostSession } = require('../server/auth/sso');
+
+const person = { sub: 'usr_01JABCDEFGHJKMNPQRSTVWXYZ0', subject_id: 'usr_01JABCDEFGHJKMNPQRSTVWXYZ0' };
+const auth = {
+ async verify(token) {
+ if (token === 'person.jwt.sig') return person;
+ if (token === 'service.jwt.sig') return { ...person, sub: 'svc:host' };
+ if (token === 'assertion.jwt.sig') return { ...person, typ: 'fedcm' };
+ return null;
+ },
+ router: () => express.Router(),
+};
+const app = express();
+app.use('/auth', createHostSession({ auth, config: { cookies: { secure: false } } }).router(express));
+
+(async () => {
+ const server = app.listen(0, '127.0.0.1');
+ try {
+ await new Promise((resolve) => server.once('listening', resolve));
+ const base = `http://127.0.0.1:${server.address().port}`;
+ const get = (cookie) => fetch(`${base}/auth/navbar-token`, { headers: cookie ? { Cookie: `ov_host_session=${cookie}` } : {} });
+ const ok = await get('person.jwt.sig');
+ assert.strictEqual(ok.status, 200);
+ assert.strictEqual((await ok.json()).token, 'person.jwt.sig');
+ assert.strictEqual(ok.headers.get('cache-control'), 'private, no-store');
+ for (const cookie of [null, 'invalid', 'service.jwt.sig', 'assertion.jwt.sig']) {
+ assert.strictEqual((await get(cookie)).status, 401, `refuse ${cookie || 'a guest'}`);
+ }
+ console.log('navbar token: verified person only, uncached');
+ } finally { await new Promise((resolve) => server.close(resolve)); }
+})().catch((error) => { console.error(error); process.exitCode = 1; });
@@ -25,5 +25,6 @@ runTests([
@@ -25,5 +25,6 @@ runTests([
const cfg = JSON.parse(page().match(/window\.__OV_PAGE = (.+);/)[1]);
const cfg = JSON.parse(page().match(/window\.__OV_PAGE = (.+);/)[1]);
assert.strictEqual(cfg.navbar.loginUrl, '/auth/login?next={path}');
assert.strictEqual(cfg.navbar.loginUrl, '/auth/login?next={path}');
assert.strictEqual(cfg.navbar.logoutUrl, '/auth/logout?next={path}');
assert.strictEqual(cfg.navbar.logoutUrl, '/auth/logout?next={path}');
+ assert.strictEqual(cfg.navbar.tokenUrl, '/auth/navbar-token');
}),
}),
]);
]);