[EC-143] [BEEEP] Allow linking to ciphers (#1579)

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
This commit is contained in:
Oscar Hinton 2022-04-25 15:41:44 +02:00 committed by GitHub
parent 705251fbe2
commit 0444b78ad1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 246 additions and 88 deletions

View File

@ -24,7 +24,11 @@
<p>{{ "joinProviderDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
{{ "logIn" | i18n }}
</a>
<a

View File

@ -20,7 +20,11 @@
<p>{{ "setupProviderLoginDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
{{ "logIn" | i18n }}
</a>
</div>

2
jslib

@ -1 +1 @@
Subproject commit 6bcadc4f408db2c150753f53a07d6f8888b6e9ff
Subproject commit f6e3481fe96690a3c52f7701d92b4e57f69f976a

View File

@ -23,7 +23,11 @@
<p>{{ "acceptEmergencyAccess" | i18n }}</p>
<hr />
<div class="d-flex">
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
{{ "logIn" | i18n }}
</a>
<a

View File

@ -24,7 +24,11 @@
<p>{{ "joinOrganizationDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
<a
routerLink="/login"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block"
>
{{ "logIn" | i18n }}
</a>
<a

View File

@ -33,7 +33,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@ -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);
};
}
}

View File

@ -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]);
}

View File

@ -33,7 +33,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@ -65,7 +65,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@ -258,7 +258,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@ -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;

View File

@ -41,7 +41,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@ -138,7 +138,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@ -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: {

View File

@ -23,7 +23,7 @@
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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 });
}
}

View File

@ -0,0 +1,13 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<div>
<img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center">
<i
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
</div>
</div>

View File

@ -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 } });
}
}
}

View File

@ -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,
});
}

View File

@ -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" },
{

View File

@ -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,

View File

@ -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;

View File

@ -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() {

View File

@ -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 {}

View File

@ -17,10 +17,10 @@
</td>
<td (click)="checkCipher(c)" class="reduced-lh wrap">
<a
href="#"
appStopClick
appStopProp
(click)="selectCipher(c)"
[routerLink]="[]"
[queryParams]="{ cipherId: c.id }"
queryParamsHandling="merge"
title="{{ 'editItem' | i18n }}"
>{{ c.name }}</a
>

View File

@ -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,
});
}

View File

@ -4890,5 +4890,8 @@
},
"service": {
"message": "Service"
},
"unknownCipher": {
"message": "Unknown Item, you may need to login with another account to access this item."
}
}