diff --git a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts index c4ca1dc241..7e21fb8dfa 100644 --- a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts @@ -16,13 +16,14 @@ import { OrganizationPermissionsGuard } from "../../admin-console/organizations/ import { OrganizationRedirectGuard } from "../../admin-console/organizations/guards/org-redirect.guard"; import { OrganizationLayoutComponent } from "../../admin-console/organizations/layouts/organization-layout.component"; import { GroupsComponent } from "../../admin-console/organizations/manage/groups.component"; +import { deepLinkGuard } from "../../auth/guards/deep-link.guard"; import { VaultModule } from "../../vault/org-vault/vault.module"; const routes: Routes = [ { path: ":organizationId", component: OrganizationLayoutComponent, - canActivate: [AuthGuard, OrganizationPermissionsGuard], + canActivate: [deepLinkGuard(), AuthGuard, OrganizationPermissionsGuard], data: { organizationPermissions: canAccessOrgAdmin, }, diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 97d065c689..734862081f 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -111,14 +111,12 @@ export class AppComponent implements OnDestroy, OnInit { 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": @@ -132,7 +130,6 @@ export class AppComponent implements OnDestroy, OnInit { this.router.navigate(["lock"]); break; case "lockedUrl": - this.routerService.setPreviousUrl(message.url); break; case "syncStarted": break; diff --git a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts index 5acd7b53af..d5b99a9909 100644 --- a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts +++ b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts @@ -36,7 +36,6 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent { async authedHandler(qParams: Params): Promise { this.actionPromise = this.emergencyAccessService.accept(qParams.id, qParams.token); await this.actionPromise; - await this.stateService.setEmergencyAccessInvitation(null); this.platformUtilService.showToast( "success", this.i18nService.t("inviteAccepted"), @@ -52,8 +51,5 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent { // Fix URL encoding of space issue with Angular this.name = this.name.replace(/\+/g, " "); } - - // save the invitation to state so sso logins can find it later - await this.stateService.setEmergencyAccessInvitation(qParams); } } diff --git a/apps/web/src/app/auth/guards/deep-link.guard.spec.ts b/apps/web/src/app/auth/guards/deep-link.guard.spec.ts new file mode 100644 index 0000000000..f9ced556e4 --- /dev/null +++ b/apps/web/src/app/auth/guards/deep-link.guard.spec.ts @@ -0,0 +1,190 @@ +import { Component } from "@angular/core"; +import { TestBed } from "@angular/core/testing"; +import { Router, provideRouter } from "@angular/router"; +import { RouterTestingHarness } from "@angular/router/testing"; +import { MockProxy, mock } from "jest-mock-extended"; + +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; + +import { RouterService } from "../../core/router.service"; + +import { deepLinkGuard } from "./deep-link.guard"; + +@Component({ + template: "", +}) +export class GuardedRouteTestComponent {} + +@Component({ + template: "", +}) +export class LockTestComponent {} + +@Component({ + template: "", +}) +export class RedirectTestComponent {} + +/** + * We are assuming the guard is always being called. We are creating routes using the + * RouterTestingHarness. + * + * when persisting a URL to storage we don't care wether or not the user is locked or logged out. + * We only care about where the user is going, and has been. + * + * We are testing the activatedComponent because we are testing that the guard redirects when a user is + * unlocked. + */ +describe("Deep Link Guard", () => { + let authService: MockProxy; + let routerService: MockProxy; + let routerHarness: RouterTestingHarness; + + beforeEach(async () => { + authService = mock(); + routerService = mock(); + TestBed.configureTestingModule({ + providers: [ + { provide: AuthService, useValue: authService }, + { provide: RouterService, useValue: routerService }, + provideRouter([ + { + path: "guarded-route", + component: GuardedRouteTestComponent, + canActivate: [deepLinkGuard()], + }, + { + path: "lock-route", + component: LockTestComponent, + canActivate: [deepLinkGuard()], + }, + { + path: "redirect-route", + component: RedirectTestComponent, + }, + ]), + ], + }); + + routerHarness = await RouterTestingHarness.create(); + }); + + // Story: User's vault times out + it('should persist routerService.previousUrl when routerService.previousUrl does not contain "lock"', async () => { + // Arrange + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Locked); + routerService.getPreviousUrl.mockReturnValue("/previous-url"); + + // Act + await routerHarness.navigateByUrl("/lock-route"); + + // Assert + expect(routerService.persistLoginRedirectUrl).toHaveBeenCalledWith("/previous-url"); + }); + + // Story: User's vault times out and previousUrl contains "lock" + it('should not persist routerService.previousUrl when routerService.previousUrl contains "lock"', async () => { + // Arrange + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Locked); + routerService.getPreviousUrl.mockReturnValue("/lock"); + + // Act + await routerHarness.navigateByUrl("/lock-route"); + + // Assert + expect(routerService.persistLoginRedirectUrl).not.toHaveBeenCalled(); + }); + + // Story: User's vault times out and previousUrl is undefined + it("should not persist routerService.previousUrl when routerService.previousUrl is undefined", async () => { + // Arrange + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Locked); + routerService.getPreviousUrl.mockReturnValue(undefined); + + // Act + await routerHarness.navigateByUrl("/lock-route"); + + // Assert + expect(routerService.persistLoginRedirectUrl).not.toHaveBeenCalled(); + }); + + // Story: User tries to deep link to a guarded route and is logged out + it('should persist currentUrl when currentUrl does not contain "lock"', async () => { + // Arrange + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.LoggedOut); + + // Act + await routerHarness.navigateByUrl("/guarded-route?item=123"); + + // Assert + expect(routerService.persistLoginRedirectUrl).toHaveBeenCalledWith("/guarded-route?item=123"); + }); + + // Story: User tries to deep link to "lock" + it('should not persist currentUrl if the currentUrl contains "lock"', async () => { + // Arrange + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.LoggedOut); + + // Act + await routerHarness.navigateByUrl("/lock-route"); + + // Assert + expect(routerService.persistLoginRedirectUrl).not.toHaveBeenCalled(); + }); + + // Story: User tries to deep link to a guarded route from the lock page + it("should persist currentUrl over previousUrl", async () => { + // Arrange + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Locked); + routerService.getPreviousUrl.mockReturnValue("/previous-url"); + + // Act + await routerHarness.navigateByUrl("/guarded-route?item=123"); + + // Assert + expect(routerService.persistLoginRedirectUrl).toHaveBeenCalledWith("/guarded-route?item=123"); + }); + + // Story: user tries to deep link and is unlocked + it("should not persist any URL if the user is unlocked", async () => { + // Arrange + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Unlocked); + + // Act + await routerHarness.navigateByUrl("/guarded-route"); + + // Assert + expect(routerService.persistLoginRedirectUrl).not.toHaveBeenCalled(); + }); + + // Story: User is redirected + it("should redirect user", async () => { + // Arrange + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Unlocked); + routerService.getAndClearLoginRedirectUrl.mockResolvedValue("/redirect-route"); + + // Act + const activatedComponent = await routerHarness.navigateByUrl("/guarded-route"); + + // Assert + expect(routerService.persistLoginRedirectUrl).not.toHaveBeenCalled(); + expect(TestBed.inject(Router).url).toEqual("/redirect-route"); + expect(activatedComponent).toBeInstanceOf(RedirectTestComponent); + }); + + // Story: User is not redirected + it("should not redirect user", async () => { + // Arrange + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Unlocked); + routerService.getAndClearLoginRedirectUrl.mockResolvedValue(""); + + // Act + const activatedComponent = await routerHarness.navigateByUrl("/guarded-route"); + + // Assert + expect(routerService.persistLoginRedirectUrl).not.toHaveBeenCalled(); + expect(TestBed.inject(Router).url).toEqual("/guarded-route"); + expect(activatedComponent).toBeInstanceOf(GuardedRouteTestComponent); + }); +}); diff --git a/apps/web/src/app/auth/guards/deep-link.guard.ts b/apps/web/src/app/auth/guards/deep-link.guard.ts new file mode 100644 index 0000000000..9e9212b377 --- /dev/null +++ b/apps/web/src/app/auth/guards/deep-link.guard.ts @@ -0,0 +1,56 @@ +import { inject } from "@angular/core"; +import { CanActivateFn, Router } from "@angular/router"; + +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +import { RouterService } from "../../core/router.service"; + +/** + * Guard to persist and apply deep links to handle users who are not unlocked. + * @returns returns true. If user is not Unlocked will store URL to state for redirect once + * user is unlocked/Authenticated. + */ +export function deepLinkGuard(): CanActivateFn { + return async (route, routerState) => { + // Inject Services + const authService = inject(AuthService); + const router = inject(Router); + const routerService = inject(RouterService); + + // Fetch State + const currentUrl = routerState.url; + const transientPreviousUrl = routerService.getPreviousUrl(); + const authStatus = await authService.getAuthStatus(); + + // Evaluate State + /** before anything else, check if the user is already unlocked. */ + if (authStatus === AuthenticationStatus.Unlocked) { + const persistedPreLoginUrl = await routerService.getAndClearLoginRedirectUrl(); + if (!Utils.isNullOrEmpty(persistedPreLoginUrl)) { + return router.navigateByUrl(persistedPreLoginUrl); + } + return true; + } + /** + * At this point the user is either `locked` or `loggedOut`, it doesn't matter. + * We opt to persist the currentUrl over the transient previousUrl. This supports + * the case where a user is locked out of their vault and they deep link from + * the "lock" page. + * + * When the user is locked out of their vault the currentUrl contains "lock" so it will + * not be persisted, the previousUrl will be persisted instead. + */ + if (isValidUrl(currentUrl)) { + await routerService.persistLoginRedirectUrl(currentUrl); + } else if (isValidUrl(transientPreviousUrl)) { + await routerService.persistLoginRedirectUrl(transientPreviousUrl); + } + return true; + }; + + function isValidUrl(url: string | null | undefined): boolean { + return !Utils.isNullOrEmpty(url) && !url?.toLocaleLowerCase().includes("lock"); + } +} diff --git a/apps/web/src/app/auth/lock.component.ts b/apps/web/src/app/auth/lock.component.ts index 1cb97fbd55..d19bd1d01f 100644 --- a/apps/web/src/app/auth/lock.component.ts +++ b/apps/web/src/app/auth/lock.component.ts @@ -19,8 +19,6 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { DialogService } from "@bitwarden/components"; -import { RouterService } from "../core"; - @Component({ selector: "app-lock", templateUrl: "lock.component.html", @@ -35,7 +33,6 @@ export class LockComponent extends BaseLockComponent { vaultTimeoutService: VaultTimeoutService, vaultTimeoutSettingsService: VaultTimeoutSettingsService, environmentService: EnvironmentService, - private routerService: RouterService, stateService: StateService, apiService: ApiService, logService: LogService, @@ -72,10 +69,6 @@ export class LockComponent extends BaseLockComponent { async ngOnInit() { await super.ngOnInit(); this.onSuccessfulSubmit = async () => { - const previousUrl = this.routerService.getPreviousUrl(); - if (previousUrl && previousUrl !== "/" && previousUrl.indexOf("lock") === -1) { - this.successRoute = previousUrl; - } this.router.navigateByUrl(this.successRoute); }; } diff --git a/apps/web/src/app/auth/login/login.component.ts b/apps/web/src/app/auth/login/login.component.ts index f6b83d6bf5..c67c84286a 100644 --- a/apps/web/src/app/auth/login/login.component.ts +++ b/apps/web/src/app/auth/login/login.component.ts @@ -172,13 +172,8 @@ export class LoginComponent extends BaseLoginComponent implements OnInit { } } - const previousUrl = this.routerService.getPreviousUrl(); - if (previousUrl) { - this.router.navigateByUrl(previousUrl); - } else { - this.loginService.clearValues(); - this.router.navigate([this.successRoute]); - } + this.loginService.clearValues(); + this.router.navigate([this.successRoute]); } goToHint() { diff --git a/apps/web/src/app/auth/sso.component.ts b/apps/web/src/app/auth/sso.component.ts index cb78d156bf..b36cc4857e 100644 --- a/apps/web/src/app/auth/sso.component.ts +++ b/apps/web/src/app/auth/sso.component.ts @@ -7,7 +7,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction"; import { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain-sso-details.response"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { HttpStatusCode } from "@bitwarden/common/enums"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; @@ -39,7 +38,6 @@ export class SsoComponent extends BaseSsoComponent { passwordGenerationService: PasswordGenerationServiceAbstraction, logService: LogService, private orgDomainApiService: OrgDomainApiServiceAbstraction, - private loginService: LoginService, private validationService: ValidationService, configService: ConfigServiceAbstraction ) { @@ -64,21 +62,6 @@ export class SsoComponent extends BaseSsoComponent { async ngOnInit() { super.ngOnInit(); - // if we have an emergency access invite, redirect to emergency access - const emergencyAccessInvite = await this.stateService.getEmergencyAccessInvitation(); - if (emergencyAccessInvite != null) { - this.onSuccessfulLoginNavigate = async () => { - this.router.navigate(["/accept-emergency"], { - queryParams: { - id: emergencyAccessInvite.id, - name: emergencyAccessInvite.name, - email: emergencyAccessInvite.email, - token: emergencyAccessInvite.token, - }, - }); - }; - } - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.queryParams.pipe(first()).subscribe(async (qParams) => { if (qParams.identifier != null) { diff --git a/apps/web/src/app/auth/two-factor.component.ts b/apps/web/src/app/auth/two-factor.component.ts index 2dbb650874..3de77e63ee 100644 --- a/apps/web/src/app/auth/two-factor.component.ts +++ b/apps/web/src/app/auth/two-factor.component.ts @@ -18,8 +18,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { RouterService } from "../core"; - import { TwoFactorOptionsComponent } from "./two-factor-options.component"; @Component({ @@ -44,7 +42,6 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { logService: LogService, twoFactorService: TwoFactorService, appIdService: AppIdService, - private routerService: RouterService, loginService: LoginService, configService: ConfigServiceAbstraction, @Inject(WINDOW) protected win: Window @@ -97,29 +94,10 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { goAfterLogIn = async () => { this.loginService.clearValues(); - const previousUrl = this.routerService.getPreviousUrl(); - if (previousUrl) { - this.router.navigateByUrl(previousUrl); - } else { - // if we have an emergency access invite, redirect to emergency access - const emergencyAccessInvite = await this.stateService.getEmergencyAccessInvitation(); - if (emergencyAccessInvite != null) { - this.router.navigate(["/accept-emergency"], { - queryParams: { - id: emergencyAccessInvite.id, - name: emergencyAccessInvite.name, - email: emergencyAccessInvite.email, - token: emergencyAccessInvite.token, - }, - }); - return; - } - - this.router.navigate([this.successRoute], { - queryParams: { - identifier: this.orgIdentifier, - }, - }); - } + this.router.navigate([this.successRoute], { + queryParams: { + identifier: this.orgIdentifier, + }, + }); }; } diff --git a/apps/web/src/app/core/router.service.ts b/apps/web/src/app/core/router.service.ts index 82399d4e49..18656be775 100644 --- a/apps/web/src/app/core/router.service.ts +++ b/apps/web/src/app/core/router.service.ts @@ -4,6 +4,8 @@ import { ActivatedRoute, NavigationEnd, Router } from "@angular/router"; import { filter } from "rxjs"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; @Injectable() export class RouterService { @@ -14,6 +16,7 @@ export class RouterService { private router: Router, private activatedRoute: ActivatedRoute, private titleService: Title, + private stateService: StateService, i18nService: I18nService ) { this.currentUrl = this.router.url; @@ -51,11 +54,33 @@ export class RouterService { }); } - getPreviousUrl() { + getPreviousUrl(): string | undefined { return this.previousUrl; } - setPreviousUrl(url: string) { + setPreviousUrl(url: string): void { this.previousUrl = url; } + + /** + * Save URL to Global State. This service is used during the login process + * @param url URL being saved to the Global State + */ + async persistLoginRedirectUrl(url: string): Promise { + await this.stateService.setDeepLinkRedirectUrl(url); + } + + /** + * Fetch and clear persisted LoginRedirectUrl if present in state + */ + async getAndClearLoginRedirectUrl(): Promise | undefined { + const persistedPreLoginUrl = await this.stateService.getDeepLinkRedirectUrl(); + + if (!Utils.isNullOrEmpty(persistedPreLoginUrl)) { + await this.persistLoginRedirectUrl(null); + return persistedPreLoginUrl; + } + + return; + } } diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 386b9b3023..5f5baab2e3 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -18,6 +18,8 @@ import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizatio import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component"; import { SponsoredFamiliesComponent } from "./admin-console/settings/sponsored-families.component"; import { AcceptOrganizationComponent } from "./auth/accept-organization.component"; +import { AcceptEmergencyComponent } from "./auth/emergency-access/accept/accept-emergency.component"; +import { deepLinkGuard } from "./auth/guards/deep-link.guard"; import { HintComponent } from "./auth/hint.component"; import { LockComponent } from "./auth/lock.component"; import { LoginDecryptionOptionsComponent } from "./auth/login/login-decryption-options/login-decryption-options.component"; @@ -113,16 +115,19 @@ const routes: Routes = [ { path: "lock", component: LockComponent, - canActivate: [lockGuard()], + canActivate: [deepLinkGuard(), lockGuard()], }, { path: "verify-email", component: VerifyEmailTokenComponent }, { path: "accept-organization", component: AcceptOrganizationComponent, + canActivate: [deepLinkGuard()], data: { titleId: "joinOrganization", doNotSaveUrl: false }, }, { path: "accept-emergency", + component: AcceptEmergencyComponent, + canActivate: [deepLinkGuard()], data: { titleId: "acceptEmergency", doNotSaveUrl: false }, loadComponent: () => import("./auth/emergency-access/accept/accept-emergency.component").then( @@ -132,6 +137,7 @@ const routes: Routes = [ { path: "accept-families-for-enterprise", component: AcceptFamilySponsorshipComponent, + canActivate: [deepLinkGuard()], data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false }, }, { path: "recover", pathMatch: "full", redirectTo: "recover-2fa" }, @@ -188,7 +194,7 @@ const routes: Routes = [ { path: "", component: UserLayoutComponent, - canActivate: [AuthGuard], + canActivate: [deepLinkGuard(), AuthGuard], children: [ { path: "vault", diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 5a0236989e..374f5f7171 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -430,8 +430,6 @@ export abstract class StateService { setOpenAtLogin: (value: boolean, options?: StorageOptions) => Promise; getOrganizationInvitation: (options?: StorageOptions) => Promise; setOrganizationInvitation: (value: any, options?: StorageOptions) => Promise; - getEmergencyAccessInvitation: (options?: StorageOptions) => Promise; - setEmergencyAccessInvitation: (value: any, options?: StorageOptions) => Promise; /** * @deprecated Do not call this directly, use OrganizationService */ @@ -532,4 +530,17 @@ export abstract class StateService { value: Record>, options?: StorageOptions ) => Promise; + /** + * fetches string value of URL user tried to navigate to while unauthenticated. + * @param options Defines the storage options for the URL; Defaults to session Storage. + * @returns route called prior to successful login. + */ + getDeepLinkRedirectUrl: (options?: StorageOptions) => Promise; + /** + * Store URL in session storage by default, but can be configured. Developed to handle + * unauthN interrupted navigation. + * @param url URL of route + * @param options Defines the storage options for the URL; Defaults to session Storage. + */ + setDeepLinkRedirectUrl: (url: string, options?: StorageOptions) => Promise; } diff --git a/libs/common/src/platform/models/domain/global-state.ts b/libs/common/src/platform/models/domain/global-state.ts index 7515aa8a8d..61fc5ffa34 100644 --- a/libs/common/src/platform/models/domain/global-state.ts +++ b/libs/common/src/platform/models/domain/global-state.ts @@ -7,7 +7,6 @@ export class GlobalState { installedVersion?: string; locale?: string; organizationInvitation?: any; - emergencyAccessInvitation?: any; ssoCodeVerifier?: string; ssoOrganizationIdentifier?: string; ssoState?: string; @@ -41,4 +40,5 @@ export class GlobalState { disableChangedPasswordNotification?: boolean; disableContextMenuItem?: boolean; autoFillOverlayVisibility?: number; + deepLinkRedirectUrl?: string; } diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 0ff655e34c..2ddba9b2ff 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -2386,23 +2386,6 @@ export class StateService< ); } - async getEmergencyAccessInvitation(options?: StorageOptions): Promise { - return ( - await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) - )?.emergencyAccessInvitation; - } - - async setEmergencyAccessInvitation(value: any, options?: StorageOptions): Promise { - const globals = await this.getGlobals( - this.reconcileOptions(options, await this.defaultOnDiskOptions()) - ); - globals.emergencyAccessInvitation = value; - await this.saveGlobals( - globals, - this.reconcileOptions(options, await this.defaultOnDiskOptions()) - ); - } - /** * @deprecated Do not call this directly, use OrganizationService */ @@ -2884,6 +2867,23 @@ export class StateService< ); } + async getDeepLinkRedirectUrl(options?: StorageOptions): Promise { + return ( + await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.deepLinkRedirectUrl; + } + + async setDeepLinkRedirectUrl(url: string, options?: StorageOptions): Promise { + const globals = await this.getGlobals( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + globals.deepLinkRedirectUrl = url; + await this.saveGlobals( + globals, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + protected async getGlobals(options: StorageOptions): Promise { let globals: TGlobalState; if (this.useMemory(options.storageLocation)) {