username generator (#2468)
* username generator * pass usernameWebsite * update jslib ref * update jslib ref * update jslib ref * update jslib ref * Update jslib to point to jslib master * Updated package-lock.json after running npm i * add missing translations * pr feedback Co-authored-by: Daniel James Smith <djsmith@web.de>
This commit is contained in:
parent
4607e9d0ba
commit
bf081e0322
2
jslib
2
jslib
|
@ -1 +1 @@
|
|||
Subproject commit 9950fb42a15bad434a4b404419ff4a87af67a27b
|
||||
Subproject commit fa73c13b8c9ed35cbb9909e342b143fa8a57f1a0
|
|
@ -121,7 +121,7 @@
|
|||
"big-integer": "1.6.48",
|
||||
"browser-hrtime": "^1.1.8",
|
||||
"lunr": "^2.3.9",
|
||||
"node-forge": "^0.10.0",
|
||||
"node-forge": "^1.2.1",
|
||||
"papaparse": "^5.3.0",
|
||||
"rxjs": "^7.4.0",
|
||||
"tldjs": "^2.3.1",
|
||||
|
@ -130,7 +130,7 @@
|
|||
"devDependencies": {
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/node": "^16.11.12",
|
||||
"@types/node-forge": "^0.9.7",
|
||||
"@types/node-forge": "^1.0.1",
|
||||
"@types/papaparse": "^5.2.5",
|
||||
"@types/tldjs": "^2.3.0",
|
||||
"@types/zxcvbn": "^4.4.1",
|
||||
|
@ -1037,9 +1037,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node-forge": {
|
||||
"version": "0.9.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-0.9.10.tgz",
|
||||
"integrity": "sha512-+BbPlhZeYs/WETWftQi2LeRx9VviWSwawNo+Pid5qNrSZHb60loYjpph3OrbwXMMseadu9rE9NeK34r4BHT+QQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.0.1.tgz",
|
||||
"integrity": "sha512-96ELNKv9tQJ19afdBUiM5iDw7OYEc53iUc51gAPR2aGaqRsO1DBROjqgZRjZa1tkPj7TnEOR0EnyAX6iryGkzA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
|
@ -8281,11 +8281,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz",
|
||||
"integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
"node": ">= 6.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
|
@ -12566,14 +12566,14 @@
|
|||
"@microsoft/signalr-protocol-msgpack": "5.0.10",
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/node": "^16.11.12",
|
||||
"@types/node-forge": "^0.9.7",
|
||||
"@types/node-forge": "^1.0.1",
|
||||
"@types/papaparse": "^5.2.5",
|
||||
"@types/tldjs": "^2.3.0",
|
||||
"@types/zxcvbn": "^4.4.1",
|
||||
"big-integer": "1.6.48",
|
||||
"browser-hrtime": "^1.1.8",
|
||||
"lunr": "^2.3.9",
|
||||
"node-forge": "^0.10.0",
|
||||
"node-forge": "^1.2.1",
|
||||
"papaparse": "^5.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.4.0",
|
||||
|
@ -12894,9 +12894,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/node-forge": {
|
||||
"version": "0.9.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-0.9.10.tgz",
|
||||
"integrity": "sha512-+BbPlhZeYs/WETWftQi2LeRx9VviWSwawNo+Pid5qNrSZHb60loYjpph3OrbwXMMseadu9rE9NeK34r4BHT+QQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.0.1.tgz",
|
||||
"integrity": "sha512-96ELNKv9tQJ19afdBUiM5iDw7OYEc53iUc51gAPR2aGaqRsO1DBROjqgZRjZa1tkPj7TnEOR0EnyAX6iryGkzA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
|
@ -18471,9 +18471,9 @@
|
|||
}
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA=="
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz",
|
||||
"integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA=="
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "2.0.1",
|
||||
|
|
|
@ -542,6 +542,12 @@
|
|||
"overwritePasswordConfirmation": {
|
||||
"message": "Are you sure you want to overwrite the current password?"
|
||||
},
|
||||
"overwriteUsername": {
|
||||
"message": "Overwrite Username"
|
||||
},
|
||||
"overwriteUsernameConfirmation": {
|
||||
"message": "Are you sure you want to overwrite the current username?"
|
||||
},
|
||||
"searchFolder": {
|
||||
"message": "Search folder"
|
||||
},
|
||||
|
@ -1233,7 +1239,12 @@
|
|||
"message": "This password was not found in any known data breaches. It should be safe to use."
|
||||
},
|
||||
"baseDomain": {
|
||||
"message": "Base domain"
|
||||
"message": "Base domain",
|
||||
"description": "Domain name. Ex. website.com"
|
||||
},
|
||||
"domainName": {
|
||||
"message": "Domain Name",
|
||||
"description": "Domain name. Ex. website.com"
|
||||
},
|
||||
"host": {
|
||||
"message": "Host",
|
||||
|
@ -1887,5 +1898,44 @@
|
|||
},
|
||||
"error": {
|
||||
"message": "Error"
|
||||
},
|
||||
"regenerateUsername": {
|
||||
"message": "Regenerate Username"
|
||||
},
|
||||
"generateUsername": {
|
||||
"message": "Generate Username"
|
||||
},
|
||||
"usernameType": {
|
||||
"message": "Username Type"
|
||||
},
|
||||
"plusAddressedEmail": {
|
||||
"message": "Plus Addressed Email"
|
||||
},
|
||||
"plusAddressedEmailDesc": {
|
||||
"message": "Use your email provider's sub-addressing capabilities."
|
||||
},
|
||||
"catchallEmail": {
|
||||
"message": "Catch-all Email"
|
||||
},
|
||||
"catchallEmailDesc": {
|
||||
"message": "Use your domain's configured catch-all inbox."
|
||||
},
|
||||
"random": {
|
||||
"message": "Random"
|
||||
},
|
||||
"randomWord": {
|
||||
"message": "Random Word"
|
||||
},
|
||||
"websiteName": {
|
||||
"message": "Website Name"
|
||||
},
|
||||
"whatWouldYouLikeToGenerate": {
|
||||
"message": "What would you like to generate?"
|
||||
},
|
||||
"passwordType": {
|
||||
"message": "Password Type"
|
||||
},
|
||||
"service": {
|
||||
"message": "Service"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import { TokenService as TokenServiceAbstraction } from "jslib-common/abstractio
|
|||
import { TotpService as TotpServiceAbstraction } from "jslib-common/abstractions/totp.service";
|
||||
import { TwoFactorService as TwoFactorServiceAbstraction } from "jslib-common/abstractions/twoFactor.service";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "jslib-common/abstractions/userVerification.service";
|
||||
import { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "jslib-common/abstractions/usernameGeneration.service";
|
||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service";
|
||||
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
|
||||
import { CipherType } from "jslib-common/enums/cipherType";
|
||||
|
@ -66,6 +67,7 @@ import { TokenService } from "jslib-common/services/token.service";
|
|||
import { TotpService } from "jslib-common/services/totp.service";
|
||||
import { TwoFactorService } from "jslib-common/services/twoFactor.service";
|
||||
import { UserVerificationService } from "jslib-common/services/userVerification.service";
|
||||
import { UsernameGenerationService } from "jslib-common/services/usernameGeneration.service";
|
||||
import { WebCryptoFunctionService } from "jslib-common/services/webCryptoFunction.service";
|
||||
|
||||
import { BrowserApi } from "../browser/browserApi";
|
||||
|
@ -136,6 +138,7 @@ export default class MainBackground {
|
|||
keyConnectorService: KeyConnectorServiceAbstraction;
|
||||
userVerificationService: UserVerificationServiceAbstraction;
|
||||
twoFactorService: TwoFactorServiceAbstraction;
|
||||
usernameGenerationService: UsernameGenerationServiceAbstraction;
|
||||
|
||||
onUpdatedRan: boolean;
|
||||
onReplacedRan: boolean;
|
||||
|
@ -200,7 +203,7 @@ export default class MainBackground {
|
|||
}
|
||||
);
|
||||
this.i18nService = new I18nService(BrowserApi.getUILanguage(window));
|
||||
this.cryptoFunctionService = new WebCryptoFunctionService(window, this.platformUtilsService);
|
||||
this.cryptoFunctionService = new WebCryptoFunctionService(window);
|
||||
this.cryptoService = new BrowserCryptoService(
|
||||
this.cryptoFunctionService,
|
||||
this.platformUtilsService,
|
||||
|
@ -475,6 +478,10 @@ export default class MainBackground {
|
|||
this.twoFactorService,
|
||||
this.i18nService
|
||||
);
|
||||
this.usernameGenerationService = new UsernameGenerationService(
|
||||
this.cryptoService,
|
||||
this.stateService
|
||||
);
|
||||
}
|
||||
|
||||
async bootstrap() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<ng-container>
|
||||
<ng-container *ngIf="show">
|
||||
<button type="button" (click)="expand()" appA11yTitle="{{ 'popOutNewWindow' | i18n }}">
|
||||
<i class="bwi bwi-external-link bwi-rotate-270 bwi-lg bwi-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</button>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "passGen" | i18n }}</span>
|
||||
<span class="title">{{ "generator" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right">
|
||||
<button type="button" appBlurClick (click)="select()" *ngIf="showSelect">
|
||||
|
@ -15,58 +15,69 @@
|
|||
</div>
|
||||
</header>
|
||||
<content>
|
||||
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
|
||||
<app-callout type="info" *ngIf="enforcedPasswordPolicyOptions?.inEffect() && type === 'password'">
|
||||
{{ "passwordGeneratorPolicyInEffect" | i18n }}
|
||||
</app-callout>
|
||||
<div class="password-block">
|
||||
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<div class="box-content single-line">
|
||||
<div class="generated-block" *ngIf="type === 'password'">
|
||||
<div class="generated-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row text-primary"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appBlurClick
|
||||
(click)="regenerate()"
|
||||
appA11yTitle="{{ 'copyPassword' | i18n }}"
|
||||
(click)="copy()"
|
||||
>
|
||||
{{ "regeneratePassword" | i18n }}
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="box-content-row text-primary"
|
||||
appStopClick
|
||||
appBlurClick
|
||||
(click)="copy()"
|
||||
appA11yTitle="{{ 'regeneratePassword' | i18n }}"
|
||||
(click)="regenerate()"
|
||||
>
|
||||
{{ "copyPassword" | i18n }}
|
||||
<i class="bwi bwi-lg bwi-generate" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list">
|
||||
<div class="box-content single-line">
|
||||
<a class="box-content-row box-content-row-flex" routerLink="/generator-history">
|
||||
<div class="row-main">{{ "passwordHistory" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</a>
|
||||
<div class="generated-block" *ngIf="type === 'username'">
|
||||
<div class="generated-wrapper" [innerHTML]="username | colorPassword" appSelectCopy></div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'copyUsername' | i18n }}"
|
||||
(click)="copy()"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
appStopClick
|
||||
appBlurClick
|
||||
appA11yTitle="{{ 'regenerateUsername' | i18n }}"
|
||||
(click)="regenerate()"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-generate" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h2 class="box-header">
|
||||
{{ "options" | i18n }}
|
||||
</h2>
|
||||
<div class="box-content">
|
||||
<div class="box-content-row">
|
||||
<label class="sr-only radio-header">{{ "type" | i18n }}</label>
|
||||
<div class="radio-group text-default" appBoxRow *ngFor="let o of passTypeOptions">
|
||||
<label class="radio-header">{{ "whatWouldYouLikeToGenerate" | i18n }}</label>
|
||||
<div class="radio-group text-default" appBoxRow *ngFor="let o of typeOptions">
|
||||
<input
|
||||
type="radio"
|
||||
[(ngModel)]="options.type"
|
||||
[(ngModel)]="type"
|
||||
name="Type_{{ o.value }}"
|
||||
id="type_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="saveOptions()"
|
||||
[checked]="options.type === o.value"
|
||||
(change)="typeChanged()"
|
||||
[checked]="type === o.value"
|
||||
[disabled]="showSelect"
|
||||
/>
|
||||
<label for="type_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
|
@ -75,152 +86,344 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" *ngIf="options.type === 'passphrase'">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="num-words">{{ "numWords" | i18n }}</label>
|
||||
<input
|
||||
id="num-words"
|
||||
type="number"
|
||||
min="3"
|
||||
max="20"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="options.numWords"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
|
||||
<input
|
||||
id="word-separator"
|
||||
type="text"
|
||||
maxlength="1"
|
||||
(input)="saveOptions()"
|
||||
[(ngModel)]="options.wordSeparator"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="capitalize">{{ "capitalize" | i18n }}</label>
|
||||
<input
|
||||
id="capitalize"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="options.capitalize"
|
||||
[disabled]="enforcedPolicyOptions?.capitalize"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="include-number">{{ "includeNumber" | i18n }}</label>
|
||||
<input
|
||||
id="include-number"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="options.includeNumber"
|
||||
[disabled]="enforcedPolicyOptions?.includeNumber"
|
||||
/>
|
||||
<ng-container *ngIf="type === 'password'">
|
||||
<div class="box">
|
||||
<h2 class="box-header">
|
||||
{{ "options" | i18n }}
|
||||
</h2>
|
||||
<div class="box-content">
|
||||
<div class="box-content-row">
|
||||
<label class="radio-header">{{ "passwordType" | i18n }}</label>
|
||||
<div class="radio-group text-default" appBoxRow *ngFor="let o of passTypeOptions">
|
||||
<input
|
||||
type="radio"
|
||||
[(ngModel)]="passwordOptions.type"
|
||||
name="PasswordType_{{ o.value }}"
|
||||
id="passwordtype_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="savePasswordOptions()"
|
||||
[checked]="passwordOptions.type === o.value"
|
||||
/>
|
||||
<label for="passwordtype_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="options.type === 'password'">
|
||||
<div class="box">
|
||||
<div class="box" *ngIf="passwordOptions.type === 'passphrase'">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-slider" appBoxRow>
|
||||
<label for="length">{{ "length" | i18n }}</label>
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="num-words">{{ "numWords" | i18n }}</label>
|
||||
<input
|
||||
id="length"
|
||||
id="num-words"
|
||||
type="number"
|
||||
min="5"
|
||||
max="128"
|
||||
[(ngModel)]="options.length"
|
||||
(change)="saveOptions()"
|
||||
min="3"
|
||||
max="20"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.numWords"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
|
||||
<input
|
||||
id="lengthRange"
|
||||
type="range"
|
||||
min="5"
|
||||
max="128"
|
||||
step="1"
|
||||
[(ngModel)]="options.length"
|
||||
(change)="sliderChanged()"
|
||||
(input)="sliderInput()"
|
||||
id="word-separator"
|
||||
type="text"
|
||||
maxlength="1"
|
||||
(input)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.wordSeparator"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="uppercase">A-Z</label>
|
||||
<label for="capitalize">{{ "capitalize" | i18n }}</label>
|
||||
<input
|
||||
id="uppercase"
|
||||
id="capitalize"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
attr.aria-label="{{ 'uppercase' | i18n }}"
|
||||
[disabled]="enforcedPolicyOptions.useUppercase"
|
||||
[(ngModel)]="options.uppercase"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.capitalize"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.capitalize"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="lowercase">a-z</label>
|
||||
<label for="include-number">{{ "includeNumber" | i18n }}</label>
|
||||
<input
|
||||
id="lowercase"
|
||||
id="include-number"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
attr.aria-label="{{ 'lowercase' | i18n }}"
|
||||
[disabled]="enforcedPolicyOptions.useLowercase"
|
||||
[(ngModel)]="options.lowercase"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="numbers">0-9</label>
|
||||
<input
|
||||
id="numbers"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
attr.aria-label="{{ 'numbers' | i18n }}"
|
||||
[disabled]="enforcedPolicyOptions.useNumbers"
|
||||
[(ngModel)]="options.number"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="special">!@#$%^&*</label>
|
||||
<input
|
||||
id="special"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
attr.aria-label="{{ 'specialCharacters' | i18n }}"
|
||||
[disabled]="enforcedPolicyOptions.useSpecial"
|
||||
[(ngModel)]="options.special"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.includeNumber"
|
||||
[disabled]="enforcedPasswordPolicyOptions?.includeNumber"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="passwordOptions.type === 'password'">
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-slider" appBoxRow>
|
||||
<label for="length">{{ "length" | i18n }}</label>
|
||||
<input
|
||||
id="length"
|
||||
type="number"
|
||||
min="5"
|
||||
max="128"
|
||||
[(ngModel)]="passwordOptions.length"
|
||||
(change)="savePasswordOptions()"
|
||||
/>
|
||||
<input
|
||||
id="lengthRange"
|
||||
type="range"
|
||||
min="5"
|
||||
max="128"
|
||||
step="1"
|
||||
[(ngModel)]="passwordOptions.length"
|
||||
(change)="sliderChanged()"
|
||||
(input)="sliderInput()"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="uppercase">A-Z</label>
|
||||
<input
|
||||
id="uppercase"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
attr.aria-label="{{ 'uppercase' | i18n }}"
|
||||
[disabled]="enforcedPasswordPolicyOptions.useUppercase"
|
||||
[(ngModel)]="passwordOptions.uppercase"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="lowercase">a-z</label>
|
||||
<input
|
||||
id="lowercase"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
attr.aria-label="{{ 'lowercase' | i18n }}"
|
||||
[disabled]="enforcedPasswordPolicyOptions.useLowercase"
|
||||
[(ngModel)]="passwordOptions.lowercase"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="numbers">0-9</label>
|
||||
<input
|
||||
id="numbers"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
attr.aria-label="{{ 'numbers' | i18n }}"
|
||||
[disabled]="enforcedPasswordPolicyOptions.useNumbers"
|
||||
[(ngModel)]="passwordOptions.number"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="special">!@#$%^&*</label>
|
||||
<input
|
||||
id="special"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
attr.aria-label="{{ 'specialCharacters' | i18n }}"
|
||||
[disabled]="enforcedPasswordPolicyOptions.useSpecial"
|
||||
[(ngModel)]="passwordOptions.special"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="min-number">{{ "minNumbers" | i18n }}</label>
|
||||
<input
|
||||
id="min-number"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.minNumber"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="min-special">{{ "minSpecial" | i18n }}</label>
|
||||
<input
|
||||
id="min-special"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="passwordOptions.minSpecial"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="ambiguous">{{ "avoidAmbChar" | i18n }}</label>
|
||||
<input
|
||||
id="ambiguous"
|
||||
type="checkbox"
|
||||
(change)="savePasswordOptions()"
|
||||
[(ngModel)]="avoidAmbiguous"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="box list">
|
||||
<div class="box-content single-line">
|
||||
<a class="box-content-row box-content-row-flex" routerLink="/generator-history">
|
||||
<div class="row-main">{{ "passwordHistory" | i18n }}</div>
|
||||
<i class="bwi bwi-angle-right bwi-lg row-sub-icon" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="type === 'username'">
|
||||
<div class="box">
|
||||
<h2 class="box-header">
|
||||
{{ "options" | i18n }}
|
||||
</h2>
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="min-number">{{ "minNumbers" | i18n }}</label>
|
||||
<div class="box-content-row">
|
||||
<label class="radio-header">{{ "usernameType" | i18n }}</label>
|
||||
<div
|
||||
class="radio-group align-start text-default"
|
||||
appBoxRow
|
||||
*ngFor="let o of usernameTypeOptions"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
[(ngModel)]="usernameOptions.type"
|
||||
name="Type_{{ o.value }}"
|
||||
id="type_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="saveUsernameOptions()"
|
||||
[checked]="usernameOptions.type === o.value"
|
||||
/>
|
||||
<label for="type_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
<div class="small text-muted" *ngIf="o.desc">{{ o.desc }}</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" *ngIf="usernameOptions.type === 'forwarded'">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row">
|
||||
<label class="radio-header">{{ "service" | i18n }}</label>
|
||||
<div class="radio-group text-default" appBoxRow *ngFor="let o of forwardOptions">
|
||||
<input
|
||||
type="radio"
|
||||
[(ngModel)]="usernameOptions.forwardedService"
|
||||
name="ForwardType_{{ o.value }}"
|
||||
id="forwardtype_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="saveUsernameOptions()"
|
||||
[checked]="usernameOptions.forwardedService === o.value"
|
||||
/>
|
||||
<label for="forwardtype_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" *ngIf="usernameOptions.type === 'subaddress'">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="subaddress-email">{{ "emailAddress" | i18n }}</label>
|
||||
<input
|
||||
id="min-number"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="options.minNumber"
|
||||
id="subaddress-email"
|
||||
type="text"
|
||||
name="SubaddressEmail"
|
||||
[(ngModel)]="usernameOptions.subaddressEmail"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||
<label for="min-special">{{ "minSpecial" | i18n }}</label>
|
||||
<div class="box-content-row" *ngIf="subaddressOptions.length > 1">
|
||||
<label class="radio-header">{{ "type" | i18n }}</label>
|
||||
<div class="radio-group text-default" appBoxRow *ngFor="let o of subaddressOptions">
|
||||
<input
|
||||
type="radio"
|
||||
[(ngModel)]="usernameOptions.subaddressType"
|
||||
name="SubaddressType_{{ o.value }}"
|
||||
id="subaddresstype_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="saveUsernameOptions()"
|
||||
[checked]="usernameOptions.subaddressType === o.value"
|
||||
/>
|
||||
<label for="subaddresstype_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow *ngIf="showWebsiteOption">
|
||||
<label for="subaddress-website">{{ "website" | i18n }}</label>
|
||||
<input
|
||||
id="min-special"
|
||||
type="number"
|
||||
min="0"
|
||||
max="9"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="options.minSpecial"
|
||||
id="subaddress-website"
|
||||
type="text"
|
||||
name="SubaddressWebsite"
|
||||
[value]="usernameOptions.website"
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" *ngIf="usernameOptions.type === 'catchall'">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="catchall-domain">{{ "domainName" | i18n }}</label>
|
||||
<input
|
||||
id="catchall-domain"
|
||||
type="text"
|
||||
name="CatchallDomain"
|
||||
[(ngModel)]="usernameOptions.catchallDomain"
|
||||
(blur)="saveUsernameOptions()"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row" *ngIf="catchallOptions.length > 1">
|
||||
<label class="radio-header">{{ "type" | i18n }}</label>
|
||||
<div class="radio-group text-default" appBoxRow *ngFor="let o of catchallOptions">
|
||||
<input
|
||||
type="radio"
|
||||
[(ngModel)]="usernameOptions.catchallType"
|
||||
name="CatchallType_{{ o.value }}"
|
||||
id="catchalltype_{{ o.value }}"
|
||||
[value]="o.value"
|
||||
(change)="saveUsernameOptions()"
|
||||
[checked]="usernameOptions.catchallType === o.value"
|
||||
/>
|
||||
<label for="catchalltype_{{ o.value }}">
|
||||
{{ o.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow *ngIf="showWebsiteOption">
|
||||
<label for="catchall-website">{{ "website" | i18n }}</label>
|
||||
<input
|
||||
id="catchall-website"
|
||||
type="text"
|
||||
name="CatchallWebsite"
|
||||
[value]="usernameOptions.website"
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box" *ngIf="usernameOptions.type === 'word'">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="capitalize">{{ "capitalize" | i18n }}</label>
|
||||
<input
|
||||
id="capitalize"
|
||||
type="checkbox"
|
||||
(change)="saveUsernameOptions()"
|
||||
[(ngModel)]="usernameOptions.wordCapitalize"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="ambiguous">{{ "avoidAmbChar" | i18n }}</label>
|
||||
<label for="include-number">{{ "includeNumber" | i18n }}</label>
|
||||
<input
|
||||
id="ambiguous"
|
||||
id="include-number"
|
||||
type="checkbox"
|
||||
(change)="saveOptions()"
|
||||
[(ngModel)]="avoidAmbiguous"
|
||||
(change)="saveUsernameOptions()"
|
||||
[(ngModel)]="usernameOptions.wordIncludeNumber"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { Location } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.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";
|
||||
import { UsernameGenerationService } from "jslib-common/abstractions/usernameGeneration.service";
|
||||
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||
|
||||
@Component({
|
||||
|
@ -13,30 +15,52 @@ import { CipherView } from "jslib-common/models/view/cipherView";
|
|||
templateUrl: "password-generator.component.html",
|
||||
})
|
||||
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
|
||||
private addEditCipherInfo: any;
|
||||
private cipherState: CipherView;
|
||||
|
||||
constructor(
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
usernameGenerationService: UsernameGenerationService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
private stateService: StateService,
|
||||
stateService: StateService,
|
||||
route: ActivatedRoute,
|
||||
private location: Location
|
||||
) {
|
||||
super(passwordGenerationService, platformUtilsService, i18nService, window);
|
||||
super(
|
||||
passwordGenerationService,
|
||||
usernameGenerationService,
|
||||
platformUtilsService,
|
||||
stateService,
|
||||
i18nService,
|
||||
route,
|
||||
window
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
const addEditCipherInfo = await this.stateService.getAddEditCipherInfo();
|
||||
if (addEditCipherInfo != null) {
|
||||
this.cipherState = addEditCipherInfo.cipher;
|
||||
this.addEditCipherInfo = await this.stateService.getAddEditCipherInfo();
|
||||
if (this.addEditCipherInfo != null) {
|
||||
this.cipherState = this.addEditCipherInfo.cipher;
|
||||
}
|
||||
this.showSelect = this.cipherState != null;
|
||||
this.showWebsiteOption =
|
||||
this.cipherState?.login?.hasUris && this.cipherState.login.uris[0].hostname != null;
|
||||
if (this.showWebsiteOption) {
|
||||
this.usernameWebsite = this.cipherState.login.uris[0].hostname;
|
||||
}
|
||||
await super.ngOnInit();
|
||||
}
|
||||
|
||||
select() {
|
||||
super.select();
|
||||
this.cipherState.login.password = this.password;
|
||||
if (this.type === "password") {
|
||||
this.cipherState.login.password = this.password;
|
||||
} else if (this.type === "username") {
|
||||
this.cipherState.login.username = this.username;
|
||||
}
|
||||
this.addEditCipherInfo.cipher = this.cipherState;
|
||||
this.stateService.setAddEditCipherInfo(this.addEditCipherInfo);
|
||||
this.close();
|
||||
}
|
||||
|
||||
|
|
|
@ -677,5 +677,14 @@
|
|||
color: themed("textColor");
|
||||
}
|
||||
}
|
||||
|
||||
&.align-start {
|
||||
align-items: start;
|
||||
margin-top: 10px;
|
||||
|
||||
label {
|
||||
margin-top: -4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,28 @@ app-sync {
|
|||
}
|
||||
}
|
||||
|
||||
app-password-generator .password-block {
|
||||
app-password-generator .generated-block {
|
||||
font-size: $font-size-large;
|
||||
font-family: $font-family-monospace;
|
||||
margin: 20px;
|
||||
display: flex;
|
||||
|
||||
.password-wrapper {
|
||||
text-align: center;
|
||||
.generated-wrapper {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
|
||||
button {
|
||||
padding-left: 5px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import { TokenService } from "jslib-common/abstractions/token.service";
|
|||
import { TotpService } from "jslib-common/abstractions/totp.service";
|
||||
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
|
||||
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
|
||||
import { UsernameGenerationService } from "jslib-common/abstractions/usernameGeneration.service";
|
||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||
import { ThemeType } from "jslib-common/enums/themeType";
|
||||
import { AuthService } from "jslib-common/services/auth.service";
|
||||
|
@ -311,6 +312,11 @@ export function initFactory(
|
|||
useFactory: getBgService<StateServiceAbstraction>("stateService"),
|
||||
deps: [],
|
||||
},
|
||||
{
|
||||
provide: UsernameGenerationService,
|
||||
useFactory: getBgService<UsernameGenerationService>("usernameGenerationService"),
|
||||
deps: [],
|
||||
},
|
||||
{
|
||||
provide: BaseStateServiceAbstraction,
|
||||
useExisting: StateServiceAbstraction,
|
||||
|
|
|
@ -34,16 +34,30 @@
|
|||
</div>
|
||||
<!-- Login -->
|
||||
<div *ngIf="cipher.type === cipherType.Login">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="loginUsername">{{ "username" | i18n }}</label>
|
||||
<input
|
||||
id="loginUsername"
|
||||
type="text"
|
||||
name="Login.Username"
|
||||
[(ngModel)]="cipher.login.username"
|
||||
inputmode="email"
|
||||
appInputVerbatim
|
||||
/>
|
||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||
<div class="row-main">
|
||||
<label for="loginUsername">{{ "username" | i18n }}</label>
|
||||
<input
|
||||
id="loginUsername"
|
||||
type="text"
|
||||
name="Login.Username"
|
||||
[(ngModel)]="cipher.login.username"
|
||||
inputmode="email"
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appBlurClick
|
||||
appA11yTitle="{{ 'generateUsername' | i18n }}"
|
||||
(click)="generateUsername()"
|
||||
>
|
||||
<i class="bwi bwi-lg bwi-generate" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||
<div class="row-main">
|
||||
|
|
|
@ -182,17 +182,20 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||
this.location.back();
|
||||
}
|
||||
|
||||
async generateUsername(): Promise<boolean> {
|
||||
const confirmed = await super.generateUsername();
|
||||
if (confirmed) {
|
||||
await this.saveCipherState();
|
||||
this.router.navigate(["generator"], { queryParams: { type: "username" } });
|
||||
}
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
async generatePassword(): Promise<boolean> {
|
||||
const confirmed = await super.generatePassword();
|
||||
if (confirmed) {
|
||||
this.stateService.setAddEditCipherInfo({
|
||||
cipher: this.cipher,
|
||||
collectionIds:
|
||||
this.collections == null
|
||||
? []
|
||||
: this.collections.filter((c) => (c as any).checked).map((c) => c.id),
|
||||
});
|
||||
this.router.navigate(["generator"]);
|
||||
await this.saveCipherState();
|
||||
this.router.navigate(["generator"], { queryParams: { type: "password" } });
|
||||
}
|
||||
return confirmed;
|
||||
}
|
||||
|
@ -217,4 +220,14 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||
(this.ownershipOptions.length > 1 || !this.allowPersonal)
|
||||
);
|
||||
}
|
||||
|
||||
private saveCipherState() {
|
||||
return this.stateService.setAddEditCipherInfo({
|
||||
cipher: this.cipher,
|
||||
collectionIds:
|
||||
this.collections == null
|
||||
? []
|
||||
: this.collections.filter((c) => (c as any).checked).map((c) => c.id),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue