1
0
mirror of https://github.com/bitwarden/web synced 2025-12-06 00:03:28 +00:00

[End User Vault Refresh] Security sub-page (#1538)

* [End User Vault Refresh] Security section

* Updated routing module

* Update routing for change-password

* Updated buttons of all modified classes // imported button module

* Converted modified class to use bit-callout

* removed comments

* Update small button to current cl button
This commit is contained in:
Vincent Salucci
2022-03-17 08:55:00 -05:00
committed by GitHub
parent 5c5ae81fdf
commit 8a36536d40
17 changed files with 244 additions and 102 deletions

View File

@@ -58,9 +58,9 @@ import { EmergencyAccessComponent } from "./settings/emergency-access.component"
import { OrganizationsComponent } from "./settings/organizations.component";
import { PreferencesComponent } from "./settings/preferences.component";
import { PremiumComponent } from "./settings/premium.component";
import { SecurityComponent } from "./settings/security.component";
import { SettingsComponent } from "./settings/settings.component";
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
import { UserBillingComponent } from "./settings/user-billing.component";
import { UserSubscriptionComponent } from "./settings/user-subscription.component";
import { BreachReportComponent } from "./tools/breach-report.component";
@@ -183,16 +183,15 @@ const routes: Routes = [
component: PreferencesComponent,
data: { titleId: "preferences" },
},
{
path: "security",
loadChildren: async () => (await import("./settings/security.module")).SecurityModule,
},
{
path: "domain-rules",
component: DomainRulesComponent,
data: { titleId: "domainRules" },
},
{
path: "two-factor",
component: TwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{ path: "premium", component: PremiumComponent, data: { titleId: "goPremium" } },
{ path: "billing", component: UserBillingComponent, data: { titleId: "billing" } },
{

View File

@@ -53,7 +53,7 @@ import localeZhTw from "@angular/common/locales/zh-Hant";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { RouterModule } from "@angular/router";
import { BadgeModule } from "@bitwarden/components";
import { BadgeModule, ButtonModule, CalloutModule } from "@bitwarden/components";
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { ToastrModule } from "ngx-toastr";
@@ -191,6 +191,8 @@ import { PreferencesComponent } from "./settings/preferences.component";
import { PremiumComponent } from "./settings/premium.component";
import { ProfileComponent } from "./settings/profile.component";
import { PurgeVaultComponent } from "./settings/purge-vault.component";
import { SecurityKeysComponent } from "./settings/security-keys.component";
import { SecurityComponent } from "./settings/security.component";
import { SettingsComponent } from "./settings/settings.component";
import { SponsoredFamiliesComponent } from "./settings/sponsored-families.component";
import { SponsoringOrgRowComponent } from "./settings/sponsoring-org-row.component";
@@ -296,6 +298,8 @@ registerLocaleData(localeZhTw, "zh-TW");
ReactiveFormsModule,
RouterModule,
BadgeModule,
ButtonModule,
CalloutModule,
],
declarations: [
A11yInvalidDirective,
@@ -431,6 +435,8 @@ registerLocaleData(localeZhTw, "zh-TW");
ReusedPasswordsReportComponent,
SearchCiphersPipe,
SearchPipe,
SecurityComponent,
SecurityKeysComponent,
SelectCopyDirective,
SendAddEditComponent,
SendComponent,

View File

@@ -8,43 +8,19 @@
</div>
<app-change-email></app-change-email>
</ng-container>
<ng-container *ngIf="showChangePassword">
<div class="secondary-header">
<h1>{{ "changeMasterPassword" | i18n }}</h1>
</div>
<app-change-password></app-change-password>
</ng-container>
<ng-container *ngIf="showChangeKdf">
<div class="secondary-header">
<h1>{{ "encKeySettings" | i18n }}</h1>
</div>
<app-change-kdf></app-change-kdf>
</ng-container>
<div class="secondary-header border-0 mb-0">
<h1>{{ "apiKey" | i18n }}</h1>
</div>
<p>
{{ "userApiKeyDesc" | i18n }}
</p>
<button type="button" class="btn btn-outline-secondary" (click)="viewUserApiKey()">
{{ "viewApiKey" | i18n }}
</button>
<button type="button" class="btn btn-outline-secondary" (click)="rotateUserApiKey()">
{{ "rotateApiKey" | i18n }}
</button>
<div class="secondary-header text-danger border-0 mb-0">
<h1>{{ "dangerZone" | i18n }}</h1>
</div>
<div class="card border-danger">
<div class="card-body">
<p>{{ "dangerZoneDesc" | i18n }}</p>
<button type="button" class="btn btn-outline-danger" (click)="deauthorizeSessions()">
<button bit-button buttonType="danger" (click)="deauthorizeSessions()">
{{ "deauthorizeSessions" | i18n }}
</button>
<button type="button" class="btn btn-outline-danger" (click)="purgeVault()">
<button bit-button buttonType="danger" (click)="purgeVault()">
{{ "purgeVault" | i18n }}
</button>
<button type="button" class="btn btn-outline-danger" (click)="deleteAccount()">
<button bit-button buttonType="danger" (click)="deleteAccount()">
{{ "deleteAccount" | i18n }}
</button>
</div>

View File

@@ -5,7 +5,6 @@ import { ApiService } from "jslib-common/abstractions/api.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ApiKeyComponent } from "./api-key.component";
import { DeauthorizeSessionsComponent } from "./deauthorize-sessions.component";
import { DeleteAccountComponent } from "./delete-account.component";
import { PurgeVaultComponent } from "./purge-vault.component";
@@ -21,13 +20,7 @@ export class AccountComponent {
purgeModalRef: ViewContainerRef;
@ViewChild("deleteAccountTemplate", { read: ViewContainerRef, static: true })
deleteModalRef: ViewContainerRef;
@ViewChild("viewUserApiKeyTemplate", { read: ViewContainerRef, static: true })
viewUserApiKeyModalRef: ViewContainerRef;
@ViewChild("rotateUserApiKeyTemplate", { read: ViewContainerRef, static: true })
rotateUserApiKeyModalRef: ViewContainerRef;
showChangePassword = true;
showChangeKdf = true;
showChangeEmail = true;
constructor(
@@ -38,10 +31,7 @@ export class AccountComponent {
) {}
async ngOnInit() {
this.showChangeEmail =
this.showChangeKdf =
this.showChangePassword =
!(await this.keyConnectorService.getUsesKeyConnector());
this.showChangeEmail = !(await this.keyConnectorService.getUsesKeyConnector());
}
async deauthorizeSessions() {
@@ -55,33 +45,4 @@ export class AccountComponent {
async deleteAccount() {
await this.modalService.openViewRef(DeleteAccountComponent, this.deleteModalRef);
}
async viewUserApiKey() {
const entityId = await this.stateService.getUserId();
await this.modalService.openViewRef(ApiKeyComponent, this.viewUserApiKeyModalRef, (comp) => {
comp.keyType = "user";
comp.entityId = entityId;
comp.postKey = this.apiService.postUserApiKey.bind(this.apiService);
comp.scope = "api";
comp.grantType = "client_credentials";
comp.apiKeyTitle = "apiKey";
comp.apiKeyWarning = "userApiKeyWarning";
comp.apiKeyDescription = "userApiKeyDesc";
});
}
async rotateUserApiKey() {
const entityId = await this.stateService.getUserId();
await this.modalService.openViewRef(ApiKeyComponent, this.rotateUserApiKeyModalRef, (comp) => {
comp.keyType = "user";
comp.isRotation = true;
comp.entityId = entityId;
comp.postKey = this.apiService.postUserRotateApiKey.bind(this.apiService);
comp.scope = "api";
comp.grantType = "client_credentials";
comp.apiKeyTitle = "apiKey";
comp.apiKeyWarning = "userApiKeyWarning";
comp.apiKeyDescription = "apiKeyRotateDesc";
});
}
}

View File

@@ -1,4 +1,7 @@
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
<div class="tabbed-header">
<h1>{{ "encKeySettings" | i18n }}</h1>
</div>
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row">
<div class="col-6">
@@ -68,7 +71,7 @@
</div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<button bit-button buttonType="primary" class="btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "changeKdf" | i18n }}</span>
</button>

View File

@@ -1,4 +1,8 @@
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
<div class="tabbed-header">
<h1>{{ "changeMasterPassword" | i18n }}</h1>
</div>
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
@@ -83,7 +87,7 @@
</a>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<button bit-button buttonType="primary" class="btn-submit" [disabled]="form.loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "changeMasterPassword" | i18n }}</span>
</button>

View File

@@ -1,4 +1,5 @@
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "jslib-angular/components/change-password.component";
import { ApiService } from "jslib-common/abstractions/api.service";
@@ -6,6 +7,7 @@ import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
@@ -47,7 +49,9 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
private syncService: SyncService,
private apiService: ApiService,
private sendService: SendService,
private organizationService: OrganizationService
private organizationService: OrganizationService,
private keyConnectorService: KeyConnectorService,
private router: Router
) {
super(
i18nService,
@@ -60,6 +64,12 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
);
}
async ngOnInit() {
if (await this.keyConnectorService.getUsesKeyConnector()) {
this.router.navigate(["/settings/security/two-factor"]);
}
}
async rotateEncKeyClicked() {
if (this.rotateEncKey) {
const ciphers = await this.cipherService.getAllDecrypted();

View File

@@ -0,0 +1,18 @@
<app-change-kdf *ngIf="showChangeKdf"></app-change-kdf>
<div
[ngClass]="{ 'tabbed-header': !showChangeKdf, 'secondary-header': showChangeKdf }"
class="border-0 mb-0"
>
<h1>{{ "apiKey" | i18n }}</h1>
</div>
<p>
{{ "userApiKeyDesc" | i18n }}
</p>
<button bit-button buttonType="secondary" (click)="viewUserApiKey()">
{{ "viewApiKey" | i18n }}
</button>
<button bit-button buttonType="secondary" (click)="rotateUserApiKey()">
{{ "rotateApiKey" | i18n }}
</button>
<ng-template #viewUserApiKeyTemplate></ng-template>
<ng-template #rotateUserApiKeyTemplate></ng-template>

View File

@@ -0,0 +1,61 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from "jslib-common/abstractions/api.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ApiKeyComponent } from "./api-key.component";
@Component({
selector: "app-security-keys",
templateUrl: "security-keys.component.html",
})
export class SecurityKeysComponent implements OnInit {
@ViewChild("viewUserApiKeyTemplate", { read: ViewContainerRef, static: true })
viewUserApiKeyModalRef: ViewContainerRef;
@ViewChild("rotateUserApiKeyTemplate", { read: ViewContainerRef, static: true })
rotateUserApiKeyModalRef: ViewContainerRef;
showChangeKdf = true;
constructor(
private keyConnectorService: KeyConnectorService,
private stateService: StateService,
private modalService: ModalService,
private apiService: ApiService
) {}
async ngOnInit() {
this.showChangeKdf = !(await this.keyConnectorService.getUsesKeyConnector());
}
async viewUserApiKey() {
const entityId = await this.stateService.getUserId();
await this.modalService.openViewRef(ApiKeyComponent, this.viewUserApiKeyModalRef, (comp) => {
comp.keyType = "user";
comp.entityId = entityId;
comp.postKey = this.apiService.postUserApiKey.bind(this.apiService);
comp.scope = "api";
comp.grantType = "client_credentials";
comp.apiKeyTitle = "apiKey";
comp.apiKeyWarning = "userApiKeyWarning";
comp.apiKeyDescription = "userApiKeyDesc";
});
}
async rotateUserApiKey() {
const entityId = await this.stateService.getUserId();
await this.modalService.openViewRef(ApiKeyComponent, this.rotateUserApiKeyModalRef, (comp) => {
comp.keyType = "user";
comp.isRotation = true;
comp.entityId = entityId;
comp.postKey = this.apiService.postUserRotateApiKey.bind(this.apiService);
comp.scope = "api";
comp.grantType = "client_credentials";
comp.apiKeyTitle = "apiKey";
comp.apiKeyWarning = "userApiKeyWarning";
comp.apiKeyDescription = "apiKeyRotateDesc";
});
}
}

View File

@@ -0,0 +1,22 @@
<div class="tabbed-nav d-flex flex-column">
<ul class="nav nav-tabs">
<ng-container *ngIf="showChangePassword">
<li class="nav-item">
<a class="nav-link" routerLink="change-password" routerLinkActive="active">
{{ "masterPassword" | i18n }}
</a>
</li>
</ng-container>
<li class="nav-item">
<a class="nav-link" routerLink="two-factor" routerLinkActive="active">
{{ "twoStepLogin" | i18n }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="security-keys" routerLinkActive="active">
{{ "keys" | i18n }}
</a>
</li>
</ul>
</div>
<router-outlet></router-outlet>

View File

@@ -0,0 +1,17 @@
import { Component } from "@angular/core";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
@Component({
selector: "app-security",
templateUrl: "security.component.html",
})
export class SecurityComponent {
showChangePassword = true;
constructor(private keyConnectorService: KeyConnectorService) {}
async ngOnInit() {
this.showChangePassword = !(await this.keyConnectorService.getUsesKeyConnector());
}
}

View File

@@ -0,0 +1,39 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { ChangePasswordComponent } from "./change-password.component";
import { SecurityKeysComponent } from "./security-keys.component";
import { SecurityComponent } from "./security.component";
import { TwoFactorSetupComponent } from "./two-factor-setup.component";
const routes: Routes = [
{
path: "",
component: SecurityComponent,
data: { titleId: "security" },
children: [
{ path: "", pathMatch: "full", redirectTo: "change-password" },
{
path: "change-password",
component: ChangePasswordComponent,
data: { titleId: "masterPassword" },
},
{
path: "two-factor",
component: TwoFactorSetupComponent,
data: { titleId: "twoStepLogin" },
},
{
path: "security-keys",
component: SecurityKeysComponent,
data: { titleId: "keys" },
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class SecurityModule {}

View File

@@ -7,6 +7,9 @@
<a routerLink="account" class="list-group-item" routerLinkActive="active">
{{ "myAccount" | i18n }}
</a>
<a routerLink="security" class="list-group-item" routerLinkActive="active">
{{ "security" | i18n }}
</a>
<a routerLink="preferences" class="list-group-item" routerLinkActive="active">
{{ "preferences" | i18n }}
</a>
@@ -37,9 +40,6 @@
>
{{ "billing" | i18n }}
</a>
<a routerLink="two-factor" class="list-group-item" routerLinkActive="active">
{{ "twoStepLogin" | i18n }}
</a>
<a routerLink="domain-rules" class="list-group-item" routerLinkActive="active">
{{ "domainRules" | i18n }}
</a>

View File

@@ -1,14 +1,14 @@
<div class="page-header">
<div class="tabbed-header">
<h1>{{ "twoStepLogin" | i18n }}</h1>
</div>
<p *ngIf="!organizationId">{{ "twoStepLoginDesc" | i18n }}</p>
<p *ngIf="organizationId">{{ "twoStepLoginOrganizationDesc" | i18n }}</p>
<app-callout type="warning" *ngIf="!organizationId">
<bit-callout type="warning" *ngIf="!organizationId">
<p>{{ "twoStepLoginRecoveryWarning" | i18n }}</p>
<button type="button" class="btn btn-outline-secondary" (click)="recoveryCode()">
<button bit-button buttonType="secondary" (click)="recoveryCode()">
{{ "viewRecoveryCode" | i18n }}
</button>
</app-callout>
</bit-callout>
<h2 [ngClass]="{ 'mt-5': !organizationId }">
{{ "providers" | i18n }}
<small *ngIf="loading">
@@ -20,9 +20,9 @@
<span class="sr-only">{{ "loading" | i18n }}</span>
</small>
</h2>
<app-callout type="warning" *ngIf="showPolicyWarning">
<bit-callout type="warning" *ngIf="showPolicyWarning">
{{ "twoStepLoginPolicyUserWarning" | i18n }}
</app-callout>
</bit-callout>
<ul class="list-group list-group-2fa">
<li *ngFor="let p of providers" class="list-group-item d-flex align-items-center">
<div class="logo-2fa d-flex justify-content-center">
@@ -45,8 +45,8 @@
</div>
<div class="ml-auto">
<button
type="button"
class="btn btn-outline-secondary btn-sm"
bit-button
buttonType="secondary"
[disabled]="!canAccessPremium && p.premium"
(click)="manage(p.type)"
>

View File

@@ -4829,5 +4829,14 @@
"example": "My Org Name"
}
}
},
"masterPassword": {
"message": "Master Password"
},
"security": {
"message": "Security"
},
"keys": {
"message": "Keys"
}
}

View File

@@ -39,7 +39,8 @@ body {
}
.page-header,
.secondary-header {
.secondary-header,
.tabbed-header {
margin-bottom: 0.5rem;
padding-bottom: 0.6rem;
@include themify($themes) {
@@ -64,6 +65,11 @@ body {
margin-top: 4rem;
}
.tabbed-header {
margin-top: 2rem;
margin-bottom: 1rem;
}
img.logo {
display: block;
height: 43px;

View File

@@ -74,6 +74,27 @@
height: 100%;
}
.org-name {
line-height: 1;
span {
display: block;
font-size: $font-size-lg;
@include themify($themes) {
color: themed("textHeadingColor");
}
}
}
}
.tabbed-nav {
@include themify($themes) {
border-bottom: 1px solid themed("borderColor");
color: themed("textColor");
}
}
.org-nav,
.tabbed-nav {
.nav-tabs {
border-bottom: none;
@@ -90,6 +111,7 @@
padding-top: calc(#{$nav-link-padding-y} - 2px);
@include themify($themes) {
border-top: 3px solid themed("primary");
border-bottom: 1px solid themed("backgroundColor");
color: themed("linkColor");
}
}
@@ -101,15 +123,4 @@
}
}
}
.org-name {
line-height: 1;
span {
display: block;
font-size: $font-size-lg;
@include themify($themes) {
color: themed("textHeadingColor");
}
}
}
}