[PM-1060] Added new forwarder (Forward Email <https://forwardemail.net>) (#4809)

* Added new forwarder (Forward Email <https://forwardemail.net>)

* fix: fixed Basic authorization header

* fix: fixed returned email value

* feat: added verbose message for end-users (e.g. "Not Found" vs. "Domain does not exist on your account." (automatically localized with i18n for user)

* fix: fixed Buffer.from to Utils.fromBufferToB64

* fix: fixed fromBufferToB64 to fromUtf8ToB64

* Remove try-catch to properly display api errors

---------

Co-authored-by: Daniel James Smith <djsmith@web.de>
This commit is contained in:
titanism 2023-06-09 02:55:12 -05:00 committed by GitHub
parent ba5e890e86
commit d18b45a87e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 129 additions and 0 deletions

View File

@ -405,6 +405,28 @@
/>
</div>
</ng-container>
<ng-container *ngIf="usernameOptions.forwardedService === 'forwardemail'">
<div class="box-content-row" appBoxRow>
<label for="forwardemail-accessToken">{{ "apiAccessToken" | i18n }}</label>
<input
id="forwardemail-accessToken"
type="password"
name="ForwardEmailAccessToken"
[(ngModel)]="usernameOptions.forwardedForwardEmailApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="forwardemail-domain">{{ "domainName" | i18n }}</label>
<input
id="forwardemail-domain"
type="text"
name="ForwardEmailDomain"
[(ngModel)]="usernameOptions.forwardedForwardEmailDomain"
(blur)="saveUsernameOptions()"
/>
</div>
</ng-container>
</div>
</div>
<div class="box" *ngIf="usernameOptions.type === 'subaddress'">

View File

@ -432,6 +432,28 @@
/>
</div>
</ng-container>
<ng-container *ngIf="usernameOptions.forwardedService === 'forwardemail'">
<div class="box-content-row" appBoxRow>
<label for="forwardemail-accessToken">{{ "apiAccessToken" | i18n }}</label>
<input
id="forwardemail-accessToken"
type="password"
name="ForwardEmailAccessToken"
[(ngModel)]="usernameOptions.forwardedForwardEmailApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="forwardemail-domain">{{ "domainName" | i18n }}</label>
<input
id="forwardemail-domain"
type="text"
name="ForwardEmailDomain"
[(ngModel)]="usernameOptions.forwardedForwardEmailDomain"
(blur)="saveUsernameOptions()"
/>
</div>
</ng-container>
</div>
</div>
<div class="box" *ngIf="usernameOptions.type === 'subaddress'" [hidden]="!showOptions">

View File

@ -343,6 +343,28 @@
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'forwardemail'">
<div class="form-group col-4">
<label for="forwardemail-apikey">{{ "apiAccessToken" | i18n }}</label>
<input
id="forwardemail-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedForwardEmailApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4">
<label for="forwardemail-domain">{{ "domainName" | i18n }}</label>
<input
id="forwardemail-domain"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.forwardedForwardEmailDomain"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
</ng-container>
<div class="row" *ngIf="usernameOptions.type === 'subaddress'">
<div class="form-group col-4">

View File

@ -280,6 +280,7 @@ const devServer =
https://quack.duckduckgo.com/api/email/addresses
https://app.anonaddy.com/api/v1/aliases
https://api.fastmail.com
https://api.forwardemail.net
http://localhost:5000
;object-src
'self'

View File

@ -242,6 +242,7 @@ export class GeneratorComponent implements OnInit {
{ name: "Fastmail", value: "fastmail", validForSelfHosted: true },
{ name: "Firefox Relay", value: "firefoxrelay", validForSelfHosted: false },
{ name: "SimpleLogin", value: "simplelogin", validForSelfHosted: true },
{ name: "Forward Email", value: "forwardemail", validForSelfHosted: true },
];
this.usernameOptions = await this.usernameGenerationService.getOptions();

View File

@ -0,0 +1,49 @@
import { ApiService } from "../../../../abstractions/api.service";
import { Utils } from "../../../../misc/utils";
import { Forwarder } from "./forwarder";
import { ForwarderOptions } from "./forwarder-options";
export class ForwardEmailForwarder implements Forwarder {
async generate(apiService: ApiService, options: ForwarderOptions): Promise<string> {
if (options.apiKey == null || options.apiKey === "") {
throw "Invalid Forward Email API key.";
}
if (options.forwardemail?.domain == null || options.forwardemail.domain === "") {
throw "Invalid Forward Email domain.";
}
const requestInit: RequestInit = {
redirect: "manual",
cache: "no-store",
method: "POST",
headers: new Headers({
Authorization: "Basic " + Utils.fromUtf8ToB64(options.apiKey + ":"),
"Content-Type": "application/json",
}),
};
const url = `https://api.forwardemail.net/v1/domains/${options.forwardemail.domain}/aliases`;
requestInit.body = JSON.stringify({
labels: options.website,
description:
(options.website != null ? "Website: " + options.website + ". " : "") +
"Generated by Bitwarden.",
});
const request = new Request(url, requestInit);
const response = await apiService.nativeFetch(request);
if (response.status === 200 || response.status === 201) {
const json = await response.json();
return json?.name + "@" + (json?.domain?.name || options.forwardemail.domain);
}
if (response.status === 401) {
throw "Invalid Forward Email API key.";
}
const json = await response.json();
if (json?.message != null) {
throw "Forward Email error:\n" + json.message;
}
if (json?.error != null) {
throw "Forward Email error:\n" + json.error;
}
throw "Unknown Forward Email error occurred.";
}
}

View File

@ -3,6 +3,7 @@ export class ForwarderOptions {
website: string;
fastmail = new FastmailForwarderOptions();
anonaddy = new AnonAddyForwarderOptions();
forwardemail = new ForwardEmailForwarderOptions();
}
export class FastmailForwarderOptions {
@ -12,3 +13,7 @@ export class FastmailForwarderOptions {
export class AnonAddyForwarderOptions {
domain: string;
}
export class ForwardEmailForwarderOptions {
domain: string;
}

View File

@ -5,3 +5,4 @@ export { FirefoxRelayForwarder } from "./firefox-relay-forwarder";
export { Forwarder } from "./forwarder";
export { ForwarderOptions } from "./forwarder-options";
export { SimpleLoginForwarder } from "./simple-login-forwarder";
export { ForwardEmailForwarder } from "./forward-email-forwarder";

View File

@ -8,6 +8,7 @@ import {
DuckDuckGoForwarder,
FastmailForwarder,
FirefoxRelayForwarder,
ForwardEmailForwarder,
Forwarder,
ForwarderOptions,
SimpleLoginForwarder,
@ -22,6 +23,7 @@ const DefaultOptions = {
catchallType: "random",
forwardedService: "",
forwardedAnonAddyDomain: "anonaddy.me",
forwardedForwardEmailDomain: "hideaddress.net",
};
export class UsernameGenerationService implements UsernameGenerationServiceAbstraction {
@ -137,6 +139,10 @@ export class UsernameGenerationService implements UsernameGenerationServiceAbstr
} else if (o.forwardedService === "duckduckgo") {
forwarder = new DuckDuckGoForwarder();
forwarderOptions.apiKey = o.forwardedDuckDuckGoToken;
} else if (o.forwardedService === "forwardemail") {
forwarder = new ForwardEmailForwarder();
forwarderOptions.apiKey = o.forwardedForwardEmailApiToken;
forwarderOptions.forwardemail.domain = o.forwardedForwardEmailDomain;
}
if (forwarder == null) {