mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 17:27:16 +00:00
Merge pull request #2969 from Infisical/misc/fix-shared-secret-within-org-and-login-redirect
misc: resolve shared secret within org and added login redirect
This commit is contained in:
@@ -206,8 +206,13 @@ export const secretSharingServiceFactory = ({
|
|||||||
|
|
||||||
const orgName = sharedSecret.orgId ? (await orgDAL.findOrgById(sharedSecret.orgId))?.name : "";
|
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();
|
throw new ForbiddenRequestError();
|
||||||
|
}
|
||||||
|
|
||||||
// all secrets pass through here, meaning we check if its expired first and then check if it needs verification
|
// 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.
|
// or can be safely sent to the client.
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ export const leaveConfirmDefaultMessage =
|
|||||||
"Your changes will be lost if you leave the page. Are you sure you want to continue?";
|
"Your changes will be lost if you leave the page. Are you sure you want to continue?";
|
||||||
|
|
||||||
export enum SessionStorageKeys {
|
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 = [
|
export const secretTagsColors = [
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
|
|
||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
import { SessionStorageKeys } from "@app/const";
|
||||||
|
|
||||||
import { organizationKeys } from "../organization/queries";
|
import { organizationKeys } from "../organization/queries";
|
||||||
import { setAuthToken } from "../reactQuery";
|
import { setAuthToken } from "../reactQuery";
|
||||||
@@ -86,6 +87,21 @@ export const useSelectOrganization = () => {
|
|||||||
SecurityClient.setProviderAuthToken("");
|
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;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
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 { 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 { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import { useGetActiveSharedSecretById } from "@app/hooks/api/secretSharing";
|
import { useGetActiveSharedSecretById } from "@app/hooks/api/secretSharing";
|
||||||
|
|
||||||
@@ -36,7 +39,6 @@ export const ViewSharedSecretByIDPage = () => {
|
|||||||
select: (el) => el.key
|
select: (el) => el.key
|
||||||
});
|
});
|
||||||
const [password, setPassword] = useState<string>();
|
const [password, setPassword] = useState<string>();
|
||||||
|
|
||||||
const { hashedHex, key } = extractDetailsFromUrl(urlEncodedKey);
|
const { hashedHex, key } = extractDetailsFromUrl(urlEncodedKey);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -50,10 +52,47 @@ export const ViewSharedSecretByIDPage = () => {
|
|||||||
password
|
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 =
|
const isInvalidCredential =
|
||||||
((error as AxiosError)?.response?.data as { message: string })?.message ===
|
((error as AxiosError)?.response?.data as { message: string })?.message ===
|
||||||
"Invalid credentials";
|
"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 =
|
const shouldShowPasswordPrompt =
|
||||||
isInvalidCredential || (fetchSecret?.isPasswordProtected && !fetchSecret.secret);
|
isInvalidCredential || (fetchSecret?.isPasswordProtected && !fetchSecret.secret);
|
||||||
const isValidatingPassword = Boolean(password) && isFetching;
|
const isValidatingPassword = Boolean(password) && isFetching;
|
||||||
@@ -111,7 +150,7 @@ export const ViewSharedSecretByIDPage = () => {
|
|||||||
{!error && fetchSecret?.secret && (
|
{!error && fetchSecret?.secret && (
|
||||||
<SecretContainer secret={fetchSecret.secret} secretKey={key} />
|
<SecretContainer secret={fetchSecret.secret} secretKey={key} />
|
||||||
)}
|
)}
|
||||||
{error && !isInvalidCredential && <SecretErrorContainer />}
|
{error && !isInvalidCredential && !isUnauthorized && <SecretErrorContainer />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="m-auto my-8 flex w-full">
|
<div className="m-auto my-8 flex w-full">
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
|
|||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { authKeys, fetchAuthToken } from "@app/hooks/api/auth/queries";
|
||||||
|
|
||||||
import { ViewSharedSecretByIDPage } from "./ViewSharedSecretByIDPage";
|
import { ViewSharedSecretByIDPage } from "./ViewSharedSecretByIDPage";
|
||||||
|
|
||||||
const SharedSecretByIDPageQuerySchema = z.object({
|
const SharedSecretByIDPageQuerySchema = z.object({
|
||||||
@@ -9,9 +11,18 @@ const SharedSecretByIDPageQuerySchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute("/shared/secret/$secretId")({
|
export const Route = createFileRoute("/shared/secret/$secretId")({
|
||||||
component: ViewSharedSecretByIDPage,
|
|
||||||
validateSearch: zodValidator(SharedSecretByIDPageQuerySchema),
|
validateSearch: zodValidator(SharedSecretByIDPageQuerySchema),
|
||||||
|
component: ViewSharedSecretByIDPage,
|
||||||
search: {
|
search: {
|
||||||
middlewares: [stripSearchParams({ key: "" })]
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user