mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
Username generator (#734)
* add support for username generation * remove unused Router * pr feedback
This commit is contained in:
@@ -265,6 +265,10 @@ export abstract class StateService<T extends Account = Account> {
|
||||
) => Promise<void>;
|
||||
getPasswordGenerationOptions: (options?: StorageOptions) => Promise<any>;
|
||||
setPasswordGenerationOptions: (value: any, options?: StorageOptions) => Promise<void>;
|
||||
getUsernameGenerationOptions: (options?: StorageOptions) => Promise<any>;
|
||||
setUsernameGenerationOptions: (value: any, options?: StorageOptions) => Promise<void>;
|
||||
getGeneratorOptions: (options?: StorageOptions) => Promise<any>;
|
||||
setGeneratorOptions: (value: any, options?: StorageOptions) => Promise<void>;
|
||||
getProtectedPin: (options?: StorageOptions) => Promise<string>;
|
||||
setProtectedPin: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getProviders: (options?: StorageOptions) => Promise<{ [id: string]: ProviderData }>;
|
||||
|
||||
8
common/src/abstractions/usernameGeneration.service.ts
Normal file
8
common/src/abstractions/usernameGeneration.service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export abstract class UsernameGenerationService {
|
||||
generateUsername: (options: any) => Promise<string>;
|
||||
generateWord: (options: any) => Promise<string>;
|
||||
generateSubaddress: (options: any) => Promise<string>;
|
||||
generateCatchall: (options: any) => Promise<string>;
|
||||
getOptions: () => Promise<any>;
|
||||
saveOptions: (options: any) => Promise<void>;
|
||||
}
|
||||
@@ -125,6 +125,8 @@ export class AccountSettings {
|
||||
minimizeOnCopyToClipboard?: boolean;
|
||||
neverDomains?: { [id: string]: any };
|
||||
passwordGenerationOptions?: any;
|
||||
usernameGenerationOptions?: any;
|
||||
generatorOptions?: any;
|
||||
pinProtected?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>();
|
||||
protectedPin?: string;
|
||||
settings?: any; // TODO: Merge whatever is going on here into the AccountSettings model properly
|
||||
|
||||
@@ -1797,6 +1797,40 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getUsernameGenerationOptions(options?: StorageOptions): Promise<any> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||
)?.settings?.usernameGenerationOptions;
|
||||
}
|
||||
|
||||
async setUsernameGenerationOptions(value: any, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||
);
|
||||
account.settings.usernameGenerationOptions = value;
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||
);
|
||||
}
|
||||
|
||||
async getGeneratorOptions(options?: StorageOptions): Promise<any> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||
)?.settings?.generatorOptions;
|
||||
}
|
||||
|
||||
async setGeneratorOptions(value: any, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||
);
|
||||
account.settings.generatorOptions = value;
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||
);
|
||||
}
|
||||
|
||||
async getProtectedPin(options?: StorageOptions): Promise<string> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||
|
||||
128
common/src/services/usernameGeneration.service.ts
Normal file
128
common/src/services/usernameGeneration.service.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { UsernameGenerationService as BaseUsernameGenerationService } from "../abstractions/usernameGeneration.service";
|
||||
import { EEFLongWordList } from "../misc/wordlist";
|
||||
|
||||
const DefaultOptions = {
|
||||
type: "word",
|
||||
wordCapitalize: true,
|
||||
wordIncludeNumber: true,
|
||||
subaddressType: "random",
|
||||
catchallType: "random",
|
||||
};
|
||||
|
||||
export class UsernameGenerationService implements BaseUsernameGenerationService {
|
||||
constructor(private cryptoService: CryptoService, private stateService: StateService) {}
|
||||
|
||||
generateUsername(options: any): Promise<string> {
|
||||
if (options.type === "catchall") {
|
||||
return this.generateCatchall(options);
|
||||
} else if (options.type === "subaddress") {
|
||||
return this.generateSubaddress(options);
|
||||
} else if (options.type === "forwarded") {
|
||||
return this.generateSubaddress(options);
|
||||
} else {
|
||||
return this.generateWord(options);
|
||||
}
|
||||
}
|
||||
|
||||
async generateWord(options: any): Promise<string> {
|
||||
const o = Object.assign({}, DefaultOptions, options);
|
||||
|
||||
if (o.wordCapitalize == null) {
|
||||
o.wordCapitalize = true;
|
||||
}
|
||||
if (o.wordIncludeNumber == null) {
|
||||
o.wordIncludeNumber = true;
|
||||
}
|
||||
|
||||
const wordIndex = await this.cryptoService.randomNumber(0, EEFLongWordList.length - 1);
|
||||
let word = EEFLongWordList[wordIndex];
|
||||
if (o.wordCapitalize) {
|
||||
word = word.charAt(0).toUpperCase() + word.slice(1);
|
||||
}
|
||||
if (o.wordIncludeNumber) {
|
||||
const num = await this.cryptoService.randomNumber(1, 9999);
|
||||
word = word + this.zeroPad(num.toString(), 4);
|
||||
}
|
||||
return word;
|
||||
}
|
||||
|
||||
async generateSubaddress(options: any): Promise<string> {
|
||||
const o = Object.assign({}, DefaultOptions, options);
|
||||
|
||||
const subaddressEmail = o.subaddressEmail;
|
||||
if (subaddressEmail == null || subaddressEmail.length < 3) {
|
||||
return o.subaddressEmail;
|
||||
}
|
||||
const atIndex = subaddressEmail.indexOf("@");
|
||||
if (atIndex < 1 || atIndex >= subaddressEmail.length - 1) {
|
||||
return subaddressEmail;
|
||||
}
|
||||
if (o.subaddressType == null) {
|
||||
o.subaddressType = "random";
|
||||
}
|
||||
|
||||
const emailBeginning = subaddressEmail.substr(0, atIndex);
|
||||
const emailEnding = subaddressEmail.substr(atIndex + 1, subaddressEmail.length);
|
||||
|
||||
let subaddressString = "";
|
||||
if (o.subaddressType === "random") {
|
||||
subaddressString = await this.randomString(8);
|
||||
} else if (o.subaddressType === "website-name") {
|
||||
subaddressString = o.website;
|
||||
}
|
||||
return emailBeginning + "+" + subaddressString + "@" + emailEnding;
|
||||
}
|
||||
|
||||
async generateCatchall(options: any): Promise<string> {
|
||||
const o = Object.assign({}, DefaultOptions, options);
|
||||
|
||||
if (o.catchallDomain == null || o.catchallDomain === "") {
|
||||
return null;
|
||||
}
|
||||
if (o.catchallType == null) {
|
||||
o.catchallType = "random";
|
||||
}
|
||||
|
||||
let startString = "";
|
||||
if (o.catchallType === "random") {
|
||||
startString = await this.randomString(8);
|
||||
} else if (o.catchallType === "website-name") {
|
||||
startString = o.website;
|
||||
}
|
||||
return startString + "@" + o.catchallDomain;
|
||||
}
|
||||
|
||||
async getOptions(): Promise<any> {
|
||||
let options = await this.stateService.getUsernameGenerationOptions();
|
||||
if (options == null) {
|
||||
options = DefaultOptions;
|
||||
} else {
|
||||
options = Object.assign({}, DefaultOptions, options);
|
||||
}
|
||||
await this.stateService.setUsernameGenerationOptions(options);
|
||||
return options;
|
||||
}
|
||||
|
||||
async saveOptions(options: any) {
|
||||
await this.stateService.setUsernameGenerationOptions(options);
|
||||
}
|
||||
|
||||
private async randomString(length: number) {
|
||||
let str = "";
|
||||
const charSet = "abcdefghijklmnopqrstuvwxyz1234567890";
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomCharIndex = await this.cryptoService.randomNumber(0, charSet.length - 1);
|
||||
str += charSet.charAt(randomCharIndex);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// ref: https://stackoverflow.com/a/10073788
|
||||
private zeroPad(number: string, width: number) {
|
||||
return number.length >= width
|
||||
? number
|
||||
: new Array(width - number.length + 1).join("0") + number;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user