mirror of
https://github.com/bitwarden/browser.git
synced 2024-10-29 07:59:42 +01: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:
parent
e40e7de808
commit
fe65a337c8
@ -3,6 +3,7 @@ import { ActivatedRoute } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
@ -15,6 +16,7 @@ export class GeneratorComponent implements OnInit {
|
||||
@Input() type: string;
|
||||
@Output() onSelected = new EventEmitter<string>();
|
||||
|
||||
usernameGeneratingPromise: Promise<string>;
|
||||
typeOptions: any[];
|
||||
passTypeOptions: any[];
|
||||
usernameTypeOptions: any[];
|
||||
@ -36,6 +38,7 @@ export class GeneratorComponent implements OnInit {
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected stateService: StateService,
|
||||
protected i18nService: I18nService,
|
||||
protected logService: LogService,
|
||||
protected route: ActivatedRoute,
|
||||
private win: Window
|
||||
) {
|
||||
@ -58,13 +61,20 @@ export class GeneratorComponent implements OnInit {
|
||||
value: "catchall",
|
||||
desc: i18nService.t("catchallEmailDesc"),
|
||||
},
|
||||
{
|
||||
name: i18nService.t("forwardedEmail"),
|
||||
value: "forwarded",
|
||||
desc: i18nService.t("forwardedEmailDesc"),
|
||||
},
|
||||
{ name: i18nService.t("randomWord"), value: "word" },
|
||||
];
|
||||
this.subaddressOptions = [{ name: i18nService.t("random"), value: "random" }];
|
||||
this.catchallOptions = [{ name: i18nService.t("random"), value: "random" }];
|
||||
this.forwardOptions = [
|
||||
{ name: "SimpleLogin", value: "simplelogin" },
|
||||
{ name: "FastMail", value: "fastmail" },
|
||||
{ name: "AnonAddy", value: "anonaddy" },
|
||||
{ name: "Firefox Relay", value: "firefoxrelay" },
|
||||
// { name: "FastMail", value: "fastmail" },
|
||||
];
|
||||
}
|
||||
|
||||
@ -104,13 +114,17 @@ export class GeneratorComponent implements OnInit {
|
||||
this.type = generatorOptions?.type ?? "password";
|
||||
}
|
||||
}
|
||||
await this.regenerate();
|
||||
if (this.regenerateWithoutButtonPress()) {
|
||||
await this.regenerate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async typeChanged() {
|
||||
await this.stateService.setGeneratorOptions({ type: this.type });
|
||||
await this.regenerate();
|
||||
if (this.regenerateWithoutButtonPress()) {
|
||||
await this.regenerate();
|
||||
}
|
||||
}
|
||||
|
||||
async regenerate() {
|
||||
@ -135,14 +149,17 @@ export class GeneratorComponent implements OnInit {
|
||||
this.normalizePasswordOptions();
|
||||
await this.passwordGenerationService.saveOptions(this.passwordOptions);
|
||||
|
||||
if (regenerate) {
|
||||
if (regenerate && this.regenerateWithoutButtonPress()) {
|
||||
await this.regeneratePassword();
|
||||
}
|
||||
}
|
||||
|
||||
async saveUsernameOptions(regenerate = true) {
|
||||
await this.usernameGenerationService.saveOptions(this.usernameOptions);
|
||||
if (regenerate) {
|
||||
if (this.usernameOptions.type === "forwarded") {
|
||||
this.username = "-";
|
||||
}
|
||||
if (regenerate && this.regenerateWithoutButtonPress()) {
|
||||
await this.regenerateUsername();
|
||||
}
|
||||
}
|
||||
@ -157,9 +174,16 @@ export class GeneratorComponent implements OnInit {
|
||||
}
|
||||
|
||||
async generateUsername() {
|
||||
this.username = await this.usernameGenerationService.generateUsername(this.usernameOptions);
|
||||
if (this.username === "" || this.username === null) {
|
||||
this.username = "-";
|
||||
try {
|
||||
this.usernameGeneratingPromise = this.usernameGenerationService.generateUsername(
|
||||
this.usernameOptions
|
||||
);
|
||||
this.username = await this.usernameGeneratingPromise;
|
||||
if (this.username === "" || this.username === null) {
|
||||
this.username = "-";
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,6 +209,10 @@ export class GeneratorComponent implements OnInit {
|
||||
this.showOptions = !this.showOptions;
|
||||
}
|
||||
|
||||
regenerateWithoutButtonPress() {
|
||||
return this.type !== "username" || this.usernameOptions.type !== "forwarded";
|
||||
}
|
||||
|
||||
private normalizePasswordOptions() {
|
||||
// Application level normalize options depedent on class variables
|
||||
this.passwordOptions.ambiguous = !this.avoidAmbiguous;
|
||||
|
@ -232,7 +232,7 @@ export const CLIENT_TYPE = new InjectionToken<boolean>("CLIENT_TYPE");
|
||||
{
|
||||
provide: UsernameGenerationServiceAbstraction,
|
||||
useClass: UsernameGenerationService,
|
||||
deps: [CryptoServiceAbstraction, StateServiceAbstraction],
|
||||
deps: [CryptoServiceAbstraction, StateServiceAbstraction, ApiServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: ApiServiceAbstraction,
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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.";
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user