1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-22 11:45:59 +01:00

[PM-5840] add duck duck go forwarder (#7674)

This commit is contained in:
✨ Audrey ✨ 2024-01-25 10:24:22 -05:00 committed by GitHub
parent af4cafa2b9
commit 6b97c0e716
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 172 additions and 0 deletions

View File

@ -0,0 +1,120 @@
/**
* include Request in test environment.
* @jest-environment ../../../../shared/test.environment.ts
*/
import { Forwarders } from "../options/constants";
import { DuckDuckGoForwarder } from "./duck-duck-go";
import { mockApiService, mockI18nService } from "./mocks.jest";
describe("DuckDuckGo Forwarder", () => {
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
await expect(
async () =>
await forwarder.generate(null, {
token,
}),
).rejects.toEqual("forwaderInvalidToken");
expect(apiService.nativeFetch).not.toHaveBeenCalled();
expect(i18nService.t).toHaveBeenCalledWith(
"forwaderInvalidToken",
Forwarders.DuckDuckGo.name,
);
});
it.each([
["jane.doe@duck.com", 201, "jane.doe"],
["john.doe@duck.com", 201, "john.doe"],
["jane.doe@duck.com", 200, "jane.doe"],
["john.doe@duck.com", 200, "john.doe"],
])(
"returns the generated email address (= %p) if the request is successful (status = %p)",
async (email, status, address) => {
const apiService = mockApiService(status, { address });
const i18nService = mockI18nService();
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
const result = await forwarder.generate(null, {
token: "token",
});
expect(result).toEqual(email);
expect(apiService.nativeFetch).toHaveBeenCalledWith(expect.any(Request));
},
);
it("throws an invalid token error if the request fails with a 401", async () => {
const apiService = mockApiService(401, {});
const i18nService = mockI18nService();
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
await expect(
async () =>
await forwarder.generate(null, {
token: "token",
}),
).rejects.toEqual("forwaderInvalidToken");
expect(apiService.nativeFetch).toHaveBeenCalledWith(expect.any(Request));
// counting instances is terribly flaky over changes, but jest doesn't have a better way to do this
expect(i18nService.t).toHaveBeenCalledWith(
"forwaderInvalidToken",
Forwarders.DuckDuckGo.name,
);
});
it("throws an unknown error if the request is successful but an address isn't present", async () => {
const apiService = mockApiService(200, {});
const i18nService = mockI18nService();
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
await expect(
async () =>
await forwarder.generate(null, {
token: "token",
}),
).rejects.toEqual("forwarderUnknownError");
expect(apiService.nativeFetch).toHaveBeenCalledWith(expect.any(Request));
// counting instances is terribly flaky over changes, but jest doesn't have a better way to do this
expect(i18nService.t).toHaveBeenCalledWith(
"forwarderUnknownError",
Forwarders.DuckDuckGo.name,
);
});
it.each([100, 202, 300, 418, 500, 600])(
"throws an unknown error if the request returns any other status code (= %i)",
async (statusCode) => {
const apiService = mockApiService(statusCode, {});
const i18nService = mockI18nService();
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
await expect(
async () =>
await forwarder.generate(null, {
token: "token",
}),
).rejects.toEqual("forwarderUnknownError");
expect(apiService.nativeFetch).toHaveBeenCalledWith(expect.any(Request));
// counting instances is terribly flaky over changes, but jest doesn't have a better way to do this
expect(i18nService.t).toHaveBeenCalledWith(
"forwarderUnknownError",
Forwarders.DuckDuckGo.name,
);
},
);
});
});

View File

@ -0,0 +1,52 @@
import { ApiService } from "../../../../abstractions/api.service";
import { I18nService } from "../../../../platform/abstractions/i18n.service";
import { Forwarders } from "../options/constants";
import { ApiOptions, Forwarder } from "../options/forwarder-options";
/** Generates a forwarding address for DuckDuckGo */
export class DuckDuckGoForwarder implements Forwarder {
/** Instantiates the forwarder
* @param apiService used for ajax requests to the forwarding service
* @param i18nService used to look up error strings
*/
constructor(
private apiService: ApiService,
private i18nService: I18nService,
) {}
/** {@link Forwarder.generate} */
async generate(_website: string | null, options: ApiOptions): Promise<string> {
if (!options.token || options.token === "") {
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.DuckDuckGo.name);
throw error;
}
const url = "https://quack.duckduckgo.com/api/email/addresses";
const request = new Request(url, {
redirect: "manual",
cache: "no-store",
method: "POST",
headers: new Headers({
Authorization: "Bearer " + options.token,
"Content-Type": "application/json",
}),
});
const response = await this.apiService.nativeFetch(request);
if (response.status === 200 || response.status === 201) {
const json = await response.json();
if (json.address) {
return `${json.address}@duck.com`;
} else {
const error = this.i18nService.t("forwarderUnknownError", Forwarders.DuckDuckGo.name);
throw error;
}
} else if (response.status === 401) {
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.DuckDuckGo.name);
throw error;
} else {
const error = this.i18nService.t("forwarderUnknownError", Forwarders.DuckDuckGo.name);
throw error;
}
}
}