mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
misc: add instance banner and consent support
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasAuthConsentContentCol = await knex.schema.hasColumn(TableName.SuperAdmin, "authConsentContent");
|
||||||
|
const hasPageFrameContentCol = await knex.schema.hasColumn(TableName.SuperAdmin, "pageFrameContent");
|
||||||
|
if (await knex.schema.hasTable(TableName.SuperAdmin)) {
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||||
|
if (!hasAuthConsentContentCol) {
|
||||||
|
t.text("authConsentContent");
|
||||||
|
}
|
||||||
|
if (!hasPageFrameContentCol) {
|
||||||
|
t.text("pageFrameContent");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasAuthConsentContentCol = await knex.schema.hasColumn(TableName.SuperAdmin, "authConsentContent");
|
||||||
|
const hasPageFrameContentCol = await knex.schema.hasColumn(TableName.SuperAdmin, "pageFrameContent");
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||||
|
if (hasAuthConsentContentCol) {
|
||||||
|
t.dropColumn("authConsentContent");
|
||||||
|
}
|
||||||
|
if (hasPageFrameContentCol) {
|
||||||
|
t.dropColumn("pageFrameContent");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -23,7 +23,9 @@ export const SuperAdminSchema = z.object({
|
|||||||
defaultAuthOrgId: z.string().uuid().nullable().optional(),
|
defaultAuthOrgId: z.string().uuid().nullable().optional(),
|
||||||
enabledLoginMethods: z.string().array().nullable().optional(),
|
enabledLoginMethods: z.string().array().nullable().optional(),
|
||||||
encryptedSlackClientId: zodBuffer.nullable().optional(),
|
encryptedSlackClientId: zodBuffer.nullable().optional(),
|
||||||
encryptedSlackClientSecret: zodBuffer.nullable().optional()
|
encryptedSlackClientSecret: zodBuffer.nullable().optional(),
|
||||||
|
authConsentContent: z.string().nullable().optional(),
|
||||||
|
pageFrameContent: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||||
|
|||||||
@@ -72,7 +72,9 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
message: "At least one login method should be enabled."
|
message: "At least one login method should be enabled."
|
||||||
}),
|
}),
|
||||||
slackClientId: z.string().optional(),
|
slackClientId: z.string().optional(),
|
||||||
slackClientSecret: z.string().optional()
|
slackClientSecret: z.string().optional(),
|
||||||
|
authConsentContent: z.string().optional(),
|
||||||
|
pageFrameContent: z.string().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
24
frontend/package-lock.json
generated
24
frontend/package-lock.json
generated
@@ -54,6 +54,7 @@
|
|||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"cva": "npm:class-variance-authority@^0.7.1",
|
"cva": "npm:class-variance-authority@^0.7.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"dompurify": "^3.2.4",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"framer-motion": "^11.14.1",
|
"framer-motion": "^11.14.1",
|
||||||
"i18next": "^24.1.0",
|
"i18next": "^24.1.0",
|
||||||
@@ -4078,6 +4079,12 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/trusted-types": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.18.0",
|
"version": "8.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz",
|
||||||
@@ -5945,11 +5952,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "2.5.8",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz",
|
||||||
"integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
|
"integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==",
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
"optionalDependencies": {
|
||||||
"optional": true
|
"@types/trusted-types": "^2.0.7"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -8358,6 +8366,12 @@
|
|||||||
"html2canvas": "^1.0.0-rc.5"
|
"html2canvas": "^1.0.0-rc.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jspdf/node_modules/dompurify": {
|
||||||
|
"version": "2.5.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz",
|
||||||
|
"integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/jsrp": {
|
"node_modules/jsrp": {
|
||||||
"version": "0.2.4",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/jsrp/-/jsrp-0.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/jsrp/-/jsrp-0.2.4.tgz",
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"cva": "npm:class-variance-authority@^0.7.1",
|
"cva": "npm:class-variance-authority@^0.7.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"dompurify": "^3.2.4",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"framer-motion": "^11.14.1",
|
"framer-motion": "^11.14.1",
|
||||||
"i18next": "^24.1.0",
|
"i18next": "^24.1.0",
|
||||||
|
|||||||
17
frontend/src/components/page-frames/Banner.tsx
Normal file
17
frontend/src/components/page-frames/Banner.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* eslint-disable react/no-danger */
|
||||||
|
import DOMPurify from "dompurify";
|
||||||
|
|
||||||
|
import { useServerConfig } from "@app/context";
|
||||||
|
|
||||||
|
export const Banner = () => {
|
||||||
|
const { config } = useServerConfig();
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/no-danger-with-children
|
||||||
|
return config.pageFrameContent ? (
|
||||||
|
<div className="h-[3vh] w-full text-center">
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(config.pageFrameContent) }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div />
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -59,7 +59,8 @@ export const leaveConfirmDefaultMessage =
|
|||||||
|
|
||||||
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"
|
ORG_LOGIN_SUCCESS_REDIRECT_URL = "ORG_LOGIN_SUCCESS_REDIRECT_URL",
|
||||||
|
AUTH_CONSENT = "AUTH_CONSENT"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const secretTagsColors = [
|
export const secretTagsColors = [
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export type TServerConfig = {
|
|||||||
defaultAuthOrgAuthMethod?: string | null;
|
defaultAuthOrgAuthMethod?: string | null;
|
||||||
defaultAuthOrgAuthEnforced?: boolean | null;
|
defaultAuthOrgAuthEnforced?: boolean | null;
|
||||||
enabledLoginMethods: LoginMethod[];
|
enabledLoginMethods: LoginMethod[];
|
||||||
|
authConsentContent?: string;
|
||||||
|
pageFrameContent?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateAdminUserDTO = {
|
export type TCreateAdminUserDTO = {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { Link, Outlet } from "@tanstack/react-router";
|
import { Link, Outlet } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { WishForm } from "@app/components/features/WishForm";
|
import { WishForm } from "@app/components/features/WishForm";
|
||||||
|
import { Banner } from "@app/components/page-frames/Banner";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -11,6 +12,7 @@ import {
|
|||||||
DropdownMenuTrigger
|
DropdownMenuTrigger
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { envConfig } from "@app/config/env";
|
import { envConfig } from "@app/config/env";
|
||||||
|
import { useServerConfig } from "@app/context";
|
||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
||||||
@@ -18,10 +20,14 @@ import { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/Mini
|
|||||||
|
|
||||||
export const AdminLayout = () => {
|
export const AdminLayout = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { config } = useServerConfig();
|
||||||
|
|
||||||
|
const containerHeight = config.pageFrameContent ? "h-[94vh]" : "h-screen";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
|
<Banner />
|
||||||
|
<div className={`dark hidden ${containerHeight} w-full flex-col overflow-x-hidden md:flex`}>
|
||||||
{!window.isSecureContext && <InsecureConnectionBanner />}
|
{!window.isSecureContext && <InsecureConnectionBanner />}
|
||||||
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
|
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
|
||||||
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
|
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
|
||||||
@@ -69,7 +75,6 @@ export const AdminLayout = () => {
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
||||||
@@ -83,6 +88,7 @@ export const AdminLayout = () => {
|
|||||||
{` ${t("common.no-mobile")} `}
|
{` ${t("common.no-mobile")} `}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<Banner />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { AnimatePresence, motion } from "framer-motion";
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
|
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
|
||||||
|
import { Banner } from "@app/components/page-frames/Banner";
|
||||||
import {
|
import {
|
||||||
BreadcrumbContainer,
|
BreadcrumbContainer,
|
||||||
Menu,
|
Menu,
|
||||||
@@ -13,6 +14,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
TBreadcrumbFormat
|
TBreadcrumbFormat
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
|
import { useServerConfig } from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
|
|
||||||
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
|
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
|
||||||
@@ -22,6 +24,7 @@ import { SidebarHeader } from "./components/SidebarHeader";
|
|||||||
export const OrganizationLayout = () => {
|
export const OrganizationLayout = () => {
|
||||||
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
|
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { config } = useServerConfig();
|
||||||
const isOrganizationSpecificPage = location.pathname.startsWith("/organization");
|
const isOrganizationSpecificPage = location.pathname.startsWith("/organization");
|
||||||
const breadcrumbs =
|
const breadcrumbs =
|
||||||
isOrganizationSpecificPage && matches && "breadcrumbs" in matches
|
isOrganizationSpecificPage && matches && "breadcrumbs" in matches
|
||||||
@@ -45,9 +48,14 @@ export const OrganizationLayout = () => {
|
|||||||
] as string[]
|
] as string[]
|
||||||
).includes(location.pathname);
|
).includes(location.pathname);
|
||||||
|
|
||||||
|
const containerHeight = config.pageFrameContent ? "h-[94vh]" : "h-screen";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden bg-bunker-800 transition-all md:flex">
|
<Banner />
|
||||||
|
<div
|
||||||
|
className={`dark hidden ${containerHeight} w-full flex-col overflow-x-hidden bg-bunker-800 transition-all md:flex`}
|
||||||
|
>
|
||||||
{!window.isSecureContext && <InsecureConnectionBanner />}
|
{!window.isSecureContext && <InsecureConnectionBanner />}
|
||||||
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
|
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
|
||||||
<MinimizedOrgSidebar />
|
<MinimizedOrgSidebar />
|
||||||
@@ -137,6 +145,7 @@ export const OrganizationLayout = () => {
|
|||||||
{` ${t("common.no-mobile")} `}
|
{` ${t("common.no-mobile")} `}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<Banner />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import {
|
|||||||
Tab,
|
Tab,
|
||||||
TabList,
|
TabList,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
Tabs
|
Tabs,
|
||||||
|
TextArea
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useServerConfig, useUser } from "@app/context";
|
import { useServerConfig, useUser } from "@app/context";
|
||||||
import {
|
import {
|
||||||
@@ -55,7 +56,9 @@ const formSchema = z.object({
|
|||||||
trustSamlEmails: z.boolean(),
|
trustSamlEmails: z.boolean(),
|
||||||
trustLdapEmails: z.boolean(),
|
trustLdapEmails: z.boolean(),
|
||||||
trustOidcEmails: z.boolean(),
|
trustOidcEmails: z.boolean(),
|
||||||
defaultAuthOrgId: z.string()
|
defaultAuthOrgId: z.string(),
|
||||||
|
authConsentContent: z.string().optional(),
|
||||||
|
pageFrameContent: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
type TDashboardForm = z.infer<typeof formSchema>;
|
type TDashboardForm = z.infer<typeof formSchema>;
|
||||||
@@ -80,7 +83,9 @@ export const OverviewPage = () => {
|
|||||||
trustSamlEmails: config.trustSamlEmails,
|
trustSamlEmails: config.trustSamlEmails,
|
||||||
trustLdapEmails: config.trustLdapEmails,
|
trustLdapEmails: config.trustLdapEmails,
|
||||||
trustOidcEmails: config.trustOidcEmails,
|
trustOidcEmails: config.trustOidcEmails,
|
||||||
defaultAuthOrgId: config.defaultAuthOrgId ?? ""
|
defaultAuthOrgId: config.defaultAuthOrgId ?? "",
|
||||||
|
authConsentContent: config.authConsentContent,
|
||||||
|
pageFrameContent: config.pageFrameContent
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,7 +100,14 @@ export const OverviewPage = () => {
|
|||||||
const isNotAllowed = !user?.superAdmin;
|
const isNotAllowed = !user?.superAdmin;
|
||||||
const onFormSubmit = async (formData: TDashboardForm) => {
|
const onFormSubmit = async (formData: TDashboardForm) => {
|
||||||
try {
|
try {
|
||||||
const { allowedSignUpDomain, trustSamlEmails, trustLdapEmails, trustOidcEmails } = formData;
|
const {
|
||||||
|
allowedSignUpDomain,
|
||||||
|
trustSamlEmails,
|
||||||
|
trustLdapEmails,
|
||||||
|
trustOidcEmails,
|
||||||
|
authConsentContent,
|
||||||
|
pageFrameContent
|
||||||
|
} = formData;
|
||||||
|
|
||||||
await updateServerConfig({
|
await updateServerConfig({
|
||||||
defaultAuthOrgId: defaultAuthOrgId || null,
|
defaultAuthOrgId: defaultAuthOrgId || null,
|
||||||
@@ -103,7 +115,9 @@ export const OverviewPage = () => {
|
|||||||
allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null,
|
allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null,
|
||||||
trustSamlEmails,
|
trustSamlEmails,
|
||||||
trustLdapEmails,
|
trustLdapEmails,
|
||||||
trustOidcEmails
|
trustOidcEmails,
|
||||||
|
authConsentContent,
|
||||||
|
pageFrameContent
|
||||||
});
|
});
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully changed sign up setting.",
|
text: "Successfully changed sign up setting.",
|
||||||
@@ -324,6 +338,51 @@ export const OverviewPage = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<div className="mb-2 text-xl font-semibold text-mineshaft-100">Notices</div>
|
||||||
|
<div className="mb-4 max-w-lg text-sm text-mineshaft-400">
|
||||||
|
Configure system-wide notification banners and security messages. These
|
||||||
|
settings control the text displayed to users during authentication and
|
||||||
|
throughout their session
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Auth Consent Content"
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
placeholder="Auth Consent Message"
|
||||||
|
{...field}
|
||||||
|
rows={3}
|
||||||
|
className="thin-scrollbar h-48 max-w-lg !resize-none bg-mineshaft-800"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="authConsentContent"
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Page Frame Content"
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
placeholder="Page Frame Content"
|
||||||
|
{...field}
|
||||||
|
rows={3}
|
||||||
|
className="thin-scrollbar h-48 max-w-lg !resize-none bg-mineshaft-800"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="pageFrameContent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
isLoading={isSubmitting}
|
isLoading={isSubmitting}
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import { createFileRoute, redirect, stripSearchParams } from "@tanstack/react-router";
|
import { useState } from "react";
|
||||||
|
import { createFileRoute, Outlet, redirect, stripSearchParams } from "@tanstack/react-router";
|
||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
|
import { addSeconds, formatISO } from "date-fns";
|
||||||
|
import DOMPurify from "dompurify";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { SessionStorageKeys } from "@app/const";
|
||||||
|
import { useServerConfig } from "@app/context";
|
||||||
import { authKeys, fetchAuthToken } from "@app/hooks/api/auth/queries";
|
import { authKeys, fetchAuthToken } from "@app/hooks/api/auth/queries";
|
||||||
import { setAuthToken } from "@app/hooks/api/reactQuery";
|
import { setAuthToken } from "@app/hooks/api/reactQuery";
|
||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
@@ -10,6 +16,59 @@ const QueryParamsSchema = z.object({
|
|||||||
callback_port: z.coerce.number().optional().catch(undefined)
|
callback_port: z.coerce.number().optional().catch(undefined)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const AuthConsentWrapper = () => {
|
||||||
|
const { config } = useServerConfig();
|
||||||
|
const [hasConsented, setHasConsented] = useState(() => {
|
||||||
|
const consentInfo = sessionStorage.getItem(SessionStorageKeys.AUTH_CONSENT);
|
||||||
|
if (!consentInfo) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { expiry, data } = JSON.parse(consentInfo);
|
||||||
|
if (new Date() > new Date(expiry)) {
|
||||||
|
sessionStorage.removeItem(SessionStorageKeys.AUTH_CONSENT);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data === "true";
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleConsent = () => {
|
||||||
|
sessionStorage.setItem(
|
||||||
|
SessionStorageKeys.AUTH_CONSENT,
|
||||||
|
JSON.stringify({
|
||||||
|
expiry: formatISO(addSeconds(new Date(), 60)),
|
||||||
|
data: "true"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setHasConsented(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{config.authConsentContent && !hasConsented && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-mineshaft-700/80 bg-opacity-90">
|
||||||
|
<div className="max-h-[80vh] w-4/12 overflow-y-auto rounded-lg bg-bunker-800 p-6 text-white">
|
||||||
|
<div
|
||||||
|
// eslint-disable-next-line react/no-danger
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: DOMPurify.sanitize(config.authConsentContent)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="mt-6 flex justify-end">
|
||||||
|
<Button onClick={handleConsent} colorSchema="secondary">
|
||||||
|
OK
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute("/_restrict-login-signup")({
|
export const Route = createFileRoute("/_restrict-login-signup")({
|
||||||
validateSearch: zodValidator(QueryParamsSchema),
|
validateSearch: zodValidator(QueryParamsSchema),
|
||||||
search: {
|
search: {
|
||||||
@@ -45,5 +104,6 @@ export const Route = createFileRoute("/_restrict-login-signup")({
|
|||||||
throw redirect({
|
throw redirect({
|
||||||
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
component: AuthConsentWrapper
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user