1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

password generator

This commit is contained in:
Kyle Spearrin
2018-04-09 17:35:16 -04:00
parent 82f3c39167
commit 58f920821d
17 changed files with 316 additions and 6 deletions

View File

@@ -1085,5 +1085,8 @@
}, },
"allItems": { "allItems": {
"message": "All Items" "message": "All Items"
},
"noPasswordsInList": {
"message": "There are no passwords to list."
} }
} }

View File

@@ -103,4 +103,10 @@ export const routerTransition = trigger('routerTransition', [
transition('tabs => add-cipher, ciphers => add-cipher', inSlideUp), transition('tabs => add-cipher, ciphers => add-cipher', inSlideUp),
transition('add-cipher => tabs, add-cipher => ciphers', outSlideDown), transition('add-cipher => tabs, add-cipher => ciphers', outSlideDown),
transition('generator => generator-history', inSlideLeft),
transition('generator-history => generator', outSlideRight),
transition('add-cipher => generator, edit-cipher => generator, tabs => generator', inSlideUp),
transition('generator => add-cipher, generator => edit-cipher, generator => tabs', outSlideDown),
]); ]);

View File

@@ -14,7 +14,11 @@ import { LoginComponent } from './accounts/login.component';
import { RegisterComponent } from './accounts/register.component'; import { RegisterComponent } from './accounts/register.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component'; import { TwoFactorComponent } from './accounts/two-factor.component';
import { SettingsComponent } from './settings/settings.component';
import { TabsComponent } from './tabs.component'; import { TabsComponent } from './tabs.component';
import { PasswordGeneratorComponent } from './tools/password-generator.component';
import { PasswordGeneratorHistoryComponent } from './tools/password-generator-history.component';
import { ToolsComponent } from './tools/tools.component';
import { AddEditComponent } from './vault/add-edit.component'; import { AddEditComponent } from './vault/add-edit.component';
import { CiphersComponent } from './vault/ciphers.component'; import { CiphersComponent } from './vault/ciphers.component';
import { CurrentTabComponent } from './vault/current-tab.component'; import { CurrentTabComponent } from './vault/current-tab.component';
@@ -36,6 +40,8 @@ const routes: Routes = [
{ path: 'view-cipher', component: ViewComponent, data: { state: 'view-cipher' } }, { path: 'view-cipher', component: ViewComponent, data: { state: 'view-cipher' } },
{ path: 'add-cipher', component: AddEditComponent, data: { state: 'add-cipher' } }, { path: 'add-cipher', component: AddEditComponent, data: { state: 'add-cipher' } },
{ path: 'edit-cipher', component: AddEditComponent, data: { state: 'edit-cipher' } }, { path: 'edit-cipher', component: AddEditComponent, data: { state: 'edit-cipher' } },
{ path: 'generator', component: PasswordGeneratorComponent, data: { state: 'generator' } },
{ path: 'generator-history', component: PasswordGeneratorHistoryComponent, data: { state: 'generator-history' } },
{ {
path: 'tabs', component: TabsComponent, path: 'tabs', component: TabsComponent,
data: { state: 'tabs' }, data: { state: 'tabs' },
@@ -52,7 +58,19 @@ const routes: Routes = [
component: GroupingsComponent, component: GroupingsComponent,
canActivate: [AuthGuardService], canActivate: [AuthGuardService],
data: { state: 'tabs_vault' }, data: { state: 'tabs_vault' },
} },
{
path: 'tools',
component: ToolsComponent,
canActivate: [AuthGuardService],
data: { state: 'tabs_tools' },
},
{
path: 'settings',
component: SettingsComponent,
canActivate: [AuthGuardService],
data: { state: 'tabs_settings' },
},
] ]
} }
]; ];

View File

@@ -92,6 +92,9 @@ export class AppComponent implements OnInit {
this.stateService.remove('GroupingsComponent'); this.stateService.remove('GroupingsComponent');
this.stateService.remove('CiphersComponent'); this.stateService.remove('CiphersComponent');
} }
if (url.startsWith('/tabs/')) {
this.stateService.remove('addEditCipher');
}
this.previousUrl = url; this.previousUrl = url;
} }
}); });

View File

@@ -23,7 +23,11 @@ import { LoginComponent } from './accounts/login.component';
import { RegisterComponent } from './accounts/register.component'; import { RegisterComponent } from './accounts/register.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component'; import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component'; import { TwoFactorComponent } from './accounts/two-factor.component';
import { SettingsComponent } from './settings/settings.component';
import { TabsComponent } from './tabs.component'; import { TabsComponent } from './tabs.component';
import { PasswordGeneratorComponent } from './tools/password-generator.component';
import { PasswordGeneratorHistoryComponent } from './tools/password-generator-history.component';
import { ToolsComponent } from './tools/tools.component';
import { AddEditComponent } from './vault/add-edit.component'; import { AddEditComponent } from './vault/add-edit.component';
import { CiphersComponent } from './vault/ciphers.component'; import { CiphersComponent } from './vault/ciphers.component';
import { CurrentTabComponent } from './vault/current-tab.component'; import { CurrentTabComponent } from './vault/current-tab.component';
@@ -81,12 +85,16 @@ import { IconComponent } from 'jslib/angular/components/icon.component';
IconComponent, IconComponent,
LockComponent, LockComponent,
LoginComponent, LoginComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PopOutComponent, PopOutComponent,
RegisterComponent, RegisterComponent,
SearchCiphersPipe, SearchCiphersPipe,
SettingsComponent,
StopClickDirective, StopClickDirective,
StopPropDirective, StopPropDirective,
TabsComponent, TabsComponent,
ToolsComponent,
TwoFactorOptionsComponent, TwoFactorOptionsComponent,
TwoFactorComponent, TwoFactorComponent,
ViewComponent, ViewComponent,

View File

@@ -0,0 +1,16 @@
<header>
<div class="left">
<app-pop-out></app-pop-out>
</div>
<div class="center">
<span class="title">{{'settings' | i18n}}</span>
</div>
<div class="right"></div>
</header>
<content>
<div class="box list">
<div class="box-content">
</div>
</div>
</content>

View File

@@ -0,0 +1,7 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-settings',
templateUrl: 'settings.component.html',
})
export class SettingsComponent { }

View File

@@ -0,0 +1,39 @@
<header>
<div class="left">
<button appBlurClick type="button" (click)="close()">
<i class="fa fa-chevron-left"></i>
<span>{{'back' | i18n}}</span>
</button>
</div>
<div class="center">
<span class="title">{{'passwordHistory' | i18n}}</span>
</div>
<div class="right">
<button appBlurClick type="button" (click)="clear()">
{{'clear' | i18n}}
</button>
</div>
</header>
<content>
<div class="box list full-list" *ngIf="history && history.length">
<div class="box-content">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<span class="text monospaced no-ellipsis">
{{h.password}}
</span>
<span class="detail">{{h.date | date:'medium'}}</span>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick title="{{'copyPassword' | i18n}}"
(click)="copy(h.password)">
<i class="fa fa-lg fa-clipboard"></i>
</a>
</div>
</div>
</div>
</div>
<div class="no-items" *ngIf="!history || !history.length">
<p>{{'noPasswordsInList' | i18n}}</p>
</div>
</content>

View File

@@ -0,0 +1,29 @@
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { Location } from '@angular/common';
import { Component } from '@angular/core';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import {
PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent
} from 'jslib/angular/components/password-generator-history.component';
@Component({
selector: 'app-password-generator-history',
templateUrl: 'password-generator-history.component.html',
})
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
constructor(passwordGenerationService: PasswordGenerationService, analytics: Angulartics2,
platformUtilsService: PlatformUtilsService, i18nService: I18nService,
toasterService: ToasterService, private location: Location) {
super(passwordGenerationService, analytics, platformUtilsService, i18nService, toasterService);
}
close() {
this.location.back();
}
}

View File

@@ -0,0 +1,79 @@
<header>
<div class="left">
<button type="button" appBlurClick (click)="close()">{{'close' | i18n}}</button>
</div>
<div class="center">
<span class="title">{{'passGen' | i18n}}</span>
</div>
<div class="right">
<button type="button" appBlurClick (click)="select()" *ngIf="showSelect">{{'select' | i18n}}</button>
</div>
</header>
<content>
<div class="password-block">{{password}}</div>
<div class="box">
<div class="box-content">
<a class="box-content-row" href="#" appStopClick appBlurClick
(click)="regenerate()">{{'regeneratePassword' | i18n}}</a>
<a class="box-content-row" href="#" appStopClick appBlurClick
(click)="copy()">{{'copyPassword' | i18n}}</a>
<a class="box-content-row box-content-row-flex" routerLink="/generator-history">
<div class="row-main">{{'passwordHistory' | i18n}}</div>
<i class="fa fa-chevron-right row-sub-icon"></i>
</a>
</div>
</div>
<div class="box">
<div class="box-header">
{{'options' | i18n}}
</div>
<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)]="options.length"
(input)="saveOptions()">
<input id="lengthRange" type="range" min="5" max="128" step="1"
[(ngModel)]="options.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)="saveOptions()"
[(ngModel)]="options.uppercase">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label>
<input id="lowercase" type="checkbox" (change)="saveOptions()"
[(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()"
[(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()"
[(ngModel)]="options.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" (input)="saveOptions()"
[(ngModel)]="options.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" (input)="saveOptions()"
[(ngModel)]="options.minSpecial">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{'avoidAmbChar' | i18n}}</label>
<input id="ambiguous" type="checkbox" (change)="saveOptions()"
[(ngModel)]="avoidAmbiguous">
</div>
</div>
</div>
</content>

View File

@@ -0,0 +1,52 @@
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { Location } from '@angular/common';
import {
Component,
} from '@angular/core';
import {
Router,
} from '@angular/router';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { CipherView } from 'jslib/models/view/cipherView';
import {
PasswordGeneratorComponent as BasePasswordGeneratorComponent
} from 'jslib/angular/components/password-generator.component';
@Component({
selector: 'app-password-generator',
templateUrl: 'password-generator.component.html',
})
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
private cipherState: CipherView;
constructor(passwordGenerationService: PasswordGenerationService, analytics: Angulartics2,
platformUtilsService: PlatformUtilsService, i18nService: I18nService,
toasterService: ToasterService, private stateService: StateService,
private router: Router, private location: Location) {
super(passwordGenerationService, analytics, platformUtilsService, i18nService, toasterService);
}
async ngOnInit() {
await super.ngOnInit();
this.cipherState = await this.stateService.get<CipherView>('addEditCipher');
super.showSelect = this.cipherState != null;
}
select() {
super.select();
this.cipherState.login.password = this.password;
this.close();
}
close() {
this.location.back();
}
}

View File

@@ -0,0 +1,19 @@
<header>
<div class="left">
<app-pop-out></app-pop-out>
</div>
<div class="center">
<span class="title">{{'tools' | i18n}}</span>
</div>
<div class="right"></div>
</header>
<content>
<div class="box list">
<div class="box-content">
<a routerLink="/generator" class="box-content-row box-content-row-flex text-default">
<div class="row-main">{{'passGen' | i18n}}</div>
<i class="fa fa-chevron-right row-sub-icon"></i>
</a>
</div>
</div>
</content>

View File

@@ -0,0 +1,7 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-tools',
templateUrl: 'tools.component.html',
})
export class ToolsComponent { }

View File

@@ -16,6 +16,7 @@ import { CipherService } from 'jslib/abstractions/cipher.service';
import { FolderService } from 'jslib/abstractions/folder.service'; import { FolderService } from 'jslib/abstractions/folder.service';
import { I18nService } from 'jslib/abstractions/i18n.service'; import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/add-edit.component'; import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/add-edit.component';
@@ -27,10 +28,11 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit {
constructor(cipherService: CipherService, folderService: FolderService, constructor(cipherService: CipherService, folderService: FolderService,
i18nService: I18nService, platformUtilsService: PlatformUtilsService, i18nService: I18nService, platformUtilsService: PlatformUtilsService,
analytics: Angulartics2, toasterService: ToasterService, analytics: Angulartics2, toasterService: ToasterService,
auditService: AuditService, private route: ActivatedRoute, auditService: AuditService, stateService: StateService,
private router: Router, private location: Location) { private route: ActivatedRoute, private router: Router,
private location: Location) {
super(cipherService, folderService, i18nService, platformUtilsService, analytics, super(cipherService, folderService, i18nService, platformUtilsService, analytics,
toasterService, auditService); toasterService, auditService, stateService);
} }
ngOnInit() { ngOnInit() {
@@ -56,4 +58,10 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit {
super.cancel(); super.cancel();
this.location.back(); this.location.back();
} }
async generatePassword() {
await super.generatePassword();
this.stateService.save('addEditCipher', this.cipher);
this.router.navigate(['generator']);
}
} }

View File

@@ -125,7 +125,7 @@
<div class="no-items" *ngIf="!searchedCiphers.length"> <div class="no-items" *ngIf="!searchedCiphers.length">
<p>{{'noItemsInList' | i18n}}</p> <p>{{'noItemsInList' | i18n}}</p>
</div> </div>
<div class="box list only-list" *ngIf="searchedCiphers.length > 0"> <div class="box list full-list" *ngIf="searchedCiphers.length > 0">
<div class="box-content"> <div class="box-content">
<app-ciphers-list [ciphers]="searchedCiphers" title="{{'viewItem' | i18n}}" <app-ciphers-list [ciphers]="searchedCiphers" title="{{'viewItem' | i18n}}"
(onSelected)="selectCipher($event)"></app-ciphers-list> (onSelected)="selectCipher($event)"></app-ciphers-list>

View File

@@ -368,7 +368,7 @@
} }
} }
.text, .detail { .text:not(.no-ellipsis), .detail {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -391,4 +391,12 @@
border-bottom: none; border-bottom: none;
} }
} }
&.full-list {
margin: 0;
.box-content {
border: none;
}
}
} }

View File

@@ -97,3 +97,11 @@ app-root > #loading {
width: 100%; width: 100%;
color: $text-muted; color: $text-muted;
} }
app-password-generator .password-block {
font-size: $font-size-large;
word-break: break-all;
font-family: $font-family-monospace;
text-align: center;
margin: 20px;
}