openvibers/openvibe.actor Commits
Open Actor messages in Dock Chats (#369)
Changes
@@@ -97,6 +97,13 @@ server {proxy_pass http://127.0.0.1:4950;}++ # The Dock token is fetched on signed-in page views; give it the session probe's budget.++ location = /auth/navbar-token {++ limit_req zone=ovactor_api burst=30 nodelay;++ limit_req_status 429;++ proxy_pass http://127.0.0.1:4950;++ }++location /auth/ {limit_req zone=ovactor_auth burst=10 nodelay;limit_req_status 429;@@@ -263,7 +263,7 @@ async function createApp(opts = {}) {// ── Sign-in (OAuth2 + PKCE client of OpenVibe.Network) ──// /auth/me is the navbar's who-is-signed-in read on every page, so it is not counted with sign-ins: 60 page views in 15 minutes made a signed-in visitor look signed out.-- app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, limit: 60, skip: (req) => req.path === '/me', standardHeaders: true, legacyHeaders: false }));++ app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, limit: 60, skip: (req) => req.path === '/me' || req.path === '/navbar-token', standardHeaders: true, legacyHeaders: false }));app.use('/auth', sso.routes());{ const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'actor', service: 'actor', host: 'openvibe.actor', name: 'OpenVibe.Actor', profile: 'ugc' })); }@@@ -204,6 +204,16 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Datreturn res.json({ user: meOf(claims) });});++ // The navbar needs a Network access token for the Dock. Keep /auth/me's++ // profile response token-free; this same-origin answer is never cached.++ r.get('/navbar-token', async (req, res) => {++ res.set('Cache-Control', 'private, no-store');++ const token = sso.extractToken(req);++ const claims = await sso.verify(token);++ if (!claims) return res.status(401).json({ error: 'Not signed in' });++ return res.json({ token, expires_at: claims.exp ? claims.exp * 1000 : null });++ });++// Sign-out stays a same-site action: another site cannot sign a person out.const sameOriginRequest = (req) => {const site = String(req.get('sec-fetch-site') || '');@@@ -65,6 +65,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)@@@ -49,6 +49,12 @@ const { signJwt } = require('./helpers/mocks');const me = await s.get('/auth/me', { as: staff });assert.strictEqual(me.status, 200, me.text.slice(0, 200));assert.strictEqual(JSON.parse(me.text).user.username, staff.username);++ const token = s.network.userToken(staff);++ const nav = await s.get('/auth/navbar-token', { cookie: `ov_token=${token}` });++ assert.strictEqual(nav.status, 200);++ assert.strictEqual(JSON.parse(nav.text).token, token, 'the Dock receives the verified Network token');++ assert.strictEqual(nav.headers.get('cache-control'), 'private, no-store');++ assert.strictEqual((await s.get('/auth/navbar-token')).status, 401, 'a guest receives no token');});await check('another site cannot sign a person out; this site can', async () => {
@@ -97,6 +97,13 @@ server {
@@ -97,6 +97,13 @@ server {
proxy_pass http://127.0.0.1:4950;
proxy_pass http://127.0.0.1:4950;
}
}
+ # The Dock token is fetched on signed-in page views; give it the session probe's budget.
+ location = /auth/navbar-token {
+ limit_req zone=ovactor_api burst=30 nodelay;
+ limit_req_status 429;
+ proxy_pass http://127.0.0.1:4950;
+ }
+
location /auth/ {
location /auth/ {
limit_req zone=ovactor_auth burst=10 nodelay;
limit_req zone=ovactor_auth burst=10 nodelay;
limit_req_status 429;
limit_req_status 429;
@@ -263,7 +263,7 @@ async function createApp(opts = {}) {
@@ -263,7 +263,7 @@ async function createApp(opts = {}) {
// ── Sign-in (OAuth2 + PKCE client of OpenVibe.Network) ──
// ── Sign-in (OAuth2 + PKCE client of OpenVibe.Network) ──
// /auth/me is the navbar's who-is-signed-in read on every page, so it is not counted with sign-ins: 60 page views in 15 minutes made a signed-in visitor look signed out.
// /auth/me is the navbar's who-is-signed-in read on every page, so it is not counted with sign-ins: 60 page views in 15 minutes made a signed-in visitor look signed out.
- app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, limit: 60, skip: (req) => req.path === '/me', standardHeaders: true, legacyHeaders: false }));
+ app.use('/auth/', rateLimit({ windowMs: 15 * 60_000, limit: 60, skip: (req) => req.path === '/me' || req.path === '/navbar-token', standardHeaders: true, legacyHeaders: false }));
app.use('/auth', sso.routes());
app.use('/auth', sso.routes());
{ const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'actor', service: 'actor', host: 'openvibe.actor', name: 'OpenVibe.Actor', profile: 'ugc' })); }
{ const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'actor', service: 'actor', host: 'openvibe.actor', name: 'OpenVibe.Actor', profile: 'ugc' })); }
@@ -204,6 +204,16 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
@@ -204,6 +204,16 @@ function createSso({ config, keys, fetchImpl = globalThis.fetch, now = () => Dat
return res.json({ user: meOf(claims) });
return res.json({ user: meOf(claims) });
});
});
+ // The navbar needs a Network access token for the Dock. Keep /auth/me's
+ // profile response token-free; this same-origin answer is never cached.
+ r.get('/navbar-token', async (req, res) => {
+ res.set('Cache-Control', 'private, no-store');
+ const token = sso.extractToken(req);
+ const claims = await sso.verify(token);
+ if (!claims) return res.status(401).json({ error: 'Not signed in' });
+ return res.json({ token, expires_at: claims.exp ? claims.exp * 1000 : null });
+ });
+
// Sign-out stays a same-site action: another site cannot sign a person out.
// Sign-out stays a same-site action: another site cannot sign a person out.
const sameOriginRequest = (req) => {
const sameOriginRequest = (req) => {
const site = String(req.get('sec-fetch-site') || '');
const site = String(req.get('sec-fetch-site') || '');
@@ -65,6 +65,7 @@ function renderPage(o) {
@@ -65,6 +65,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)
@@ -49,6 +49,12 @@ const { signJwt } = require('./helpers/mocks');
@@ -49,6 +49,12 @@ const { signJwt } = require('./helpers/mocks');
const me = await s.get('/auth/me', { as: staff });
const me = await s.get('/auth/me', { as: staff });
assert.strictEqual(me.status, 200, me.text.slice(0, 200));
assert.strictEqual(me.status, 200, me.text.slice(0, 200));
assert.strictEqual(JSON.parse(me.text).user.username, staff.username);
assert.strictEqual(JSON.parse(me.text).user.username, staff.username);
+ const token = s.network.userToken(staff);
+ const nav = await s.get('/auth/navbar-token', { cookie: `ov_token=${token}` });
+ assert.strictEqual(nav.status, 200);
+ assert.strictEqual(JSON.parse(nav.text).token, token, 'the Dock receives the verified Network token');
+ assert.strictEqual(nav.headers.get('cache-control'), 'private, no-store');
+ assert.strictEqual((await s.get('/auth/navbar-token')).status, 401, 'a guest receives no token');
});
});
await check('another site cannot sign a person out; this site can', async () => {
await check('another site cannot sign a person out; this site can', async () => {