From 0444b78ad110ea69a666316b4e27b1bae2517b32 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 25 Apr 2022 15:41:44 +0200 Subject: [PATCH] [EC-143] [BEEEP] Allow linking to ciphers (#1579) Co-authored-by: Matt Gibson --- .../manage/accept-provider.component.html | 6 +++- .../setup/setup-provider.component.html | 6 +++- jslib | 2 +- .../accounts/accept-emergency.component.html | 6 +++- .../accept-organization.component.html | 6 +++- src/app/accounts/hint.component.html | 2 +- src/app/accounts/lock.component.ts | 2 +- src/app/accounts/login.component.ts | 26 +++++++------- .../accounts/recover-delete.component.html | 2 +- .../recover-two-factor.component.html | 2 +- src/app/accounts/register.component.html | 2 +- src/app/accounts/register.component.ts | 19 +++++----- src/app/accounts/sso.component.html | 2 +- src/app/accounts/two-factor.component.html | 2 +- src/app/accounts/two-factor.component.ts | 12 ++++--- .../verify-recover-delete.component.html | 2 +- src/app/app.component.ts | 8 ++++- src/app/common/base.accept.component.ts | 12 ------- src/app/guards/home.guard.ts | 24 +++++++++++++ .../accept-family-sponsorship.component.html | 13 +++++++ .../accept-family-sponsorship.component.ts | 26 ++++++++++++++ .../organizations/vault/vault.component.ts | 34 ++++++++++++++++-- src/app/oss-routing.module.ts | 20 +++++++++-- src/app/oss.module.ts | 2 ++ .../services/organization-guard.service.ts | 15 +++++--- src/app/services/router.service.ts | 36 ++++++++----------- src/app/services/services.module.ts | 2 ++ src/app/vault/ciphers.component.html | 6 ++-- src/app/vault/vault.component.ts | 34 ++++++++++++++++-- src/locales/en/messages.json | 3 ++ 30 files changed, 246 insertions(+), 88 deletions(-) create mode 100644 src/app/guards/home.guard.ts create mode 100644 src/app/organizations/sponsorships/accept-family-sponsorship.component.html create mode 100644 src/app/organizations/sponsorships/accept-family-sponsorship.component.ts diff --git a/bitwarden_license/src/app/providers/manage/accept-provider.component.html b/bitwarden_license/src/app/providers/manage/accept-provider.component.html index 7f34664a68..a5927cbc9a 100644 --- a/bitwarden_license/src/app/providers/manage/accept-provider.component.html +++ b/bitwarden_license/src/app/providers/manage/accept-provider.component.html @@ -24,7 +24,11 @@

{{ "joinProviderDesc" | i18n }}


- + {{ "logIn" | i18n }} {{ "setupProviderLoginDesc" | i18n }}


diff --git a/jslib b/jslib index 6bcadc4f40..f6e3481fe9 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 6bcadc4f408db2c150753f53a07d6f8888b6e9ff +Subproject commit f6e3481fe96690a3c52f7701d92b4e57f69f976a diff --git a/src/app/accounts/accept-emergency.component.html b/src/app/accounts/accept-emergency.component.html index 919b3f4ed0..4690a4e63a 100644 --- a/src/app/accounts/accept-emergency.component.html +++ b/src/app/accounts/accept-emergency.component.html @@ -23,7 +23,11 @@

{{ "acceptEmergencyAccess" | i18n }}


- + {{ "logIn" | i18n }} {{ "joinOrganizationDesc" | i18n }}


diff --git a/src/app/accounts/lock.component.ts b/src/app/accounts/lock.component.ts index e61a98a658..4b8a0d4b42 100644 --- a/src/app/accounts/lock.component.ts +++ b/src/app/accounts/lock.component.ts @@ -58,7 +58,7 @@ export class LockComponent extends BaseLockComponent { if (previousUrl !== "/" && previousUrl.indexOf("lock") === -1) { this.successRoute = previousUrl; } - this.router.navigate([this.successRoute]); + this.router.navigateByUrl(this.successRoute); }; } } diff --git a/src/app/accounts/login.component.ts b/src/app/accounts/login.component.ts index d5b38bde52..d00a6e1031 100644 --- a/src/app/accounts/login.component.ts +++ b/src/app/accounts/login.component.ts @@ -20,6 +20,7 @@ import { ListResponse } from "jslib-common/models/response/listResponse"; import { PolicyResponse } from "jslib-common/models/response/policyResponse"; import { StateService } from "../../abstractions/state.service"; +import { RouterService } from "../services/router.service"; @Component({ selector: "app-login", @@ -44,7 +45,8 @@ export class LoginComponent extends BaseLoginComponent { logService: LogService, ngZone: NgZone, protected stateService: StateService, - private messagingService: MessagingService + private messagingService: MessagingService, + private routerService: RouterService ) { super( authService, @@ -70,21 +72,20 @@ export class LoginComponent extends BaseLoginComponent { this.email = qParams.email; } if (qParams.premium != null) { - this.stateService.setLoginRedirect({ route: "/settings/premium" }); + this.routerService.setPreviousUrl("/settings/premium"); } else if (qParams.org != null) { - this.stateService.setLoginRedirect({ - route: "/settings/create-organization", - qParams: { plan: qParams.org }, + const route = this.router.createUrlTree(["settings/create-organization"], { + queryParams: { plan: qParams.org }, }); + this.routerService.setPreviousUrl(route.toString()); } // Are they coming from an email for sponsoring a families organization if (qParams.sponsorshipToken != null) { - // After logging in redirect them to setup the families sponsorship - this.stateService.setLoginRedirect({ - route: "/setup/families-for-enterprise", - qParams: { token: qParams.sponsorshipToken }, + const route = this.router.createUrlTree(["setup/families-for-enterprise"], { + queryParams: { plan: qParams.sponsorshipToken }, }); + this.routerService.setPreviousUrl(route.toString()); } await super.ngOnInit(); this.rememberEmail = await this.stateService.getRememberEmail(); @@ -145,10 +146,9 @@ export class LoginComponent extends BaseLoginComponent { } } - const loginRedirect = await this.stateService.getLoginRedirect(); - if (loginRedirect != null) { - this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams }); - await this.stateService.setLoginRedirect(null); + const previousUrl = this.routerService.getPreviousUrl(); + if (previousUrl) { + this.router.navigateByUrl(previousUrl); } else { this.router.navigate([this.successRoute]); } diff --git a/src/app/accounts/recover-delete.component.html b/src/app/accounts/recover-delete.component.html index a0678e8449..5b3ba90565 100644 --- a/src/app/accounts/recover-delete.component.html +++ b/src/app/accounts/recover-delete.component.html @@ -33,7 +33,7 @@ aria-hidden="true" > - + {{ "cancel" | i18n }}
diff --git a/src/app/accounts/recover-two-factor.component.html b/src/app/accounts/recover-two-factor.component.html index 47744b8990..744ce3dae1 100644 --- a/src/app/accounts/recover-two-factor.component.html +++ b/src/app/accounts/recover-two-factor.component.html @@ -65,7 +65,7 @@ aria-hidden="true" > - + {{ "cancel" | i18n }}
diff --git a/src/app/accounts/register.component.html b/src/app/accounts/register.component.html index 75d3883dcd..d86e0a4351 100644 --- a/src/app/accounts/register.component.html +++ b/src/app/accounts/register.component.html @@ -258,7 +258,7 @@ aria-hidden="true" > - + {{ "cancel" | i18n }} diff --git a/src/app/accounts/register.component.ts b/src/app/accounts/register.component.ts index b0fe063f46..fd2f3b567d 100644 --- a/src/app/accounts/register.component.ts +++ b/src/app/accounts/register.component.ts @@ -18,6 +18,8 @@ import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPa import { Policy } from "jslib-common/models/domain/policy"; import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest"; +import { RouterService } from "../services/router.service"; + @Component({ selector: "app-register", templateUrl: "register.component.html", @@ -41,7 +43,8 @@ export class RegisterComponent extends BaseRegisterComponent { passwordGenerationService: PasswordGenerationService, private policyService: PolicyService, environmentService: EnvironmentService, - logService: LogService + logService: LogService, + private routerService: RouterService ) { super( authService, @@ -64,14 +67,14 @@ export class RegisterComponent extends BaseRegisterComponent { this.email = qParams.email; } if (qParams.premium != null) { - this.stateService.setLoginRedirect({ route: "/settings/premium" }); + this.routerService.setPreviousUrl("/settings/premium"); } else if (qParams.org != null) { this.showCreateOrgMessage = true; this.referenceData.flow = qParams.org; - this.stateService.setLoginRedirect({ - route: "/settings/create-organization", - qParams: { plan: qParams.org }, + const route = this.router.createUrlTree(["settings/create-organization"], { + queryParams: { plan: qParams.org }, }); + this.routerService.setPreviousUrl(route.toString()); } if (qParams.layout != null) { this.layout = this.referenceData.layout = qParams.layout; @@ -88,10 +91,10 @@ export class RegisterComponent extends BaseRegisterComponent { // Are they coming from an email for sponsoring a families organization if (qParams.sponsorshipToken != null) { // After logging in redirect them to setup the families sponsorship - this.stateService.setLoginRedirect({ - route: "/setup/families-for-enterprise", - qParams: { token: qParams.sponsorshipToken }, + const route = this.router.createUrlTree(["setup/families-for-enterprise"], { + queryParams: { plan: qParams.sponsorshipToken }, }); + this.routerService.setPreviousUrl(route.toString()); } if (this.referenceData.id === "") { this.referenceData.id = null; diff --git a/src/app/accounts/sso.component.html b/src/app/accounts/sso.component.html index 84ae47495f..f558895398 100644 --- a/src/app/accounts/sso.component.html +++ b/src/app/accounts/sso.component.html @@ -41,7 +41,7 @@ aria-hidden="true" > - + {{ "cancel" | i18n }} diff --git a/src/app/accounts/two-factor.component.html b/src/app/accounts/two-factor.component.html index 64961e98b9..835fd12e78 100644 --- a/src/app/accounts/two-factor.component.html +++ b/src/app/accounts/two-factor.component.html @@ -138,7 +138,7 @@ aria-hidden="true" > - + {{ "cancel" | i18n }} diff --git a/src/app/accounts/two-factor.component.ts b/src/app/accounts/two-factor.component.ts index 34cb40a93b..5c75577b23 100644 --- a/src/app/accounts/two-factor.component.ts +++ b/src/app/accounts/two-factor.component.ts @@ -13,6 +13,8 @@ import { StateService } from "jslib-common/abstractions/state.service"; import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service"; import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType"; +import { RouterService } from "../services/router.service"; + import { TwoFactorOptionsComponent } from "./two-factor-options.component"; @Component({ @@ -34,7 +36,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { private modalService: ModalService, route: ActivatedRoute, logService: LogService, - twoFactorService: TwoFactorService + twoFactorService: TwoFactorService, + private routerService: RouterService ) { super( authService, @@ -70,10 +73,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { } async goAfterLogIn() { - const loginRedirect = await this.stateService.getLoginRedirect(); - if (loginRedirect != null) { - this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams }); - await this.stateService.setLoginRedirect(null); + const previousUrl = this.routerService.getPreviousUrl(); + if (previousUrl) { + this.router.navigateByUrl(previousUrl); } else { this.router.navigate([this.successRoute], { queryParams: { diff --git a/src/app/accounts/verify-recover-delete.component.html b/src/app/accounts/verify-recover-delete.component.html index 79fc2fcd78..975858ac5e 100644 --- a/src/app/accounts/verify-recover-delete.component.html +++ b/src/app/accounts/verify-recover-delete.component.html @@ -23,7 +23,7 @@ aria-hidden="true" > - + {{ "cancel" | i18n }} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index cd94408f94..9e26f68078 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -91,11 +91,17 @@ export class AppComponent implements OnDestroy, OnInit { this.ngZone.run(async () => { switch (message.command) { case "loggedIn": + this.notificationsService.updateConnection(false); + break; case "loggedOut": + this.routerService.setPreviousUrl(null); + this.notificationsService.updateConnection(false); + break; case "unlocked": this.notificationsService.updateConnection(false); break; case "authBlocked": + this.routerService.setPreviousUrl(message.url); this.router.navigate(["/"]); break; case "logout": @@ -109,7 +115,7 @@ export class AppComponent implements OnDestroy, OnInit { this.router.navigate(["lock"]); break; case "lockedUrl": - window.setTimeout(() => this.routerService.setPreviousUrl(message.url), 500); + this.routerService.setPreviousUrl(message.url); break; case "syncStarted": break; diff --git a/src/app/common/base.accept.component.ts b/src/app/common/base.accept.component.ts index fa3398f88b..36375fbd1e 100644 --- a/src/app/common/base.accept.component.ts +++ b/src/app/common/base.accept.component.ts @@ -30,7 +30,6 @@ export abstract class BaseAcceptComponent implements OnInit { ngOnInit() { this.route.queryParams.pipe(first()).subscribe(async (qParams) => { - await this.stateService.setLoginRedirect(null); let error = this.requiredParameters.some((e) => qParams?.[e] == null || qParams[e] === ""); let errorMessage: string = null; if (!error) { @@ -44,11 +43,6 @@ export abstract class BaseAcceptComponent implements OnInit { errorMessage = e.message; } } else { - await this.stateService.setLoginRedirect({ - route: this.getRedirectRoute(), - qParams: qParams, - }); - this.email = qParams.email; await this.unauthedHandler(qParams); } @@ -66,10 +60,4 @@ export abstract class BaseAcceptComponent implements OnInit { this.loading = false; }); } - - getRedirectRoute() { - const urlTree = this.router.parseUrl(this.router.url); - urlTree.queryParams = {}; - return urlTree.toString(); - } } diff --git a/src/app/guards/home.guard.ts b/src/app/guards/home.guard.ts new file mode 100644 index 0000000000..c7fa9402ee --- /dev/null +++ b/src/app/guards/home.guard.ts @@ -0,0 +1,24 @@ +import { Injectable } from "@angular/core"; +import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router"; + +import { StateService } from "jslib-common/abstractions/state.service"; +import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service"; + +@Injectable() +export class HomeGuard implements CanActivate { + constructor( + private vaultTimeoutService: VaultTimeoutService, + private router: Router, + private stateService: StateService + ) {} + + async canActivate(route: ActivatedRouteSnapshot) { + if (!(await this.stateService.getIsAuthenticated())) { + return this.router.createUrlTree(["/login"], { queryParams: route.queryParams }); + } + if (await this.vaultTimeoutService.isLocked()) { + return this.router.createUrlTree(["/lock"], { queryParams: route.queryParams }); + } + return this.router.createUrlTree(["/vault"], { queryParams: route.queryParams }); + } +} diff --git a/src/app/organizations/sponsorships/accept-family-sponsorship.component.html b/src/app/organizations/sponsorships/accept-family-sponsorship.component.html new file mode 100644 index 0000000000..e7eb29a3ac --- /dev/null +++ b/src/app/organizations/sponsorships/accept-family-sponsorship.component.html @@ -0,0 +1,13 @@ +
+
+ +

+ + {{ "loading" | i18n }} +

+
+
diff --git a/src/app/organizations/sponsorships/accept-family-sponsorship.component.ts b/src/app/organizations/sponsorships/accept-family-sponsorship.component.ts new file mode 100644 index 0000000000..acbcaa85e5 --- /dev/null +++ b/src/app/organizations/sponsorships/accept-family-sponsorship.component.ts @@ -0,0 +1,26 @@ +import { Component } from "@angular/core"; + +import { BaseAcceptComponent } from "src/app/common/base.accept.component"; + +@Component({ + selector: "app-accept-family-sponsorship", + templateUrl: "accept-family-sponsorship.component.html", +}) +export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent { + failedShortMessage = "inviteAcceptFailedShort"; + failedMessage = "inviteAcceptFailed"; + + requiredParameters = ["email", "token"]; + + async authedHandler(qParams: any) { + this.router.navigate(["/setup/families-for-enterprise"], { queryParams: qParams }); + } + + async unauthedHandler(qParams: any) { + if (!qParams.register) { + this.router.navigate(["/login"], { queryParams: { email: qParams.email } }); + } else { + this.router.navigate(["/register"], { queryParams: { email: qParams.email } }); + } + } +} diff --git a/src/app/organizations/vault/vault.component.ts b/src/app/organizations/vault/vault.component.ts index ea7d683ecb..61c8b100d1 100644 --- a/src/app/organizations/vault/vault.component.ts +++ b/src/app/organizations/vault/vault.component.ts @@ -12,6 +12,7 @@ import { first } from "rxjs/operators"; import { ModalService } from "jslib-angular/services/modal.service"; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; +import { CipherService } from "jslib-common/abstractions/cipher.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service"; import { OrganizationService } from "jslib-common/abstractions/organization.service"; @@ -64,7 +65,8 @@ export class VaultComponent implements OnInit, OnDestroy { private messagingService: MessagingService, private broadcasterService: BroadcasterService, private ngZone: NgZone, - private platformUtilsService: PlatformUtilsService + private platformUtilsService: PlatformUtilsService, + private cipherService: CipherService ) {} ngOnInit() { @@ -126,6 +128,24 @@ export class VaultComponent implements OnInit, OnDestroy { this.viewEvents(cipher[0]); } } + + this.route.queryParams.subscribe(async (params) => { + if (params.cipherId) { + if ((await this.cipherService.get(params.cipherId)) != null) { + this.editCipherId(params.cipherId); + } else { + this.platformUtilsService.showToast( + "error", + this.i18nService.t("errorOccurred"), + this.i18nService.t("unknownCipher") + ); + this.router.navigate([], { + queryParams: { cipherId: null }, + queryParamsHandling: "merge", + }); + } + } + }); }); }); } @@ -257,12 +277,16 @@ export class VaultComponent implements OnInit, OnDestroy { } async editCipher(cipher: CipherView) { + return this.editCipherId(cipher?.id); + } + + async editCipherId(cipherId: string) { const [modal, childComponent] = await this.modalService.openViewRef( AddEditComponent, this.cipherAddEditModalRef, (comp) => { comp.organization = this.organization; - comp.cipherId = cipher == null ? null : cipher.id; + comp.cipherId = cipherId; comp.onSavedCipher.subscribe(async () => { modal.close(); await this.ciphersComponent.refresh(); @@ -278,6 +302,11 @@ export class VaultComponent implements OnInit, OnDestroy { } ); + modal.onClosedPromise().then(() => { + this.route.params; + this.router.navigate([], { queryParams: { cipherId: null }, queryParamsHandling: "merge" }); + }); + return childComponent; } @@ -321,6 +350,7 @@ export class VaultComponent implements OnInit, OnDestroy { this.router.navigate([], { relativeTo: this.route, queryParams: queryParams, + queryParamsHandling: "merge", replaceUrl: true, }); } diff --git a/src/app/oss-routing.module.ts b/src/app/oss-routing.module.ts index 6c408c13f1..440113bfa6 100644 --- a/src/app/oss-routing.module.ts +++ b/src/app/oss-routing.module.ts @@ -22,6 +22,7 @@ import { UpdatePasswordComponent } from "./accounts/update-password.component"; import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component"; import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component"; +import { HomeGuard } from "./guards/home.guard"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; import { OrganizationLayoutComponent } from "./layouts/organization-layout.component"; import { UserLayoutComponent } from "./layouts/user-layout.component"; @@ -36,6 +37,7 @@ import { OrganizationBillingComponent } from "./organizations/settings/organizat import { OrganizationSubscriptionComponent } from "./organizations/settings/organization-subscription.component"; import { SettingsComponent as OrgSettingsComponent } from "./organizations/settings/settings.component"; import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "./organizations/settings/two-factor-setup.component"; +import { AcceptFamilySponsorshipComponent } from "./organizations/sponsorships/accept-family-sponsorship.component"; import { FamiliesForEnterpriseSetupComponent } from "./organizations/sponsorships/families-for-enterprise-setup.component"; import { ExportComponent as OrgExportComponent } from "./organizations/tools/export.component"; import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "./organizations/tools/exposed-passwords-report.component"; @@ -73,8 +75,15 @@ const routes: Routes = [ { path: "", component: FrontendLayoutComponent, + data: { doNotSaveUrl: true }, children: [ - { path: "", pathMatch: "full", component: LoginComponent, canActivate: [UnauthGuardService] }, + { + path: "", + pathMatch: "full", + children: [], // Children lets us have an empty component. + canActivate: [HomeGuard], // Redirects either to vault, login or lock page. + }, + { path: "login", component: LoginComponent, canActivate: [UnauthGuardService] }, { path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuardService] }, { path: "register", @@ -108,12 +117,17 @@ const routes: Routes = [ { path: "accept-organization", component: AcceptOrganizationComponent, - data: { titleId: "joinOrganization" }, + data: { titleId: "joinOrganization", doNotSaveUrl: false }, }, { path: "accept-emergency", component: AcceptEmergencyComponent, - data: { titleId: "acceptEmergency" }, + data: { titleId: "acceptEmergency", doNotSaveUrl: false }, + }, + { + path: "accept-families-for-enterprise", + component: AcceptFamilySponsorshipComponent, + data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false }, }, { path: "recover", pathMatch: "full", redirectTo: "recover-2fa" }, { diff --git a/src/app/oss.module.ts b/src/app/oss.module.ts index b4470f0e63..4be1f09486 100644 --- a/src/app/oss.module.ts +++ b/src/app/oss.module.ts @@ -122,6 +122,7 @@ import { OrganizationBillingComponent } from "./organizations/settings/organizat import { OrganizationSubscriptionComponent } from "./organizations/settings/organization-subscription.component"; import { SettingsComponent as OrgSettingComponent } from "./organizations/settings/settings.component"; import { TwoFactorSetupComponent as OrgTwoFactorSetupComponent } from "./organizations/settings/two-factor-setup.component"; +import { AcceptFamilySponsorshipComponent } from "./organizations/sponsorships/accept-family-sponsorship.component"; import { FamiliesForEnterpriseSetupComponent } from "./organizations/sponsorships/families-for-enterprise-setup.component"; import { ExportComponent as OrgExportComponent } from "./organizations/tools/export.component"; import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "./organizations/tools/exposed-passwords-report.component"; @@ -283,6 +284,7 @@ registerLocaleData(localeZhTw, "zh-TW"); declarations: [ PremiumBadgeComponent, AcceptEmergencyComponent, + AcceptFamilySponsorshipComponent, AcceptOrganizationComponent, AccessComponent, AccountComponent, diff --git a/src/app/services/organization-guard.service.ts b/src/app/services/organization-guard.service.ts index bd999eea4e..8e883648bd 100644 --- a/src/app/services/organization-guard.service.ts +++ b/src/app/services/organization-guard.service.ts @@ -4,6 +4,7 @@ import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { OrganizationService } from "jslib-common/abstractions/organization.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { SyncService } from "jslib-common/abstractions/sync.service"; @Injectable() export class OrganizationGuardService implements CanActivate { @@ -11,14 +12,19 @@ export class OrganizationGuardService implements CanActivate { private router: Router, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, - private organizationService: OrganizationService + private organizationService: OrganizationService, + private syncService: SyncService ) {} async canActivate(route: ActivatedRouteSnapshot) { + // TODO: We need to fix this issue once and for all. + if ((await this.syncService.getLastSync()) == null) { + await this.syncService.fullSync(false); + } + const org = await this.organizationService.get(route.params.organizationId); if (org == null) { - this.router.navigate(["/"]); - return false; + return this.router.createUrlTree(["/"]); } if (!org.isOwner && !org.enabled) { this.platformUtilsService.showToast( @@ -26,8 +32,7 @@ export class OrganizationGuardService implements CanActivate { null, this.i18nService.t("organizationIsDisabled") ); - this.router.navigate(["/"]); - return false; + return this.router.createUrlTree(["/"]); } return true; diff --git a/src/app/services/router.service.ts b/src/app/services/router.service.ts index c71c1a4625..7493e2cb5a 100644 --- a/src/app/services/router.service.ts +++ b/src/app/services/router.service.ts @@ -1,6 +1,7 @@ import { Injectable } from "@angular/core"; import { Title } from "@angular/platform-browser"; import { ActivatedRoute, NavigationEnd, Router } from "@angular/router"; +import { filter } from "rxjs"; import { I18nService } from "jslib-common/abstractions/i18n.service"; @@ -16,31 +17,22 @@ export class RouterService { i18nService: I18nService ) { this.currentUrl = this.router.url; - router.events.subscribe((event) => { - if (event instanceof NavigationEnd) { - this.previousUrl = this.currentUrl; + + router.events + .pipe(filter((e) => e instanceof NavigationEnd)) + .subscribe((event: NavigationEnd) => { this.currentUrl = event.url; let title = i18nService.t("pageTitle", "Bitwarden"); - let titleId: string = null; - let rawTitle: string = null; let child = this.activatedRoute.firstChild; - while (child != null) { - if (child.firstChild != null) { - child = child.firstChild; - } else if (child.snapshot.data != null && child.snapshot.data.title != null) { - rawTitle = child.snapshot.data.title; - break; - } else if (child.snapshot.data != null && child.snapshot.data.titleId != null) { - titleId = child.snapshot.data.titleId; - break; - } else { - titleId = null; - rawTitle = null; - break; - } + while (child.firstChild) { + child = child.firstChild; } + const titleId: string = child?.snapshot?.data?.titleId; + const rawTitle: string = child?.snapshot?.data?.title; + const updateUrl = !child?.snapshot?.data?.doNotSaveUrl ?? true; + if (titleId != null || rawTitle != null) { const newTitle = rawTitle != null ? rawTitle : i18nService.t(titleId); if (newTitle != null && newTitle !== "") { @@ -48,8 +40,10 @@ export class RouterService { } } this.titleService.setTitle(title); - } - }); + if (updateUrl) { + this.setPreviousUrl(this.currentUrl); + } + }); } getPreviousUrl() { diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts index c8e95320db..cb63235825 100644 --- a/src/app/services/services.module.ts +++ b/src/app/services/services.module.ts @@ -45,6 +45,7 @@ import { PasswordRepromptService } from "../../services/passwordReprompt.service import { StateService } from "../../services/state.service"; import { StateMigrationService } from "../../services/stateMigration.service"; import { WebPlatformUtilsService } from "../../services/webPlatformUtils.service"; +import { HomeGuard } from "../guards/home.guard"; import { EventService } from "./event.service"; import { ModalService } from "./modal.service"; @@ -218,6 +219,7 @@ export function initFactory( provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService, }, + HomeGuard, ], }) export class ServicesModule {} diff --git a/src/app/vault/ciphers.component.html b/src/app/vault/ciphers.component.html index f36366b5a2..17db5838ad 100644 --- a/src/app/vault/ciphers.component.html +++ b/src/app/vault/ciphers.component.html @@ -17,10 +17,10 @@ {{ c.name }} diff --git a/src/app/vault/vault.component.ts b/src/app/vault/vault.component.ts index c3730bfabd..694f2bc656 100644 --- a/src/app/vault/vault.component.ts +++ b/src/app/vault/vault.component.ts @@ -12,6 +12,7 @@ import { first } from "rxjs/operators"; import { ModalService } from "jslib-angular/services/modal.service"; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; +import { CipherService } from "jslib-common/abstractions/cipher.service"; import { CryptoService } from "jslib-common/abstractions/crypto.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { MessagingService } from "jslib-common/abstractions/messaging.service"; @@ -85,7 +86,8 @@ export class VaultComponent implements OnInit, OnDestroy { private ngZone: NgZone, private stateService: StateService, private organizationService: OrganizationService, - private providerService: ProviderService + private providerService: ProviderService, + private cipherService: CipherService ) {} async ngOnInit() { @@ -136,6 +138,24 @@ export class VaultComponent implements OnInit, OnDestroy { } } + this.route.queryParams.subscribe(async (params) => { + if (params.cipherId) { + if ((await this.cipherService.get(params.cipherId)) != null) { + this.editCipherId(params.cipherId); + } else { + this.platformUtilsService.showToast( + "error", + this.i18nService.t("errorOccurred"), + this.i18nService.t("unknownCipher") + ); + this.router.navigate([], { + queryParams: { cipherId: null }, + queryParamsHandling: "merge", + }); + } + } + }); + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { this.ngZone.run(async () => { switch (message.command) { @@ -334,11 +354,15 @@ export class VaultComponent implements OnInit, OnDestroy { } async editCipher(cipher: CipherView) { + return this.editCipherId(cipher?.id); + } + + async editCipherId(id: string) { const [modal, childComponent] = await this.modalService.openViewRef( AddEditComponent, this.cipherAddEditModalRef, (comp) => { - comp.cipherId = cipher == null ? null : cipher.id; + comp.cipherId = id; comp.onSavedCipher.subscribe(async () => { modal.close(); await this.ciphersComponent.refresh(); @@ -354,6 +378,11 @@ export class VaultComponent implements OnInit, OnDestroy { } ); + modal.onClosedPromise().then(() => { + this.route.params; + this.router.navigate([], { queryParams: { cipherId: null }, queryParamsHandling: "merge" }); + }); + return childComponent; } @@ -388,6 +417,7 @@ export class VaultComponent implements OnInit, OnDestroy { this.router.navigate([], { relativeTo: this.route, queryParams: queryParams, + queryParamsHandling: "merge", replaceUrl: true, }); } diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 2dca55ab1c..7966776984 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -4890,5 +4890,8 @@ }, "service": { "message": "Service" + }, + "unknownCipher": { + "message": "Unknown Item, you may need to login with another account to access this item." } }