misc: resolve shared secret within org and added login redirect

This commit is contained in:
Sheen Capadngan
2025-01-10 23:55:21 +08:00
parent 27abfa4fff
commit 502429d914
5 changed files with 79 additions and 7 deletions

View File

@@ -206,8 +206,13 @@ export const secretSharingServiceFactory = ({
const orgName = sharedSecret.orgId ? (await orgDAL.findOrgById(sharedSecret.orgId))?.name : "";
if (accessType === SecretSharingAccessType.Organization && orgId !== sharedSecret.orgId)
if (accessType === SecretSharingAccessType.Organization && orgId === undefined) {
throw new UnauthorizedError();
}
if (accessType === SecretSharingAccessType.Organization && orgId !== sharedSecret.orgId) {
throw new ForbiddenRequestError();
}
// all secrets pass through here, meaning we check if its expired first and then check if it needs verification
// or can be safely sent to the client.

View File

@@ -58,7 +58,8 @@ export const leaveConfirmDefaultMessage =
"Your changes will be lost if you leave the page. Are you sure you want to continue?";
export enum SessionStorageKeys {
CLI_TERMINAL_TOKEN = "CLI_TERMINAL_TOKEN"
CLI_TERMINAL_TOKEN = "CLI_TERMINAL_TOKEN",
ORG_LOGIN_SUCCESS_REDIRECT_URL = "ORG_LOGIN_SUCCESS_REDIRECT_URL"
}
export const secretTagsColors = [

View File

@@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import SecurityClient from "@app/components/utilities/SecurityClient";
import { apiRequest } from "@app/config/request";
import { SessionStorageKeys } from "@app/const";
import { organizationKeys } from "../organization/queries";
import { setAuthToken } from "../reactQuery";
@@ -86,6 +87,21 @@ export const useSelectOrganization = () => {
SecurityClient.setProviderAuthToken("");
}
if (data.token && !data.isMfaEnabled) {
// We check if there is a pending callback after organization login success and redirect to it if valid
const loginRedirectInfo = sessionStorage.getItem(
SessionStorageKeys.ORG_LOGIN_SUCCESS_REDIRECT_URL
);
sessionStorage.removeItem(SessionStorageKeys.ORG_LOGIN_SUCCESS_REDIRECT_URL);
if (loginRedirectInfo) {
const { expiry, data: redirectUrl } = JSON.parse(loginRedirectInfo);
if (new Date() < new Date(expiry)) {
window.location.assign(redirectUrl);
}
}
}
return data;
},
onSuccess: () => {

View File

@@ -1,10 +1,13 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { Helmet } from "react-helmet";
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useParams, useSearch } from "@tanstack/react-router";
import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
import { AxiosError } from "axios";
import { addSeconds, formatISO } from "date-fns";
import { createNotification } from "@app/components/notifications";
import { SessionStorageKeys } from "@app/const";
import { ROUTE_PATHS } from "@app/const/routes";
import { useGetActiveSharedSecretById } from "@app/hooks/api/secretSharing";
@@ -36,7 +39,6 @@ export const ViewSharedSecretByIDPage = () => {
select: (el) => el.key
});
const [password, setPassword] = useState<string>();
const { hashedHex, key } = extractDetailsFromUrl(urlEncodedKey);
const {
@@ -50,10 +52,47 @@ export const ViewSharedSecretByIDPage = () => {
password
});
const navigate = useNavigate();
const isUnauthorized =
((error as AxiosError)?.response?.data as { statusCode: number })?.statusCode === 401;
const isForbidden =
((error as AxiosError)?.response?.data as { statusCode: number })?.statusCode === 403;
const isInvalidCredential =
((error as AxiosError)?.response?.data as { message: string })?.message ===
"Invalid credentials";
useEffect(() => {
if (isUnauthorized && !isInvalidCredential) {
// persist current URL in session storage so that we can come back to this after successful login
sessionStorage.setItem(
SessionStorageKeys.ORG_LOGIN_SUCCESS_REDIRECT_URL,
JSON.stringify({
expiry: formatISO(addSeconds(new Date(), 60)),
data: window.location.href
})
);
createNotification({
type: "info",
text: "Login is required in order to access the shared secret."
});
navigate({
to: "/login"
});
}
if (isForbidden) {
createNotification({
type: "error",
text: "You do not have access to this shared secret."
});
}
}, [error]);
const shouldShowPasswordPrompt =
isInvalidCredential || (fetchSecret?.isPasswordProtected && !fetchSecret.secret);
const isValidatingPassword = Boolean(password) && isFetching;
@@ -111,7 +150,7 @@ export const ViewSharedSecretByIDPage = () => {
{!error && fetchSecret?.secret && (
<SecretContainer secret={fetchSecret.secret} secretKey={key} />
)}
{error && !isInvalidCredential && <SecretErrorContainer />}
{error && !isInvalidCredential && !isUnauthorized && <SecretErrorContainer />}
</>
)}
<div className="m-auto my-8 flex w-full">

View File

@@ -2,6 +2,8 @@ import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
import { authKeys, fetchAuthToken } from "@app/hooks/api/auth/queries";
import { ViewSharedSecretByIDPage } from "./ViewSharedSecretByIDPage";
const SharedSecretByIDPageQuerySchema = z.object({
@@ -9,9 +11,18 @@ const SharedSecretByIDPageQuerySchema = z.object({
});
export const Route = createFileRoute("/shared/secret/$secretId")({
component: ViewSharedSecretByIDPage,
validateSearch: zodValidator(SharedSecretByIDPageQuerySchema),
component: ViewSharedSecretByIDPage,
search: {
middlewares: [stripSearchParams({ key: "" })]
},
beforeLoad: async ({ context }) => {
// we load the auth token because the view shared secret screen serves both public and authenticated users
await context.queryClient
.ensureQueryData({
queryKey: authKeys.getAuthToken,
queryFn: fetchAuthToken
})
.catch(() => undefined);
}
});