mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-02 18:17:46 +01:00
[PM-5974] introduce ForwarderGeneratorStrategy (#8207)
* update defaults to include `website` parameter * update utilities tests to include `website` parameter
This commit is contained in:
parent
a5c78fbe0d
commit
c7318311af
@ -5,6 +5,12 @@ import {
|
||||
SUBADDRESS_SETTINGS,
|
||||
PASSPHRASE_SETTINGS,
|
||||
PASSWORD_SETTINGS,
|
||||
SIMPLE_LOGIN_FORWARDER,
|
||||
FORWARD_EMAIL_FORWARDER,
|
||||
FIREFOX_RELAY_FORWARDER,
|
||||
FASTMAIL_FORWARDER,
|
||||
DUCK_DUCK_GO_FORWARDER,
|
||||
ADDY_IO_FORWARDER,
|
||||
} from "./key-definitions";
|
||||
|
||||
describe("Key definitions", () => {
|
||||
@ -48,6 +54,54 @@ describe("Key definitions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("ADDY_IO_FORWARDER", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value: any = {};
|
||||
const result = ADDY_IO_FORWARDER.deserializer(value);
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DUCK_DUCK_GO_FORWARDER", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value: any = {};
|
||||
const result = DUCK_DUCK_GO_FORWARDER.deserializer(value);
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("FASTMAIL_FORWARDER", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value: any = {};
|
||||
const result = FASTMAIL_FORWARDER.deserializer(value);
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("FIREFOX_RELAY_FORWARDER", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value: any = {};
|
||||
const result = FIREFOX_RELAY_FORWARDER.deserializer(value);
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("FORWARD_EMAIL_FORWARDER", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value: any = {};
|
||||
const result = FORWARD_EMAIL_FORWARDER.deserializer(value);
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("SIMPLE_LOGIN_FORWARDER", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value: any = {};
|
||||
const result = SIMPLE_LOGIN_FORWARDER.deserializer(value);
|
||||
expect(result).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ENCRYPTED_HISTORY", () => {
|
||||
it("should pass through deserialization", () => {
|
||||
const value = {};
|
||||
|
@ -5,6 +5,12 @@ import { GeneratedPasswordHistory } from "./password/generated-password-history"
|
||||
import { PasswordGenerationOptions } from "./password/password-generation-options";
|
||||
import { CatchallGenerationOptions } from "./username/catchall-generator-options";
|
||||
import { EffUsernameGenerationOptions } from "./username/eff-username-generator-options";
|
||||
import {
|
||||
ApiOptions,
|
||||
EmailDomainOptions,
|
||||
EmailPrefixOptions,
|
||||
SelfHostedApiOptions,
|
||||
} from "./username/options/forwarder-options";
|
||||
import { SubaddressGenerationOptions } from "./username/subaddress-generator-options";
|
||||
|
||||
/** plaintext password generation options */
|
||||
@ -52,6 +58,54 @@ export const SUBADDRESS_SETTINGS = new KeyDefinition<SubaddressGenerationOptions
|
||||
},
|
||||
);
|
||||
|
||||
export const ADDY_IO_FORWARDER = new KeyDefinition<SelfHostedApiOptions & EmailDomainOptions>(
|
||||
GENERATOR_DISK,
|
||||
"addyIoForwarder",
|
||||
{
|
||||
deserializer: (value) => value,
|
||||
},
|
||||
);
|
||||
|
||||
export const DUCK_DUCK_GO_FORWARDER = new KeyDefinition<ApiOptions>(
|
||||
GENERATOR_DISK,
|
||||
"duckDuckGoForwarder",
|
||||
{
|
||||
deserializer: (value) => value,
|
||||
},
|
||||
);
|
||||
|
||||
export const FASTMAIL_FORWARDER = new KeyDefinition<ApiOptions & EmailPrefixOptions>(
|
||||
GENERATOR_DISK,
|
||||
"fastmailForwarder",
|
||||
{
|
||||
deserializer: (value) => value,
|
||||
},
|
||||
);
|
||||
|
||||
export const FIREFOX_RELAY_FORWARDER = new KeyDefinition<ApiOptions>(
|
||||
GENERATOR_DISK,
|
||||
"firefoxRelayForwarder",
|
||||
{
|
||||
deserializer: (value) => value,
|
||||
},
|
||||
);
|
||||
|
||||
export const FORWARD_EMAIL_FORWARDER = new KeyDefinition<ApiOptions & EmailDomainOptions>(
|
||||
GENERATOR_DISK,
|
||||
"forwardEmailForwarder",
|
||||
{
|
||||
deserializer: (value) => value,
|
||||
},
|
||||
);
|
||||
|
||||
export const SIMPLE_LOGIN_FORWARDER = new KeyDefinition<SelfHostedApiOptions>(
|
||||
GENERATOR_DISK,
|
||||
"simpleLoginForwarder",
|
||||
{
|
||||
deserializer: (value) => value,
|
||||
},
|
||||
);
|
||||
|
||||
/** encrypted password generation history */
|
||||
export const ENCRYPTED_HISTORY = new KeyDefinition<GeneratedPasswordHistory>(
|
||||
GENERATOR_DISK,
|
||||
|
@ -0,0 +1,73 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec";
|
||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
|
||||
import { StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { DefaultPolicyEvaluator } from "../default-policy-evaluator";
|
||||
import { DUCK_DUCK_GO_FORWARDER } from "../key-definitions";
|
||||
import { SecretState } from "../state/secret-state";
|
||||
|
||||
import { ForwarderGeneratorStrategy } from "./forwarder-generator-strategy";
|
||||
import { ApiOptions } from "./options/forwarder-options";
|
||||
|
||||
class TestForwarder extends ForwarderGeneratorStrategy<ApiOptions> {
|
||||
constructor(
|
||||
encryptService: EncryptService,
|
||||
keyService: CryptoService,
|
||||
stateProvider: StateProvider,
|
||||
) {
|
||||
super(encryptService, keyService, stateProvider);
|
||||
}
|
||||
|
||||
get key() {
|
||||
// arbitrary.
|
||||
return DUCK_DUCK_GO_FORWARDER;
|
||||
}
|
||||
}
|
||||
|
||||
const SomeUser = "some user" as UserId;
|
||||
const AnotherUser = "another user" as UserId;
|
||||
|
||||
describe("ForwarderGeneratorStrategy", () => {
|
||||
const encryptService = mock<EncryptService>();
|
||||
const keyService = mock<CryptoService>();
|
||||
const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser));
|
||||
|
||||
describe("durableState", () => {
|
||||
it("constructs a secret state", () => {
|
||||
const strategy = new TestForwarder(encryptService, keyService, stateProvider);
|
||||
|
||||
const result = strategy.durableState(SomeUser);
|
||||
|
||||
expect(result).toBeInstanceOf(SecretState);
|
||||
});
|
||||
|
||||
it("returns the same secret state for a single user", () => {
|
||||
const strategy = new TestForwarder(encryptService, keyService, stateProvider);
|
||||
|
||||
const firstResult = strategy.durableState(SomeUser);
|
||||
const secondResult = strategy.durableState(SomeUser);
|
||||
|
||||
expect(firstResult).toBe(secondResult);
|
||||
});
|
||||
|
||||
it("returns a different secret state for a different user", () => {
|
||||
const strategy = new TestForwarder(encryptService, keyService, stateProvider);
|
||||
|
||||
const firstResult = strategy.durableState(SomeUser);
|
||||
const secondResult = strategy.durableState(AnotherUser);
|
||||
|
||||
expect(firstResult).not.toBe(secondResult);
|
||||
});
|
||||
});
|
||||
|
||||
it("evaluator returns the default policy evaluator", () => {
|
||||
const strategy = new TestForwarder(null, null, null);
|
||||
|
||||
const result = strategy.evaluator(null);
|
||||
|
||||
expect(result).toBeInstanceOf(DefaultPolicyEvaluator);
|
||||
});
|
||||
});
|
@ -0,0 +1,73 @@
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
|
||||
import { KeyDefinition, StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { GeneratorStrategy } from "../abstractions";
|
||||
import { DefaultPolicyEvaluator } from "../default-policy-evaluator";
|
||||
import { NoPolicy } from "../no-policy";
|
||||
import { PaddedDataPacker } from "../state/padded-data-packer";
|
||||
import { SecretClassifier } from "../state/secret-classifier";
|
||||
import { SecretState } from "../state/secret-state";
|
||||
import { UserKeyEncryptor } from "../state/user-key-encryptor";
|
||||
|
||||
import { ApiOptions } from "./options/forwarder-options";
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
const OPTIONS_FRAME_SIZE = 512;
|
||||
|
||||
/** An email forwarding service configurable through an API. */
|
||||
export abstract class ForwarderGeneratorStrategy<
|
||||
Options extends ApiOptions,
|
||||
> extends GeneratorStrategy<Options, NoPolicy> {
|
||||
/** Initializes the generator strategy
|
||||
* @param encryptService protects sensitive forwarder options
|
||||
* @param keyService looks up the user key when protecting data.
|
||||
* @param stateProvider creates the durable state for options storage
|
||||
*/
|
||||
constructor(
|
||||
private readonly encryptService: EncryptService,
|
||||
private readonly keyService: CryptoService,
|
||||
private stateProvider: StateProvider,
|
||||
) {
|
||||
super();
|
||||
// Uses password generator since there aren't policies
|
||||
// specific to usernames.
|
||||
this.policy = PolicyType.PasswordGenerator;
|
||||
|
||||
this.cache_ms = ONE_MINUTE;
|
||||
}
|
||||
|
||||
private durableStates = new Map<UserId, SecretState<Options, Record<string, never>>>();
|
||||
|
||||
/** {@link GeneratorStrategy.durableState} */
|
||||
durableState = (userId: UserId) => {
|
||||
let state = this.durableStates.get(userId);
|
||||
|
||||
if (!state) {
|
||||
const encryptor = this.createEncryptor();
|
||||
state = SecretState.from(userId, this.key, this.stateProvider, encryptor);
|
||||
this.durableStates.set(userId, state);
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
private createEncryptor() {
|
||||
// always exclude request properties
|
||||
const classifier = SecretClassifier.allSecret<Options>().exclude("website");
|
||||
|
||||
// construct the encryptor
|
||||
const packer = new PaddedDataPacker(OPTIONS_FRAME_SIZE);
|
||||
return new UserKeyEncryptor(this.encryptService, this.keyService, classifier, packer);
|
||||
}
|
||||
|
||||
/** Determine where forwarder configuration is stored */
|
||||
protected abstract readonly key: KeyDefinition<Options>;
|
||||
|
||||
/** {@link GeneratorStrategy.evaluator} */
|
||||
evaluator = (_policy: Policy) => {
|
||||
return new DefaultPolicyEvaluator<Options>();
|
||||
};
|
||||
}
|
@ -2,22 +2,30 @@
|
||||
* include Request in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
import { ADDY_IO_FORWARDER } from "../../key-definitions";
|
||||
import { Forwarders } from "../options/constants";
|
||||
|
||||
import { AddyIoForwarder } from "./addy-io";
|
||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||
|
||||
describe("Addy.io Forwarder", () => {
|
||||
it("key returns the Addy IO forwarder key", () => {
|
||||
const forwarder = new AddyIoForwarder(null, null, null, null, null);
|
||||
|
||||
expect(forwarder.key).toBe(ADDY_IO_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 AddyIoForwarder(apiService, i18nService);
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token,
|
||||
domain: "example.com",
|
||||
baseUrl: "https://api.example.com",
|
||||
@ -34,11 +42,12 @@ describe("Addy.io Forwarder", () => {
|
||||
const apiService = mockApiService(200, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain,
|
||||
baseUrl: "https://api.example.com",
|
||||
@ -56,11 +65,12 @@ describe("Addy.io Forwarder", () => {
|
||||
const apiService = mockApiService(200, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
baseUrl,
|
||||
@ -83,9 +93,10 @@ describe("Addy.io Forwarder", () => {
|
||||
const apiService = mockApiService(200, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await forwarder.generate(website, {
|
||||
await forwarder.generate({
|
||||
website,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
baseUrl: "https://api.example.com",
|
||||
@ -107,9 +118,10 @@ describe("Addy.io Forwarder", () => {
|
||||
const apiService = mockApiService(status, { data: { email } });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
const result = await forwarder.generate(null, {
|
||||
const result = await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
baseUrl: "https://api.example.com",
|
||||
@ -124,11 +136,12 @@ describe("Addy.io Forwarder", () => {
|
||||
const apiService = mockApiService(401, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
baseUrl: "https://api.example.com",
|
||||
@ -148,11 +161,12 @@ describe("Addy.io Forwarder", () => {
|
||||
const apiService = mockApiService(500, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
baseUrl: "https://api.example.com",
|
||||
@ -181,11 +195,12 @@ describe("Addy.io Forwarder", () => {
|
||||
const apiService = mockApiService(statusCode, {}, statusText);
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
||||
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
baseUrl: "https://api.example.com",
|
||||
|
@ -1,24 +1,41 @@
|
||||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "../../../../platform/state";
|
||||
import { ADDY_IO_FORWARDER } from "../../key-definitions";
|
||||
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
|
||||
import { Forwarders } from "../options/constants";
|
||||
import { EmailDomainOptions, Forwarder, SelfHostedApiOptions } from "../options/forwarder-options";
|
||||
import { EmailDomainOptions, SelfHostedApiOptions } from "../options/forwarder-options";
|
||||
|
||||
/** Generates a forwarding address for addy.io (formerly anon addy) */
|
||||
export class AddyIoForwarder implements Forwarder {
|
||||
export class AddyIoForwarder extends ForwarderGeneratorStrategy<
|
||||
SelfHostedApiOptions & EmailDomainOptions
|
||||
> {
|
||||
/** Instantiates the forwarder
|
||||
* @param apiService used for ajax requests to the forwarding service
|
||||
* @param i18nService used to look up error strings
|
||||
* @param encryptService protects sensitive forwarder options
|
||||
* @param keyService looks up the user key when protecting data.
|
||||
* @param stateProvider creates the durable state for options storage
|
||||
*/
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
) {}
|
||||
encryptService: EncryptService,
|
||||
keyService: CryptoService,
|
||||
stateProvider: StateProvider,
|
||||
) {
|
||||
super(encryptService, keyService, stateProvider);
|
||||
}
|
||||
|
||||
/** {@link Forwarder.generate} */
|
||||
async generate(
|
||||
website: string | null,
|
||||
options: SelfHostedApiOptions & EmailDomainOptions,
|
||||
): Promise<string> {
|
||||
/** {@link ForwarderGeneratorStrategy.key} */
|
||||
get key() {
|
||||
return ADDY_IO_FORWARDER;
|
||||
}
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||
generate = async (options: SelfHostedApiOptions & EmailDomainOptions) => {
|
||||
if (!options.token || options.token === "") {
|
||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.AddyIo.name);
|
||||
throw error;
|
||||
@ -32,9 +49,11 @@ export class AddyIoForwarder implements Forwarder {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const descriptionId =
|
||||
website && website !== "" ? "forwarderGeneratedByWithWebsite" : "forwarderGeneratedBy";
|
||||
const description = this.i18nService.t(descriptionId, website ?? "");
|
||||
let descriptionId = "forwarderGeneratedByWithWebsite";
|
||||
if (!options.website || options.website === "") {
|
||||
descriptionId = "forwarderGeneratedBy";
|
||||
}
|
||||
const description = this.i18nService.t(descriptionId, options.website ?? "");
|
||||
|
||||
const url = options.baseUrl + "/api/v1/aliases";
|
||||
const request = new Request(url, {
|
||||
@ -70,5 +89,5 @@ export class AddyIoForwarder implements Forwarder {
|
||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.AddyIo.name);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -2,22 +2,30 @@
|
||||
* include Request in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
import { DUCK_DUCK_GO_FORWARDER } from "../../key-definitions";
|
||||
import { Forwarders } from "../options/constants";
|
||||
|
||||
import { DuckDuckGoForwarder } from "./duck-duck-go";
|
||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||
|
||||
describe("DuckDuckGo Forwarder", () => {
|
||||
it("key returns the Duck Duck Go forwarder key", () => {
|
||||
const forwarder = new DuckDuckGoForwarder(null, null, null, null, null);
|
||||
|
||||
expect(forwarder.key).toBe(DUCK_DUCK_GO_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);
|
||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token,
|
||||
}),
|
||||
).rejects.toEqual("forwaderInvalidToken");
|
||||
@ -40,9 +48,10 @@ describe("DuckDuckGo Forwarder", () => {
|
||||
const apiService = mockApiService(status, { address });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
const result = await forwarder.generate(null, {
|
||||
const result = await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
});
|
||||
|
||||
@ -55,11 +64,12 @@ describe("DuckDuckGo Forwarder", () => {
|
||||
const apiService = mockApiService(401, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
}),
|
||||
).rejects.toEqual("forwaderInvalidToken");
|
||||
@ -76,11 +86,12 @@ describe("DuckDuckGo Forwarder", () => {
|
||||
const apiService = mockApiService(200, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
}),
|
||||
).rejects.toEqual("forwarderUnknownError");
|
||||
@ -99,11 +110,12 @@ describe("DuckDuckGo Forwarder", () => {
|
||||
const apiService = mockApiService(statusCode, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
}),
|
||||
).rejects.toEqual("forwarderUnknownError");
|
||||
|
@ -1,21 +1,39 @@
|
||||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "../../../../platform/state";
|
||||
import { DUCK_DUCK_GO_FORWARDER } from "../../key-definitions";
|
||||
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
|
||||
import { Forwarders } from "../options/constants";
|
||||
import { ApiOptions, Forwarder } from "../options/forwarder-options";
|
||||
import { ApiOptions } from "../options/forwarder-options";
|
||||
|
||||
/** Generates a forwarding address for DuckDuckGo */
|
||||
export class DuckDuckGoForwarder implements Forwarder {
|
||||
export class DuckDuckGoForwarder extends ForwarderGeneratorStrategy<ApiOptions> {
|
||||
/** Instantiates the forwarder
|
||||
* @param apiService used for ajax requests to the forwarding service
|
||||
* @param i18nService used to look up error strings
|
||||
* @param encryptService protects sensitive forwarder options
|
||||
* @param keyService looks up the user key when protecting data.
|
||||
* @param stateProvider creates the durable state for options storage
|
||||
*/
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
) {}
|
||||
encryptService: EncryptService,
|
||||
keyService: CryptoService,
|
||||
stateProvider: StateProvider,
|
||||
) {
|
||||
super(encryptService, keyService, stateProvider);
|
||||
}
|
||||
|
||||
/** {@link Forwarder.generate} */
|
||||
async generate(_website: string | null, options: ApiOptions): Promise<string> {
|
||||
/** {@link ForwarderGeneratorStrategy.key} */
|
||||
get key() {
|
||||
return DUCK_DUCK_GO_FORWARDER;
|
||||
}
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||
generate = async (options: ApiOptions): Promise<string> => {
|
||||
if (!options.token || options.token === "") {
|
||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.DuckDuckGo.name);
|
||||
throw error;
|
||||
@ -48,5 +66,5 @@ export class DuckDuckGoForwarder implements Forwarder {
|
||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.DuckDuckGo.name);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { FASTMAIL_FORWARDER } from "../../key-definitions";
|
||||
import { Forwarders } from "../options/constants";
|
||||
|
||||
import { FastmailForwarder } from "./fastmail";
|
||||
@ -45,16 +46,23 @@ const AccountIdSuccess: MockResponse = Object.freeze({
|
||||
|
||||
// the tests
|
||||
describe("Fastmail Forwarder", () => {
|
||||
it("key returns the Fastmail forwarder key", () => {
|
||||
const forwarder = new FastmailForwarder(null, null, null, null, null);
|
||||
|
||||
expect(forwarder.key).toBe(FASTMAIL_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(AccountIdSuccess, EmptyResponse);
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token,
|
||||
domain: "example.com",
|
||||
prefix: "prefix",
|
||||
@ -71,11 +79,12 @@ describe("Fastmail Forwarder", () => {
|
||||
const apiService = mockApiService({ status, body: {} }, EmptyResponse);
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
prefix: "prefix",
|
||||
@ -105,9 +114,10 @@ describe("Fastmail Forwarder", () => {
|
||||
});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
const result = await forwarder.generate(null, {
|
||||
const result = await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
prefix: "prefix",
|
||||
@ -138,11 +148,12 @@ describe("Fastmail Forwarder", () => {
|
||||
});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
prefix: "prefix",
|
||||
@ -165,11 +176,12 @@ describe("Fastmail Forwarder", () => {
|
||||
const apiService = mockApiService(AccountIdSuccess, { status, body: {} });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
prefix: "prefix",
|
||||
@ -206,11 +218,12 @@ describe("Fastmail Forwarder", () => {
|
||||
});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
prefix: "prefix",
|
||||
@ -232,11 +245,12 @@ describe("Fastmail Forwarder", () => {
|
||||
const apiService = mockApiService(AccountIdSuccess, { status: statusCode, body: {} });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
||||
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
prefix: "prefix",
|
||||
|
@ -1,24 +1,39 @@
|
||||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "../../../../platform/state";
|
||||
import { FASTMAIL_FORWARDER } from "../../key-definitions";
|
||||
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
|
||||
import { Forwarders } from "../options/constants";
|
||||
import { EmailPrefixOptions, Forwarder, ApiOptions } from "../options/forwarder-options";
|
||||
import { EmailPrefixOptions, ApiOptions } from "../options/forwarder-options";
|
||||
|
||||
/** Generates a forwarding address for Fastmail */
|
||||
export class FastmailForwarder implements Forwarder {
|
||||
export class FastmailForwarder extends ForwarderGeneratorStrategy<ApiOptions & EmailPrefixOptions> {
|
||||
/** Instantiates the forwarder
|
||||
* @param apiService used for ajax requests to the forwarding service
|
||||
* @param i18nService used to look up error strings
|
||||
* @param encryptService protects sensitive forwarder options
|
||||
* @param keyService looks up the user key when protecting data.
|
||||
* @param stateProvider creates the durable state for options storage
|
||||
*/
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
) {}
|
||||
encryptService: EncryptService,
|
||||
keyService: CryptoService,
|
||||
stateProvider: StateProvider,
|
||||
) {
|
||||
super(encryptService, keyService, stateProvider);
|
||||
}
|
||||
|
||||
/** {@link Forwarder.generate} */
|
||||
async generate(
|
||||
website: string | null,
|
||||
options: ApiOptions & EmailPrefixOptions,
|
||||
): Promise<string> {
|
||||
/** {@link ForwarderGeneratorStrategy.key} */
|
||||
get key() {
|
||||
return FASTMAIL_FORWARDER;
|
||||
}
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||
generate = async (options: ApiOptions & EmailPrefixOptions) => {
|
||||
if (!options.token || options.token === "") {
|
||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.Fastmail.name);
|
||||
throw error;
|
||||
@ -41,7 +56,7 @@ export class FastmailForwarder implements Forwarder {
|
||||
"new-masked-email": {
|
||||
state: "enabled",
|
||||
description: "",
|
||||
forDomain: website,
|
||||
forDomain: options.website,
|
||||
emailPrefix: options.prefix,
|
||||
},
|
||||
},
|
||||
@ -104,7 +119,7 @@ export class FastmailForwarder implements Forwarder {
|
||||
|
||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.Fastmail.name);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
private async getAccountId(options: ApiOptions): Promise<string> {
|
||||
const requestInit: RequestInit = {
|
||||
|
@ -2,22 +2,30 @@
|
||||
* include Request in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
import { FIREFOX_RELAY_FORWARDER } from "../../key-definitions";
|
||||
import { Forwarders } from "../options/constants";
|
||||
|
||||
import { FirefoxRelayForwarder } from "./firefox-relay";
|
||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||
|
||||
describe("Firefox Relay Forwarder", () => {
|
||||
it("key returns the Firefox Relay forwarder key", () => {
|
||||
const forwarder = new FirefoxRelayForwarder(null, null, null, null, null);
|
||||
|
||||
expect(forwarder.key).toBe(FIREFOX_RELAY_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 FirefoxRelayForwarder(apiService, i18nService);
|
||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token,
|
||||
}),
|
||||
).rejects.toEqual("forwaderInvalidToken");
|
||||
@ -40,9 +48,10 @@ describe("Firefox Relay Forwarder", () => {
|
||||
const apiService = mockApiService(200, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
|
||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await forwarder.generate(website, {
|
||||
await forwarder.generate({
|
||||
website,
|
||||
token: "token",
|
||||
});
|
||||
|
||||
@ -62,9 +71,10 @@ describe("Firefox Relay Forwarder", () => {
|
||||
const apiService = mockApiService(status, { full_address });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
|
||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
const result = await forwarder.generate(null, {
|
||||
const result = await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
});
|
||||
|
||||
@ -77,11 +87,12 @@ describe("Firefox Relay Forwarder", () => {
|
||||
const apiService = mockApiService(401, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
|
||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
}),
|
||||
).rejects.toEqual("forwaderInvalidToken");
|
||||
@ -101,11 +112,12 @@ describe("Firefox Relay Forwarder", () => {
|
||||
const apiService = mockApiService(statusCode, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
|
||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
}),
|
||||
).rejects.toEqual("forwarderUnknownError");
|
||||
|
@ -1,21 +1,39 @@
|
||||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "../../../../platform/state";
|
||||
import { FIREFOX_RELAY_FORWARDER } from "../../key-definitions";
|
||||
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
|
||||
import { Forwarders } from "../options/constants";
|
||||
import { Forwarder, ApiOptions } from "../options/forwarder-options";
|
||||
import { ApiOptions } from "../options/forwarder-options";
|
||||
|
||||
/** Generates a forwarding address for Firefox Relay */
|
||||
export class FirefoxRelayForwarder implements Forwarder {
|
||||
export class FirefoxRelayForwarder extends ForwarderGeneratorStrategy<ApiOptions> {
|
||||
/** Instantiates the forwarder
|
||||
* @param apiService used for ajax requests to the forwarding service
|
||||
* @param i18nService used to look up error strings
|
||||
* @param encryptService protects sensitive forwarder options
|
||||
* @param keyService looks up the user key when protecting data.
|
||||
* @param stateProvider creates the durable state for options storage
|
||||
*/
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
) {}
|
||||
encryptService: EncryptService,
|
||||
keyService: CryptoService,
|
||||
stateProvider: StateProvider,
|
||||
) {
|
||||
super(encryptService, keyService, stateProvider);
|
||||
}
|
||||
|
||||
/** {@link Forwarder.generate} */
|
||||
async generate(website: string | null, options: ApiOptions): Promise<string> {
|
||||
/** {@link ForwarderGeneratorStrategy.key} */
|
||||
get key() {
|
||||
return FIREFOX_RELAY_FORWARDER;
|
||||
}
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||
generate = async (options: ApiOptions) => {
|
||||
if (!options.token || options.token === "") {
|
||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.FirefoxRelay.name);
|
||||
throw error;
|
||||
@ -23,9 +41,11 @@ export class FirefoxRelayForwarder implements Forwarder {
|
||||
|
||||
const url = "https://relay.firefox.com/api/v1/relayaddresses/";
|
||||
|
||||
const descriptionId =
|
||||
website && website !== "" ? "forwarderGeneratedByWithWebsite" : "forwarderGeneratedBy";
|
||||
const description = this.i18nService.t(descriptionId, website ?? "");
|
||||
let descriptionId = "forwarderGeneratedByWithWebsite";
|
||||
if (!options.website || options.website === "") {
|
||||
descriptionId = "forwarderGeneratedBy";
|
||||
}
|
||||
const description = this.i18nService.t(descriptionId, options.website ?? "");
|
||||
|
||||
const request = new Request(url, {
|
||||
redirect: "manual",
|
||||
@ -37,7 +57,7 @@ export class FirefoxRelayForwarder implements Forwarder {
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
enabled: true,
|
||||
generated_for: website,
|
||||
generated_for: options.website,
|
||||
description,
|
||||
}),
|
||||
});
|
||||
@ -53,5 +73,5 @@ export class FirefoxRelayForwarder implements Forwarder {
|
||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.FirefoxRelay.name);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -2,22 +2,30 @@
|
||||
* include Request in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
import { FORWARD_EMAIL_FORWARDER } from "../../key-definitions";
|
||||
import { Forwarders } from "../options/constants";
|
||||
|
||||
import { ForwardEmailForwarder } from "./forward-email";
|
||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||
|
||||
describe("ForwardEmail Forwarder", () => {
|
||||
it("key returns the Forward Email forwarder key", () => {
|
||||
const forwarder = new ForwardEmailForwarder(null, null, null, null, null);
|
||||
|
||||
expect(forwarder.key).toBe(FORWARD_EMAIL_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 ForwardEmailForwarder(apiService, i18nService);
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token,
|
||||
domain: "example.com",
|
||||
}),
|
||||
@ -36,11 +44,12 @@ describe("ForwardEmail Forwarder", () => {
|
||||
const apiService = mockApiService(200, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain,
|
||||
}),
|
||||
@ -65,9 +74,10 @@ describe("ForwardEmail Forwarder", () => {
|
||||
const apiService = mockApiService(200, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await forwarder.generate(website, {
|
||||
await forwarder.generate({
|
||||
website,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
});
|
||||
@ -92,9 +102,10 @@ describe("ForwardEmail Forwarder", () => {
|
||||
const apiService = mockApiService(status, response);
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
const result = await forwarder.generate(null, {
|
||||
const result = await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
});
|
||||
@ -108,11 +119,12 @@ describe("ForwardEmail Forwarder", () => {
|
||||
const apiService = mockApiService(401, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
}),
|
||||
@ -132,11 +144,12 @@ describe("ForwardEmail Forwarder", () => {
|
||||
const apiService = mockApiService(401, { message: "A message" });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
}),
|
||||
@ -158,11 +171,12 @@ describe("ForwardEmail Forwarder", () => {
|
||||
const apiService = mockApiService(500, json);
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
}),
|
||||
@ -191,11 +205,12 @@ describe("ForwardEmail Forwarder", () => {
|
||||
const apiService = mockApiService(statusCode, { message });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
}),
|
||||
@ -225,11 +240,12 @@ describe("ForwardEmail Forwarder", () => {
|
||||
const apiService = mockApiService(statusCode, { error });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
domain: "example.com",
|
||||
}),
|
||||
|
@ -1,25 +1,42 @@
|
||||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||
import { Utils } from "../../../../platform/misc/utils";
|
||||
import { StateProvider } from "../../../../platform/state";
|
||||
import { FORWARD_EMAIL_FORWARDER } from "../../key-definitions";
|
||||
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
|
||||
import { Forwarders } from "../options/constants";
|
||||
import { EmailDomainOptions, Forwarder, ApiOptions } from "../options/forwarder-options";
|
||||
import { EmailDomainOptions, ApiOptions } from "../options/forwarder-options";
|
||||
|
||||
/** Generates a forwarding address for Forward Email */
|
||||
export class ForwardEmailForwarder implements Forwarder {
|
||||
export class ForwardEmailForwarder extends ForwarderGeneratorStrategy<
|
||||
ApiOptions & EmailDomainOptions
|
||||
> {
|
||||
/** Instantiates the forwarder
|
||||
* @param apiService used for ajax requests to the forwarding service
|
||||
* @param i18nService used to look up error strings
|
||||
* @param encryptService protects sensitive forwarder options
|
||||
* @param keyService looks up the user key when protecting data.
|
||||
* @param stateProvider creates the durable state for options storage
|
||||
*/
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
) {}
|
||||
encryptService: EncryptService,
|
||||
keyService: CryptoService,
|
||||
stateProvider: StateProvider,
|
||||
) {
|
||||
super(encryptService, keyService, stateProvider);
|
||||
}
|
||||
|
||||
/** {@link Forwarder.generate} */
|
||||
async generate(
|
||||
website: string | null,
|
||||
options: ApiOptions & EmailDomainOptions,
|
||||
): Promise<string> {
|
||||
/** {@link ForwarderGeneratorStrategy.key} */
|
||||
get key() {
|
||||
return FORWARD_EMAIL_FORWARDER;
|
||||
}
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||
generate = async (options: ApiOptions & EmailDomainOptions) => {
|
||||
if (!options.token || options.token === "") {
|
||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.ForwardEmail.name);
|
||||
throw error;
|
||||
@ -31,9 +48,11 @@ export class ForwardEmailForwarder implements Forwarder {
|
||||
|
||||
const url = `https://api.forwardemail.net/v1/domains/${options.domain}/aliases`;
|
||||
|
||||
const descriptionId =
|
||||
website && website !== "" ? "forwarderGeneratedByWithWebsite" : "forwarderGeneratedBy";
|
||||
const description = this.i18nService.t(descriptionId, website ?? "");
|
||||
let descriptionId = "forwarderGeneratedByWithWebsite";
|
||||
if (!options.website || options.website === "") {
|
||||
descriptionId = "forwarderGeneratedBy";
|
||||
}
|
||||
const description = this.i18nService.t(descriptionId, options.website ?? "");
|
||||
|
||||
const request = new Request(url, {
|
||||
redirect: "manual",
|
||||
@ -44,7 +63,7 @@ export class ForwardEmailForwarder implements Forwarder {
|
||||
"Content-Type": "application/json",
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
labels: website,
|
||||
labels: options.website,
|
||||
description,
|
||||
}),
|
||||
});
|
||||
@ -75,5 +94,5 @@ export class ForwardEmailForwarder implements Forwarder {
|
||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.ForwardEmail.name);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -2,22 +2,30 @@
|
||||
* include Request in test environment.
|
||||
* @jest-environment ../../../../shared/test.environment.ts
|
||||
*/
|
||||
import { SIMPLE_LOGIN_FORWARDER } from "../../key-definitions";
|
||||
import { Forwarders } from "../options/constants";
|
||||
|
||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||
import { SimpleLoginForwarder } from "./simple-login";
|
||||
|
||||
describe("SimpleLogin Forwarder", () => {
|
||||
it("key returns the Simple Login forwarder key", () => {
|
||||
const forwarder = new SimpleLoginForwarder(null, null, null, null, null);
|
||||
|
||||
expect(forwarder.key).toBe(SIMPLE_LOGIN_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);
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token,
|
||||
baseUrl: "https://api.example.com",
|
||||
}),
|
||||
@ -36,11 +44,12 @@ describe("SimpleLogin Forwarder", () => {
|
||||
const apiService = mockApiService(200, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
baseUrl,
|
||||
}),
|
||||
@ -62,9 +71,10 @@ describe("SimpleLogin Forwarder", () => {
|
||||
const apiService = mockApiService(200, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await forwarder.generate(website, {
|
||||
await forwarder.generate({
|
||||
website,
|
||||
token: "token",
|
||||
baseUrl: "https://api.example.com",
|
||||
});
|
||||
@ -85,9 +95,10 @@ describe("SimpleLogin Forwarder", () => {
|
||||
const apiService = mockApiService(status, { alias });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
const result = await forwarder.generate(null, {
|
||||
const result = await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
baseUrl: "https://api.example.com",
|
||||
});
|
||||
@ -101,11 +112,12 @@ describe("SimpleLogin Forwarder", () => {
|
||||
const apiService = mockApiService(401, {});
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
baseUrl: "https://api.example.com",
|
||||
}),
|
||||
@ -126,11 +138,12 @@ describe("SimpleLogin Forwarder", () => {
|
||||
const apiService = mockApiService(500, body);
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
baseUrl: "https://api.example.com",
|
||||
}),
|
||||
@ -159,11 +172,12 @@ describe("SimpleLogin Forwarder", () => {
|
||||
const apiService = mockApiService(statusCode, { error });
|
||||
const i18nService = mockI18nService();
|
||||
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await forwarder.generate(null, {
|
||||
await forwarder.generate({
|
||||
website: null,
|
||||
token: "token",
|
||||
baseUrl: "https://api.example.com",
|
||||
}),
|
||||
|
@ -1,21 +1,39 @@
|
||||
import { ApiService } from "../../../../abstractions/api.service";
|
||||
import { CryptoService } from "../../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "../../../../platform/state";
|
||||
import { SIMPLE_LOGIN_FORWARDER } from "../../key-definitions";
|
||||
import { ForwarderGeneratorStrategy } from "../forwarder-generator-strategy";
|
||||
import { Forwarders } from "../options/constants";
|
||||
import { Forwarder, SelfHostedApiOptions } from "../options/forwarder-options";
|
||||
import { SelfHostedApiOptions } from "../options/forwarder-options";
|
||||
|
||||
/** Generates a forwarding address for Simple Login */
|
||||
export class SimpleLoginForwarder implements Forwarder {
|
||||
export class SimpleLoginForwarder extends ForwarderGeneratorStrategy<SelfHostedApiOptions> {
|
||||
/** Instantiates the forwarder
|
||||
* @param apiService used for ajax requests to the forwarding service
|
||||
* @param i18nService used to look up error strings
|
||||
* @param encryptService protects sensitive forwarder options
|
||||
* @param keyService looks up the user key when protecting data.
|
||||
* @param stateProvider creates the durable state for options storage
|
||||
*/
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
) {}
|
||||
encryptService: EncryptService,
|
||||
keyService: CryptoService,
|
||||
stateProvider: StateProvider,
|
||||
) {
|
||||
super(encryptService, keyService, stateProvider);
|
||||
}
|
||||
|
||||
/** {@link Forwarder.generate} */
|
||||
async generate(website: string, options: SelfHostedApiOptions): Promise<string> {
|
||||
/** {@link ForwarderGeneratorStrategy.key} */
|
||||
get key() {
|
||||
return SIMPLE_LOGIN_FORWARDER;
|
||||
}
|
||||
|
||||
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||
generate = async (options: SelfHostedApiOptions) => {
|
||||
if (!options.token || options.token === "") {
|
||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.SimpleLogin.name);
|
||||
throw error;
|
||||
@ -27,11 +45,11 @@ export class SimpleLoginForwarder implements Forwarder {
|
||||
|
||||
let url = options.baseUrl + "/api/alias/random/new";
|
||||
let noteId = "forwarderGeneratedBy";
|
||||
if (website && website !== "") {
|
||||
url += "?hostname=" + website;
|
||||
if (options.website && options.website !== "") {
|
||||
url += "?hostname=" + options.website;
|
||||
noteId = "forwarderGeneratedByWithWebsite";
|
||||
}
|
||||
const note = this.i18nService.t(noteId, website ?? "");
|
||||
const note = this.i18nService.t(noteId, options.website ?? "");
|
||||
|
||||
const request = new Request(url, {
|
||||
redirect: "manual",
|
||||
@ -60,5 +78,5 @@ export class SimpleLoginForwarder implements Forwarder {
|
||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.SimpleLogin.name);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -85,27 +85,33 @@ export const DefaultOptions: UsernameGeneratorOptions = Object.freeze({
|
||||
forwarders: Object.freeze({
|
||||
service: Forwarders.Fastmail.id,
|
||||
fastMail: Object.freeze({
|
||||
website: null,
|
||||
domain: "",
|
||||
prefix: "",
|
||||
token: "",
|
||||
}),
|
||||
addyIo: Object.freeze({
|
||||
website: null,
|
||||
baseUrl: "https://app.addy.io",
|
||||
domain: "",
|
||||
token: "",
|
||||
}),
|
||||
forwardEmail: Object.freeze({
|
||||
website: null,
|
||||
token: "",
|
||||
domain: "",
|
||||
}),
|
||||
simpleLogin: Object.freeze({
|
||||
website: null,
|
||||
baseUrl: "https://app.simplelogin.io",
|
||||
token: "",
|
||||
}),
|
||||
duckDuckGo: Object.freeze({
|
||||
website: null,
|
||||
token: "",
|
||||
}),
|
||||
firefoxRelay: Object.freeze({
|
||||
website: null,
|
||||
token: "",
|
||||
}),
|
||||
}),
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { EncString } from "../../../../platform/models/domain/enc-string";
|
||||
|
||||
/** Identifiers for email forwarding services.
|
||||
* @remarks These are used to select forwarder-specific options.
|
||||
* The must be kept in sync with the forwarder implementations.
|
||||
@ -24,26 +22,24 @@ export type ForwarderMetadata = {
|
||||
validForSelfHosted: boolean;
|
||||
};
|
||||
|
||||
/** An email forwarding service configurable through an API. */
|
||||
export interface Forwarder {
|
||||
/** Generate a forwarding email.
|
||||
* @param website The website to generate a username for.
|
||||
* @param options The options to use when generating the username.
|
||||
*/
|
||||
generate(website: string | null, options: ApiOptions): Promise<string>;
|
||||
}
|
||||
|
||||
/** Options common to all forwarder APIs */
|
||||
export type ApiOptions = {
|
||||
/** bearer token that authenticates bitwarden to the forwarder.
|
||||
* This is required to issue an API request.
|
||||
*/
|
||||
token?: string;
|
||||
} & RequestOptions;
|
||||
|
||||
/** encrypted bearer token that authenticates bitwarden to the forwarder.
|
||||
* This is used to store the token at rest and must be decoded before use.
|
||||
/** Options that provide contextual information about the application state
|
||||
* when a forwarder is invoked.
|
||||
* @remarks these fields should always be omitted when saving options.
|
||||
*/
|
||||
export type RequestOptions = {
|
||||
/** @param website The domain of the website the generated email is used
|
||||
* within. This should be set to `null` when the request is not specific
|
||||
* to any website.
|
||||
*/
|
||||
encryptedToken?: EncString;
|
||||
website: string | null;
|
||||
};
|
||||
|
||||
/** Api configuration for forwarders that support self-hosted installations. */
|
||||
|
@ -24,27 +24,33 @@ const TestOptions: UsernameGeneratorOptions = {
|
||||
forwarders: {
|
||||
service: Forwarders.Fastmail.id,
|
||||
fastMail: {
|
||||
website: null,
|
||||
domain: "httpbin.com",
|
||||
prefix: "foo",
|
||||
token: "some-token",
|
||||
},
|
||||
addyIo: {
|
||||
website: null,
|
||||
baseUrl: "https://app.addy.io",
|
||||
domain: "example.com",
|
||||
token: "some-token",
|
||||
},
|
||||
forwardEmail: {
|
||||
website: null,
|
||||
token: "some-token",
|
||||
domain: "example.com",
|
||||
},
|
||||
simpleLogin: {
|
||||
website: null,
|
||||
baseUrl: "https://app.simplelogin.io",
|
||||
token: "some-token",
|
||||
},
|
||||
duckDuckGo: {
|
||||
website: null,
|
||||
token: "some-token",
|
||||
},
|
||||
firefoxRelay: {
|
||||
website: null,
|
||||
token: "some-token",
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user