[PM-5974] introduce ForwarderGeneratorStrategy (#8207)

* update defaults to include `website` parameter
* update utilities tests to include `website` parameter
This commit is contained in:
✨ Audrey ✨ 2024-03-07 12:45:56 -05:00 committed by GitHub
parent a5c78fbe0d
commit c7318311af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 610 additions and 156 deletions

View File

@ -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 = {};

View File

@ -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,

View File

@ -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);
});
});

View File

@ -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>();
};
}

View File

@ -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",

View File

@ -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;
}
}
};
}

View File

@ -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");

View File

@ -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;
}
}
};
}

View File

@ -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",

View File

@ -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 = {

View File

@ -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");

View File

@ -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;
}
}
};
}

View File

@ -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",
}),

View File

@ -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;
}
}
};
}

View File

@ -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",
}),

View File

@ -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;
}
}
};
}

View File

@ -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: "",
}),
}),

View File

@ -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. */

View File

@ -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",
},
},