[PM-5844] add simple login forwarder (#7679)
This commit is contained in:
parent
3e6c525e94
commit
2c69810460
|
@ -0,0 +1,183 @@
|
|||
/**
|
||||
* include Request in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
import { Forwarders } from "../options/constants";
|
||||
|
||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||
import { SimpleLoginForwarder } from "./simple-login";
|
||||
|
||||
describe("SimpleLogin 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 SimpleLoginForwarder(apiService, i18nService);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
token,
|
||||
baseUrl: "https://api.example.com",
|
||||
}),
|
||||
).rejects.toEqual("forwaderInvalidToken");
|
||||
|
||||
expect(apiService.nativeFetch).not.toHaveBeenCalled();
|
||||
expect(i18nService.t).toHaveBeenCalledWith(
|
||||
"forwaderInvalidToken",
|
||||
Forwarders.SimpleLogin.name,
|
||||
);
|
||||
});
|
||||
|
||||
it.each([null, ""])(
|
||||
"throws an error if the baseUrl is missing (baseUrl = %p)",
|
||||
async (baseUrl) => {
|
||||
const apiService = mockApiService(200, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
token: "token",
|
||||
baseUrl,
|
||||
}),
|
||||
).rejects.toEqual("forwarderNoUrl");
|
||||
|
||||
expect(apiService.nativeFetch).not.toHaveBeenCalled();
|
||||
expect(i18nService.t).toHaveBeenCalledWith("forwarderNoUrl", Forwarders.SimpleLogin.name);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["forwarderGeneratedByWithWebsite", "provided", "bitwarden.com", "bitwarden.com"],
|
||||
["forwarderGeneratedByWithWebsite", "provided", "httpbin.org", "httpbin.org"],
|
||||
["forwarderGeneratedBy", "not provided", null, ""],
|
||||
["forwarderGeneratedBy", "not provided", "", ""],
|
||||
])(
|
||||
"describes the website with %p when the website is %s (= %p)",
|
||||
async (translationKey, _ignored, website, expectedWebsite) => {
|
||||
const apiService = mockApiService(200, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
||||
|
||||
await forwarder.generate(website, {
|
||||
token: "token",
|
||||
baseUrl: "https://api.example.com",
|
||||
});
|
||||
|
||||
// counting instances is terribly flaky over changes, but jest doesn't have a better way to do this
|
||||
expect(i18nService.t).toHaveBeenCalledWith(translationKey, expectedWebsite);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["jane.doe@example.com", 201],
|
||||
["john.doe@example.com", 201],
|
||||
["jane.doe@example.com", 200],
|
||||
["john.doe@example.com", 200],
|
||||
])(
|
||||
"returns the generated email address (= %p) if the request is successful (status = %p)",
|
||||
async (alias, status) => {
|
||||
const apiService = mockApiService(status, { alias });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
||||
|
||||
const result = await forwarder.generate(null, {
|
||||
token: "token",
|
||||
baseUrl: "https://api.example.com",
|
||||
});
|
||||
|
||||
expect(result).toEqual(alias);
|
||||
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 SimpleLoginForwarder(apiService, i18nService);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
token: "token",
|
||||
baseUrl: "https://api.example.com",
|
||||
}),
|
||||
).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).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"forwaderInvalidToken",
|
||||
Forwarders.SimpleLogin.name,
|
||||
);
|
||||
});
|
||||
|
||||
it.each([{}, null])(
|
||||
"throws an unknown error if the request fails and no status (=%p) is provided",
|
||||
async (body) => {
|
||||
const apiService = mockApiService(500, body);
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
token: "token",
|
||||
baseUrl: "https://api.example.com",
|
||||
}),
|
||||
).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).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"forwarderUnknownError",
|
||||
Forwarders.SimpleLogin.name,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
[100, "Continue"],
|
||||
[202, "Accepted"],
|
||||
[300, "Multiple Choices"],
|
||||
[418, "I'm a teapot"],
|
||||
[500, "Internal Server Error"],
|
||||
[600, "Unknown Status"],
|
||||
])(
|
||||
"throws an error with the status text if the request returns any other status code (= %i) and a status (= %p) is provided",
|
||||
async (statusCode, error) => {
|
||||
const apiService = mockApiService(statusCode, { error });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
token: "token",
|
||||
baseUrl: "https://api.example.com",
|
||||
}),
|
||||
).rejects.toEqual("forwarderError");
|
||||
|
||||
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).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"forwarderError",
|
||||
Forwarders.SimpleLogin.name,
|
||||
error,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||
import { Forwarders } from "../options/constants";
|
||||
import { Forwarder, SelfHostedApiOptions } from "../options/forwarder-options";
|
||||
|
||||
/** Generates a forwarding address for Simple Login */
|
||||
export class SimpleLoginForwarder 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, options: SelfHostedApiOptions): Promise<string> {
|
||||
if (!options.token || options.token === "") {
|
||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.SimpleLogin.name);
|
||||
throw error;
|
||||
}
|
||||
if (!options.baseUrl || options.baseUrl === "") {
|
||||
const error = this.i18nService.t("forwarderNoUrl", Forwarders.SimpleLogin.name);
|
||||
throw error;
|
||||
}
|
||||
|
||||
let url = options.baseUrl + "/api/alias/random/new";
|
||||
let noteId = "forwarderGeneratedBy";
|
||||
if (website && website !== "") {
|
||||
url += "?hostname=" + website;
|
||||
noteId = "forwarderGeneratedByWithWebsite";
|
||||
}
|
||||
const note = this.i18nService.t(noteId, website ?? "");
|
||||
|
||||
const request = new Request(url, {
|
||||
redirect: "manual",
|
||||
cache: "no-store",
|
||||
method: "POST",
|
||||
headers: new Headers({
|
||||
Authentication: options.token,
|
||||
"Content-Type": "application/json",
|
||||
}),
|
||||
body: JSON.stringify({ note }),
|
||||
});
|
||||
|
||||
const response = await this.apiService.nativeFetch(request);
|
||||
if (response.status === 401) {
|
||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.SimpleLogin.name);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
if (response.status === 200 || response.status === 201) {
|
||||
return json.alias;
|
||||
} else if (json?.error) {
|
||||
const error = this.i18nService.t("forwarderError", Forwarders.SimpleLogin.name, json.error);
|
||||
throw error;
|
||||
} else {
|
||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.SimpleLogin.name);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue