From 8885f5da24c7ecebf98c947625bc8545dc840135 Mon Sep 17 00:00:00 2001 From: Alexander Aronov Date: Mon, 14 Apr 2025 14:42:41 +0200 Subject: [PATCH] [PM-19914][PM-19913] trim domains and long fields in forwarders (#14141) * PM-19913: Added max length to the generated_for and description peroperties in the FirefoxRelay API payload * [PM-19913] Added maxLength restriction to the website and generatedBy methods. Added maxLength limit of 200 to the description of addy.io --- .../integration/integration-context.spec.ts | 37 +++++++++++++++++++ .../tools/integration/integration-context.ts | 26 ++++++++++--- .../core/src/integration/addy-io.spec.ts | 4 ++ .../generator/core/src/integration/addy-io.ts | 2 +- .../src/integration/firefox-relay.spec.ts | 5 +++ .../core/src/integration/firefox-relay.ts | 4 +- 6 files changed, 70 insertions(+), 8 deletions(-) diff --git a/libs/common/src/tools/integration/integration-context.spec.ts b/libs/common/src/tools/integration/integration-context.spec.ts index 67a40afb337..33694aefea1 100644 --- a/libs/common/src/tools/integration/integration-context.spec.ts +++ b/libs/common/src/tools/integration/integration-context.spec.ts @@ -189,6 +189,33 @@ describe("IntegrationContext", () => { expect(result).toBe(""); }); + + it("extracts the hostname when extractHostname is true", () => { + const context = new IntegrationContext(EXAMPLE_META, null, i18n); + + const result = context.website( + { website: "https://www.example.com/path" }, + { extractHostname: true }, + ); + + expect(result).toBe("www.example.com"); + }); + + it("falls back to the full URL when Utils.getHost cannot extract the hostname", () => { + const context = new IntegrationContext(EXAMPLE_META, null, i18n); + + const result = context.website({ website: "invalid-url" }, { extractHostname: true }); + + expect(result).toBe("invalid-url"); + }); + + it("truncates the website to maxLength", () => { + const context = new IntegrationContext(EXAMPLE_META, null, i18n); + + const result = context.website({ website: "www.example.com" }, { maxLength: 3 }); + + expect(result).toBe("www"); + }); }); describe("generatedBy", () => { @@ -211,5 +238,15 @@ describe("IntegrationContext", () => { expect(result).toBe("result"); expect(i18n.t).toHaveBeenCalledWith("forwarderGeneratedByWithWebsite", "www.example.com"); }); + + it("truncates generated text to maxLength", () => { + const context = new IntegrationContext(EXAMPLE_META, null, i18n); + i18n.t.mockReturnValue("This is the result text"); + + const result = context.generatedBy({ website: null }, { maxLength: 4 }); + + expect(result).toBe("This"); + expect(i18n.t).toHaveBeenCalledWith("forwarderGeneratedBy", ""); + }); }); }); diff --git a/libs/common/src/tools/integration/integration-context.ts b/libs/common/src/tools/integration/integration-context.ts index 40648df6803..49edafc026b 100644 --- a/libs/common/src/tools/integration/integration-context.ts +++ b/libs/common/src/tools/integration/integration-context.ts @@ -79,24 +79,40 @@ export class IntegrationContext { /** look up the website the integration is working with. * @param request supplies information about the state of the extension site + * @param options optional parameters + * @param options.extractHostname when `true`, tries to extract the hostname from the website URL, returns full URL otherwise + * @param options.maxLength limits the length of the return value * @returns The website or an empty string if a website isn't available * @remarks `website` is usually supplied when generating a credential from the vault */ - website(request: IntegrationRequest) { - return request.website ?? ""; + website( + request: IntegrationRequest, + options?: { extractHostname?: boolean; maxLength?: number }, + ) { + let url = request.website ?? ""; + if (options?.extractHostname) { + url = Utils.getHost(url) ?? url; + } + return url.slice(0, options?.maxLength); } /** look up localized text indicating Bitwarden requested the forwarding address. * @param request supplies information about the state of the extension site + * @param options optional parameters + * @param options.extractHostname when `true`, extracts the hostname from the website URL + * @param options.maxLength limits the length of the return value * @returns localized text describing a generated forwarding address */ - generatedBy(request: IntegrationRequest) { - const website = this.website(request); + generatedBy( + request: IntegrationRequest, + options?: { extractHostname?: boolean; maxLength?: number }, + ) { + const website = this.website(request, { extractHostname: options?.extractHostname ?? false }); const descriptionId = website === "" ? "forwarderGeneratedBy" : "forwarderGeneratedByWithWebsite"; const description = this.i18n.t(descriptionId, website); - return description; + return description.slice(0, options?.maxLength); } } diff --git a/libs/tools/generator/core/src/integration/addy-io.spec.ts b/libs/tools/generator/core/src/integration/addy-io.spec.ts index 9c816330616..40d17e9d888 100644 --- a/libs/tools/generator/core/src/integration/addy-io.spec.ts +++ b/libs/tools/generator/core/src/integration/addy-io.spec.ts @@ -55,6 +55,10 @@ describe("Addy.io forwarder", () => { const result = AddyIo.forwarder.createForwardingEmail.body(null, context); + expect(context.generatedBy).toHaveBeenCalledWith(null, { + extractHostname: true, + maxLength: 200, + }); expect(result).toEqual({ domain: "domain", description: "generated by", diff --git a/libs/tools/generator/core/src/integration/addy-io.ts b/libs/tools/generator/core/src/integration/addy-io.ts index 631c5fdb510..93ffed3392a 100644 --- a/libs/tools/generator/core/src/integration/addy-io.ts +++ b/libs/tools/generator/core/src/integration/addy-io.ts @@ -39,7 +39,7 @@ const createForwardingEmail = Object.freeze({ body(request: IntegrationRequest, context: ForwarderContext) { return { domain: context.emailDomain(), - description: context.generatedBy(request), + description: context.generatedBy(request, { extractHostname: true, maxLength: 200 }), }; }, hasJsonPayload(response: Response) { diff --git a/libs/tools/generator/core/src/integration/firefox-relay.spec.ts b/libs/tools/generator/core/src/integration/firefox-relay.spec.ts index ed487b7f49f..08798b154b3 100644 --- a/libs/tools/generator/core/src/integration/firefox-relay.spec.ts +++ b/libs/tools/generator/core/src/integration/firefox-relay.spec.ts @@ -56,6 +56,11 @@ describe("Firefox Relay forwarder", () => { const result = FirefoxRelay.forwarder.createForwardingEmail.body(null, context); + expect(context.website).toHaveBeenCalledWith(null, { maxLength: 255 }); + expect(context.generatedBy).toHaveBeenCalledWith(null, { + extractHostname: true, + maxLength: 64, + }); expect(result).toEqual({ enabled: true, generated_for: "website", diff --git a/libs/tools/generator/core/src/integration/firefox-relay.ts b/libs/tools/generator/core/src/integration/firefox-relay.ts index 9f40a3631ff..f80de0c95dd 100644 --- a/libs/tools/generator/core/src/integration/firefox-relay.ts +++ b/libs/tools/generator/core/src/integration/firefox-relay.ts @@ -33,8 +33,8 @@ const createForwardingEmail = Object.freeze({ body(request: IntegrationRequest, context: ForwarderContext) { return { enabled: true, - generated_for: context.website(request), - description: context.generatedBy(request), + generated_for: context.website(request, { maxLength: 255 }), + description: context.generatedBy(request, { extractHostname: true, maxLength: 64 }), }; }, hasJsonPayload(response: Response) {