openvibers/openvibe.media Commits
Accept legacy public_key JWKS shape; legacy /api/thumbnails/<basename> route
Changes
@@@ -31,9 +31,15 @@ async function fetchNetworkPublicKey() {if (!res.ok) throw new Error(`JWKS fetch failed: ${res.status}`);const body = await res.json();const jwk = (body.keys || []).find(k => k.kty === 'RSA') || (body.keys || [])[0];-- if (!jwk) throw new Error('JWKS contained no keys');-- const keyObj = crypto.createPublicKey({ key: jwk, format: 'jwk' });-- _networkPublicKeyPem = keyObj.export({ type: 'spki', format: 'pem' });++ if (jwk) {++ const keyObj = crypto.createPublicKey({ key: jwk, format: 'jwk' });++ _networkPublicKeyPem = keyObj.export({ type: 'spki', format: 'pem' });++ } else if (typeof body.public_key === 'string' && body.public_key.includes('BEGIN')) {++ // Network's inherited endpoint shape: { public_key: <PEM>, algorithm }++ _networkPublicKeyPem = crypto.createPublicKey(body.public_key).export({ type: 'spki', format: 'pem' });++ } else {++ throw new Error('JWKS contained no keys');++ }console.log('[Auth] Network JWKS public key loaded');return _networkPublicKeyPem;} catch (err) {@@@ -193,6 +193,26 @@ router.get('/t/:id', (req, res) => {require('../thumbnails/thumbnail-service').serveThumbnail(req, res);});++// ── Legacy thumbnail URLs ────────────────────────────────────++// Old-stack thumbnails lived at /api/thumbnails/<basename>, and migrated Live++// rows still carry those absolute URLs. Basenames change when a thumbnail is++// regenerated, so resolve the vod/clip id from the name and redirect to the++// current canonical URL; serve the exact file when it still exists.++router.get('/api/thumbnails/:name', (req, res) => {++ const name = path.basename(String(req.params.name || ''));++ const m = /^(vod|clip)-(\d+)-\d+\.(?:jpg|jpeg|png)$/i.exec(name);++ if (m) {++ const row = m[1].toLowerCase() === 'vod'++ ? db.getVodById(parseInt(m[2], 10), 'live')++ : db.getClipById(parseInt(m[2], 10), 'live');++ if (row && row.thumbnail_url && !row.thumbnail_url.endsWith(`/${name}`)) {++ return res.redirect(302, row.thumbnail_url);++ }++ }++ req.params.id = name;++ require('../thumbnails/thumbnail-service').serveThumbnail(req, res);++});++// ── Paste screenshots by filename ────────────────────────────// Legacy /data/pastes/screenshots/<name> URLs (old avatars, hero moments,// pre-cutover pastes) map here. Migrated screenshot files have arbitrary
@@ -31,9 +31,15 @@ async function fetchNetworkPublicKey() {
@@ -31,9 +31,15 @@ async function fetchNetworkPublicKey() {
if (!res.ok) throw new Error(`JWKS fetch failed: ${res.status}`);
if (!res.ok) throw new Error(`JWKS fetch failed: ${res.status}`);
const body = await res.json();
const body = await res.json();
const jwk = (body.keys || []).find(k => k.kty === 'RSA') || (body.keys || [])[0];
const jwk = (body.keys || []).find(k => k.kty === 'RSA') || (body.keys || [])[0];
- if (!jwk) throw new Error('JWKS contained no keys');
- const keyObj = crypto.createPublicKey({ key: jwk, format: 'jwk' });
- _networkPublicKeyPem = keyObj.export({ type: 'spki', format: 'pem' });
+ if (jwk) {
+ const keyObj = crypto.createPublicKey({ key: jwk, format: 'jwk' });
+ _networkPublicKeyPem = keyObj.export({ type: 'spki', format: 'pem' });
+ } else if (typeof body.public_key === 'string' && body.public_key.includes('BEGIN')) {
+ // Network's inherited endpoint shape: { public_key: <PEM>, algorithm }
+ _networkPublicKeyPem = crypto.createPublicKey(body.public_key).export({ type: 'spki', format: 'pem' });
+ } else {
+ throw new Error('JWKS contained no keys');
+ }
console.log('[Auth] Network JWKS public key loaded');
console.log('[Auth] Network JWKS public key loaded');
return _networkPublicKeyPem;
return _networkPublicKeyPem;
} catch (err) {
} catch (err) {
@@ -193,6 +193,26 @@ router.get('/t/:id', (req, res) => {
@@ -193,6 +193,26 @@ router.get('/t/:id', (req, res) => {
require('../thumbnails/thumbnail-service').serveThumbnail(req, res);
require('../thumbnails/thumbnail-service').serveThumbnail(req, res);
});
});
+// ── Legacy thumbnail URLs ────────────────────────────────────
+// Old-stack thumbnails lived at /api/thumbnails/<basename>, and migrated Live
+// rows still carry those absolute URLs. Basenames change when a thumbnail is
+// regenerated, so resolve the vod/clip id from the name and redirect to the
+// current canonical URL; serve the exact file when it still exists.
+router.get('/api/thumbnails/:name', (req, res) => {
+ const name = path.basename(String(req.params.name || ''));
+ const m = /^(vod|clip)-(\d+)-\d+\.(?:jpg|jpeg|png)$/i.exec(name);
+ if (m) {
+ const row = m[1].toLowerCase() === 'vod'
+ ? db.getVodById(parseInt(m[2], 10), 'live')
+ : db.getClipById(parseInt(m[2], 10), 'live');
+ if (row && row.thumbnail_url && !row.thumbnail_url.endsWith(`/${name}`)) {
+ return res.redirect(302, row.thumbnail_url);
+ }
+ }
+ req.params.id = name;
+ require('../thumbnails/thumbnail-service').serveThumbnail(req, res);
+});
+
// ── Paste screenshots by filename ────────────────────────────
// ── Paste screenshots by filename ────────────────────────────
// Legacy /data/pastes/screenshots/<name> URLs (old avatars, hero moments,
// Legacy /data/pastes/screenshots/<name> URLs (old avatars, hero moments,
// pre-cutover pastes) map here. Migrated screenshot files have arbitrary
// pre-cutover pastes) map here. Migrated screenshot files have arbitrary