1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-29 12:55:21 +01:00

Cherry pick premium badge and reports page (#1525, #1536) (#1571)

This commit is contained in:
Oscar Hinton 2022-03-29 21:55:47 +02:00 committed by GitHub
parent 23b02a770a
commit 612442c1bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 410 additions and 147 deletions

View File

@ -0,0 +1,19 @@
import { Component } from "@angular/core";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
@Component({
selector: "app-premium-badge",
template: `
<button *appNotPremium bit-badge badgeType="success" (click)="premiumRequired()">
{{ "premium" | i18n }}
</button>
`,
})
export class PremiumBadgeComponent {
constructor(private messagingService: MessagingService) {}
premiumRequired() {
this.messagingService.send("premiumRequired");
}
}

View File

@ -24,6 +24,9 @@
<li class="nav-item" routerLinkActive="active"> <li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a> <a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a>
</li> </li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/reports">{{ "reports" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active"> <li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/settings">{{ "settings" | i18n }}</a> <a class="nav-link" routerLink="/settings">{{ "settings" | i18n }}</a>
</li> </li>

View File

@ -11,11 +11,11 @@ import { StateService } from "jslib-common/abstractions/state.service";
import { Cipher } from "jslib-common/models/domain/cipher"; import { Cipher } from "jslib-common/models/domain/cipher";
import { CipherView } from "jslib-common/models/view/cipherView"; import { CipherView } from "jslib-common/models/view/cipherView";
import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../../tools/exposed-passwords-report.component"; import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent } from "../../reports/exposed-passwords-report.component";
@Component({ @Component({
selector: "app-exposed-passwords-report", selector: "app-exposed-passwords-report",
templateUrl: "../../tools/exposed-passwords-report.component.html", templateUrl: "../../reports/exposed-passwords-report.component.html",
}) })
export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent { export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent {
manageableCiphers: Cipher[]; manageableCiphers: Cipher[];

View File

@ -10,11 +10,11 @@ import { PasswordRepromptService } from "jslib-common/abstractions/passwordRepro
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { CipherView } from "jslib-common/models/view/cipherView"; import { CipherView } from "jslib-common/models/view/cipherView";
import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../../tools/inactive-two-factor-report.component"; import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../../reports/inactive-two-factor-report.component";
@Component({ @Component({
selector: "app-inactive-two-factor-report", selector: "app-inactive-two-factor-report",
templateUrl: "../../tools/inactive-two-factor-report.component.html", templateUrl: "../../reports/inactive-two-factor-report.component.html",
}) })
export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorReportComponent { export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorReportComponent {
constructor( constructor(

View File

@ -10,11 +10,11 @@ import { StateService } from "jslib-common/abstractions/state.service";
import { Cipher } from "jslib-common/models/domain/cipher"; import { Cipher } from "jslib-common/models/domain/cipher";
import { CipherView } from "jslib-common/models/view/cipherView"; import { CipherView } from "jslib-common/models/view/cipherView";
import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } from "../../tools/reused-passwords-report.component"; import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } from "../../reports/reused-passwords-report.component";
@Component({ @Component({
selector: "app-reused-passwords-report", selector: "app-reused-passwords-report",
templateUrl: "../../tools/reused-passwords-report.component.html", templateUrl: "../../reports/reused-passwords-report.component.html",
}) })
export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent { export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent {
manageableCiphers: Cipher[]; manageableCiphers: Cipher[];

View File

@ -9,11 +9,11 @@ import { PasswordRepromptService } from "jslib-common/abstractions/passwordRepro
import { StateService } from "jslib-common/abstractions/state.service"; import { StateService } from "jslib-common/abstractions/state.service";
import { CipherView } from "jslib-common/models/view/cipherView"; import { CipherView } from "jslib-common/models/view/cipherView";
import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponent } from "../../tools/unsecured-websites-report.component"; import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponent } from "../../reports/unsecured-websites-report.component";
@Component({ @Component({
selector: "app-unsecured-websites-report", selector: "app-unsecured-websites-report",
templateUrl: "../../tools/unsecured-websites-report.component.html", templateUrl: "../../reports/unsecured-websites-report.component.html",
}) })
export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesReportComponent { export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesReportComponent {
constructor( constructor(

View File

@ -11,11 +11,11 @@ import { StateService } from "jslib-common/abstractions/state.service";
import { Cipher } from "jslib-common/models/domain/cipher"; import { Cipher } from "jslib-common/models/domain/cipher";
import { CipherView } from "jslib-common/models/view/cipherView"; import { CipherView } from "jslib-common/models/view/cipherView";
import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from "../../tools/weak-passwords-report.component"; import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from "../../reports/weak-passwords-report.component";
@Component({ @Component({
selector: "app-weak-passwords-report", selector: "app-weak-passwords-report",
templateUrl: "../../tools/weak-passwords-report.component.html", templateUrl: "../../reports/weak-passwords-report.component.html",
}) })
export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent { export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent {
manageableCiphers: Cipher[]; manageableCiphers: Cipher[];

View File

@ -64,16 +64,10 @@ import { SponsoredFamiliesComponent } from "./settings/sponsored-families.compon
import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component"; import { TwoFactorSetupComponent } from "./settings/two-factor-setup.component";
import { UserBillingComponent } from "./settings/user-billing.component"; import { UserBillingComponent } from "./settings/user-billing.component";
import { UserSubscriptionComponent } from "./settings/user-subscription.component"; import { UserSubscriptionComponent } from "./settings/user-subscription.component";
import { BreachReportComponent } from "./tools/breach-report.component";
import { ExportComponent } from "./tools/export.component"; import { ExportComponent } from "./tools/export.component";
import { ExposedPasswordsReportComponent } from "./tools/exposed-passwords-report.component";
import { ImportComponent } from "./tools/import.component"; import { ImportComponent } from "./tools/import.component";
import { InactiveTwoFactorReportComponent } from "./tools/inactive-two-factor-report.component";
import { PasswordGeneratorComponent } from "./tools/password-generator.component"; import { PasswordGeneratorComponent } from "./tools/password-generator.component";
import { ReusedPasswordsReportComponent } from "./tools/reused-passwords-report.component";
import { ToolsComponent } from "./tools/tools.component"; import { ToolsComponent } from "./tools/tools.component";
import { UnsecuredWebsitesReportComponent } from "./tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./tools/weak-passwords-report.component";
import { VaultComponent } from "./vault/vault.component"; import { VaultComponent } from "./vault/vault.component";
const routes: Routes = [ const routes: Routes = [
@ -242,38 +236,12 @@ const routes: Routes = [
component: PasswordGeneratorComponent, component: PasswordGeneratorComponent,
data: { titleId: "generator" }, data: { titleId: "generator" },
}, },
{
path: "breach-report",
component: BreachReportComponent,
data: { titleId: "dataBreachReport" },
},
{
path: "reused-passwords-report",
component: ReusedPasswordsReportComponent,
data: { titleId: "reusedPasswordsReport" },
},
{
path: "unsecured-websites-report",
component: UnsecuredWebsitesReportComponent,
data: { titleId: "unsecuredWebsitesReport" },
},
{
path: "weak-passwords-report",
component: WeakPasswordsReportComponent,
data: { titleId: "weakPasswordsReport" },
},
{
path: "exposed-passwords-report",
component: ExposedPasswordsReportComponent,
data: { titleId: "exposedPasswordsReport" },
},
{
path: "inactive-two-factor-report",
component: InactiveTwoFactorReportComponent,
data: { titleId: "inactive2faReport" },
},
], ],
}, },
{
path: "reports",
loadChildren: async () => (await import("./reports/reports.module")).ReportsModule,
},
{ path: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent }, { path: "setup/families-for-enterprise", component: FamiliesForEnterpriseSetupComponent },
], ],
}, },

View File

@ -53,6 +53,7 @@ import localeZhTw from "@angular/common/locales/zh-Hant";
import { NgModule } from "@angular/core"; import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { RouterModule } from "@angular/router"; import { RouterModule } from "@angular/router";
import { BadgeModule, ButtonModule } from "@bitwarden/components";
import { InfiniteScrollModule } from "ngx-infinite-scroll"; import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { ToastrModule } from "ngx-toastr"; import { ToastrModule } from "ngx-toastr";
@ -78,6 +79,7 @@ import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.c
import { NestedCheckboxComponent } from "./components/nested-checkbox.component"; import { NestedCheckboxComponent } from "./components/nested-checkbox.component";
import { PasswordRepromptComponent } from "./components/password-reprompt.component"; import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { PasswordStrengthComponent } from "./components/password-strength.component"; import { PasswordStrengthComponent } from "./components/password-strength.component";
import { PremiumBadgeComponent } from "./components/premium-badge.component";
import { FooterComponent } from "./layouts/footer.component"; import { FooterComponent } from "./layouts/footer.component";
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
import { NavbarComponent } from "./layouts/navbar.component"; import { NavbarComponent } from "./layouts/navbar.component";
@ -135,6 +137,15 @@ import { CollectionsComponent as OrgCollectionsComponent } from "./organizations
import { GroupingsComponent as OrgGroupingsComponent } from "./organizations/vault/groupings.component"; import { GroupingsComponent as OrgGroupingsComponent } from "./organizations/vault/groupings.component";
import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component"; import { VaultComponent as OrgVaultComponent } from "./organizations/vault/vault.component";
import { ProvidersComponent } from "./providers/providers.component"; import { ProvidersComponent } from "./providers/providers.component";
import { BreachReportComponent } from "./reports/breach-report.component";
import { ExposedPasswordsReportComponent } from "./reports/exposed-passwords-report.component";
import { InactiveTwoFactorReportComponent } from "./reports/inactive-two-factor-report.component";
import { ReportCardComponent } from "./reports/report-card.component";
import { ReportListComponent } from "./reports/report-list.component";
import { ReportsComponent } from "./reports/reports.component";
import { ReusedPasswordsReportComponent } from "./reports/reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent } from "./reports/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./reports/weak-passwords-report.component";
import { AccessComponent } from "./send/access.component"; import { AccessComponent } from "./send/access.component";
import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component"; import { AddEditComponent as SendAddEditComponent } from "./send/add-edit.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component"; import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
@ -184,17 +195,11 @@ import { UserBillingComponent } from "./settings/user-billing.component";
import { UserSubscriptionComponent } from "./settings/user-subscription.component"; import { UserSubscriptionComponent } from "./settings/user-subscription.component";
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component"; import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
import { VerifyEmailComponent } from "./settings/verify-email.component"; import { VerifyEmailComponent } from "./settings/verify-email.component";
import { BreachReportComponent } from "./tools/breach-report.component";
import { ExportComponent } from "./tools/export.component"; import { ExportComponent } from "./tools/export.component";
import { ExposedPasswordsReportComponent } from "./tools/exposed-passwords-report.component";
import { ImportComponent } from "./tools/import.component"; import { ImportComponent } from "./tools/import.component";
import { InactiveTwoFactorReportComponent } from "./tools/inactive-two-factor-report.component";
import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component"; import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component";
import { PasswordGeneratorComponent } from "./tools/password-generator.component"; import { PasswordGeneratorComponent } from "./tools/password-generator.component";
import { ReusedPasswordsReportComponent } from "./tools/reused-passwords-report.component";
import { ToolsComponent } from "./tools/tools.component"; import { ToolsComponent } from "./tools/tools.component";
import { UnsecuredWebsitesReportComponent } from "./tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./tools/weak-passwords-report.component";
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component"; import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from "./vault/add-edit.component"; import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component"; import { AttachmentsComponent } from "./vault/attachments.component";
@ -271,8 +276,11 @@ registerLocaleData(localeZhTw, "zh-TW");
ReactiveFormsModule, ReactiveFormsModule,
RouterModule, RouterModule,
ToastrModule, ToastrModule,
BadgeModule,
ButtonModule,
], ],
declarations: [ declarations: [
PremiumBadgeComponent,
AcceptEmergencyComponent, AcceptEmergencyComponent,
AcceptOrganizationComponent, AcceptOrganizationComponent,
AccessComponent, AccessComponent,
@ -384,6 +392,9 @@ registerLocaleData(localeZhTw, "zh-TW");
RecoverTwoFactorComponent, RecoverTwoFactorComponent,
RegisterComponent, RegisterComponent,
RemovePasswordComponent, RemovePasswordComponent,
ReportCardComponent,
ReportListComponent,
ReportsComponent,
RequireSsoPolicyComponent, RequireSsoPolicyComponent,
ResetPasswordPolicyComponent, ResetPasswordPolicyComponent,
ReusedPasswordsReportComponent, ReusedPasswordsReportComponent,

View File

@ -17,7 +17,13 @@
<small class="form-text text-muted">{{ "breachCheckUsernameEmail" | i18n }}</small> <small class="form-text text-muted">{{ "breachCheckUsernameEmail" | i18n }}</small>
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"> <button
bit-button
buttonType="primary"
class="btn-submit"
type="submit"
[disabled]="form.loading"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "checkBreaches" | i18n }}</span> <span>{{ "checkBreaches" | i18n }}</span>
</button> </button>

View File

@ -2,7 +2,14 @@
<h1>{{ "exposedPasswordsReport" | i18n }}</h1> <h1>{{ "exposedPasswordsReport" | i18n }}</h1>
</div> </div>
<p>{{ "exposedPasswordsReportDesc" | i18n }}</p> <p>{{ "exposedPasswordsReportDesc" | i18n }}</p>
<button type="button" class="btn btn-primary btn-submit" [disabled]="loading" (click)="load()"> <button
bit-button
buttonType="primary"
type="button"
class="btn-submit"
[disabled]="loading"
(click)="load()"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i> <i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "checkExposedPasswords" | i18n }}</span> <span>{{ "checkExposedPasswords" | i18n }}</span>
</button> </button>

View File

@ -0,0 +1,25 @@
<a
class="tw-border tw-border-solid tw-border-secondary-300 tw-rounded tw-overflow-hidden tw-h-full tw-w-72 tw-block !tw-text-main hover:tw-no-underline hover:tw-scale-105 tw-transition-all focus:tw-outline-none focus:tw-ring focus:tw-ring-offset-2 focus:tw-ring-primary-700"
[routerLink]="route"
(click)="click()"
>
<div class="tw-relative">
<div
class="tw-text-center tw-h-28 tw-flex tw-bg-background-alt2 tw-text-primary-300"
[ngClass]="{ 'tw-grayscale': premium }"
>
<div class="tw-m-auto" [innerHtml]="icon"></div>
</div>
<div class="tw-p-5" [ngClass]="{ 'tw-grayscale': report.requiresPremium }">
<h3 class="tw-text-xl tw-font-bold tw-mb-4">{{ report.title | i18n }}</h3>
<p class="tw-mb-0">{{ report.description | i18n }}</p>
</div>
<span
bit-badge
badgeType="success"
class="tw-absolute tw-left-2 tw-top-2 tw-leading-none"
*ngIf="premium"
>{{ "premium" | i18n }}</span
>
</div>
</a>

View File

@ -0,0 +1,166 @@
import { Component, Input, OnInit } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { StateService } from "jslib-common/abstractions/state.service";
export enum ReportTypes {
"exposedPasswords" = "exposedPasswords",
"reusedPasswords" = "reusedPasswords",
"weakPasswords" = "weakPasswords",
"unsecuredWebsites" = "unsecuredWebsites",
"inactive2fa" = "inactive2fa",
"dataBreach" = "dataBreach",
}
type ReportEntry = {
title: string;
description: string;
route: string;
icon: string;
requiresPremium: boolean;
};
const reports: Record<ReportTypes, ReportEntry> = {
exposedPasswords: {
title: "exposedPasswordsReport",
description: "exposedPasswordsReportDesc",
route: "exposed-passwords-report",
icon: `
<svg width="101" height="77" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.374 50.192a26.42 26.42 0 0 0 9.111 1.608c14.34 0 25.965-11.372 25.965-25.4 0-.337-.007-.673-.02-1.008h25.299v34.85H32.374v-10.05Z" fill="currentColor" />
<path d="M15.805 26.4c0 14.028 11.625 25.4 25.965 25.4s25.964-11.372 25.964-25.4C67.734 12.372 56.11 1 41.77 1 27.43 1 15.805 12.372 15.805 26.4Z" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M27.914 47.849a1 1 0 0 0-2 0h2Zm68.288-26.792a2.12 2.12 0 0 1 2.14 2.11h2c0-2.253-1.83-4.11-4.14-4.11v2Zm2.14 2.11v40.552h2V23.167h-2Zm0 40.552c0 1.172-.958 2.11-2.14 2.11v2c2.25 0 4.14-1.798 4.14-4.11h-2Zm-2.14 2.11H30.054v2h66.148v-2Zm-66.148 0a2.12 2.12 0 0 1-2.14-2.11h-2a4.12 4.12 0 0 0 4.14 4.11v-2Zm-2.14-2.11V47.85h-2v15.87h2Zm39.254-42.662h29.034v-2H67.168v2Z" fill="#fff" />
<path d="M67.203 25.56h25.64v34.85H32.487V50.011" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
<path d="M47.343 76h31.571" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M57.557 66.83V76M67.771 66.83V76" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
<path d="m20.995 42.873-3.972 3.972-14.61 14.61a3.413 3.413 0 0 0 0 4.826v0a3.413 3.413 0 0 0 4.827 0l14.61-14.61 3.972-3.972" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M86.037 32.488H71.845M86.037 37.81H76.28M71.845 37.81h-6.652M86.037 43.132h-6.209M74.95 43.132H61.2M86.037 48.454H71.845M66.967 48.454h-7.54M86.037 53.776H66.08M61.201 53.776h-11.53M44.793 53.776h-7.096" stroke="#fff" stroke-width="2" stroke-linecap="round" />
<rect width="40.801" height="9.757" rx="4" transform="matrix(-1 0 0 1 61.201 14.748)" stroke="#fff" stroke-width="2" />
<path d="M16.852 33.375h28.375a4 4 0 0 1 4 4v1.757a4 4 0 0 1-4 4H22.174M66.523 33.375h-3.539a4 4 0 0 0-4 4v3.761c0 1.102.894 1.996 1.996 1.996v0" stroke="#fff" stroke-width="2" stroke-linecap="round" />
</svg>
`,
requiresPremium: true,
},
reusedPasswords: {
title: "reusedPasswordsReport",
description: "reusedPasswordsReportDesc",
route: "reused-passwords-report",
icon: `
<svg width="102" height="102" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M57.983 15.06a35.664 35.664 0 0 1 14.531 6.27c16.164 11.78 19.585 34.613 7.643 51a37.227 37.227 0 0 1-6.81 7.138m-32.842 6.697a35.708 35.708 0 0 1-11.239-5.495c-16.163-11.78-19.585-34.613-7.642-51a37.55 37.55 0 0 1 3.295-3.929" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M93.909 64.598H7.72c-.708 0-1.275-.662-1.275-1.49V40.273c0-.828.567-1.49 1.275-1.49H93.91c.708 0 1.275.663 1.275 1.49v22.837c.047.827-.567 1.49-1.275 1.49Z" fill="currentColor" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M21.532 52.186v-5.965M21.532 52.187l5.748-1.844M21.532 52.186l3.524 4.881M21.531 52.186l-3.47 4.881M21.532 52.187l-5.694-1.844M40.944 52.186v-5.965M40.944 52.187l5.694-1.844M40.944 52.187l3.525 4.88M40.944 52.187l-3.525 4.88M40.944 52.187l-5.694-1.844M54.849 57.337h11.294M74.21 57.337h11.295M41.75 83l.71 4.75-4.75.71M58.664 18.66 56 14.665 59.996 12" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
`,
requiresPremium: true,
},
weakPasswords: {
title: "weakPasswordsReport",
description: "weakPasswordsReportDesc",
route: "weak-passwords-report",
icon: `
<svg width="78" height="78" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M66.493 64.415V77H9.979V64.324M9.979 44.065V32.106h56.514v12.148" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
<path d="M75.44 64.852H2.085c-.603 0-1.085-.555-1.085-1.25V44.448c0-.694.482-1.25 1.085-1.25H75.44c.603 0 1.085.556 1.085 1.25v19.156c.04.694-.482 1.25-1.085 1.25Z" fill="currentColor" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M13.84 54.56v-5.077M13.84 54.56l4.893-1.57M13.84 54.56l3 4.153M13.84 54.56l-2.954 4.153M13.84 54.56l-4.846-1.57M30.363 54.56v-5.077M30.363 54.56l4.846-1.57M30.363 54.56l3 4.153M30.363 54.56l-3 4.153M30.363 54.56l-4.846-1.57M42.197 59.042h9.506M58.57 59.042h9.507" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M20.863 31.364c-.274-5.285 0-15.817 1.093-18.863 1.276-3.554 6.233-10.826 15.856-11.482 4.83-.273 15.2 2.296 18.043 14.763" stroke="#fff" stroke-width="2" />
</svg>
`,
requiresPremium: true,
},
unsecuredWebsites: {
title: "unsecuredWebsitesReport",
description: "unsecuredWebsitesReportDesc",
route: "unsecured-websites-report",
icon: `
<svg width="113" height="76" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.71 12.983h110.362v55.11a6 6 0 0 1-6 6H7.711a6 6 0 0 1-6-6v-55.11Z" fill="currentColor" />
<rect x="1" y="1.073" width="110.5" height="73.454" rx="9" stroke="#fff" stroke-width="2" />
<path d="M89.48 8.048V7.47M96.363 8.048V7.47M103.246 8.048V7.47" stroke="#fff" stroke-width="4" stroke-linecap="round" />
<path d="M0 12.983h111.217" stroke="#fff" stroke-width="2" />
<path d="m93.236 44.384-18.42-11.026 2.93 21.266 5.582-5.237 4.27 6.46 2.98-1.971-4.26-6.446 6.918-3.046Z" fill="#175DDC" stroke="#fff" stroke-width="2" stroke-linejoin="round" />
<rect width="96.673" height="6.886" rx="3.443" transform="matrix(-1 0 0 1 104.373 18.721)" stroke="#fff" />
</svg>
`,
requiresPremium: true,
},
inactive2fa: {
title: "inactive2faReport",
description: "inactive2faReportDesc",
route: "inactive-two-factor-report",
icon: `
<svg width="42" height="75" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" stroke="#fff" stroke-width="2" d="M1 13.121h39.595v48.758H1z" />
<rect x="1" y="1" width="39.595" height="73" rx="8" stroke="#fff" stroke-width="2" />
<path stroke="#fff" stroke-width="2" stroke-linecap="round" d="M12.344 8.091h16.907M18.907 67.424h3.025M31.503 32.515c-2.047-4.337-6.717-7.061-11.73-6.414a11.356 11.356 0 0 0-9.125 7.126M10.816 42.016c2.047 4.337 6.718 7.062 11.73 6.414 4.346-.562 7.8-3.51 9.213-7.358" />
<path d="m33.584 29.293-1.295 4.625-4.625-1.295M8.523 44.725l1.441-4.581 4.582 1.441" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
`,
requiresPremium: true,
},
dataBreach: {
title: "dataBreachReport",
description: "breachDesc",
route: "breach-report",
icon: `
<svg width="58" height="75" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M39.569 74H13.007a7 7 0 0 1-7-7V31.077a7 7 0 0 1 7-7h19.101a7 7 0 0 1 4.988 2.088l7.46 7.576a7 7 0 0 1 2.013 4.912V67a7 7 0 0 1-7 7Z" fill="#175DDC" stroke="#fff" stroke-width="2" />
<path d="M44.576 69.055H18.015a7 7 0 0 1-7-7V26.132a7 7 0 0 1 7-7h19.1a7 7 0 0 1 4.988 2.088l7.46 7.576a7 7 0 0 1 2.013 4.911v28.348a7 7 0 0 1-7 7Z" fill="#175DDC" stroke="#fff" stroke-width="2" />
<path d="M50 63.698H23.439a7 7 0 0 1-7-7V20.775a7 7 0 0 1 7-7h19.1a7 7 0 0 1 4.988 2.088l7.46 7.575A7 7 0 0 1 57 28.35v28.348a7 7 0 0 1-7 7Z" fill="#175DDC" stroke="#fff" stroke-width="2" />
<path d="M44.648 13.599v3.95a8 8 0 0 0 8 8h4.518" stroke="#fff" stroke-width="2" />
<path stroke="#fff" stroke-width="2" stroke-linecap="round" d="M23.533 37.736H49.49M23.533 46.802H49.49M23.533 42.269H49.49M23.533 55.456H49.49M23.533 50.923H49.49" />
<path d="M1 16.483C1 7.944 8.013 1 16.69 1c8.678 0 15.691 6.944 15.691 15.483 0 8.54-7.013 15.484-15.69 15.484C8.012 31.967 1 25.023 1 16.484Z" fill="#518FFF" stroke="#fff" stroke-width="2" />
<path d="m16.562 7.979.1 11.538" stroke="#fff" stroke-width="2" stroke-linecap="round" />
<ellipse rx="1.252" ry="1.236" transform="rotate(-.479 2802.219 -1964.476) skewX(.012)" fill="#fff" />
</svg>
`,
requiresPremium: false,
},
};
@Component({
selector: "app-report-card",
templateUrl: "report-card.component.html",
})
export class ReportCardComponent implements OnInit {
@Input() type: ReportTypes;
report: ReportEntry;
hasPremium: boolean;
constructor(
private stateService: StateService,
private messagingService: MessagingService,
private sanitizer: DomSanitizer
) {}
async ngOnInit() {
this.report = reports[this.type];
this.hasPremium = await this.stateService.getCanAccessPremium();
}
get premium() {
return this.report.requiresPremium && !this.hasPremium;
}
get route() {
if (this.premium) {
return null;
}
return this.report.route;
}
get icon() {
return this.sanitizer.bypassSecurityTrustHtml(this.report.icon);
}
click() {
if (this.premium) {
this.messagingService.send("premiumRequired");
}
}
}

View File

@ -0,0 +1,11 @@
<div class="page-header">
<h1>{{ "reports" | i18n }}</h1>
</div>
<p>{{ "reportsDesc" | i18n }}</p>
<div class="tw-inline-grid tw-grid-cols-3 tw-gap-4">
<div *ngFor="let report of reports">
<app-report-card [type]="report"></app-report-card>
</div>
</div>

View File

@ -0,0 +1,18 @@
import { Component } from "@angular/core";
import { ReportTypes } from "./report-card.component";
@Component({
selector: "app-report-list",
templateUrl: "report-list.component.html",
})
export class ReportListComponent {
reports = [
ReportTypes.exposedPasswords,
ReportTypes.reusedPasswords,
ReportTypes.weakPasswords,
ReportTypes.unsecuredWebsites,
ReportTypes.inactive2fa,
ReportTypes.dataBreach,
];
}

View File

@ -0,0 +1,12 @@
<div class="container page-content">
<router-outlet></router-outlet>
<div class="row mt-4">
<div class="col">
<a bit-button routerLink="./" *ngIf="!homepage">
<i class="bwi bwi-angle-left" aria-hidden="true"></i>
{{ "backToReports" | i18n }}
</a>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { Component, OnDestroy } from "@angular/core";
import { NavigationEnd, Router } from "@angular/router";
import { Subscription } from "rxjs";
import { filter } from "rxjs/operators";
@Component({
selector: "app-reports",
templateUrl: "reports.component.html",
})
export class ReportsComponent implements OnDestroy {
homepage = true;
subscription: Subscription;
constructor(router: Router) {
this.subscription = router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.subscribe((event) => {
this.homepage = (event as NavigationEnd).url == "/reports";
});
}
ngOnDestroy(): void {
this.subscription?.unsubscribe();
}
}

View File

@ -0,0 +1,60 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { BreachReportComponent } from "./breach-report.component";
import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component";
import { InactiveTwoFactorReportComponent } from "./inactive-two-factor-report.component";
import { ReportListComponent } from "./report-list.component";
import { ReportsComponent } from "./reports.component";
import { ReusedPasswordsReportComponent } from "./reused-passwords-report.component";
import { UnsecuredWebsitesReportComponent } from "./unsecured-websites-report.component";
import { WeakPasswordsReportComponent } from "./weak-passwords-report.component";
const routes: Routes = [
{
path: "",
component: ReportsComponent,
canActivate: [AuthGuardService],
children: [
{ path: "", pathMatch: "full", component: ReportListComponent, data: { homepage: true } },
{
path: "breach-report",
component: BreachReportComponent,
data: { titleId: "dataBreachReport" },
},
{
path: "reused-passwords-report",
component: ReusedPasswordsReportComponent,
data: { titleId: "reusedPasswordsReport" },
},
{
path: "unsecured-websites-report",
component: UnsecuredWebsitesReportComponent,
data: { titleId: "unsecuredWebsitesReport" },
},
{
path: "weak-passwords-report",
component: WeakPasswordsReportComponent,
data: { titleId: "weakPasswordsReport" },
},
{
path: "exposed-passwords-report",
component: ExposedPasswordsReportComponent,
data: { titleId: "exposedPasswordsReport" },
},
{
path: "inactive-two-factor-report",
component: InactiveTwoFactorReportComponent,
data: { titleId: "inactive2faReport" },
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ReportsModule {}

View File

@ -9,7 +9,7 @@
> >
<div class="modal-header"> <div class="modal-header">
<h2 class="modal-title" id="userAddEditTitle"> <h2 class="modal-title" id="userAddEditTitle">
<span class="badge badge-primary" *ngIf="readOnly">{{ "premium" | i18n }}</span> <app-premium-badge *ngIf="readOnly"></app-premium-badge>
{{ title }} {{ title }}
<small class="text-muted" *ngIf="name">{{ name }}</small> <small class="text-muted" *ngIf="name">{{ name }}</small>
</h2> </h2>

View File

@ -15,15 +15,7 @@
<div class="page-header d-flex"> <div class="page-header d-flex">
<h2> <h2>
{{ "trustedEmergencyContacts" | i18n }} {{ "trustedEmergencyContacts" | i18n }}
<a <app-premium-badge></app-premium-badge>
href="#"
appStopClick
class="badge badge-primary"
*ngIf="!canAccessPremium"
(click)="premiumRequired()"
>
{{ "premium" | i18n }}
</a>
</h2> </h2>
<div class="ml-auto d-flex"> <div class="ml-auto d-flex">
<button <button

View File

@ -39,15 +39,7 @@
></i> ></i>
<span class="sr-only">{{ "enabled" | i18n }}</span> <span class="sr-only">{{ "enabled" | i18n }}</span>
</ng-container> </ng-container>
<a <app-premium-badge *ngIf="p.premium"></app-premium-badge>
href="#"
appStopClick
class="badge badge-primary"
*ngIf="!canAccessPremium && p.premium"
(click)="premiumRequired()"
>
{{ "premium" | i18n }}
</a>
</h3> </h3>
{{ p.description }} {{ p.description }}
</div> </div>

View File

@ -15,59 +15,6 @@
</a> </a>
</div> </div>
</div> </div>
<div class="card">
<div class="card-header d-flex">
{{ "reports" | i18n }}
<div class="ml-auto">
<a
href="#"
appStopClick
class="badge badge-primary"
*ngIf="!canAccessPremium"
(click)="premiumRequired()"
>
{{ "premium" | i18n }}
</a>
</div>
</div>
<div class="list-group list-group-flush">
<a
routerLink="exposed-passwords-report"
class="list-group-item"
routerLinkActive="active"
>
{{ "exposedPasswordsReport" | i18n }}
</a>
<a routerLink="reused-passwords-report" class="list-group-item" routerLinkActive="active">
{{ "reusedPasswordsReport" | i18n }}
</a>
<a routerLink="weak-passwords-report" class="list-group-item" routerLinkActive="active">
{{ "weakPasswordsReport" | i18n }}
</a>
<a
routerLink="unsecured-websites-report"
class="list-group-item"
routerLinkActive="active"
>
{{ "unsecuredWebsitesReport" | i18n }}
</a>
<a
routerLink="inactive-two-factor-report"
class="list-group-item"
routerLinkActive="active"
>
{{ "inactive2faReport" | i18n }}
</a>
<a routerLink="breach-report" class="list-group-item d-flex" routerLinkActive="active">
{{ "dataBreachReport" | i18n }}
<div class="ml-auto">
<span class="badge badge-success" *ngIf="!canAccessPremium">
{{ "free" | i18n | uppercase }}
</span>
</div>
</a>
</div>
</div>
</div> </div>
<div class="col-9"> <div class="col-9">
<router-outlet></router-outlet> <router-outlet></router-outlet>

View File

@ -187,15 +187,10 @@
title="{{ 'verificationCodeTotp' | i18n }}" title="{{ 'verificationCodeTotp' | i18n }}"
class="ml-2" class="ml-2"
/> />
<a <app-premium-badge
href="#" *ngIf="!organization && !cipher.organizationId"
appStopClick class="ml-3"
class="badge badge-primary ml-3" ></app-premium-badge>
(click)="premiumRequired()"
*ngIf="!organization && !cipher.organizationId && !canAccessPremium"
>
{{ "premium" | i18n }}
</a>
<a <a
href="#" href="#"
appStopClick appStopClick

View File

@ -1430,11 +1430,14 @@
"reports": { "reports": {
"message": "Reports" "message": "Reports"
}, },
"reportsDesc": {
"message": "Identify and close security gaps in your online accounts by clicking the reports below."
},
"unsecuredWebsitesReport": { "unsecuredWebsitesReport": {
"message": "Unsecured Websites Report" "message": "Unsecure Websites"
}, },
"unsecuredWebsitesReportDesc": { "unsecuredWebsitesReportDesc": {
"message": "Using unsecured websites with the http:// scheme can be dangerous. If the website allows, you should always access it using the https:// scheme so that your connection is encrypted." "message": "URLs that start with http:// dont use the best available encryption. Change the Login URIs for these accounts to https:// for safer browsing."
}, },
"unsecuredWebsitesFound": { "unsecuredWebsitesFound": {
"message": "Unsecured Websites Found" "message": "Unsecured Websites Found"
@ -1452,10 +1455,10 @@
"message": "No items in your vault have unsecured URIs." "message": "No items in your vault have unsecured URIs."
}, },
"inactive2faReport": { "inactive2faReport": {
"message": "Inactive 2FA Report" "message": "Inactive Two-step Login"
}, },
"inactive2faReportDesc": { "inactive2faReportDesc": {
"message": "Two-factor authentication (2FA) is an important security setting that helps secure your accounts. If the website offers it, you should always enable two-factor authentication." "message": "Two-step Login adds a layer of protection to your accounts. Turn on Two-Step Login using Bitwarden Authenticator for these accounts or use an alternative method."
}, },
"inactive2faFound": { "inactive2faFound": {
"message": "Logins Without 2FA Found" "message": "Logins Without 2FA Found"
@ -1476,10 +1479,10 @@
"message": "Instructions" "message": "Instructions"
}, },
"exposedPasswordsReport": { "exposedPasswordsReport": {
"message": "Exposed Passwords Report" "message": "Exposed Passwords"
}, },
"exposedPasswordsReportDesc": { "exposedPasswordsReportDesc": {
"message": "Exposed passwords are passwords that have been uncovered in known data breaches that were released publicly or sold on the dark web by hackers." "message": "Passwords exposed in a data breach are easy targets for attackers. Change these passwords to prevent potential break-ins."
}, },
"exposedPasswordsFound": { "exposedPasswordsFound": {
"message": "Exposed Passwords Found" "message": "Exposed Passwords Found"
@ -1509,10 +1512,10 @@
} }
}, },
"weakPasswordsReport": { "weakPasswordsReport": {
"message": "Weak Passwords Report" "message": "Weak Passwords"
}, },
"weakPasswordsReportDesc": { "weakPasswordsReportDesc": {
"message": "Weak passwords can easily be guessed by hackers and automated tools that are used to crack passwords. The Bitwarden password generator can help you create strong passwords." "message": "Weak passwords can be easily guessed by attackers. Change these passwords to strong ones using the Password Generator."
}, },
"weakPasswordsFound": { "weakPasswordsFound": {
"message": "Weak Passwords Found" "message": "Weak Passwords Found"
@ -1530,10 +1533,10 @@
"message": "No items in your vault have weak passwords." "message": "No items in your vault have weak passwords."
}, },
"reusedPasswordsReport": { "reusedPasswordsReport": {
"message": "Reused Passwords Report" "message": "Reused Passwords"
}, },
"reusedPasswordsReportDesc": { "reusedPasswordsReportDesc": {
"message": "If a service that you use is compromised, reusing the same password elsewhere can allow hackers to easily gain access to more of your online accounts. You should use a unique password for every account or service." "message": "Reusing passwords makes it easier for attackers to break into multiple accounts. Change these passwords so that each is unique."
}, },
"reusedPasswordsFound": { "reusedPasswordsFound": {
"message": "Reused Passwords Found" "message": "Reused Passwords Found"
@ -1560,10 +1563,10 @@
} }
}, },
"dataBreachReport": { "dataBreachReport": {
"message": "Data Breach Report" "message": "Data Breach"
}, },
"breachDesc": { "breachDesc": {
"message": "A \"breach\" is an incident where a site's data has been illegally accessed by hackers and then released publicly. Review the types of data that were compromised (email addresses, passwords, credit cards etc.) and take appropriate action, such as changing passwords." "message": "Breached accounts can expose your personal information. Secure breached accounts by enabling 2FA or creating a stronger password."
}, },
"breachCheckUsernameEmail": { "breachCheckUsernameEmail": {
"message": "Check any usernames or email addresses that you use." "message": "Check any usernames or email addresses that you use."
@ -4832,6 +4835,9 @@
} }
} }
}, },
"backToReports": {
"message": "Back to Reports"
},
"generator": { "generator": {
"message": "Generator" "message": "Generator"
}, },