diff --git a/README.md b/README.md index 3963232..91da516 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,7 @@ cd op-blocknote-hocuspocus npm install # Start the server with the appropriate environment variables setup -ALLOWED_DOMAINS=your-openproject-domain.com SECRET=secret12345 npm run start -``` - -For the server to be able to reach to an OpenProject instance, it is necessary to set the environment variable `ALLOWED_DOMAINS`. It is a comma-separated list of domains (and it allows subdomain matching). - -``` -ALLOWED_DOMAINS=subdomain-openproject.example.com,top-level-openproject.com` +SECRET=secret12345 npm run start ``` The `SECRET` environment variable is a shared value between this application and OpenProject. Make sure to configure the same value in OpenProject - Settings Hocuspocus secret and in the `SECRET` environment variable of this project. @@ -36,7 +30,6 @@ docker pull openproject/hocuspocus:latest docker run -d \ -p 1234:1234 \ - -e ALLOWED_DOMAINS=your-openproject-domain.com \ -e SECRET=secret12345 \ openproject/hocuspocus:latest ``` diff --git a/src/extensions/openProjectApi.ts b/src/extensions/openProjectApi.ts index 96ec378..5b6ee70 100644 --- a/src/extensions/openProjectApi.ts +++ b/src/extensions/openProjectApi.ts @@ -30,27 +30,6 @@ export class OpenProjectApi implements Extension { } const decryptedToken = decryptToken(token); - const allowedDomains = process.env.ALLOWED_DOMAINS?.split(',') || []; - if (allowedDomains.length <= 0) { - throw new Error('Unauthorized: No allowed domains configured.'); - } - - try { - const url = new URL(resourceUrl); - const isAllowed = allowedDomains.some(domain => - url.hostname === domain.trim() || url.hostname.endsWith('.' + domain.trim()) - ); - - if (!isAllowed) { - throw new Error('Unauthorized: Invalid base URL domain.'); - } - } catch (error) { - if (error instanceof TypeError) { - throw new Error('Unauthorized: Invalid base URL format.'); - } - throw error; - } - const response = await fetch(resourceUrl, { method: "GET", headers: { @@ -64,7 +43,6 @@ export class OpenProjectApi implements Extension { } const jsonData = await response.json() as ApiResponseDocument; - // data.documentName = resourceUrl; data.context.resourceUrl = resourceUrl; data.context.token = decryptedToken; if (!jsonData._links?.update) { diff --git a/test/extensions/openProjectApi.test.ts b/test/extensions/openProjectApi.test.ts index 77960af..cff3aab 100644 --- a/test/extensions/openProjectApi.test.ts +++ b/test/extensions/openProjectApi.test.ts @@ -5,18 +5,14 @@ import { OpenProjectApi, createEditor } from "../../src/extensions/openProjectAp describe("OpenProjectApi", () => { let fetchMock: any; - let originalAllowedDomains: string | undefined; beforeEach(() => { fetchMock = vi.fn(); vi.stubGlobal('fetch', fetchMock); - originalAllowedDomains = process.env.ALLOWED_DOMAINS; - process.env.ALLOWED_DOMAINS = 'test.api,example.com'; }); afterEach(() => { vi.unstubAllGlobals(); - process.env.ALLOWED_DOMAINS = originalAllowedDomains; }); describe("onAuthenticate", () => { @@ -28,7 +24,7 @@ describe("OpenProjectApi", () => { ).rejects.toThrowError("Unauthorized: Token missing."); }); - test("when the token is invalid", async () => { + test("when the token is invalid throw an error", async () => { await expect(() => new OpenProjectApi().onAuthenticate({ // Invalid token, generated with a different secret @@ -37,51 +33,15 @@ describe("OpenProjectApi", () => { ).rejects.toThrowError("Unsupported state or unable to authenticate data"); }); - test("when ALLOWED_DOMAINS is not configured throw an error", async () => { - delete process.env.ALLOWED_DOMAINS; - - await expect(() => - new OpenProjectApi().onAuthenticate({ - token: "7u+b+QRJN7qANls=--URNw83hIWBq3MMIA--jtl+UPdtbniQVFNOs2EcAw==", - } as unknown as onAuthenticatePayload) - ).rejects.toThrowError("Unauthorized: No allowed domains configured."); - }); - test("when the resourceUrl has invalid format throw an error", async () => { - await expect(() => - new OpenProjectApi().onAuthenticate({ - token: "7u+b+QRJN7qANls=--URNw83hIWBq3MMIA--jtl+UPdtbniQVFNOs2EcAw==", - documentName: "not a valid url", - } as unknown as onAuthenticatePayload) - ).rejects.toThrowError("Unauthorized: Invalid base URL format."); - }); + fetchMock.mockResolvedValueOnce({ throws: new TypeError("is not a valid URL") }); - test("when the resourceUrl domain is not in ALLOWED_DOMAINS throw an error", async () => { await expect(() => new OpenProjectApi().onAuthenticate({ token: "7u+b+QRJN7qANls=--URNw83hIWBq3MMIA--jtl+UPdtbniQVFNOs2EcAw==", - documentName: "https://malicious.com/something/1", + documentName: "not a valid url", } as unknown as onAuthenticatePayload) - ).rejects.toThrowError("Unauthorized: Invalid base URL domain."); - }); - - test("when the resourceUrl subdomain matches ALLOWED_DOMAINS it should be accepted", async () => { - fetchMock.mockResolvedValueOnce({ - ok: true, - status: 200, - json: () => Promise.resolve({}), - }); - - const data = { - context: {}, - connectionConfig: {}, - token: "7u+b+QRJN7qANls=--URNw83hIWBq3MMIA--jtl+UPdtbniQVFNOs2EcAw==", - documentName: "https://subdomain.test.api/api/v3/documents/1", - } as unknown as onAuthenticatePayload; - - await new OpenProjectApi().onAuthenticate(data); - - expect(data.context.resourceUrl).toEqual("https://subdomain.test.api/api/v3/documents/1"); + ).rejects.toThrowError("Unauthorized: Invalid token or document access denied."); }); test("when the server does not authorize the request throw an error", async () => {