1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 01:03:35 +00:00

Forwarded email alias generation (#772)

* generate forwarded alias with SL and AD

* added forwarded email to type list

* add ApiService dep

* ApiServiceAbstraction

* use proper status codes

* only generate on button press

* reset username to `-`

* reset username when forwarded

* Authorization header for anonaddy

* use proper anonaddy json path

* firefox relay support

* update description for firefox

* log username generation errors
This commit is contained in:
Kyle Spearrin
2022-04-27 10:08:46 -04:00
committed by GitHub
parent e40e7de808
commit fe65a337c8
4 changed files with 195 additions and 11 deletions

View File

@@ -3,6 +3,7 @@ export abstract class UsernameGenerationService {
generateWord: (options: any) => Promise<string>;
generateSubaddress: (options: any) => Promise<string>;
generateCatchall: (options: any) => Promise<string>;
generateForwarded: (options: any) => Promise<string>;
getOptions: () => Promise<any>;
saveOptions: (options: any) => Promise<void>;
}

View File

@@ -1,3 +1,4 @@
import { ApiService } from "../abstractions/api.service";
import { CryptoService } from "../abstractions/crypto.service";
import { StateService } from "../abstractions/state.service";
import { UsernameGenerationService as BaseUsernameGenerationService } from "../abstractions/usernameGeneration.service";
@@ -9,10 +10,16 @@ const DefaultOptions = {
wordIncludeNumber: true,
subaddressType: "random",
catchallType: "random",
forwardedType: "simplelogin",
forwardedAnonAddyDomain: "anonaddy.me",
};
export class UsernameGenerationService implements BaseUsernameGenerationService {
constructor(private cryptoService: CryptoService, private stateService: StateService) {}
constructor(
private cryptoService: CryptoService,
private stateService: StateService,
private apiService: ApiService
) {}
generateUsername(options: any): Promise<string> {
if (options.type === "catchall") {
@@ -20,7 +27,7 @@ export class UsernameGenerationService implements BaseUsernameGenerationService
} else if (options.type === "subaddress") {
return this.generateSubaddress(options);
} else if (options.type === "forwarded") {
return this.generateSubaddress(options);
return this.generateForwarded(options);
} else {
return this.generateWord(options);
}
@@ -94,6 +101,46 @@ export class UsernameGenerationService implements BaseUsernameGenerationService
return startString + "@" + o.catchallDomain;
}
async generateForwarded(options: any): Promise<string> {
const o = Object.assign({}, DefaultOptions, options);
if (o.forwardedService == null) {
return null;
}
if (o.forwardedService === "simplelogin") {
if (o.forwardedSimpleLoginApiKey == null || o.forwardedSimpleLoginApiKey === "") {
return null;
}
return this.generateSimpleLoginAlias(
o.forwardedSimpleLoginApiKey,
o.forwardedSimpleLoginHostname,
o.website
);
} else if (o.forwardedService === "anonaddy") {
if (
o.forwardedAnonAddyApiToken == null ||
o.forwardedAnonAddyApiToken === "" ||
o.forwardedAnonAddyDomain == null ||
o.forwardedAnonAddyDomain == ""
) {
return null;
}
return this.generateAnonAddyAlias(
o.forwardedAnonAddyApiToken,
o.forwardedAnonAddyDomain,
o.website
);
} else if (o.forwardedService === "firefoxrelay") {
if (o.forwardedFirefoxApiToken == null || o.forwardedFirefoxApiToken === "") {
return null;
}
return this.generateFirefoxRelayAlias(o.forwardedFirefoxApiToken, o.website);
}
return null;
}
async getOptions(): Promise<any> {
let options = await this.stateService.getUsernameGenerationOptions();
if (options == null) {
@@ -125,4 +172,112 @@ export class UsernameGenerationService implements BaseUsernameGenerationService
? number
: new Array(width - number.length + 1).join("0") + number;
}
private async generateSimpleLoginAlias(
apiKey: string,
hostname: string,
websiteNote: string
): Promise<string> {
if (apiKey == null || apiKey === "") {
throw "Invalid SimpleLogin API key.";
}
const requestInit: RequestInit = {
cache: "no-store",
method: "POST",
headers: new Headers({
Authentication: apiKey,
"Content-Type": "application/json",
}),
};
let url = "https://app.simplelogin.io/api/alias/random/new";
if (hostname != null) {
url += "?hostname=" + hostname;
}
requestInit.body = JSON.stringify({
note:
(websiteNote != null ? "Website: " + websiteNote + ". " : "") + "Generated by Bitwarden.",
});
const request = new Request(url, requestInit);
const response = await this.apiService.nativeFetch(request);
if (response.status === 200 || response.status === 201) {
const json = await response.json();
return json.alias;
}
if (response.status === 401) {
throw "Invalid SimpleLogin API key.";
}
try {
const json = await response.json();
if (json?.error != null) {
throw "SimpleLogin error:" + json.error;
}
} catch {
// Do nothing...
}
throw "Unknown SimpleLogin error occurred.";
}
private async generateAnonAddyAlias(
apiToken: string,
domain: string,
websiteNote: string
): Promise<string> {
if (apiToken == null || apiToken === "") {
throw "Invalid AnonAddy API token.";
}
const requestInit: RequestInit = {
cache: "no-store",
method: "POST",
headers: new Headers({
Authorization: "Bearer " + apiToken,
"Content-Type": "application/json",
}),
};
const url = "https://app.anonaddy.com/api/v1/aliases";
requestInit.body = JSON.stringify({
domain: domain,
description:
(websiteNote != null ? "Website: " + websiteNote + ". " : "") + "Generated by Bitwarden.",
});
const request = new Request(url, requestInit);
const response = await this.apiService.nativeFetch(request);
if (response.status === 200 || response.status === 201) {
const json = await response.json();
return json?.data?.email;
}
if (response.status === 401) {
throw "Invalid AnonAddy API token.";
}
throw "Unknown AnonAddy error occurred.";
}
private async generateFirefoxRelayAlias(apiToken: string, website: string): Promise<string> {
if (apiToken == null || apiToken === "") {
throw "Invalid Firefox Relay API token.";
}
const requestInit: RequestInit = {
cache: "no-store",
method: "POST",
headers: new Headers({
Authorization: "Token " + apiToken,
"Content-Type": "application/json",
}),
};
const url = "https://relay.firefox.com/api/v1/relayaddresses/";
requestInit.body = JSON.stringify({
enabled: true,
generated_for: website,
description: (website != null ? website + " - " : "") + "Generated by Bitwarden.",
});
const request = new Request(url, requestInit);
const response = await this.apiService.nativeFetch(request);
if (response.status === 200 || response.status === 201) {
const json = await response.json();
return json?.full_address;
}
if (response.status === 401) {
throw "Invalid Firefox Relay API token.";
}
throw "Unknown Firefox Relay error occurred.";
}
}