mirror of
https://github.com/bitwarden/browser
synced 2025-12-22 19:23:52 +00:00
[PM-283] Fix Reports UI behavior for premium and free users (#4926)
* Prevent rerouting to dispaly modal message, and refactored components where thsi was used * Added upgrade badge to organization reports view * created guard to prevent free organization users from accessing reports * Added isUpgradeRequired getter to organization class * Modifiewd reports home to pass upgrade badge and add new guard to organization reports module * Fixed routing bug when routing to billing subscription page * Refactored to use async pipe and observables * Renamed getter name to be more descriptive * Removed checkAccess from reports * Renamed guard * Removed unused variables * Lint fix * Lint fix * prettier fix * Corrected organiztion service reference * Moved homepage to ngonInit * [PM-1629] Update the upgrade dialog for users without billing rights (#5102) * Show dialog with description when user does not have access to the billing page * switched conditions to nested if to make the logic clearer
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class IsPaidOrgGuard implements CanActivate {
|
||||
constructor(
|
||||
private router: Router,
|
||||
private organizationService: OrganizationService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private messagingService: MessagingService,
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const org = this.organizationService.get(route.params.organizationId);
|
||||
|
||||
if (org == null) {
|
||||
return this.router.createUrlTree(["/"]);
|
||||
}
|
||||
|
||||
if (org.isFreeOrg) {
|
||||
// Users without billing permission can't access billing
|
||||
if (!org.canManageBilling) {
|
||||
await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("notAvailableForFreeOrganization"),
|
||||
this.i18nService.t("upgradeOrganization"),
|
||||
this.i18nService.t("ok")
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
this.messagingService.send("upgradeOrganization", { organizationId: org.id });
|
||||
}
|
||||
}
|
||||
|
||||
return !org.isFreeOrg;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { InactiveTwoFactorReportComponent } from "../../../admin-console/organiz
|
||||
import { ReusedPasswordsReportComponent } from "../../../admin-console/organizations/tools/reused-passwords-report.component";
|
||||
import { UnsecuredWebsitesReportComponent } from "../../../admin-console/organizations/tools/unsecured-websites-report.component";
|
||||
import { WeakPasswordsReportComponent } from "../../../admin-console/organizations/tools/weak-passwords-report.component";
|
||||
import { IsPaidOrgGuard } from "../guards/is-paid-org.guard";
|
||||
import { OrganizationPermissionsGuard } from "../guards/org-permissions.guard";
|
||||
import { OrganizationRedirectGuard } from "../guards/org-redirect.guard";
|
||||
import { EventsComponent } from "../manage/events.component";
|
||||
@@ -46,6 +47,7 @@ const routes: Routes = [
|
||||
data: {
|
||||
titleId: "exposedPasswordsReport",
|
||||
},
|
||||
canActivate: [IsPaidOrgGuard],
|
||||
},
|
||||
{
|
||||
path: "inactive-two-factor-report",
|
||||
@@ -53,6 +55,7 @@ const routes: Routes = [
|
||||
data: {
|
||||
titleId: "inactive2faReport",
|
||||
},
|
||||
canActivate: [IsPaidOrgGuard],
|
||||
},
|
||||
{
|
||||
path: "reused-passwords-report",
|
||||
@@ -60,6 +63,7 @@ const routes: Routes = [
|
||||
data: {
|
||||
titleId: "reusedPasswordsReport",
|
||||
},
|
||||
canActivate: [IsPaidOrgGuard],
|
||||
},
|
||||
{
|
||||
path: "unsecured-websites-report",
|
||||
@@ -67,6 +71,7 @@ const routes: Routes = [
|
||||
data: {
|
||||
titleId: "unsecuredWebsitesReport",
|
||||
},
|
||||
canActivate: [IsPaidOrgGuard],
|
||||
},
|
||||
{
|
||||
path: "weak-passwords-report",
|
||||
@@ -74,6 +79,7 @@ const routes: Routes = [
|
||||
data: {
|
||||
titleId: "weakPasswordsReport",
|
||||
},
|
||||
canActivate: [IsPaidOrgGuard],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<ng-container *ngIf="homepage">
|
||||
<ng-container *ngIf="homepage$ | async">
|
||||
<div class="page-header">
|
||||
<h1>{{ "reports" | i18n }}</h1>
|
||||
</div>
|
||||
|
||||
<p>{{ "orgsReportsDesc" | i18n }}</p>
|
||||
|
||||
<app-report-list [reports]="reports"></app-report-list>
|
||||
<app-report-list [reports]="reports$ | async"></app-report-list>
|
||||
</ng-container>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col">
|
||||
<a bitButton routerLink="./" *ngIf="!homepage">
|
||||
<a bitButton routerLink="./" *ngIf="!(homepage$ | async)">
|
||||
<i class="bwi bwi-angle-left" aria-hidden="true"></i>
|
||||
{{ "backToReports" | i18n }}
|
||||
</a>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { NavigationEnd, Router } from "@angular/router";
|
||||
import { filter, Subject, takeUntil } from "rxjs";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
|
||||
import { filter, map, Observable, startWith } from "rxjs";
|
||||
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
|
||||
import { ReportVariant, reports, ReportType, ReportEntry } from "../../../reports";
|
||||
|
||||
@@ -10,56 +11,56 @@ import { ReportVariant, reports, ReportType, ReportEntry } from "../../../report
|
||||
selector: "app-org-reports-home",
|
||||
templateUrl: "reports-home.component.html",
|
||||
})
|
||||
export class ReportsHomeComponent implements OnInit, OnDestroy {
|
||||
reports: ReportEntry[];
|
||||
export class ReportsHomeComponent implements OnInit {
|
||||
reports$: Observable<ReportEntry[]>;
|
||||
homepage$: Observable<boolean>;
|
||||
|
||||
homepage = true;
|
||||
private destrory$: Subject<void> = new Subject<void>();
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private stateService: StateService,
|
||||
private organizationService: OrganizationService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
constructor(private stateService: StateService, router: Router) {
|
||||
router.events
|
||||
.pipe(
|
||||
filter((event) => event instanceof NavigationEnd),
|
||||
takeUntil(this.destrory$)
|
||||
)
|
||||
.subscribe((event) => {
|
||||
this.homepage = (event as NavigationEnd).urlAfterRedirects.endsWith("/reports");
|
||||
});
|
||||
ngOnInit() {
|
||||
this.homepage$ = this.router.events.pipe(
|
||||
filter((event) => event instanceof NavigationEnd),
|
||||
map((event) => (event as NavigationEnd).urlAfterRedirects.endsWith("/reports")),
|
||||
startWith(true)
|
||||
);
|
||||
|
||||
this.reports$ = this.route.params.pipe(
|
||||
map((params) => this.organizationService.get(params.organizationId)),
|
||||
map((org) => this.buildReports(org.isFreeOrg))
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
const userHasPremium = await this.stateService.getCanAccessPremium();
|
||||
private buildReports(upgradeRequired: boolean): ReportEntry[] {
|
||||
const reportRequiresUpgrade = upgradeRequired
|
||||
? ReportVariant.RequiresUpgrade
|
||||
: ReportVariant.Enabled;
|
||||
|
||||
const reportRequiresPremium = userHasPremium
|
||||
? ReportVariant.Enabled
|
||||
: ReportVariant.RequiresPremium;
|
||||
|
||||
this.reports = [
|
||||
return [
|
||||
{
|
||||
...reports[ReportType.ExposedPasswords],
|
||||
variant: reportRequiresPremium,
|
||||
variant: reportRequiresUpgrade,
|
||||
},
|
||||
{
|
||||
...reports[ReportType.ReusedPasswords],
|
||||
variant: reportRequiresPremium,
|
||||
variant: reportRequiresUpgrade,
|
||||
},
|
||||
{
|
||||
...reports[ReportType.WeakPasswords],
|
||||
variant: reportRequiresPremium,
|
||||
variant: reportRequiresUpgrade,
|
||||
},
|
||||
{
|
||||
...reports[ReportType.UnsecuredWebsites],
|
||||
variant: reportRequiresPremium,
|
||||
variant: reportRequiresUpgrade,
|
||||
},
|
||||
{
|
||||
...reports[ReportType.Inactive2fa],
|
||||
variant: reportRequiresPremium,
|
||||
variant: reportRequiresUpgrade,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destrory$.next();
|
||||
this.destrory$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,12 +33,11 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
|
||||
super(cipherService, auditService, modalService, messagingService, passwordRepromptService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
async ngOnInit() {
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organization = await this.organizationService.get(params.organizationId);
|
||||
this.manageableCiphers = await this.cipherService.getAll();
|
||||
await this.checkAccess();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user