mirror of
https://github.com/bitwarden/browser
synced 2026-02-06 19:53:59 +00:00
implement send access trampoline
This commit is contained in:
@@ -1,2 +1,4 @@
|
||||
export { AccessComponent } from "./access.component";
|
||||
export { SendAccessExplainerComponent } from "./send-access-explainer.component";
|
||||
|
||||
export { SendAccessRoutes } from "./routes";
|
||||
|
||||
62
apps/web/src/app/tools/send/send-access/routes.ts
Normal file
62
apps/web/src/app/tools/send/send-access/routes.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Routes } from "@angular/router";
|
||||
|
||||
import { AnonLayoutWrapperData } from "@bitwarden/components";
|
||||
import { ActiveSendIcon } from "@bitwarden/send-ui";
|
||||
|
||||
import { RouteDataProperties } from "../../../core";
|
||||
|
||||
import { SendAccessExplainerComponent } from "./send-access-explainer.component";
|
||||
import { SendAccessPasswordComponent } from "./send-access-password.component";
|
||||
import { trySendAccess } from "./try-send-access.guard";
|
||||
import { ViewContentComponent } from "./view-content.component";
|
||||
|
||||
/** Routes to reach send access screens */
|
||||
export const SendAccessRoutes: Routes = [
|
||||
{
|
||||
path: "send/:sendId",
|
||||
// there are no child pages because `trySendAccess` always performs a redirect
|
||||
canActivate: [trySendAccess],
|
||||
},
|
||||
{
|
||||
path: "send/password/:sendId",
|
||||
data: {
|
||||
pageTitle: {
|
||||
key: "sendAccessPasswordTitle",
|
||||
},
|
||||
pageIcon: ActiveSendIcon,
|
||||
showReadonlyHostname: true,
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: SendAccessPasswordComponent,
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
outlet: "secondary",
|
||||
component: SendAccessExplainerComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "send/content/:sendId",
|
||||
data: {
|
||||
pageTitle: {
|
||||
key: "sendAccessContentTitle",
|
||||
},
|
||||
pageIcon: ActiveSendIcon,
|
||||
showReadonlyHostname: true,
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
children: [
|
||||
{
|
||||
path: "send/password/:sendId/:key",
|
||||
component: ViewContentComponent,
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
outlet: "secondary",
|
||||
component: SendAccessExplainerComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Router, UrlTree } from "@angular/router";
|
||||
import { map, of, from, catchError, timeout } from "rxjs";
|
||||
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { SemanticLogger } from "@bitwarden/common/tools/log";
|
||||
import { SystemServiceProvider } from "@bitwarden/common/tools/providers.js";
|
||||
import { SendAccessRequest } from "@bitwarden/common/tools/send/models/request/send-access.request";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
|
||||
import { TOKEN_KEY, SEND_KEY_KEY } from "./send-access-memory";
|
||||
import { isErrorResponse } from "./util";
|
||||
|
||||
const TEN_SECONDS = 10_000;
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class SendAccessAuthenticationService {
|
||||
private readonly logger: SemanticLogger;
|
||||
|
||||
constructor(
|
||||
private readonly state: StateProvider,
|
||||
private readonly api: SendApiService,
|
||||
private readonly router: Router,
|
||||
system: SystemServiceProvider,
|
||||
private configuration = {
|
||||
tryAccessTimeoutMs: TEN_SECONDS,
|
||||
},
|
||||
) {
|
||||
this.logger = system.log({ type: "SendAccessAuthenticationService" });
|
||||
}
|
||||
|
||||
redirect$(sendId: string) {
|
||||
const response$ = from(this.api.postSendAccess(sendId, new SendAccessRequest()));
|
||||
|
||||
const redirect$ = response$.pipe(
|
||||
timeout({ first: this.configuration.tryAccessTimeoutMs }),
|
||||
map((_response) => {
|
||||
this.logger.info("public send detected; redirecting to send access with token.");
|
||||
const url = this.toViewRedirect(sendId);
|
||||
|
||||
return url;
|
||||
}),
|
||||
catchError((error: unknown) => {
|
||||
let processed: UrlTree | undefined = undefined;
|
||||
|
||||
if (isErrorResponse(error)) {
|
||||
processed = this.toErrorRedirect(sendId, error);
|
||||
}
|
||||
|
||||
if (processed) {
|
||||
return of(processed);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}),
|
||||
);
|
||||
|
||||
return redirect$;
|
||||
}
|
||||
|
||||
private toViewRedirect(sendId: string) {
|
||||
return this.router.createUrlTree(["send", "content", sendId]);
|
||||
}
|
||||
|
||||
private toErrorRedirect(sendId: string, response: ErrorResponse) {
|
||||
let url: UrlTree | undefined = undefined;
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 401:
|
||||
this.logger.debug(response, "redirecting to password flow");
|
||||
url = this.router.createUrlTree(["send/", sendId]);
|
||||
break;
|
||||
|
||||
case 404:
|
||||
this.logger.debug(response, "redirecting to unavailable page");
|
||||
url = this.router.parseUrl("/404.html");
|
||||
break;
|
||||
|
||||
default:
|
||||
this.logger.warn(response, "received unexpected error response");
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
async setToken(token: string) {
|
||||
return this.state.getGlobal(TOKEN_KEY).update(() => token);
|
||||
}
|
||||
|
||||
async setKey(key: string) {
|
||||
return this.state.getGlobal(SEND_KEY_KEY).update(() => key);
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
await this.state.getGlobal(TOKEN_KEY).update(() => null);
|
||||
await this.state.getGlobal(SEND_KEY_KEY).update(() => null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { inject } from "@angular/core";
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from "@angular/router";
|
||||
import { from, ignoreElements, concat } from "rxjs";
|
||||
|
||||
import { SendAccessAuthenticationService } from "./send-access-authentication.service";
|
||||
|
||||
export const trySendAccess: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
) => {
|
||||
const sendAccess = inject(SendAccessAuthenticationService);
|
||||
|
||||
const { sendId, key } = route.params;
|
||||
|
||||
const setKey$ = from(sendAccess.setKey(key)).pipe(ignoreElements());
|
||||
const redirect$ = sendAccess.redirect$(sendId);
|
||||
|
||||
return concat(setKey$, redirect$);
|
||||
};
|
||||
5
apps/web/src/app/tools/send/send-access/util.ts
Normal file
5
apps/web/src/app/tools/send/send-access/util.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
|
||||
export function isErrorResponse(value: unknown): value is ErrorResponse {
|
||||
return value instanceof ErrorResponse;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { svgIcon } from "@bitwarden/components";
|
||||
|
||||
export const SendCreatedIcon = svgIcon`
|
||||
export const ActiveSendIcon = svgIcon`
|
||||
<svg width="96" height="95" viewBox="0 0 96 95" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="tw-stroke-art-primary" d="M89.4998 48.3919C89.4998 70.5749 70.9198 88.5573 47.9998 88.5573C46.0374 88.5573 44.1068 88.4257 42.217 88.1707M6.49976 48.3919C6.49976 26.2092 25.08 8.22656 47.9998 8.22656C51.8283 8.22656 55.5353 8.72824 59.0553 9.66744" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path class="tw-stroke-art-primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M5.47085 67.8617C2.60335 61.9801 1 55.4075 1 48.4729C1 23.3503 22.0426 2.98438 48 2.98438C52.3355 2.98438 56.534 3.55257 60.5205 4.61618M92.211 32.9993C94.016 37.8295 95 43.0399 95 48.4729C95 73.5956 73.9575 93.9614 48 93.9614C45.7775 93.9614 43.5911 93.8119 41.4508 93.5235" />
|
||||
@@ -1,3 +1,7 @@
|
||||
export { ExpiredSendIcon } from "./expired-send.icon";
|
||||
export { NoSendsIcon } from "./no-send.icon";
|
||||
export { SendCreatedIcon } from "./send-created.icon";
|
||||
export {
|
||||
ActiveSendIcon,
|
||||
/** @deprecated use {@link ActiveSendIcon} instead */
|
||||
ActiveSendIcon as SendCreatedIcon,
|
||||
} from "./active-send.icon";
|
||||
|
||||
Reference in New Issue
Block a user