From f913d8b6be8621efc246550354a56b9cb5758639 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Mon, 5 Jun 2023 10:40:41 -0400 Subject: [PATCH] [SM-749] Redirect to SA list if SA isn't found (#5511) * redirect if SA isn't found * refactor to use getByServiceAccountId * add error toasts --- apps/web/src/locales/en/messages.json | 11 ++- .../projects/project/project.component.ts | 28 +++++--- .../service-account.component.ts | 67 ++++++++++++++----- 3 files changed, 79 insertions(+), 27 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index c03ee2fc9f..9224bfe8d1 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -6828,7 +6828,16 @@ }, "memberDecryptionTdeDescriptionEnd": { "message": "with automatic enrollment will turn on when this option is used.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The account recovery administration policy with automatic enrollment will turn on when this option is used.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The master password reset policy with automatic enrollment will turn on when this option is used.'" + }, + "notFound":{ + "message": "$RESOURCE$ not found", + "placeholders": { + "resource": { + "content": "$1", + "example": "Service Account" + } + } }, "recoverAccount": { "message": "Recover account" diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts index 7c4e7357af..0fef75daf2 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts @@ -3,6 +3,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { catchError, combineLatest, + EMPTY, filter, Observable, startWith, @@ -12,6 +13,8 @@ import { } from "rxjs"; import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog"; +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { ProjectView } from "../../models/view/project.view"; import { @@ -37,7 +40,9 @@ export class ProjectComponent implements OnInit, OnDestroy { private route: ActivatedRoute, private projectService: ProjectService, private router: Router, - private dialogService: DialogServiceAbstraction + private dialogService: DialogServiceAbstraction, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService ) {} ngOnInit(): void { @@ -48,10 +53,17 @@ export class ProjectComponent implements OnInit, OnDestroy { ); this.project$ = combineLatest([this.route.params, currentProjectEdited]).pipe( - switchMap(([params, _]) => { - return this.projectService.getByProjectId(params.projectId); - }), - catchError(async () => this.handleError()) + switchMap(([params, _]) => this.projectService.getByProjectId(params.projectId)), + catchError(() => { + this.router.navigate(["/sm", this.organizationId, "projects"]).then(() => { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("notFound", this.i18nService.t("project")) + ); + }); + return EMPTY; + }) ); this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { @@ -60,12 +72,6 @@ export class ProjectComponent implements OnInit, OnDestroy { }); } - handleError = () => { - const projectsListUrl = `/sm/${this.organizationId}/projects/`; - this.router.navigate([projectsListUrl]); - return new ProjectView(); - }; - ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts index 6d5dee2a9f..cc0c3a4d88 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts @@ -1,8 +1,19 @@ -import { Component } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { switchMap } from "rxjs"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { + EMPTY, + Subject, + catchError, + combineLatest, + filter, + startWith, + switchMap, + takeUntil, +} from "rxjs"; import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog"; +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { AccessTokenCreateDialogComponent } from "./access/dialogs/access-token-create-dialog.component"; import { ServiceAccountService } from "./service-account.service"; @@ -11,30 +22,56 @@ import { ServiceAccountService } from "./service-account.service"; selector: "sm-service-account", templateUrl: "./service-account.component.html", }) -export class ServiceAccountComponent { +export class ServiceAccountComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); private organizationId: string; private serviceAccountId: string; - /** - * TODO: remove when a server method is available that fetches a service account by ID - */ - protected serviceAccount$ = this.route.params.pipe( - switchMap((params) => { - this.serviceAccountId = params.serviceAccountId; - this.organizationId = params.organizationId; + private onChange$ = this.serviceAccountService.serviceAccount$.pipe( + filter((sa) => sa?.id === this.serviceAccountId), + startWith(null) + ); - return this.serviceAccountService - .getServiceAccounts(params.organizationId) - .then((saList) => saList.find((sa) => sa.id === params.serviceAccountId)); + protected serviceAccount$ = combineLatest([this.route.params, this.onChange$]).pipe( + switchMap(([params, _]) => + this.serviceAccountService.getByServiceAccountId( + params.serviceAccountId, + params.organizationId + ) + ), + catchError(() => { + this.router.navigate(["/sm", this.organizationId, "service-accounts"]).then(() => { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("notFound", this.i18nService.t("serviceAccount")) + ); + }); + return EMPTY; }) ); constructor( private route: ActivatedRoute, private serviceAccountService: ServiceAccountService, - private dialogService: DialogServiceAbstraction + private dialogService: DialogServiceAbstraction, + private router: Router, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService ) {} + ngOnInit(): void { + this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { + this.serviceAccountId = params.serviceAccountId; + this.organizationId = params.organizationId; + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + protected openNewAccessTokenDialog() { AccessTokenCreateDialogComponent.openNewAccessTokenDialog( this.dialogService,