From 115e7da62db855ae9f504495bc894248a57402cf Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Wed, 26 Nov 2025 15:29:28 +0100 Subject: [PATCH] feat: send shared secret to collab --- src/index.d.ts | 2 ++ src/routes/source.js | 4 ++-- src/storage/object/copy.js | 4 ++-- src/storage/object/delete.js | 4 ++-- src/storage/utils/object.js | 19 +++++++++++++++++-- test/storage/utils/object.test.js | 26 ++++++++++++++++++++++---- 6 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/index.d.ts b/src/index.d.ts index 53b94366..8c524b17 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -17,6 +17,8 @@ export interface Env { S3_SECRET_ACCESS_KEY: string; IMS_ORIGIN: string; AEM_BUCKET_NAME: string; + // shared secret used as authorization when invoking the collab service (eg for syncadmin) + COLLAB_SHARED_SECRET: string; DA_AUTH: KVNamespace, DA_CONFIG: KVNamespace, diff --git a/src/routes/source.js b/src/routes/source.js index ddcace43..3ac0565f 100644 --- a/src/routes/source.js +++ b/src/routes/source.js @@ -12,7 +12,7 @@ import getObject from '../storage/object/get.js'; import putObject from '../storage/object/put.js'; import deleteObjects from '../storage/object/delete.js'; -import { invalidateCollab } from '../storage/utils/object.js'; +import { notifyCollab } from '../storage/utils/object.js'; import putHelper from '../helpers/source.js'; import deleteHelper from '../helpers/delete.js'; @@ -32,7 +32,7 @@ export async function postSource({ req, env, daCtx }) { if (resp.status === 201 || resp.status === 200) { const initiator = req.headers.get('x-da-initiator'); if (initiator !== 'collab') { - await invalidateCollab('syncadmin', req.url, env); + await notifyCollab('syncadmin', req.url, env); } } return resp; diff --git a/src/storage/object/copy.js b/src/storage/object/copy.js index b3038619..cbacb6f0 100644 --- a/src/storage/object/copy.js +++ b/src/storage/object/copy.js @@ -16,7 +16,7 @@ import { import getObject from './get.js'; import getS3Config from '../utils/config.js'; -import { invalidateCollab } from '../utils/object.js'; +import { notifyCollab } from '../utils/object.js'; import { putObjectWithVersion } from '../version/put.js'; import { getUsersForMetadata } from '../utils/version.js'; import { listCommand } from '../utils/list.js'; @@ -113,7 +113,7 @@ export const copyFile = async (config, env, daCtx, sourceKey, details, isRename) } finally { if (Key.endsWith('.html')) { // Reset the collab cached state for the copied object - await invalidateCollab('syncAdmin', `${daCtx.origin}/source/${daCtx.org}/${Key}`, env); + await notifyCollab('syncAdmin', `${daCtx.origin}/source/${daCtx.org}/${Key}`, env); } } }; diff --git a/src/storage/object/delete.js b/src/storage/object/delete.js index 5ece292d..84fc5d73 100644 --- a/src/storage/object/delete.js +++ b/src/storage/object/delete.js @@ -15,7 +15,7 @@ import { } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import getS3Config from '../utils/config.js'; -import { invalidateCollab } from '../utils/object.js'; +import { notifyCollab } from '../utils/object.js'; // import { postObjectVersionWithLabel } from '../version/put.js'; import { listCommand } from '../utils/list.js'; import { hasPermission } from '../../utils/auth.js'; @@ -41,7 +41,7 @@ export async function deleteObject(client, daCtx, Key, env /* , isMove = false * } if (Key.endsWith('.html')) { - await invalidateCollab('deleteadmin', `${daCtx.origin}/source/${daCtx.org}/${Key}`, env); + await notifyCollab('deleteadmin', `${daCtx.origin}/source/${daCtx.org}/${Key}`, env); } return resp; diff --git a/src/storage/utils/object.js b/src/storage/utils/object.js index e9396304..eaf1c203 100644 --- a/src/storage/utils/object.js +++ b/src/storage/utils/object.js @@ -9,7 +9,14 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -export async function invalidateCollab(api, url, env) { +/** + * Sends a `api` request to collab + * @param {string} api The API ('syncadmin' or 'deleteadmin') + * @param {string} url Url of the resource + * @param {Env} env + * @returns {Promise} + */ +export async function notifyCollab(api, url, env) { if (!url.endsWith('.html')) { // collab only deals with .html files, no need to invalidate anything else return; @@ -19,5 +26,13 @@ export async function invalidateCollab(api, url, env) { // Use dacollab service binding, hostname is not relevant const invURL = `https://localhost${invPath}`; - await env.dacollab.fetch(invURL); + const headers = {}; + if (env.COLLAB_SHARED_SECRET) { + headers.authorization = `token ${env.COLLAB_SHARED_SECRET}`; + } + await env.dacollab.fetch(invURL, { + // TODO: use POST for state changing operations + // method: 'POST', + headers, + }); } diff --git a/test/storage/utils/object.test.js b/test/storage/utils/object.test.js index 9d6f3adc..36e666eb 100644 --- a/test/storage/utils/object.test.js +++ b/test/storage/utils/object.test.js @@ -12,7 +12,7 @@ /* eslint-env mocha */ import assert from 'node:assert'; -import { invalidateCollab } from '../../../src/storage/utils/object.js'; +import { notifyCollab } from '../../../src/storage/utils/object.js'; describe('Storage Object Utils tests', () => { function setupEnv() { @@ -31,7 +31,7 @@ describe('Storage Object Utils tests', () => { const { called, env } = setupEnv(); assert.strictEqual(called.length, 0, 'precondition'); - await invalidateCollab('syncAdmin', 'https://admin.da.live/source/a/b/c.html', env); + await notifyCollab('syncAdmin', 'https://admin.da.live/source/a/b/c.html', env); assert.strictEqual(called.length, 1); assert.strictEqual(called[0], 'https://localhost/api/v1/syncAdmin?doc=https://admin.da.live/source/a/b/c.html'); }); @@ -40,8 +40,26 @@ describe('Storage Object Utils tests', () => { const { called, env } = setupEnv(); assert.strictEqual(called.length, 0, 'precondition'); - await invalidateCollab('syncAdmin', 'https://admin.da.live/source/a/b/c.jpg', env); - await invalidateCollab('syncAdmin', 'https://admin.da.live/source/a/b/c/d', env); + await notifyCollab('syncAdmin', 'https://admin.da.live/source/a/b/c.jpg', env); + await notifyCollab('syncAdmin', 'https://admin.da.live/source/a/b/c/d', env); assert.strictEqual(called.length, 0, 'should not have invalidated anything'); }); + + it('Should invalidate (with shared secret', async () => { + const called = []; + const env = { + dacollab: { + fetch: async (url, opts) => { + console.log(`invalidate called with ${url}`); + assert.strictEqual(opts.headers.authorization, 'token example-secret'); + called.push(url); + }, + }, + COLLAB_SHARED_SECRET: 'example-secret', + }; + assert.strictEqual(called.length, 0, 'precondition'); + await notifyCollab('syncAdmin', 'https://admin.da.live/source/a/b/c.html', env); + assert.strictEqual(called.length, 1); + assert.strictEqual(called[0], 'https://localhost/api/v1/syncAdmin?doc=https://admin.da.live/source/a/b/c.html'); + }); });