mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +00:00
[PM-5974] introduce ForwarderGeneratorStrategy (#8207)
* update defaults to include `website` parameter * update utilities tests to include `website` parameter
This commit is contained in:
@@ -5,6 +5,12 @@ import {
|
|||||||
SUBADDRESS_SETTINGS,
|
SUBADDRESS_SETTINGS,
|
||||||
PASSPHRASE_SETTINGS,
|
PASSPHRASE_SETTINGS,
|
||||||
PASSWORD_SETTINGS,
|
PASSWORD_SETTINGS,
|
||||||
|
SIMPLE_LOGIN_FORWARDER,
|
||||||
|
FORWARD_EMAIL_FORWARDER,
|
||||||
|
FIREFOX_RELAY_FORWARDER,
|
||||||
|
FASTMAIL_FORWARDER,
|
||||||
|
DUCK_DUCK_GO_FORWARDER,
|
||||||
|
ADDY_IO_FORWARDER,
|
||||||
} from "./key-definitions";
|
} from "./key-definitions";
|
||||||
|
|
||||||
describe("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", () => {
|
describe("ENCRYPTED_HISTORY", () => {
|
||||||
it("should pass through deserialization", () => {
|
it("should pass through deserialization", () => {
|
||||||
const value = {};
|
const value = {};
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ import { GeneratedPasswordHistory } from "./password/generated-password-history"
|
|||||||
import { PasswordGenerationOptions } from "./password/password-generation-options";
|
import { PasswordGenerationOptions } from "./password/password-generation-options";
|
||||||
import { CatchallGenerationOptions } from "./username/catchall-generator-options";
|
import { CatchallGenerationOptions } from "./username/catchall-generator-options";
|
||||||
import { EffUsernameGenerationOptions } from "./username/eff-username-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";
|
import { SubaddressGenerationOptions } from "./username/subaddress-generator-options";
|
||||||
|
|
||||||
/** plaintext password generation 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 */
|
/** encrypted password generation history */
|
||||||
export const ENCRYPTED_HISTORY = new KeyDefinition<GeneratedPasswordHistory>(
|
export const ENCRYPTED_HISTORY = new KeyDefinition<GeneratedPasswordHistory>(
|
||||||
GENERATOR_DISK,
|
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.
|
* include Request in test environment.
|
||||||
* @jest-environment ../../../../shared/test.environment.ts
|
* @jest-environment ../../../../shared/test.environment.ts
|
||||||
*/
|
*/
|
||||||
|
import { ADDY_IO_FORWARDER } from "../../key-definitions";
|
||||||
import { Forwarders } from "../options/constants";
|
import { Forwarders } from "../options/constants";
|
||||||
|
|
||||||
import { AddyIoForwarder } from "./addy-io";
|
import { AddyIoForwarder } from "./addy-io";
|
||||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||||
|
|
||||||
describe("Addy.io Forwarder", () => {
|
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)", () => {
|
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
|
||||||
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token,
|
token,
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
@@ -34,11 +42,12 @@ describe("Addy.io Forwarder", () => {
|
|||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain,
|
domain,
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
@@ -56,11 +65,12 @@ describe("Addy.io Forwarder", () => {
|
|||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
baseUrl,
|
baseUrl,
|
||||||
@@ -83,9 +93,10 @@ describe("Addy.io Forwarder", () => {
|
|||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
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",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
@@ -107,9 +118,10 @@ describe("Addy.io Forwarder", () => {
|
|||||||
const apiService = mockApiService(status, { data: { email } });
|
const apiService = mockApiService(status, { data: { email } });
|
||||||
const i18nService = mockI18nService();
|
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",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
@@ -124,11 +136,12 @@ describe("Addy.io Forwarder", () => {
|
|||||||
const apiService = mockApiService(401, {});
|
const apiService = mockApiService(401, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
@@ -148,11 +161,12 @@ describe("Addy.io Forwarder", () => {
|
|||||||
const apiService = mockApiService(500, {});
|
const apiService = mockApiService(500, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
@@ -181,11 +195,12 @@ describe("Addy.io Forwarder", () => {
|
|||||||
const apiService = mockApiService(statusCode, {}, statusText);
|
const apiService = mockApiService(statusCode, {}, statusText);
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new AddyIoForwarder(apiService, i18nService);
|
const forwarder = new AddyIoForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
|
|||||||
@@ -1,24 +1,41 @@
|
|||||||
import { ApiService } from "../../../../abstractions/api.service";
|
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 { 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 { 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) */
|
/** 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
|
/** Instantiates the forwarder
|
||||||
* @param apiService used for ajax requests to the forwarding service
|
* @param apiService used for ajax requests to the forwarding service
|
||||||
* @param i18nService used to look up error strings
|
* @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(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
) {}
|
encryptService: EncryptService,
|
||||||
|
keyService: CryptoService,
|
||||||
|
stateProvider: StateProvider,
|
||||||
|
) {
|
||||||
|
super(encryptService, keyService, stateProvider);
|
||||||
|
}
|
||||||
|
|
||||||
/** {@link Forwarder.generate} */
|
/** {@link ForwarderGeneratorStrategy.key} */
|
||||||
async generate(
|
get key() {
|
||||||
website: string | null,
|
return ADDY_IO_FORWARDER;
|
||||||
options: SelfHostedApiOptions & EmailDomainOptions,
|
}
|
||||||
): Promise<string> {
|
|
||||||
|
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||||
|
generate = async (options: SelfHostedApiOptions & EmailDomainOptions) => {
|
||||||
if (!options.token || options.token === "") {
|
if (!options.token || options.token === "") {
|
||||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.AddyIo.name);
|
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.AddyIo.name);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -32,9 +49,11 @@ export class AddyIoForwarder implements Forwarder {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const descriptionId =
|
let descriptionId = "forwarderGeneratedByWithWebsite";
|
||||||
website && website !== "" ? "forwarderGeneratedByWithWebsite" : "forwarderGeneratedBy";
|
if (!options.website || options.website === "") {
|
||||||
const description = this.i18nService.t(descriptionId, website ?? "");
|
descriptionId = "forwarderGeneratedBy";
|
||||||
|
}
|
||||||
|
const description = this.i18nService.t(descriptionId, options.website ?? "");
|
||||||
|
|
||||||
const url = options.baseUrl + "/api/v1/aliases";
|
const url = options.baseUrl + "/api/v1/aliases";
|
||||||
const request = new Request(url, {
|
const request = new Request(url, {
|
||||||
@@ -70,5 +89,5 @@ export class AddyIoForwarder implements Forwarder {
|
|||||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.AddyIo.name);
|
const error = this.i18nService.t("forwarderUnknownError", Forwarders.AddyIo.name);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,30 @@
|
|||||||
* include Request in test environment.
|
* include Request in test environment.
|
||||||
* @jest-environment ../../../../shared/test.environment.ts
|
* @jest-environment ../../../../shared/test.environment.ts
|
||||||
*/
|
*/
|
||||||
|
import { DUCK_DUCK_GO_FORWARDER } from "../../key-definitions";
|
||||||
import { Forwarders } from "../options/constants";
|
import { Forwarders } from "../options/constants";
|
||||||
|
|
||||||
import { DuckDuckGoForwarder } from "./duck-duck-go";
|
import { DuckDuckGoForwarder } from "./duck-duck-go";
|
||||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||||
|
|
||||||
describe("DuckDuckGo Forwarder", () => {
|
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)", () => {
|
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
|
||||||
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token,
|
token,
|
||||||
}),
|
}),
|
||||||
).rejects.toEqual("forwaderInvalidToken");
|
).rejects.toEqual("forwaderInvalidToken");
|
||||||
@@ -40,9 +48,10 @@ describe("DuckDuckGo Forwarder", () => {
|
|||||||
const apiService = mockApiService(status, { address });
|
const apiService = mockApiService(status, { address });
|
||||||
const i18nService = mockI18nService();
|
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",
|
token: "token",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,11 +64,12 @@ describe("DuckDuckGo Forwarder", () => {
|
|||||||
const apiService = mockApiService(401, {});
|
const apiService = mockApiService(401, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
}),
|
}),
|
||||||
).rejects.toEqual("forwaderInvalidToken");
|
).rejects.toEqual("forwaderInvalidToken");
|
||||||
@@ -76,11 +86,12 @@ describe("DuckDuckGo Forwarder", () => {
|
|||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
}),
|
}),
|
||||||
).rejects.toEqual("forwarderUnknownError");
|
).rejects.toEqual("forwarderUnknownError");
|
||||||
@@ -99,11 +110,12 @@ describe("DuckDuckGo Forwarder", () => {
|
|||||||
const apiService = mockApiService(statusCode, {});
|
const apiService = mockApiService(statusCode, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new DuckDuckGoForwarder(apiService, i18nService);
|
const forwarder = new DuckDuckGoForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
}),
|
}),
|
||||||
).rejects.toEqual("forwarderUnknownError");
|
).rejects.toEqual("forwarderUnknownError");
|
||||||
|
|||||||
@@ -1,21 +1,39 @@
|
|||||||
import { ApiService } from "../../../../abstractions/api.service";
|
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 { 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 { Forwarders } from "../options/constants";
|
||||||
import { ApiOptions, Forwarder } from "../options/forwarder-options";
|
import { ApiOptions } from "../options/forwarder-options";
|
||||||
|
|
||||||
/** Generates a forwarding address for DuckDuckGo */
|
/** Generates a forwarding address for DuckDuckGo */
|
||||||
export class DuckDuckGoForwarder implements Forwarder {
|
export class DuckDuckGoForwarder extends ForwarderGeneratorStrategy<ApiOptions> {
|
||||||
/** Instantiates the forwarder
|
/** Instantiates the forwarder
|
||||||
* @param apiService used for ajax requests to the forwarding service
|
* @param apiService used for ajax requests to the forwarding service
|
||||||
* @param i18nService used to look up error strings
|
* @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(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
) {}
|
encryptService: EncryptService,
|
||||||
|
keyService: CryptoService,
|
||||||
|
stateProvider: StateProvider,
|
||||||
|
) {
|
||||||
|
super(encryptService, keyService, stateProvider);
|
||||||
|
}
|
||||||
|
|
||||||
/** {@link Forwarder.generate} */
|
/** {@link ForwarderGeneratorStrategy.key} */
|
||||||
async generate(_website: string | null, options: ApiOptions): Promise<string> {
|
get key() {
|
||||||
|
return DUCK_DUCK_GO_FORWARDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||||
|
generate = async (options: ApiOptions): Promise<string> => {
|
||||||
if (!options.token || options.token === "") {
|
if (!options.token || options.token === "") {
|
||||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.DuckDuckGo.name);
|
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.DuckDuckGo.name);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -48,5 +66,5 @@ export class DuckDuckGoForwarder implements Forwarder {
|
|||||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.DuckDuckGo.name);
|
const error = this.i18nService.t("forwarderUnknownError", Forwarders.DuckDuckGo.name);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* @jest-environment ../../../../shared/test.environment.ts
|
* @jest-environment ../../../../shared/test.environment.ts
|
||||||
*/
|
*/
|
||||||
import { ApiService } from "../../../../abstractions/api.service";
|
import { ApiService } from "../../../../abstractions/api.service";
|
||||||
|
import { FASTMAIL_FORWARDER } from "../../key-definitions";
|
||||||
import { Forwarders } from "../options/constants";
|
import { Forwarders } from "../options/constants";
|
||||||
|
|
||||||
import { FastmailForwarder } from "./fastmail";
|
import { FastmailForwarder } from "./fastmail";
|
||||||
@@ -45,16 +46,23 @@ const AccountIdSuccess: MockResponse = Object.freeze({
|
|||||||
|
|
||||||
// the tests
|
// the tests
|
||||||
describe("Fastmail Forwarder", () => {
|
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)", () => {
|
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
|
||||||
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
||||||
const apiService = mockApiService(AccountIdSuccess, EmptyResponse);
|
const apiService = mockApiService(AccountIdSuccess, EmptyResponse);
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token,
|
token,
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
prefix: "prefix",
|
prefix: "prefix",
|
||||||
@@ -71,11 +79,12 @@ describe("Fastmail Forwarder", () => {
|
|||||||
const apiService = mockApiService({ status, body: {} }, EmptyResponse);
|
const apiService = mockApiService({ status, body: {} }, EmptyResponse);
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
prefix: "prefix",
|
prefix: "prefix",
|
||||||
@@ -105,9 +114,10 @@ describe("Fastmail Forwarder", () => {
|
|||||||
});
|
});
|
||||||
const i18nService = mockI18nService();
|
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",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
prefix: "prefix",
|
prefix: "prefix",
|
||||||
@@ -138,11 +148,12 @@ describe("Fastmail Forwarder", () => {
|
|||||||
});
|
});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
prefix: "prefix",
|
prefix: "prefix",
|
||||||
@@ -165,11 +176,12 @@ describe("Fastmail Forwarder", () => {
|
|||||||
const apiService = mockApiService(AccountIdSuccess, { status, body: {} });
|
const apiService = mockApiService(AccountIdSuccess, { status, body: {} });
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
prefix: "prefix",
|
prefix: "prefix",
|
||||||
@@ -206,11 +218,12 @@ describe("Fastmail Forwarder", () => {
|
|||||||
});
|
});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
prefix: "prefix",
|
prefix: "prefix",
|
||||||
@@ -232,11 +245,12 @@ describe("Fastmail Forwarder", () => {
|
|||||||
const apiService = mockApiService(AccountIdSuccess, { status: statusCode, body: {} });
|
const apiService = mockApiService(AccountIdSuccess, { status: statusCode, body: {} });
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new FastmailForwarder(apiService, i18nService);
|
const forwarder = new FastmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
prefix: "prefix",
|
prefix: "prefix",
|
||||||
|
|||||||
@@ -1,24 +1,39 @@
|
|||||||
import { ApiService } from "../../../../abstractions/api.service";
|
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 { 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 { 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 */
|
/** Generates a forwarding address for Fastmail */
|
||||||
export class FastmailForwarder implements Forwarder {
|
export class FastmailForwarder extends ForwarderGeneratorStrategy<ApiOptions & EmailPrefixOptions> {
|
||||||
/** Instantiates the forwarder
|
/** Instantiates the forwarder
|
||||||
* @param apiService used for ajax requests to the forwarding service
|
* @param apiService used for ajax requests to the forwarding service
|
||||||
* @param i18nService used to look up error strings
|
* @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(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
) {}
|
encryptService: EncryptService,
|
||||||
|
keyService: CryptoService,
|
||||||
|
stateProvider: StateProvider,
|
||||||
|
) {
|
||||||
|
super(encryptService, keyService, stateProvider);
|
||||||
|
}
|
||||||
|
|
||||||
/** {@link Forwarder.generate} */
|
/** {@link ForwarderGeneratorStrategy.key} */
|
||||||
async generate(
|
get key() {
|
||||||
website: string | null,
|
return FASTMAIL_FORWARDER;
|
||||||
options: ApiOptions & EmailPrefixOptions,
|
}
|
||||||
): Promise<string> {
|
|
||||||
|
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||||
|
generate = async (options: ApiOptions & EmailPrefixOptions) => {
|
||||||
if (!options.token || options.token === "") {
|
if (!options.token || options.token === "") {
|
||||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.Fastmail.name);
|
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.Fastmail.name);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -41,7 +56,7 @@ export class FastmailForwarder implements Forwarder {
|
|||||||
"new-masked-email": {
|
"new-masked-email": {
|
||||||
state: "enabled",
|
state: "enabled",
|
||||||
description: "",
|
description: "",
|
||||||
forDomain: website,
|
forDomain: options.website,
|
||||||
emailPrefix: options.prefix,
|
emailPrefix: options.prefix,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -104,7 +119,7 @@ export class FastmailForwarder implements Forwarder {
|
|||||||
|
|
||||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.Fastmail.name);
|
const error = this.i18nService.t("forwarderUnknownError", Forwarders.Fastmail.name);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
};
|
||||||
|
|
||||||
private async getAccountId(options: ApiOptions): Promise<string> {
|
private async getAccountId(options: ApiOptions): Promise<string> {
|
||||||
const requestInit: RequestInit = {
|
const requestInit: RequestInit = {
|
||||||
|
|||||||
@@ -2,22 +2,30 @@
|
|||||||
* include Request in test environment.
|
* include Request in test environment.
|
||||||
* @jest-environment ../../../../shared/test.environment.ts
|
* @jest-environment ../../../../shared/test.environment.ts
|
||||||
*/
|
*/
|
||||||
|
import { FIREFOX_RELAY_FORWARDER } from "../../key-definitions";
|
||||||
import { Forwarders } from "../options/constants";
|
import { Forwarders } from "../options/constants";
|
||||||
|
|
||||||
import { FirefoxRelayForwarder } from "./firefox-relay";
|
import { FirefoxRelayForwarder } from "./firefox-relay";
|
||||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||||
|
|
||||||
describe("Firefox Relay Forwarder", () => {
|
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)", () => {
|
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
|
||||||
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
|
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token,
|
token,
|
||||||
}),
|
}),
|
||||||
).rejects.toEqual("forwaderInvalidToken");
|
).rejects.toEqual("forwaderInvalidToken");
|
||||||
@@ -40,9 +48,10 @@ describe("Firefox Relay Forwarder", () => {
|
|||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
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",
|
token: "token",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,9 +71,10 @@ describe("Firefox Relay Forwarder", () => {
|
|||||||
const apiService = mockApiService(status, { full_address });
|
const apiService = mockApiService(status, { full_address });
|
||||||
const i18nService = mockI18nService();
|
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",
|
token: "token",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -77,11 +87,12 @@ describe("Firefox Relay Forwarder", () => {
|
|||||||
const apiService = mockApiService(401, {});
|
const apiService = mockApiService(401, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
|
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
}),
|
}),
|
||||||
).rejects.toEqual("forwaderInvalidToken");
|
).rejects.toEqual("forwaderInvalidToken");
|
||||||
@@ -101,11 +112,12 @@ describe("Firefox Relay Forwarder", () => {
|
|||||||
const apiService = mockApiService(statusCode, {});
|
const apiService = mockApiService(statusCode, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new FirefoxRelayForwarder(apiService, i18nService);
|
const forwarder = new FirefoxRelayForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
}),
|
}),
|
||||||
).rejects.toEqual("forwarderUnknownError");
|
).rejects.toEqual("forwarderUnknownError");
|
||||||
|
|||||||
@@ -1,21 +1,39 @@
|
|||||||
import { ApiService } from "../../../../abstractions/api.service";
|
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 { 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 { Forwarders } from "../options/constants";
|
||||||
import { Forwarder, ApiOptions } from "../options/forwarder-options";
|
import { ApiOptions } from "../options/forwarder-options";
|
||||||
|
|
||||||
/** Generates a forwarding address for Firefox Relay */
|
/** Generates a forwarding address for Firefox Relay */
|
||||||
export class FirefoxRelayForwarder implements Forwarder {
|
export class FirefoxRelayForwarder extends ForwarderGeneratorStrategy<ApiOptions> {
|
||||||
/** Instantiates the forwarder
|
/** Instantiates the forwarder
|
||||||
* @param apiService used for ajax requests to the forwarding service
|
* @param apiService used for ajax requests to the forwarding service
|
||||||
* @param i18nService used to look up error strings
|
* @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(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
) {}
|
encryptService: EncryptService,
|
||||||
|
keyService: CryptoService,
|
||||||
|
stateProvider: StateProvider,
|
||||||
|
) {
|
||||||
|
super(encryptService, keyService, stateProvider);
|
||||||
|
}
|
||||||
|
|
||||||
/** {@link Forwarder.generate} */
|
/** {@link ForwarderGeneratorStrategy.key} */
|
||||||
async generate(website: string | null, options: ApiOptions): Promise<string> {
|
get key() {
|
||||||
|
return FIREFOX_RELAY_FORWARDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||||
|
generate = async (options: ApiOptions) => {
|
||||||
if (!options.token || options.token === "") {
|
if (!options.token || options.token === "") {
|
||||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.FirefoxRelay.name);
|
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.FirefoxRelay.name);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -23,9 +41,11 @@ export class FirefoxRelayForwarder implements Forwarder {
|
|||||||
|
|
||||||
const url = "https://relay.firefox.com/api/v1/relayaddresses/";
|
const url = "https://relay.firefox.com/api/v1/relayaddresses/";
|
||||||
|
|
||||||
const descriptionId =
|
let descriptionId = "forwarderGeneratedByWithWebsite";
|
||||||
website && website !== "" ? "forwarderGeneratedByWithWebsite" : "forwarderGeneratedBy";
|
if (!options.website || options.website === "") {
|
||||||
const description = this.i18nService.t(descriptionId, website ?? "");
|
descriptionId = "forwarderGeneratedBy";
|
||||||
|
}
|
||||||
|
const description = this.i18nService.t(descriptionId, options.website ?? "");
|
||||||
|
|
||||||
const request = new Request(url, {
|
const request = new Request(url, {
|
||||||
redirect: "manual",
|
redirect: "manual",
|
||||||
@@ -37,7 +57,7 @@ export class FirefoxRelayForwarder implements Forwarder {
|
|||||||
}),
|
}),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
generated_for: website,
|
generated_for: options.website,
|
||||||
description,
|
description,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -53,5 +73,5 @@ export class FirefoxRelayForwarder implements Forwarder {
|
|||||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.FirefoxRelay.name);
|
const error = this.i18nService.t("forwarderUnknownError", Forwarders.FirefoxRelay.name);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,30 @@
|
|||||||
* include Request in test environment.
|
* include Request in test environment.
|
||||||
* @jest-environment ../../../../shared/test.environment.ts
|
* @jest-environment ../../../../shared/test.environment.ts
|
||||||
*/
|
*/
|
||||||
|
import { FORWARD_EMAIL_FORWARDER } from "../../key-definitions";
|
||||||
import { Forwarders } from "../options/constants";
|
import { Forwarders } from "../options/constants";
|
||||||
|
|
||||||
import { ForwardEmailForwarder } from "./forward-email";
|
import { ForwardEmailForwarder } from "./forward-email";
|
||||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||||
|
|
||||||
describe("ForwardEmail Forwarder", () => {
|
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)", () => {
|
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
|
||||||
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token,
|
token,
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
}),
|
}),
|
||||||
@@ -36,11 +44,12 @@ describe("ForwardEmail Forwarder", () => {
|
|||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain,
|
domain,
|
||||||
}),
|
}),
|
||||||
@@ -65,9 +74,10 @@ describe("ForwardEmail Forwarder", () => {
|
|||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
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",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
});
|
});
|
||||||
@@ -92,9 +102,10 @@ describe("ForwardEmail Forwarder", () => {
|
|||||||
const apiService = mockApiService(status, response);
|
const apiService = mockApiService(status, response);
|
||||||
const i18nService = mockI18nService();
|
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",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
});
|
});
|
||||||
@@ -108,11 +119,12 @@ describe("ForwardEmail Forwarder", () => {
|
|||||||
const apiService = mockApiService(401, {});
|
const apiService = mockApiService(401, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
}),
|
}),
|
||||||
@@ -132,11 +144,12 @@ describe("ForwardEmail Forwarder", () => {
|
|||||||
const apiService = mockApiService(401, { message: "A message" });
|
const apiService = mockApiService(401, { message: "A message" });
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
}),
|
}),
|
||||||
@@ -158,11 +171,12 @@ describe("ForwardEmail Forwarder", () => {
|
|||||||
const apiService = mockApiService(500, json);
|
const apiService = mockApiService(500, json);
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
}),
|
}),
|
||||||
@@ -191,11 +205,12 @@ describe("ForwardEmail Forwarder", () => {
|
|||||||
const apiService = mockApiService(statusCode, { message });
|
const apiService = mockApiService(statusCode, { message });
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
}),
|
}),
|
||||||
@@ -225,11 +240,12 @@ describe("ForwardEmail Forwarder", () => {
|
|||||||
const apiService = mockApiService(statusCode, { error });
|
const apiService = mockApiService(statusCode, { error });
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new ForwardEmailForwarder(apiService, i18nService);
|
const forwarder = new ForwardEmailForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,25 +1,42 @@
|
|||||||
import { ApiService } from "../../../../abstractions/api.service";
|
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 { I18nService } from "../../../../platform/abstractions/i18n.service";
|
||||||
import { Utils } from "../../../../platform/misc/utils";
|
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 { 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 */
|
/** Generates a forwarding address for Forward Email */
|
||||||
export class ForwardEmailForwarder implements Forwarder {
|
export class ForwardEmailForwarder extends ForwarderGeneratorStrategy<
|
||||||
|
ApiOptions & EmailDomainOptions
|
||||||
|
> {
|
||||||
/** Instantiates the forwarder
|
/** Instantiates the forwarder
|
||||||
* @param apiService used for ajax requests to the forwarding service
|
* @param apiService used for ajax requests to the forwarding service
|
||||||
* @param i18nService used to look up error strings
|
* @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(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
) {}
|
encryptService: EncryptService,
|
||||||
|
keyService: CryptoService,
|
||||||
|
stateProvider: StateProvider,
|
||||||
|
) {
|
||||||
|
super(encryptService, keyService, stateProvider);
|
||||||
|
}
|
||||||
|
|
||||||
/** {@link Forwarder.generate} */
|
/** {@link ForwarderGeneratorStrategy.key} */
|
||||||
async generate(
|
get key() {
|
||||||
website: string | null,
|
return FORWARD_EMAIL_FORWARDER;
|
||||||
options: ApiOptions & EmailDomainOptions,
|
}
|
||||||
): Promise<string> {
|
|
||||||
|
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||||
|
generate = async (options: ApiOptions & EmailDomainOptions) => {
|
||||||
if (!options.token || options.token === "") {
|
if (!options.token || options.token === "") {
|
||||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.ForwardEmail.name);
|
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.ForwardEmail.name);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -31,9 +48,11 @@ export class ForwardEmailForwarder implements Forwarder {
|
|||||||
|
|
||||||
const url = `https://api.forwardemail.net/v1/domains/${options.domain}/aliases`;
|
const url = `https://api.forwardemail.net/v1/domains/${options.domain}/aliases`;
|
||||||
|
|
||||||
const descriptionId =
|
let descriptionId = "forwarderGeneratedByWithWebsite";
|
||||||
website && website !== "" ? "forwarderGeneratedByWithWebsite" : "forwarderGeneratedBy";
|
if (!options.website || options.website === "") {
|
||||||
const description = this.i18nService.t(descriptionId, website ?? "");
|
descriptionId = "forwarderGeneratedBy";
|
||||||
|
}
|
||||||
|
const description = this.i18nService.t(descriptionId, options.website ?? "");
|
||||||
|
|
||||||
const request = new Request(url, {
|
const request = new Request(url, {
|
||||||
redirect: "manual",
|
redirect: "manual",
|
||||||
@@ -44,7 +63,7 @@ export class ForwardEmailForwarder implements Forwarder {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
}),
|
}),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
labels: website,
|
labels: options.website,
|
||||||
description,
|
description,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -75,5 +94,5 @@ export class ForwardEmailForwarder implements Forwarder {
|
|||||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.ForwardEmail.name);
|
const error = this.i18nService.t("forwarderUnknownError", Forwarders.ForwardEmail.name);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,30 @@
|
|||||||
* include Request in test environment.
|
* include Request in test environment.
|
||||||
* @jest-environment ../../../../shared/test.environment.ts
|
* @jest-environment ../../../../shared/test.environment.ts
|
||||||
*/
|
*/
|
||||||
|
import { SIMPLE_LOGIN_FORWARDER } from "../../key-definitions";
|
||||||
import { Forwarders } from "../options/constants";
|
import { Forwarders } from "../options/constants";
|
||||||
|
|
||||||
import { mockApiService, mockI18nService } from "./mocks.jest";
|
import { mockApiService, mockI18nService } from "./mocks.jest";
|
||||||
import { SimpleLoginForwarder } from "./simple-login";
|
import { SimpleLoginForwarder } from "./simple-login";
|
||||||
|
|
||||||
describe("SimpleLogin Forwarder", () => {
|
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)", () => {
|
describe("generate(string | null, SelfHostedApiOptions & EmailDomainOptions)", () => {
|
||||||
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
it.each([null, ""])("throws an error if the token is missing (token = %p)", async (token) => {
|
||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token,
|
token,
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
}),
|
}),
|
||||||
@@ -36,11 +44,12 @@ describe("SimpleLogin Forwarder", () => {
|
|||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
baseUrl,
|
baseUrl,
|
||||||
}),
|
}),
|
||||||
@@ -62,9 +71,10 @@ describe("SimpleLogin Forwarder", () => {
|
|||||||
const apiService = mockApiService(200, {});
|
const apiService = mockApiService(200, {});
|
||||||
const i18nService = mockI18nService();
|
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",
|
token: "token",
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
});
|
});
|
||||||
@@ -85,9 +95,10 @@ describe("SimpleLogin Forwarder", () => {
|
|||||||
const apiService = mockApiService(status, { alias });
|
const apiService = mockApiService(status, { alias });
|
||||||
const i18nService = mockI18nService();
|
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",
|
token: "token",
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
});
|
});
|
||||||
@@ -101,11 +112,12 @@ describe("SimpleLogin Forwarder", () => {
|
|||||||
const apiService = mockApiService(401, {});
|
const apiService = mockApiService(401, {});
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
}),
|
}),
|
||||||
@@ -126,11 +138,12 @@ describe("SimpleLogin Forwarder", () => {
|
|||||||
const apiService = mockApiService(500, body);
|
const apiService = mockApiService(500, body);
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
}),
|
}),
|
||||||
@@ -159,11 +172,12 @@ describe("SimpleLogin Forwarder", () => {
|
|||||||
const apiService = mockApiService(statusCode, { error });
|
const apiService = mockApiService(statusCode, { error });
|
||||||
const i18nService = mockI18nService();
|
const i18nService = mockI18nService();
|
||||||
|
|
||||||
const forwarder = new SimpleLoginForwarder(apiService, i18nService);
|
const forwarder = new SimpleLoginForwarder(apiService, i18nService, null, null, null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
async () =>
|
async () =>
|
||||||
await forwarder.generate(null, {
|
await forwarder.generate({
|
||||||
|
website: null,
|
||||||
token: "token",
|
token: "token",
|
||||||
baseUrl: "https://api.example.com",
|
baseUrl: "https://api.example.com",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,21 +1,39 @@
|
|||||||
import { ApiService } from "../../../../abstractions/api.service";
|
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 { 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 { Forwarders } from "../options/constants";
|
||||||
import { Forwarder, SelfHostedApiOptions } from "../options/forwarder-options";
|
import { SelfHostedApiOptions } from "../options/forwarder-options";
|
||||||
|
|
||||||
/** Generates a forwarding address for Simple Login */
|
/** Generates a forwarding address for Simple Login */
|
||||||
export class SimpleLoginForwarder implements Forwarder {
|
export class SimpleLoginForwarder extends ForwarderGeneratorStrategy<SelfHostedApiOptions> {
|
||||||
/** Instantiates the forwarder
|
/** Instantiates the forwarder
|
||||||
* @param apiService used for ajax requests to the forwarding service
|
* @param apiService used for ajax requests to the forwarding service
|
||||||
* @param i18nService used to look up error strings
|
* @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(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
) {}
|
encryptService: EncryptService,
|
||||||
|
keyService: CryptoService,
|
||||||
|
stateProvider: StateProvider,
|
||||||
|
) {
|
||||||
|
super(encryptService, keyService, stateProvider);
|
||||||
|
}
|
||||||
|
|
||||||
/** {@link Forwarder.generate} */
|
/** {@link ForwarderGeneratorStrategy.key} */
|
||||||
async generate(website: string, options: SelfHostedApiOptions): Promise<string> {
|
get key() {
|
||||||
|
return SIMPLE_LOGIN_FORWARDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@link ForwarderGeneratorStrategy.generate} */
|
||||||
|
generate = async (options: SelfHostedApiOptions) => {
|
||||||
if (!options.token || options.token === "") {
|
if (!options.token || options.token === "") {
|
||||||
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.SimpleLogin.name);
|
const error = this.i18nService.t("forwaderInvalidToken", Forwarders.SimpleLogin.name);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -27,11 +45,11 @@ export class SimpleLoginForwarder implements Forwarder {
|
|||||||
|
|
||||||
let url = options.baseUrl + "/api/alias/random/new";
|
let url = options.baseUrl + "/api/alias/random/new";
|
||||||
let noteId = "forwarderGeneratedBy";
|
let noteId = "forwarderGeneratedBy";
|
||||||
if (website && website !== "") {
|
if (options.website && options.website !== "") {
|
||||||
url += "?hostname=" + website;
|
url += "?hostname=" + options.website;
|
||||||
noteId = "forwarderGeneratedByWithWebsite";
|
noteId = "forwarderGeneratedByWithWebsite";
|
||||||
}
|
}
|
||||||
const note = this.i18nService.t(noteId, website ?? "");
|
const note = this.i18nService.t(noteId, options.website ?? "");
|
||||||
|
|
||||||
const request = new Request(url, {
|
const request = new Request(url, {
|
||||||
redirect: "manual",
|
redirect: "manual",
|
||||||
@@ -60,5 +78,5 @@ export class SimpleLoginForwarder implements Forwarder {
|
|||||||
const error = this.i18nService.t("forwarderUnknownError", Forwarders.SimpleLogin.name);
|
const error = this.i18nService.t("forwarderUnknownError", Forwarders.SimpleLogin.name);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,27 +85,33 @@ export const DefaultOptions: UsernameGeneratorOptions = Object.freeze({
|
|||||||
forwarders: Object.freeze({
|
forwarders: Object.freeze({
|
||||||
service: Forwarders.Fastmail.id,
|
service: Forwarders.Fastmail.id,
|
||||||
fastMail: Object.freeze({
|
fastMail: Object.freeze({
|
||||||
|
website: null,
|
||||||
domain: "",
|
domain: "",
|
||||||
prefix: "",
|
prefix: "",
|
||||||
token: "",
|
token: "",
|
||||||
}),
|
}),
|
||||||
addyIo: Object.freeze({
|
addyIo: Object.freeze({
|
||||||
|
website: null,
|
||||||
baseUrl: "https://app.addy.io",
|
baseUrl: "https://app.addy.io",
|
||||||
domain: "",
|
domain: "",
|
||||||
token: "",
|
token: "",
|
||||||
}),
|
}),
|
||||||
forwardEmail: Object.freeze({
|
forwardEmail: Object.freeze({
|
||||||
|
website: null,
|
||||||
token: "",
|
token: "",
|
||||||
domain: "",
|
domain: "",
|
||||||
}),
|
}),
|
||||||
simpleLogin: Object.freeze({
|
simpleLogin: Object.freeze({
|
||||||
|
website: null,
|
||||||
baseUrl: "https://app.simplelogin.io",
|
baseUrl: "https://app.simplelogin.io",
|
||||||
token: "",
|
token: "",
|
||||||
}),
|
}),
|
||||||
duckDuckGo: Object.freeze({
|
duckDuckGo: Object.freeze({
|
||||||
|
website: null,
|
||||||
token: "",
|
token: "",
|
||||||
}),
|
}),
|
||||||
firefoxRelay: Object.freeze({
|
firefoxRelay: Object.freeze({
|
||||||
|
website: null,
|
||||||
token: "",
|
token: "",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { EncString } from "../../../../platform/models/domain/enc-string";
|
|
||||||
|
|
||||||
/** Identifiers for email forwarding services.
|
/** Identifiers for email forwarding services.
|
||||||
* @remarks These are used to select forwarder-specific options.
|
* @remarks These are used to select forwarder-specific options.
|
||||||
* The must be kept in sync with the forwarder implementations.
|
* The must be kept in sync with the forwarder implementations.
|
||||||
@@ -24,26 +22,24 @@ export type ForwarderMetadata = {
|
|||||||
validForSelfHosted: boolean;
|
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 */
|
/** Options common to all forwarder APIs */
|
||||||
export type ApiOptions = {
|
export type ApiOptions = {
|
||||||
/** bearer token that authenticates bitwarden to the forwarder.
|
/** bearer token that authenticates bitwarden to the forwarder.
|
||||||
* This is required to issue an API request.
|
* This is required to issue an API request.
|
||||||
*/
|
*/
|
||||||
token?: string;
|
token?: string;
|
||||||
|
} & RequestOptions;
|
||||||
|
|
||||||
/** encrypted bearer token that authenticates bitwarden to the forwarder.
|
/** Options that provide contextual information about the application state
|
||||||
* This is used to store the token at rest and must be decoded before use.
|
* 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. */
|
/** Api configuration for forwarders that support self-hosted installations. */
|
||||||
|
|||||||
@@ -24,27 +24,33 @@ const TestOptions: UsernameGeneratorOptions = {
|
|||||||
forwarders: {
|
forwarders: {
|
||||||
service: Forwarders.Fastmail.id,
|
service: Forwarders.Fastmail.id,
|
||||||
fastMail: {
|
fastMail: {
|
||||||
|
website: null,
|
||||||
domain: "httpbin.com",
|
domain: "httpbin.com",
|
||||||
prefix: "foo",
|
prefix: "foo",
|
||||||
token: "some-token",
|
token: "some-token",
|
||||||
},
|
},
|
||||||
addyIo: {
|
addyIo: {
|
||||||
|
website: null,
|
||||||
baseUrl: "https://app.addy.io",
|
baseUrl: "https://app.addy.io",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
token: "some-token",
|
token: "some-token",
|
||||||
},
|
},
|
||||||
forwardEmail: {
|
forwardEmail: {
|
||||||
|
website: null,
|
||||||
token: "some-token",
|
token: "some-token",
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
},
|
},
|
||||||
simpleLogin: {
|
simpleLogin: {
|
||||||
|
website: null,
|
||||||
baseUrl: "https://app.simplelogin.io",
|
baseUrl: "https://app.simplelogin.io",
|
||||||
token: "some-token",
|
token: "some-token",
|
||||||
},
|
},
|
||||||
duckDuckGo: {
|
duckDuckGo: {
|
||||||
|
website: null,
|
||||||
token: "some-token",
|
token: "some-token",
|
||||||
},
|
},
|
||||||
firefoxRelay: {
|
firefoxRelay: {
|
||||||
|
website: null,
|
||||||
token: "some-token",
|
token: "some-token",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user