mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 21:27:31 +00:00
feat: completed signup and personal settings ui
This commit is contained in:
+2
-2
@@ -20,7 +20,7 @@ import {
|
|||||||
import { useOrganization, useSubscription } from "@app/context";
|
import { useOrganization, useSubscription } from "@app/context";
|
||||||
import { useGetOrgTrialUrl } from "@app/hooks/api";
|
import { useGetOrgTrialUrl } from "@app/hooks/api";
|
||||||
|
|
||||||
const supportOptions = [
|
export const INFISICAL_SUPPORT_OPTIONS = [
|
||||||
[
|
[
|
||||||
<FontAwesomeIcon key={1} className="pr-4 text-sm" icon={faSlack} />,
|
<FontAwesomeIcon key={1} className="pr-4 text-sm" icon={faSlack} />,
|
||||||
"Support Forum",
|
"Support Forum",
|
||||||
@@ -84,7 +84,7 @@ export const SidebarFooter = () => {
|
|||||||
</div>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
{supportOptions.map(([icon, text, url]) => (
|
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
|
||||||
<DropdownMenuItem key={url as string}>
|
<DropdownMenuItem key={url as string}>
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { faArrowLeft, faInfo, faMobile, faQuestion } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { Link, Outlet, useRouteContext } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { WishForm } from "@app/components/features/WishForm";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
|
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
||||||
|
import { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/SidebarFooter/SidebarFooter";
|
||||||
|
|
||||||
|
export const PersonalSettingsLayout = () => {
|
||||||
|
const { organizationId } = useRouteContext({ from: "/_authenticate/_personal_settings_layout" });
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const infisicalPlatformVersion = import.meta.env.VITE_INFISICAL_PLATFORM_VERSION;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
|
||||||
|
{!window.isSecureContext && <InsecureConnectionBanner />}
|
||||||
|
<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">
|
||||||
|
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
||||||
|
<div className="flex-grow">
|
||||||
|
<Link
|
||||||
|
to={`/organization/$organizationId/${ProjectType.SecretManager}` as const}
|
||||||
|
params={{ organizationId }}
|
||||||
|
>
|
||||||
|
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
|
||||||
|
<FontAwesomeIcon icon={faArrowLeft} className="pr-3" />
|
||||||
|
Back to organization
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="relative mt-10 flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400">
|
||||||
|
{(window.location.origin.includes("https://app.infisical.com") ||
|
||||||
|
window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />}
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
||||||
|
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
|
||||||
|
Help & Support
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
|
||||||
|
<DropdownMenuItem key={url as string}>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href={String(url)}
|
||||||
|
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
|
||||||
|
>
|
||||||
|
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
|
||||||
|
{icon}
|
||||||
|
<div className="text-sm">{text}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
{infisicalPlatformVersion && (
|
||||||
|
<div className="mb-2 mt-2 w-full cursor-default pl-5 text-sm duration-200 hover:text-mineshaft-200">
|
||||||
|
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
|
||||||
|
Version: {infisicalPlatformVersion}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="z-[200] flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
|
||||||
|
<FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" />
|
||||||
|
<p className="max-w-sm px-6 text-center text-lg text-gray-200">
|
||||||
|
{` ${t("common.no-mobile")} `}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { PersonalSettingsLayout } from "./PersonalSettingsLayout";
|
||||||
+434
-207
@@ -11,25 +11,38 @@
|
|||||||
// Import Routes
|
// Import Routes
|
||||||
|
|
||||||
import { Route as rootRoute } from './routes/__root'
|
import { Route as rootRoute } from './routes/__root'
|
||||||
|
import { Route as RestrictloginsignupImport } from './routes/_restrict_login_signup'
|
||||||
import { Route as AuthenticateImport } from './routes/_authenticate'
|
import { Route as AuthenticateImport } from './routes/_authenticate'
|
||||||
import { Route as IndexImport } from './routes/index'
|
import { Route as IndexImport } from './routes/index'
|
||||||
import { Route as AuthenticateRestrictloginsignupImport } from './routes/_authenticate/_restrict_login_signup'
|
import { Route as RestrictloginsignupVerifyEmailImport } from './routes/_restrict_login_signup/verify-email'
|
||||||
|
import { Route as RestrictloginsignupSignupinviteImport } from './routes/_restrict_login_signup/signupinvite'
|
||||||
|
import { Route as RestrictloginsignupRequestnewinviteImport } from './routes/_restrict_login_signup/requestnewinvite'
|
||||||
|
import { Route as RestrictloginsignupPasswordResetImport } from './routes/_restrict_login_signup/password-reset'
|
||||||
|
import { Route as RestrictloginsignupEmailNotVerifiedImport } from './routes/_restrict_login_signup/email-not-verified'
|
||||||
|
import { Route as AuthenticateCliRedirectImport } from './routes/_authenticate/cli-redirect'
|
||||||
|
import { Route as AuthenticatePersonalsettingslayoutImport } from './routes/_authenticate/_personal_settings_layout'
|
||||||
import { Route as AuthenticateOrgdetailsImport } from './routes/_authenticate/_org_details'
|
import { Route as AuthenticateOrgdetailsImport } from './routes/_authenticate/_org_details'
|
||||||
|
import { Route as RestrictloginsignupSignupIndexImport } from './routes/_restrict_login_signup/signup/index'
|
||||||
|
import { Route as RestrictloginsignupLoginIndexImport } from './routes/_restrict_login_signup/login/index'
|
||||||
import { Route as AuthenticateOrgdetailsOrganizationlayoutImport } from './routes/_authenticate/_org_details/_organization_layout'
|
import { Route as AuthenticateOrgdetailsOrganizationlayoutImport } from './routes/_authenticate/_org_details/_organization_layout'
|
||||||
import { Route as AuthenticateRestrictloginsignupSignupIndexImport } from './routes/_authenticate/_restrict_login_signup/signup/index'
|
import { Route as RestrictloginsignupSignupSsoIndexImport } from './routes/_restrict_login_signup/signup/sso/index'
|
||||||
import { Route as AuthenticateRestrictloginsignupLoginIndexImport } from './routes/_authenticate/_restrict_login_signup/login/index'
|
import { Route as RestrictloginsignupLoginSsoIndexImport } from './routes/_restrict_login_signup/login/sso/index'
|
||||||
import { Route as AuthenticateRestrictloginsignupSignupSsoIndexImport } from './routes/_authenticate/_restrict_login_signup/signup/sso/index'
|
import { Route as RestrictloginsignupLoginSelectOrganizationIndexImport } from './routes/_restrict_login_signup/login/select-organization/index'
|
||||||
import { Route as AuthenticateRestrictloginsignupLoginSsoIndexImport } from './routes/_authenticate/_restrict_login_signup/login/sso/index'
|
import { Route as RestrictloginsignupLoginLdapIndexImport } from './routes/_restrict_login_signup/login/ldap/index'
|
||||||
import { Route as AuthenticateRestrictloginsignupLoginSelectOrganizationIndexImport } from './routes/_authenticate/_restrict_login_signup/login/select-organization/index'
|
import { Route as AuthenticatePersonalsettingslayoutPersonalSettingsIndexImport } from './routes/_authenticate/_personal_settings_layout/personal-settings/index'
|
||||||
import { Route as AuthenticateRestrictloginsignupLoginLdapIndexImport } from './routes/_authenticate/_restrict_login_signup/login/ldap/index'
|
import { Route as RestrictloginsignupLoginProviderSuccessImport } from './routes/_restrict_login_signup/login/provider/success'
|
||||||
|
import { Route as RestrictloginsignupLoginProviderErrorImport } from './routes/_restrict_login_signup/login/provider/error'
|
||||||
import { Route as AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexImport } from './routes/_authenticate/_org_details/_organization_layout/organization/index'
|
import { Route as AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexImport } from './routes/_authenticate/_org_details/_organization_layout/organization/index'
|
||||||
import { Route as AuthenticateRestrictloginsignupLoginProviderSuccessImport } from './routes/_authenticate/_restrict_login_signup/login/provider/success'
|
|
||||||
import { Route as AuthenticateRestrictloginsignupLoginProviderErrorImport } from './routes/_authenticate/_restrict_login_signup/login/provider/error'
|
|
||||||
import { Route as AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexImport } from './routes/_authenticate/_org_details/_organization_layout/organization/$organizationId/index'
|
import { Route as AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexImport } from './routes/_authenticate/_org_details/_organization_layout/organization/$organizationId/index'
|
||||||
import { Route as AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdSecretManagerImport } from './routes/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager'
|
import { Route as AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdSecretManagerImport } from './routes/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager'
|
||||||
|
|
||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
|
|
||||||
|
const RestrictloginsignupRoute = RestrictloginsignupImport.update({
|
||||||
|
id: '/_restrict_login_signup',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const AuthenticateRoute = AuthenticateImport.update({
|
const AuthenticateRoute = AuthenticateImport.update({
|
||||||
id: '/_authenticate',
|
id: '/_authenticate',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
@@ -41,9 +54,50 @@ const IndexRoute = IndexImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const AuthenticateRestrictloginsignupRoute =
|
const RestrictloginsignupVerifyEmailRoute =
|
||||||
AuthenticateRestrictloginsignupImport.update({
|
RestrictloginsignupVerifyEmailImport.update({
|
||||||
id: '/_restrict_login_signup',
|
id: '/verify-email',
|
||||||
|
path: '/verify-email',
|
||||||
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const RestrictloginsignupSignupinviteRoute =
|
||||||
|
RestrictloginsignupSignupinviteImport.update({
|
||||||
|
id: '/signupinvite',
|
||||||
|
path: '/signupinvite',
|
||||||
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const RestrictloginsignupRequestnewinviteRoute =
|
||||||
|
RestrictloginsignupRequestnewinviteImport.update({
|
||||||
|
id: '/requestnewinvite',
|
||||||
|
path: '/requestnewinvite',
|
||||||
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const RestrictloginsignupPasswordResetRoute =
|
||||||
|
RestrictloginsignupPasswordResetImport.update({
|
||||||
|
id: '/password-reset',
|
||||||
|
path: '/password-reset',
|
||||||
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const RestrictloginsignupEmailNotVerifiedRoute =
|
||||||
|
RestrictloginsignupEmailNotVerifiedImport.update({
|
||||||
|
id: '/email-not-verified',
|
||||||
|
path: '/email-not-verified',
|
||||||
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const AuthenticateCliRedirectRoute = AuthenticateCliRedirectImport.update({
|
||||||
|
id: '/cli-redirect',
|
||||||
|
path: '/cli-redirect',
|
||||||
|
getParentRoute: () => AuthenticateRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const AuthenticatePersonalsettingslayoutRoute =
|
||||||
|
AuthenticatePersonalsettingslayoutImport.update({
|
||||||
|
id: '/_personal_settings_layout',
|
||||||
getParentRoute: () => AuthenticateRoute,
|
getParentRoute: () => AuthenticateRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
@@ -52,52 +106,73 @@ const AuthenticateOrgdetailsRoute = AuthenticateOrgdetailsImport.update({
|
|||||||
getParentRoute: () => AuthenticateRoute,
|
getParentRoute: () => AuthenticateRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const RestrictloginsignupSignupIndexRoute =
|
||||||
|
RestrictloginsignupSignupIndexImport.update({
|
||||||
|
id: '/signup/',
|
||||||
|
path: '/signup/',
|
||||||
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const RestrictloginsignupLoginIndexRoute =
|
||||||
|
RestrictloginsignupLoginIndexImport.update({
|
||||||
|
id: '/login/',
|
||||||
|
path: '/login/',
|
||||||
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const AuthenticateOrgdetailsOrganizationlayoutRoute =
|
const AuthenticateOrgdetailsOrganizationlayoutRoute =
|
||||||
AuthenticateOrgdetailsOrganizationlayoutImport.update({
|
AuthenticateOrgdetailsOrganizationlayoutImport.update({
|
||||||
id: '/_organization_layout',
|
id: '/_organization_layout',
|
||||||
getParentRoute: () => AuthenticateOrgdetailsRoute,
|
getParentRoute: () => AuthenticateOrgdetailsRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const AuthenticateRestrictloginsignupSignupIndexRoute =
|
const RestrictloginsignupSignupSsoIndexRoute =
|
||||||
AuthenticateRestrictloginsignupSignupIndexImport.update({
|
RestrictloginsignupSignupSsoIndexImport.update({
|
||||||
id: '/signup/',
|
|
||||||
path: '/signup/',
|
|
||||||
getParentRoute: () => AuthenticateRestrictloginsignupRoute,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const AuthenticateRestrictloginsignupLoginIndexRoute =
|
|
||||||
AuthenticateRestrictloginsignupLoginIndexImport.update({
|
|
||||||
id: '/login/',
|
|
||||||
path: '/login/',
|
|
||||||
getParentRoute: () => AuthenticateRestrictloginsignupRoute,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const AuthenticateRestrictloginsignupSignupSsoIndexRoute =
|
|
||||||
AuthenticateRestrictloginsignupSignupSsoIndexImport.update({
|
|
||||||
id: '/signup/sso/',
|
id: '/signup/sso/',
|
||||||
path: '/signup/sso/',
|
path: '/signup/sso/',
|
||||||
getParentRoute: () => AuthenticateRestrictloginsignupRoute,
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const AuthenticateRestrictloginsignupLoginSsoIndexRoute =
|
const RestrictloginsignupLoginSsoIndexRoute =
|
||||||
AuthenticateRestrictloginsignupLoginSsoIndexImport.update({
|
RestrictloginsignupLoginSsoIndexImport.update({
|
||||||
id: '/login/sso/',
|
id: '/login/sso/',
|
||||||
path: '/login/sso/',
|
path: '/login/sso/',
|
||||||
getParentRoute: () => AuthenticateRestrictloginsignupRoute,
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute =
|
const RestrictloginsignupLoginSelectOrganizationIndexRoute =
|
||||||
AuthenticateRestrictloginsignupLoginSelectOrganizationIndexImport.update({
|
RestrictloginsignupLoginSelectOrganizationIndexImport.update({
|
||||||
id: '/login/select-organization/',
|
id: '/login/select-organization/',
|
||||||
path: '/login/select-organization/',
|
path: '/login/select-organization/',
|
||||||
getParentRoute: () => AuthenticateRestrictloginsignupRoute,
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const AuthenticateRestrictloginsignupLoginLdapIndexRoute =
|
const RestrictloginsignupLoginLdapIndexRoute =
|
||||||
AuthenticateRestrictloginsignupLoginLdapIndexImport.update({
|
RestrictloginsignupLoginLdapIndexImport.update({
|
||||||
id: '/login/ldap/',
|
id: '/login/ldap/',
|
||||||
path: '/login/ldap/',
|
path: '/login/ldap/',
|
||||||
getParentRoute: () => AuthenticateRestrictloginsignupRoute,
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute =
|
||||||
|
AuthenticatePersonalsettingslayoutPersonalSettingsIndexImport.update({
|
||||||
|
id: '/personal-settings/',
|
||||||
|
path: '/personal-settings/',
|
||||||
|
getParentRoute: () => AuthenticatePersonalsettingslayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const RestrictloginsignupLoginProviderSuccessRoute =
|
||||||
|
RestrictloginsignupLoginProviderSuccessImport.update({
|
||||||
|
id: '/login/provider/success',
|
||||||
|
path: '/login/provider/success',
|
||||||
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const RestrictloginsignupLoginProviderErrorRoute =
|
||||||
|
RestrictloginsignupLoginProviderErrorImport.update({
|
||||||
|
id: '/login/provider/error',
|
||||||
|
path: '/login/provider/error',
|
||||||
|
getParentRoute: () => RestrictloginsignupRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexRoute =
|
const AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexRoute =
|
||||||
@@ -107,20 +182,6 @@ const AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexRoute =
|
|||||||
getParentRoute: () => AuthenticateOrgdetailsOrganizationlayoutRoute,
|
getParentRoute: () => AuthenticateOrgdetailsOrganizationlayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const AuthenticateRestrictloginsignupLoginProviderSuccessRoute =
|
|
||||||
AuthenticateRestrictloginsignupLoginProviderSuccessImport.update({
|
|
||||||
id: '/login/provider/success',
|
|
||||||
path: '/login/provider/success',
|
|
||||||
getParentRoute: () => AuthenticateRestrictloginsignupRoute,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const AuthenticateRestrictloginsignupLoginProviderErrorRoute =
|
|
||||||
AuthenticateRestrictloginsignupLoginProviderErrorImport.update({
|
|
||||||
id: '/login/provider/error',
|
|
||||||
path: '/login/provider/error',
|
|
||||||
getParentRoute: () => AuthenticateRestrictloginsignupRoute,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
const AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexRoute =
|
const AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexRoute =
|
||||||
AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexImport.update(
|
AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexImport.update(
|
||||||
{
|
{
|
||||||
@@ -157,6 +218,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticateImport
|
preLoaderRoute: typeof AuthenticateImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/_restrict_login_signup': {
|
||||||
|
id: '/_restrict_login_signup'
|
||||||
|
path: ''
|
||||||
|
fullPath: ''
|
||||||
|
preLoaderRoute: typeof RestrictloginsignupImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
'/_authenticate/_org_details': {
|
'/_authenticate/_org_details': {
|
||||||
id: '/_authenticate/_org_details'
|
id: '/_authenticate/_org_details'
|
||||||
path: ''
|
path: ''
|
||||||
@@ -164,13 +232,55 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticateOrgdetailsImport
|
preLoaderRoute: typeof AuthenticateOrgdetailsImport
|
||||||
parentRoute: typeof AuthenticateImport
|
parentRoute: typeof AuthenticateImport
|
||||||
}
|
}
|
||||||
'/_authenticate/_restrict_login_signup': {
|
'/_authenticate/_personal_settings_layout': {
|
||||||
id: '/_authenticate/_restrict_login_signup'
|
id: '/_authenticate/_personal_settings_layout'
|
||||||
path: ''
|
path: ''
|
||||||
fullPath: ''
|
fullPath: ''
|
||||||
preLoaderRoute: typeof AuthenticateRestrictloginsignupImport
|
preLoaderRoute: typeof AuthenticatePersonalsettingslayoutImport
|
||||||
parentRoute: typeof AuthenticateImport
|
parentRoute: typeof AuthenticateImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/cli-redirect': {
|
||||||
|
id: '/_authenticate/cli-redirect'
|
||||||
|
path: '/cli-redirect'
|
||||||
|
fullPath: '/cli-redirect'
|
||||||
|
preLoaderRoute: typeof AuthenticateCliRedirectImport
|
||||||
|
parentRoute: typeof AuthenticateImport
|
||||||
|
}
|
||||||
|
'/_restrict_login_signup/email-not-verified': {
|
||||||
|
id: '/_restrict_login_signup/email-not-verified'
|
||||||
|
path: '/email-not-verified'
|
||||||
|
fullPath: '/email-not-verified'
|
||||||
|
preLoaderRoute: typeof RestrictloginsignupEmailNotVerifiedImport
|
||||||
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
|
}
|
||||||
|
'/_restrict_login_signup/password-reset': {
|
||||||
|
id: '/_restrict_login_signup/password-reset'
|
||||||
|
path: '/password-reset'
|
||||||
|
fullPath: '/password-reset'
|
||||||
|
preLoaderRoute: typeof RestrictloginsignupPasswordResetImport
|
||||||
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
|
}
|
||||||
|
'/_restrict_login_signup/requestnewinvite': {
|
||||||
|
id: '/_restrict_login_signup/requestnewinvite'
|
||||||
|
path: '/requestnewinvite'
|
||||||
|
fullPath: '/requestnewinvite'
|
||||||
|
preLoaderRoute: typeof RestrictloginsignupRequestnewinviteImport
|
||||||
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
|
}
|
||||||
|
'/_restrict_login_signup/signupinvite': {
|
||||||
|
id: '/_restrict_login_signup/signupinvite'
|
||||||
|
path: '/signupinvite'
|
||||||
|
fullPath: '/signupinvite'
|
||||||
|
preLoaderRoute: typeof RestrictloginsignupSignupinviteImport
|
||||||
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
|
}
|
||||||
|
'/_restrict_login_signup/verify-email': {
|
||||||
|
id: '/_restrict_login_signup/verify-email'
|
||||||
|
path: '/verify-email'
|
||||||
|
fullPath: '/verify-email'
|
||||||
|
preLoaderRoute: typeof RestrictloginsignupVerifyEmailImport
|
||||||
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
|
}
|
||||||
'/_authenticate/_org_details/_organization_layout': {
|
'/_authenticate/_org_details/_organization_layout': {
|
||||||
id: '/_authenticate/_org_details/_organization_layout'
|
id: '/_authenticate/_org_details/_organization_layout'
|
||||||
path: ''
|
path: ''
|
||||||
@@ -178,33 +288,68 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticateOrgdetailsOrganizationlayoutImport
|
preLoaderRoute: typeof AuthenticateOrgdetailsOrganizationlayoutImport
|
||||||
parentRoute: typeof AuthenticateOrgdetailsImport
|
parentRoute: typeof AuthenticateOrgdetailsImport
|
||||||
}
|
}
|
||||||
'/_authenticate/_restrict_login_signup/login/': {
|
'/_restrict_login_signup/login/': {
|
||||||
id: '/_authenticate/_restrict_login_signup/login/'
|
id: '/_restrict_login_signup/login/'
|
||||||
path: '/login'
|
path: '/login'
|
||||||
fullPath: '/login'
|
fullPath: '/login'
|
||||||
preLoaderRoute: typeof AuthenticateRestrictloginsignupLoginIndexImport
|
preLoaderRoute: typeof RestrictloginsignupLoginIndexImport
|
||||||
parentRoute: typeof AuthenticateRestrictloginsignupImport
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
}
|
}
|
||||||
'/_authenticate/_restrict_login_signup/signup/': {
|
'/_restrict_login_signup/signup/': {
|
||||||
id: '/_authenticate/_restrict_login_signup/signup/'
|
id: '/_restrict_login_signup/signup/'
|
||||||
path: '/signup'
|
path: '/signup'
|
||||||
fullPath: '/signup'
|
fullPath: '/signup'
|
||||||
preLoaderRoute: typeof AuthenticateRestrictloginsignupSignupIndexImport
|
preLoaderRoute: typeof RestrictloginsignupSignupIndexImport
|
||||||
parentRoute: typeof AuthenticateRestrictloginsignupImport
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
}
|
}
|
||||||
'/_authenticate/_restrict_login_signup/login/provider/error': {
|
'/_restrict_login_signup/login/provider/error': {
|
||||||
id: '/_authenticate/_restrict_login_signup/login/provider/error'
|
id: '/_restrict_login_signup/login/provider/error'
|
||||||
path: '/login/provider/error'
|
path: '/login/provider/error'
|
||||||
fullPath: '/login/provider/error'
|
fullPath: '/login/provider/error'
|
||||||
preLoaderRoute: typeof AuthenticateRestrictloginsignupLoginProviderErrorImport
|
preLoaderRoute: typeof RestrictloginsignupLoginProviderErrorImport
|
||||||
parentRoute: typeof AuthenticateRestrictloginsignupImport
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
}
|
}
|
||||||
'/_authenticate/_restrict_login_signup/login/provider/success': {
|
'/_restrict_login_signup/login/provider/success': {
|
||||||
id: '/_authenticate/_restrict_login_signup/login/provider/success'
|
id: '/_restrict_login_signup/login/provider/success'
|
||||||
path: '/login/provider/success'
|
path: '/login/provider/success'
|
||||||
fullPath: '/login/provider/success'
|
fullPath: '/login/provider/success'
|
||||||
preLoaderRoute: typeof AuthenticateRestrictloginsignupLoginProviderSuccessImport
|
preLoaderRoute: typeof RestrictloginsignupLoginProviderSuccessImport
|
||||||
parentRoute: typeof AuthenticateRestrictloginsignupImport
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
|
}
|
||||||
|
'/_authenticate/_personal_settings_layout/personal-settings/': {
|
||||||
|
id: '/_authenticate/_personal_settings_layout/personal-settings/'
|
||||||
|
path: '/personal-settings'
|
||||||
|
fullPath: '/personal-settings'
|
||||||
|
preLoaderRoute: typeof AuthenticatePersonalsettingslayoutPersonalSettingsIndexImport
|
||||||
|
parentRoute: typeof AuthenticatePersonalsettingslayoutImport
|
||||||
|
}
|
||||||
|
'/_restrict_login_signup/login/ldap/': {
|
||||||
|
id: '/_restrict_login_signup/login/ldap/'
|
||||||
|
path: '/login/ldap'
|
||||||
|
fullPath: '/login/ldap'
|
||||||
|
preLoaderRoute: typeof RestrictloginsignupLoginLdapIndexImport
|
||||||
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
|
}
|
||||||
|
'/_restrict_login_signup/login/select-organization/': {
|
||||||
|
id: '/_restrict_login_signup/login/select-organization/'
|
||||||
|
path: '/login/select-organization'
|
||||||
|
fullPath: '/login/select-organization'
|
||||||
|
preLoaderRoute: typeof RestrictloginsignupLoginSelectOrganizationIndexImport
|
||||||
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
|
}
|
||||||
|
'/_restrict_login_signup/login/sso/': {
|
||||||
|
id: '/_restrict_login_signup/login/sso/'
|
||||||
|
path: '/login/sso'
|
||||||
|
fullPath: '/login/sso'
|
||||||
|
preLoaderRoute: typeof RestrictloginsignupLoginSsoIndexImport
|
||||||
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
|
}
|
||||||
|
'/_restrict_login_signup/signup/sso/': {
|
||||||
|
id: '/_restrict_login_signup/signup/sso/'
|
||||||
|
path: '/signup/sso'
|
||||||
|
fullPath: '/signup/sso'
|
||||||
|
preLoaderRoute: typeof RestrictloginsignupSignupSsoIndexImport
|
||||||
|
parentRoute: typeof RestrictloginsignupImport
|
||||||
}
|
}
|
||||||
'/_authenticate/_org_details/_organization_layout/organization/': {
|
'/_authenticate/_org_details/_organization_layout/organization/': {
|
||||||
id: '/_authenticate/_org_details/_organization_layout/organization/'
|
id: '/_authenticate/_org_details/_organization_layout/organization/'
|
||||||
@@ -213,34 +358,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexImport
|
preLoaderRoute: typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexImport
|
||||||
parentRoute: typeof AuthenticateOrgdetailsOrganizationlayoutImport
|
parentRoute: typeof AuthenticateOrgdetailsOrganizationlayoutImport
|
||||||
}
|
}
|
||||||
'/_authenticate/_restrict_login_signup/login/ldap/': {
|
|
||||||
id: '/_authenticate/_restrict_login_signup/login/ldap/'
|
|
||||||
path: '/login/ldap'
|
|
||||||
fullPath: '/login/ldap'
|
|
||||||
preLoaderRoute: typeof AuthenticateRestrictloginsignupLoginLdapIndexImport
|
|
||||||
parentRoute: typeof AuthenticateRestrictloginsignupImport
|
|
||||||
}
|
|
||||||
'/_authenticate/_restrict_login_signup/login/select-organization/': {
|
|
||||||
id: '/_authenticate/_restrict_login_signup/login/select-organization/'
|
|
||||||
path: '/login/select-organization'
|
|
||||||
fullPath: '/login/select-organization'
|
|
||||||
preLoaderRoute: typeof AuthenticateRestrictloginsignupLoginSelectOrganizationIndexImport
|
|
||||||
parentRoute: typeof AuthenticateRestrictloginsignupImport
|
|
||||||
}
|
|
||||||
'/_authenticate/_restrict_login_signup/login/sso/': {
|
|
||||||
id: '/_authenticate/_restrict_login_signup/login/sso/'
|
|
||||||
path: '/login/sso'
|
|
||||||
fullPath: '/login/sso'
|
|
||||||
preLoaderRoute: typeof AuthenticateRestrictloginsignupLoginSsoIndexImport
|
|
||||||
parentRoute: typeof AuthenticateRestrictloginsignupImport
|
|
||||||
}
|
|
||||||
'/_authenticate/_restrict_login_signup/signup/sso/': {
|
|
||||||
id: '/_authenticate/_restrict_login_signup/signup/sso/'
|
|
||||||
path: '/signup/sso'
|
|
||||||
fullPath: '/signup/sso'
|
|
||||||
preLoaderRoute: typeof AuthenticateRestrictloginsignupSignupSsoIndexImport
|
|
||||||
parentRoute: typeof AuthenticateRestrictloginsignupImport
|
|
||||||
}
|
|
||||||
'/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager': {
|
'/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager': {
|
||||||
id: '/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager'
|
id: '/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager'
|
||||||
path: '/organization/$organizationId/secret-manager'
|
path: '/organization/$organizationId/secret-manager'
|
||||||
@@ -296,69 +413,99 @@ const AuthenticateOrgdetailsRouteWithChildren =
|
|||||||
AuthenticateOrgdetailsRouteChildren,
|
AuthenticateOrgdetailsRouteChildren,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface AuthenticateRestrictloginsignupRouteChildren {
|
interface AuthenticatePersonalsettingslayoutRouteChildren {
|
||||||
AuthenticateRestrictloginsignupLoginIndexRoute: typeof AuthenticateRestrictloginsignupLoginIndexRoute
|
AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute: typeof AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute
|
||||||
AuthenticateRestrictloginsignupSignupIndexRoute: typeof AuthenticateRestrictloginsignupSignupIndexRoute
|
|
||||||
AuthenticateRestrictloginsignupLoginProviderErrorRoute: typeof AuthenticateRestrictloginsignupLoginProviderErrorRoute
|
|
||||||
AuthenticateRestrictloginsignupLoginProviderSuccessRoute: typeof AuthenticateRestrictloginsignupLoginProviderSuccessRoute
|
|
||||||
AuthenticateRestrictloginsignupLoginLdapIndexRoute: typeof AuthenticateRestrictloginsignupLoginLdapIndexRoute
|
|
||||||
AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute: typeof AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute
|
|
||||||
AuthenticateRestrictloginsignupLoginSsoIndexRoute: typeof AuthenticateRestrictloginsignupLoginSsoIndexRoute
|
|
||||||
AuthenticateRestrictloginsignupSignupSsoIndexRoute: typeof AuthenticateRestrictloginsignupSignupSsoIndexRoute
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticateRestrictloginsignupRouteChildren: AuthenticateRestrictloginsignupRouteChildren =
|
const AuthenticatePersonalsettingslayoutRouteChildren: AuthenticatePersonalsettingslayoutRouteChildren =
|
||||||
{
|
{
|
||||||
AuthenticateRestrictloginsignupLoginIndexRoute:
|
AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute:
|
||||||
AuthenticateRestrictloginsignupLoginIndexRoute,
|
AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute,
|
||||||
AuthenticateRestrictloginsignupSignupIndexRoute:
|
|
||||||
AuthenticateRestrictloginsignupSignupIndexRoute,
|
|
||||||
AuthenticateRestrictloginsignupLoginProviderErrorRoute:
|
|
||||||
AuthenticateRestrictloginsignupLoginProviderErrorRoute,
|
|
||||||
AuthenticateRestrictloginsignupLoginProviderSuccessRoute:
|
|
||||||
AuthenticateRestrictloginsignupLoginProviderSuccessRoute,
|
|
||||||
AuthenticateRestrictloginsignupLoginLdapIndexRoute:
|
|
||||||
AuthenticateRestrictloginsignupLoginLdapIndexRoute,
|
|
||||||
AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute:
|
|
||||||
AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute,
|
|
||||||
AuthenticateRestrictloginsignupLoginSsoIndexRoute:
|
|
||||||
AuthenticateRestrictloginsignupLoginSsoIndexRoute,
|
|
||||||
AuthenticateRestrictloginsignupSignupSsoIndexRoute:
|
|
||||||
AuthenticateRestrictloginsignupSignupSsoIndexRoute,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticateRestrictloginsignupRouteWithChildren =
|
const AuthenticatePersonalsettingslayoutRouteWithChildren =
|
||||||
AuthenticateRestrictloginsignupRoute._addFileChildren(
|
AuthenticatePersonalsettingslayoutRoute._addFileChildren(
|
||||||
AuthenticateRestrictloginsignupRouteChildren,
|
AuthenticatePersonalsettingslayoutRouteChildren,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface AuthenticateRouteChildren {
|
interface AuthenticateRouteChildren {
|
||||||
AuthenticateOrgdetailsRoute: typeof AuthenticateOrgdetailsRouteWithChildren
|
AuthenticateOrgdetailsRoute: typeof AuthenticateOrgdetailsRouteWithChildren
|
||||||
AuthenticateRestrictloginsignupRoute: typeof AuthenticateRestrictloginsignupRouteWithChildren
|
AuthenticatePersonalsettingslayoutRoute: typeof AuthenticatePersonalsettingslayoutRouteWithChildren
|
||||||
|
AuthenticateCliRedirectRoute: typeof AuthenticateCliRedirectRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticateRouteChildren: AuthenticateRouteChildren = {
|
const AuthenticateRouteChildren: AuthenticateRouteChildren = {
|
||||||
AuthenticateOrgdetailsRoute: AuthenticateOrgdetailsRouteWithChildren,
|
AuthenticateOrgdetailsRoute: AuthenticateOrgdetailsRouteWithChildren,
|
||||||
AuthenticateRestrictloginsignupRoute:
|
AuthenticatePersonalsettingslayoutRoute:
|
||||||
AuthenticateRestrictloginsignupRouteWithChildren,
|
AuthenticatePersonalsettingslayoutRouteWithChildren,
|
||||||
|
AuthenticateCliRedirectRoute: AuthenticateCliRedirectRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticateRouteWithChildren = AuthenticateRoute._addFileChildren(
|
const AuthenticateRouteWithChildren = AuthenticateRoute._addFileChildren(
|
||||||
AuthenticateRouteChildren,
|
AuthenticateRouteChildren,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface RestrictloginsignupRouteChildren {
|
||||||
|
RestrictloginsignupEmailNotVerifiedRoute: typeof RestrictloginsignupEmailNotVerifiedRoute
|
||||||
|
RestrictloginsignupPasswordResetRoute: typeof RestrictloginsignupPasswordResetRoute
|
||||||
|
RestrictloginsignupRequestnewinviteRoute: typeof RestrictloginsignupRequestnewinviteRoute
|
||||||
|
RestrictloginsignupSignupinviteRoute: typeof RestrictloginsignupSignupinviteRoute
|
||||||
|
RestrictloginsignupVerifyEmailRoute: typeof RestrictloginsignupVerifyEmailRoute
|
||||||
|
RestrictloginsignupLoginIndexRoute: typeof RestrictloginsignupLoginIndexRoute
|
||||||
|
RestrictloginsignupSignupIndexRoute: typeof RestrictloginsignupSignupIndexRoute
|
||||||
|
RestrictloginsignupLoginProviderErrorRoute: typeof RestrictloginsignupLoginProviderErrorRoute
|
||||||
|
RestrictloginsignupLoginProviderSuccessRoute: typeof RestrictloginsignupLoginProviderSuccessRoute
|
||||||
|
RestrictloginsignupLoginLdapIndexRoute: typeof RestrictloginsignupLoginLdapIndexRoute
|
||||||
|
RestrictloginsignupLoginSelectOrganizationIndexRoute: typeof RestrictloginsignupLoginSelectOrganizationIndexRoute
|
||||||
|
RestrictloginsignupLoginSsoIndexRoute: typeof RestrictloginsignupLoginSsoIndexRoute
|
||||||
|
RestrictloginsignupSignupSsoIndexRoute: typeof RestrictloginsignupSignupSsoIndexRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const RestrictloginsignupRouteChildren: RestrictloginsignupRouteChildren = {
|
||||||
|
RestrictloginsignupEmailNotVerifiedRoute:
|
||||||
|
RestrictloginsignupEmailNotVerifiedRoute,
|
||||||
|
RestrictloginsignupPasswordResetRoute: RestrictloginsignupPasswordResetRoute,
|
||||||
|
RestrictloginsignupRequestnewinviteRoute:
|
||||||
|
RestrictloginsignupRequestnewinviteRoute,
|
||||||
|
RestrictloginsignupSignupinviteRoute: RestrictloginsignupSignupinviteRoute,
|
||||||
|
RestrictloginsignupVerifyEmailRoute: RestrictloginsignupVerifyEmailRoute,
|
||||||
|
RestrictloginsignupLoginIndexRoute: RestrictloginsignupLoginIndexRoute,
|
||||||
|
RestrictloginsignupSignupIndexRoute: RestrictloginsignupSignupIndexRoute,
|
||||||
|
RestrictloginsignupLoginProviderErrorRoute:
|
||||||
|
RestrictloginsignupLoginProviderErrorRoute,
|
||||||
|
RestrictloginsignupLoginProviderSuccessRoute:
|
||||||
|
RestrictloginsignupLoginProviderSuccessRoute,
|
||||||
|
RestrictloginsignupLoginLdapIndexRoute:
|
||||||
|
RestrictloginsignupLoginLdapIndexRoute,
|
||||||
|
RestrictloginsignupLoginSelectOrganizationIndexRoute:
|
||||||
|
RestrictloginsignupLoginSelectOrganizationIndexRoute,
|
||||||
|
RestrictloginsignupLoginSsoIndexRoute: RestrictloginsignupLoginSsoIndexRoute,
|
||||||
|
RestrictloginsignupSignupSsoIndexRoute:
|
||||||
|
RestrictloginsignupSignupSsoIndexRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const RestrictloginsignupRouteWithChildren =
|
||||||
|
RestrictloginsignupRoute._addFileChildren(RestrictloginsignupRouteChildren)
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'': typeof AuthenticateOrgdetailsOrganizationlayoutRouteWithChildren
|
'': typeof AuthenticateOrgdetailsOrganizationlayoutRouteWithChildren
|
||||||
'/login': typeof AuthenticateRestrictloginsignupLoginIndexRoute
|
'/cli-redirect': typeof AuthenticateCliRedirectRoute
|
||||||
'/signup': typeof AuthenticateRestrictloginsignupSignupIndexRoute
|
'/email-not-verified': typeof RestrictloginsignupEmailNotVerifiedRoute
|
||||||
'/login/provider/error': typeof AuthenticateRestrictloginsignupLoginProviderErrorRoute
|
'/password-reset': typeof RestrictloginsignupPasswordResetRoute
|
||||||
'/login/provider/success': typeof AuthenticateRestrictloginsignupLoginProviderSuccessRoute
|
'/requestnewinvite': typeof RestrictloginsignupRequestnewinviteRoute
|
||||||
|
'/signupinvite': typeof RestrictloginsignupSignupinviteRoute
|
||||||
|
'/verify-email': typeof RestrictloginsignupVerifyEmailRoute
|
||||||
|
'/login': typeof RestrictloginsignupLoginIndexRoute
|
||||||
|
'/signup': typeof RestrictloginsignupSignupIndexRoute
|
||||||
|
'/login/provider/error': typeof RestrictloginsignupLoginProviderErrorRoute
|
||||||
|
'/login/provider/success': typeof RestrictloginsignupLoginProviderSuccessRoute
|
||||||
|
'/personal-settings': typeof AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute
|
||||||
|
'/login/ldap': typeof RestrictloginsignupLoginLdapIndexRoute
|
||||||
|
'/login/select-organization': typeof RestrictloginsignupLoginSelectOrganizationIndexRoute
|
||||||
|
'/login/sso': typeof RestrictloginsignupLoginSsoIndexRoute
|
||||||
|
'/signup/sso': typeof RestrictloginsignupSignupSsoIndexRoute
|
||||||
'/organization': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexRoute
|
'/organization': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexRoute
|
||||||
'/login/ldap': typeof AuthenticateRestrictloginsignupLoginLdapIndexRoute
|
|
||||||
'/login/select-organization': typeof AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute
|
|
||||||
'/login/sso': typeof AuthenticateRestrictloginsignupLoginSsoIndexRoute
|
|
||||||
'/signup/sso': typeof AuthenticateRestrictloginsignupSignupSsoIndexRoute
|
|
||||||
'/organization/$organizationId/secret-manager': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdSecretManagerRoute
|
'/organization/$organizationId/secret-manager': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdSecretManagerRoute
|
||||||
'/organization/$organizationId': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexRoute
|
'/organization/$organizationId': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexRoute
|
||||||
}
|
}
|
||||||
@@ -366,15 +513,22 @@ export interface FileRoutesByFullPath {
|
|||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'': typeof AuthenticateOrgdetailsOrganizationlayoutRouteWithChildren
|
'': typeof AuthenticateOrgdetailsOrganizationlayoutRouteWithChildren
|
||||||
'/login': typeof AuthenticateRestrictloginsignupLoginIndexRoute
|
'/cli-redirect': typeof AuthenticateCliRedirectRoute
|
||||||
'/signup': typeof AuthenticateRestrictloginsignupSignupIndexRoute
|
'/email-not-verified': typeof RestrictloginsignupEmailNotVerifiedRoute
|
||||||
'/login/provider/error': typeof AuthenticateRestrictloginsignupLoginProviderErrorRoute
|
'/password-reset': typeof RestrictloginsignupPasswordResetRoute
|
||||||
'/login/provider/success': typeof AuthenticateRestrictloginsignupLoginProviderSuccessRoute
|
'/requestnewinvite': typeof RestrictloginsignupRequestnewinviteRoute
|
||||||
|
'/signupinvite': typeof RestrictloginsignupSignupinviteRoute
|
||||||
|
'/verify-email': typeof RestrictloginsignupVerifyEmailRoute
|
||||||
|
'/login': typeof RestrictloginsignupLoginIndexRoute
|
||||||
|
'/signup': typeof RestrictloginsignupSignupIndexRoute
|
||||||
|
'/login/provider/error': typeof RestrictloginsignupLoginProviderErrorRoute
|
||||||
|
'/login/provider/success': typeof RestrictloginsignupLoginProviderSuccessRoute
|
||||||
|
'/personal-settings': typeof AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute
|
||||||
|
'/login/ldap': typeof RestrictloginsignupLoginLdapIndexRoute
|
||||||
|
'/login/select-organization': typeof RestrictloginsignupLoginSelectOrganizationIndexRoute
|
||||||
|
'/login/sso': typeof RestrictloginsignupLoginSsoIndexRoute
|
||||||
|
'/signup/sso': typeof RestrictloginsignupSignupSsoIndexRoute
|
||||||
'/organization': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexRoute
|
'/organization': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexRoute
|
||||||
'/login/ldap': typeof AuthenticateRestrictloginsignupLoginLdapIndexRoute
|
|
||||||
'/login/select-organization': typeof AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute
|
|
||||||
'/login/sso': typeof AuthenticateRestrictloginsignupLoginSsoIndexRoute
|
|
||||||
'/signup/sso': typeof AuthenticateRestrictloginsignupSignupSsoIndexRoute
|
|
||||||
'/organization/$organizationId/secret-manager': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdSecretManagerRoute
|
'/organization/$organizationId/secret-manager': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdSecretManagerRoute
|
||||||
'/organization/$organizationId': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexRoute
|
'/organization/$organizationId': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexRoute
|
||||||
}
|
}
|
||||||
@@ -383,18 +537,26 @@ export interface FileRoutesById {
|
|||||||
__root__: typeof rootRoute
|
__root__: typeof rootRoute
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/_authenticate': typeof AuthenticateRouteWithChildren
|
'/_authenticate': typeof AuthenticateRouteWithChildren
|
||||||
|
'/_restrict_login_signup': typeof RestrictloginsignupRouteWithChildren
|
||||||
'/_authenticate/_org_details': typeof AuthenticateOrgdetailsRouteWithChildren
|
'/_authenticate/_org_details': typeof AuthenticateOrgdetailsRouteWithChildren
|
||||||
'/_authenticate/_restrict_login_signup': typeof AuthenticateRestrictloginsignupRouteWithChildren
|
'/_authenticate/_personal_settings_layout': typeof AuthenticatePersonalsettingslayoutRouteWithChildren
|
||||||
|
'/_authenticate/cli-redirect': typeof AuthenticateCliRedirectRoute
|
||||||
|
'/_restrict_login_signup/email-not-verified': typeof RestrictloginsignupEmailNotVerifiedRoute
|
||||||
|
'/_restrict_login_signup/password-reset': typeof RestrictloginsignupPasswordResetRoute
|
||||||
|
'/_restrict_login_signup/requestnewinvite': typeof RestrictloginsignupRequestnewinviteRoute
|
||||||
|
'/_restrict_login_signup/signupinvite': typeof RestrictloginsignupSignupinviteRoute
|
||||||
|
'/_restrict_login_signup/verify-email': typeof RestrictloginsignupVerifyEmailRoute
|
||||||
'/_authenticate/_org_details/_organization_layout': typeof AuthenticateOrgdetailsOrganizationlayoutRouteWithChildren
|
'/_authenticate/_org_details/_organization_layout': typeof AuthenticateOrgdetailsOrganizationlayoutRouteWithChildren
|
||||||
'/_authenticate/_restrict_login_signup/login/': typeof AuthenticateRestrictloginsignupLoginIndexRoute
|
'/_restrict_login_signup/login/': typeof RestrictloginsignupLoginIndexRoute
|
||||||
'/_authenticate/_restrict_login_signup/signup/': typeof AuthenticateRestrictloginsignupSignupIndexRoute
|
'/_restrict_login_signup/signup/': typeof RestrictloginsignupSignupIndexRoute
|
||||||
'/_authenticate/_restrict_login_signup/login/provider/error': typeof AuthenticateRestrictloginsignupLoginProviderErrorRoute
|
'/_restrict_login_signup/login/provider/error': typeof RestrictloginsignupLoginProviderErrorRoute
|
||||||
'/_authenticate/_restrict_login_signup/login/provider/success': typeof AuthenticateRestrictloginsignupLoginProviderSuccessRoute
|
'/_restrict_login_signup/login/provider/success': typeof RestrictloginsignupLoginProviderSuccessRoute
|
||||||
|
'/_authenticate/_personal_settings_layout/personal-settings/': typeof AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute
|
||||||
|
'/_restrict_login_signup/login/ldap/': typeof RestrictloginsignupLoginLdapIndexRoute
|
||||||
|
'/_restrict_login_signup/login/select-organization/': typeof RestrictloginsignupLoginSelectOrganizationIndexRoute
|
||||||
|
'/_restrict_login_signup/login/sso/': typeof RestrictloginsignupLoginSsoIndexRoute
|
||||||
|
'/_restrict_login_signup/signup/sso/': typeof RestrictloginsignupSignupSsoIndexRoute
|
||||||
'/_authenticate/_org_details/_organization_layout/organization/': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexRoute
|
'/_authenticate/_org_details/_organization_layout/organization/': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexRoute
|
||||||
'/_authenticate/_restrict_login_signup/login/ldap/': typeof AuthenticateRestrictloginsignupLoginLdapIndexRoute
|
|
||||||
'/_authenticate/_restrict_login_signup/login/select-organization/': typeof AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute
|
|
||||||
'/_authenticate/_restrict_login_signup/login/sso/': typeof AuthenticateRestrictloginsignupLoginSsoIndexRoute
|
|
||||||
'/_authenticate/_restrict_login_signup/signup/sso/': typeof AuthenticateRestrictloginsignupSignupSsoIndexRoute
|
|
||||||
'/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdSecretManagerRoute
|
'/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdSecretManagerRoute
|
||||||
'/_authenticate/_org_details/_organization_layout/organization/$organizationId/': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexRoute
|
'/_authenticate/_org_details/_organization_layout/organization/$organizationId/': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexRoute
|
||||||
}
|
}
|
||||||
@@ -404,48 +566,70 @@ export interface FileRouteTypes {
|
|||||||
fullPaths:
|
fullPaths:
|
||||||
| '/'
|
| '/'
|
||||||
| ''
|
| ''
|
||||||
|
| '/cli-redirect'
|
||||||
|
| '/email-not-verified'
|
||||||
|
| '/password-reset'
|
||||||
|
| '/requestnewinvite'
|
||||||
|
| '/signupinvite'
|
||||||
|
| '/verify-email'
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/signup'
|
| '/signup'
|
||||||
| '/login/provider/error'
|
| '/login/provider/error'
|
||||||
| '/login/provider/success'
|
| '/login/provider/success'
|
||||||
| '/organization'
|
| '/personal-settings'
|
||||||
| '/login/ldap'
|
| '/login/ldap'
|
||||||
| '/login/select-organization'
|
| '/login/select-organization'
|
||||||
| '/login/sso'
|
| '/login/sso'
|
||||||
| '/signup/sso'
|
| '/signup/sso'
|
||||||
|
| '/organization'
|
||||||
| '/organization/$organizationId/secret-manager'
|
| '/organization/$organizationId/secret-manager'
|
||||||
| '/organization/$organizationId'
|
| '/organization/$organizationId'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
| ''
|
| ''
|
||||||
|
| '/cli-redirect'
|
||||||
|
| '/email-not-verified'
|
||||||
|
| '/password-reset'
|
||||||
|
| '/requestnewinvite'
|
||||||
|
| '/signupinvite'
|
||||||
|
| '/verify-email'
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/signup'
|
| '/signup'
|
||||||
| '/login/provider/error'
|
| '/login/provider/error'
|
||||||
| '/login/provider/success'
|
| '/login/provider/success'
|
||||||
| '/organization'
|
| '/personal-settings'
|
||||||
| '/login/ldap'
|
| '/login/ldap'
|
||||||
| '/login/select-organization'
|
| '/login/select-organization'
|
||||||
| '/login/sso'
|
| '/login/sso'
|
||||||
| '/signup/sso'
|
| '/signup/sso'
|
||||||
|
| '/organization'
|
||||||
| '/organization/$organizationId/secret-manager'
|
| '/organization/$organizationId/secret-manager'
|
||||||
| '/organization/$organizationId'
|
| '/organization/$organizationId'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
| '/_authenticate'
|
| '/_authenticate'
|
||||||
|
| '/_restrict_login_signup'
|
||||||
| '/_authenticate/_org_details'
|
| '/_authenticate/_org_details'
|
||||||
| '/_authenticate/_restrict_login_signup'
|
| '/_authenticate/_personal_settings_layout'
|
||||||
|
| '/_authenticate/cli-redirect'
|
||||||
|
| '/_restrict_login_signup/email-not-verified'
|
||||||
|
| '/_restrict_login_signup/password-reset'
|
||||||
|
| '/_restrict_login_signup/requestnewinvite'
|
||||||
|
| '/_restrict_login_signup/signupinvite'
|
||||||
|
| '/_restrict_login_signup/verify-email'
|
||||||
| '/_authenticate/_org_details/_organization_layout'
|
| '/_authenticate/_org_details/_organization_layout'
|
||||||
| '/_authenticate/_restrict_login_signup/login/'
|
| '/_restrict_login_signup/login/'
|
||||||
| '/_authenticate/_restrict_login_signup/signup/'
|
| '/_restrict_login_signup/signup/'
|
||||||
| '/_authenticate/_restrict_login_signup/login/provider/error'
|
| '/_restrict_login_signup/login/provider/error'
|
||||||
| '/_authenticate/_restrict_login_signup/login/provider/success'
|
| '/_restrict_login_signup/login/provider/success'
|
||||||
|
| '/_authenticate/_personal_settings_layout/personal-settings/'
|
||||||
|
| '/_restrict_login_signup/login/ldap/'
|
||||||
|
| '/_restrict_login_signup/login/select-organization/'
|
||||||
|
| '/_restrict_login_signup/login/sso/'
|
||||||
|
| '/_restrict_login_signup/signup/sso/'
|
||||||
| '/_authenticate/_org_details/_organization_layout/organization/'
|
| '/_authenticate/_org_details/_organization_layout/organization/'
|
||||||
| '/_authenticate/_restrict_login_signup/login/ldap/'
|
|
||||||
| '/_authenticate/_restrict_login_signup/login/select-organization/'
|
|
||||||
| '/_authenticate/_restrict_login_signup/login/sso/'
|
|
||||||
| '/_authenticate/_restrict_login_signup/signup/sso/'
|
|
||||||
| '/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager'
|
| '/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager'
|
||||||
| '/_authenticate/_org_details/_organization_layout/organization/$organizationId/'
|
| '/_authenticate/_org_details/_organization_layout/organization/$organizationId/'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
@@ -454,11 +638,13 @@ export interface FileRouteTypes {
|
|||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
AuthenticateRoute: typeof AuthenticateRouteWithChildren
|
AuthenticateRoute: typeof AuthenticateRouteWithChildren
|
||||||
|
RestrictloginsignupRoute: typeof RestrictloginsignupRouteWithChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
AuthenticateRoute: AuthenticateRouteWithChildren,
|
AuthenticateRoute: AuthenticateRouteWithChildren,
|
||||||
|
RestrictloginsignupRoute: RestrictloginsignupRouteWithChildren,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const routeTree = rootRoute
|
export const routeTree = rootRoute
|
||||||
@@ -472,7 +658,8 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "__root.tsx",
|
"filePath": "__root.tsx",
|
||||||
"children": [
|
"children": [
|
||||||
"/",
|
"/",
|
||||||
"/_authenticate"
|
"/_authenticate",
|
||||||
|
"/_restrict_login_signup"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/": {
|
"/": {
|
||||||
@@ -482,7 +669,26 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "_authenticate.tsx",
|
"filePath": "_authenticate.tsx",
|
||||||
"children": [
|
"children": [
|
||||||
"/_authenticate/_org_details",
|
"/_authenticate/_org_details",
|
||||||
"/_authenticate/_restrict_login_signup"
|
"/_authenticate/_personal_settings_layout",
|
||||||
|
"/_authenticate/cli-redirect"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/_restrict_login_signup": {
|
||||||
|
"filePath": "_restrict_login_signup.tsx",
|
||||||
|
"children": [
|
||||||
|
"/_restrict_login_signup/email-not-verified",
|
||||||
|
"/_restrict_login_signup/password-reset",
|
||||||
|
"/_restrict_login_signup/requestnewinvite",
|
||||||
|
"/_restrict_login_signup/signupinvite",
|
||||||
|
"/_restrict_login_signup/verify-email",
|
||||||
|
"/_restrict_login_signup/login/",
|
||||||
|
"/_restrict_login_signup/signup/",
|
||||||
|
"/_restrict_login_signup/login/provider/error",
|
||||||
|
"/_restrict_login_signup/login/provider/success",
|
||||||
|
"/_restrict_login_signup/login/ldap/",
|
||||||
|
"/_restrict_login_signup/login/select-organization/",
|
||||||
|
"/_restrict_login_signup/login/sso/",
|
||||||
|
"/_restrict_login_signup/signup/sso/"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/_authenticate/_org_details": {
|
"/_authenticate/_org_details": {
|
||||||
@@ -492,20 +698,37 @@ export const routeTree = rootRoute
|
|||||||
"/_authenticate/_org_details/_organization_layout"
|
"/_authenticate/_org_details/_organization_layout"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/_authenticate/_restrict_login_signup": {
|
"/_authenticate/_personal_settings_layout": {
|
||||||
"filePath": "_authenticate/_restrict_login_signup.tsx",
|
"filePath": "_authenticate/_personal_settings_layout.tsx",
|
||||||
"parent": "/_authenticate",
|
"parent": "/_authenticate",
|
||||||
"children": [
|
"children": [
|
||||||
"/_authenticate/_restrict_login_signup/login/",
|
"/_authenticate/_personal_settings_layout/personal-settings/"
|
||||||
"/_authenticate/_restrict_login_signup/signup/",
|
|
||||||
"/_authenticate/_restrict_login_signup/login/provider/error",
|
|
||||||
"/_authenticate/_restrict_login_signup/login/provider/success",
|
|
||||||
"/_authenticate/_restrict_login_signup/login/ldap/",
|
|
||||||
"/_authenticate/_restrict_login_signup/login/select-organization/",
|
|
||||||
"/_authenticate/_restrict_login_signup/login/sso/",
|
|
||||||
"/_authenticate/_restrict_login_signup/signup/sso/"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"/_authenticate/cli-redirect": {
|
||||||
|
"filePath": "_authenticate/cli-redirect.tsx",
|
||||||
|
"parent": "/_authenticate"
|
||||||
|
},
|
||||||
|
"/_restrict_login_signup/email-not-verified": {
|
||||||
|
"filePath": "_restrict_login_signup/email-not-verified.tsx",
|
||||||
|
"parent": "/_restrict_login_signup"
|
||||||
|
},
|
||||||
|
"/_restrict_login_signup/password-reset": {
|
||||||
|
"filePath": "_restrict_login_signup/password-reset.tsx",
|
||||||
|
"parent": "/_restrict_login_signup"
|
||||||
|
},
|
||||||
|
"/_restrict_login_signup/requestnewinvite": {
|
||||||
|
"filePath": "_restrict_login_signup/requestnewinvite.tsx",
|
||||||
|
"parent": "/_restrict_login_signup"
|
||||||
|
},
|
||||||
|
"/_restrict_login_signup/signupinvite": {
|
||||||
|
"filePath": "_restrict_login_signup/signupinvite.tsx",
|
||||||
|
"parent": "/_restrict_login_signup"
|
||||||
|
},
|
||||||
|
"/_restrict_login_signup/verify-email": {
|
||||||
|
"filePath": "_restrict_login_signup/verify-email.tsx",
|
||||||
|
"parent": "/_restrict_login_signup"
|
||||||
|
},
|
||||||
"/_authenticate/_org_details/_organization_layout": {
|
"/_authenticate/_org_details/_organization_layout": {
|
||||||
"filePath": "_authenticate/_org_details/_organization_layout.tsx",
|
"filePath": "_authenticate/_org_details/_organization_layout.tsx",
|
||||||
"parent": "/_authenticate/_org_details",
|
"parent": "/_authenticate/_org_details",
|
||||||
@@ -515,42 +738,46 @@ export const routeTree = rootRoute
|
|||||||
"/_authenticate/_org_details/_organization_layout/organization/$organizationId/"
|
"/_authenticate/_org_details/_organization_layout/organization/$organizationId/"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/_authenticate/_restrict_login_signup/login/": {
|
"/_restrict_login_signup/login/": {
|
||||||
"filePath": "_authenticate/_restrict_login_signup/login/index.tsx",
|
"filePath": "_restrict_login_signup/login/index.tsx",
|
||||||
"parent": "/_authenticate/_restrict_login_signup"
|
"parent": "/_restrict_login_signup"
|
||||||
},
|
},
|
||||||
"/_authenticate/_restrict_login_signup/signup/": {
|
"/_restrict_login_signup/signup/": {
|
||||||
"filePath": "_authenticate/_restrict_login_signup/signup/index.tsx",
|
"filePath": "_restrict_login_signup/signup/index.tsx",
|
||||||
"parent": "/_authenticate/_restrict_login_signup"
|
"parent": "/_restrict_login_signup"
|
||||||
},
|
},
|
||||||
"/_authenticate/_restrict_login_signup/login/provider/error": {
|
"/_restrict_login_signup/login/provider/error": {
|
||||||
"filePath": "_authenticate/_restrict_login_signup/login/provider/error.tsx",
|
"filePath": "_restrict_login_signup/login/provider/error.tsx",
|
||||||
"parent": "/_authenticate/_restrict_login_signup"
|
"parent": "/_restrict_login_signup"
|
||||||
},
|
},
|
||||||
"/_authenticate/_restrict_login_signup/login/provider/success": {
|
"/_restrict_login_signup/login/provider/success": {
|
||||||
"filePath": "_authenticate/_restrict_login_signup/login/provider/success.tsx",
|
"filePath": "_restrict_login_signup/login/provider/success.tsx",
|
||||||
"parent": "/_authenticate/_restrict_login_signup"
|
"parent": "/_restrict_login_signup"
|
||||||
|
},
|
||||||
|
"/_authenticate/_personal_settings_layout/personal-settings/": {
|
||||||
|
"filePath": "_authenticate/_personal_settings_layout/personal-settings/index.tsx",
|
||||||
|
"parent": "/_authenticate/_personal_settings_layout"
|
||||||
|
},
|
||||||
|
"/_restrict_login_signup/login/ldap/": {
|
||||||
|
"filePath": "_restrict_login_signup/login/ldap/index.tsx",
|
||||||
|
"parent": "/_restrict_login_signup"
|
||||||
|
},
|
||||||
|
"/_restrict_login_signup/login/select-organization/": {
|
||||||
|
"filePath": "_restrict_login_signup/login/select-organization/index.tsx",
|
||||||
|
"parent": "/_restrict_login_signup"
|
||||||
|
},
|
||||||
|
"/_restrict_login_signup/login/sso/": {
|
||||||
|
"filePath": "_restrict_login_signup/login/sso/index.tsx",
|
||||||
|
"parent": "/_restrict_login_signup"
|
||||||
|
},
|
||||||
|
"/_restrict_login_signup/signup/sso/": {
|
||||||
|
"filePath": "_restrict_login_signup/signup/sso/index.tsx",
|
||||||
|
"parent": "/_restrict_login_signup"
|
||||||
},
|
},
|
||||||
"/_authenticate/_org_details/_organization_layout/organization/": {
|
"/_authenticate/_org_details/_organization_layout/organization/": {
|
||||||
"filePath": "_authenticate/_org_details/_organization_layout/organization/index.tsx",
|
"filePath": "_authenticate/_org_details/_organization_layout/organization/index.tsx",
|
||||||
"parent": "/_authenticate/_org_details/_organization_layout"
|
"parent": "/_authenticate/_org_details/_organization_layout"
|
||||||
},
|
},
|
||||||
"/_authenticate/_restrict_login_signup/login/ldap/": {
|
|
||||||
"filePath": "_authenticate/_restrict_login_signup/login/ldap/index.tsx",
|
|
||||||
"parent": "/_authenticate/_restrict_login_signup"
|
|
||||||
},
|
|
||||||
"/_authenticate/_restrict_login_signup/login/select-organization/": {
|
|
||||||
"filePath": "_authenticate/_restrict_login_signup/login/select-organization/index.tsx",
|
|
||||||
"parent": "/_authenticate/_restrict_login_signup"
|
|
||||||
},
|
|
||||||
"/_authenticate/_restrict_login_signup/login/sso/": {
|
|
||||||
"filePath": "_authenticate/_restrict_login_signup/login/sso/index.tsx",
|
|
||||||
"parent": "/_authenticate/_restrict_login_signup"
|
|
||||||
},
|
|
||||||
"/_authenticate/_restrict_login_signup/signup/sso/": {
|
|
||||||
"filePath": "_authenticate/_restrict_login_signup/signup/sso/index.tsx",
|
|
||||||
"parent": "/_authenticate/_restrict_login_signup"
|
|
||||||
},
|
|
||||||
"/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager": {
|
"/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager": {
|
||||||
"filePath": "_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager.tsx",
|
"filePath": "_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager.tsx",
|
||||||
"parent": "/_authenticate/_org_details/_organization_layout"
|
"parent": "/_authenticate/_org_details/_organization_layout"
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import { setAuthToken } from "@app/hooks/api/reactQuery";
|
|||||||
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticate")({
|
export const Route = createFileRoute("/_authenticate")({
|
||||||
beforeLoad: async ({ context, location }) => {
|
beforeLoad: async ({ context }) => {
|
||||||
const isLoginRoute = location.pathname.startsWith("/login");
|
|
||||||
const isSignupRoute = location.pathname.startsWith("/signup");
|
|
||||||
try {
|
try {
|
||||||
const data = await context.queryClient.fetchQuery({
|
const data = await context.queryClient.fetchQuery({
|
||||||
queryKey: authKeys.getAuthToken,
|
queryKey: authKeys.getAuthToken,
|
||||||
@@ -27,7 +25,6 @@ export const Route = createFileRoute("/_authenticate")({
|
|||||||
|
|
||||||
return { organizationId: data.organizationId as string, isAuthenticated: true, user };
|
return { organizationId: data.organizationId as string, isAuthenticated: true, user };
|
||||||
} catch {
|
} catch {
|
||||||
if (isLoginRoute || isSignupRoute) return {};
|
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Access Restricted",
|
title: "Access Restricted",
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { PersonalSettingsLayout } from "@app/layouts/PersonalSettingsLayout";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_authenticate/_personal_settings_layout")({
|
||||||
|
component: PersonalSettingsLayout
|
||||||
|
});
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Button, Tooltip } from "@app/components/v2";
|
||||||
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { AddAPIKeyModal } from "./AddAPIKeyModal";
|
||||||
|
import { APIKeyTable } from "./APIKeyTable";
|
||||||
|
|
||||||
|
export const APIKeySection = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["addAPIKey"] as const);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-8 flex justify-between">
|
||||||
|
<p className="text-xl font-semibold text-mineshaft-100">
|
||||||
|
{t("settings.personal.api-keys.title")}
|
||||||
|
</p>
|
||||||
|
<Tooltip content="API Keys are deprecated and will be removed in the future.">
|
||||||
|
<Button
|
||||||
|
isDisabled
|
||||||
|
colorSchema="secondary"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={() => handlePopUpOpen("addAPIKey")}
|
||||||
|
>
|
||||||
|
Add API Key
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<APIKeyTable />
|
||||||
|
<AddAPIKeyModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
import { faKey, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
EmptyState,
|
||||||
|
IconButton,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useDeleteAPIKey, useGetMyAPIKeys } from "@app/hooks/api";
|
||||||
|
|
||||||
|
export const APIKeyTable = () => {
|
||||||
|
const { data, isLoading } = useGetMyAPIKeys();
|
||||||
|
const { mutateAsync } = useDeleteAPIKey();
|
||||||
|
|
||||||
|
const handleDeleteAPIKeyDataClick = async (apiKeyDataId: string) => {
|
||||||
|
try {
|
||||||
|
await mutateAsync(apiKeyDataId);
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted API key",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to delete API key",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th className="flex-1">Name</Th>
|
||||||
|
<Th className="flex-1">Last active</Th>
|
||||||
|
<Th className="flex-1">Created</Th>
|
||||||
|
<Th className="flex-1">Expiration</Th>
|
||||||
|
<Th className="w-5" />
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isLoading && <TableSkeleton columns={4} innerKey="api-keys" />}
|
||||||
|
{!isLoading &&
|
||||||
|
data &&
|
||||||
|
data.length > 0 &&
|
||||||
|
data.map(({ id, name, createdAt, expiresAt, lastUsed }) => {
|
||||||
|
return (
|
||||||
|
<Tr className="h-10" key={`api-key-${id}`}>
|
||||||
|
<Td>{name}</Td>
|
||||||
|
<Td>{format(new Date(lastUsed), "yyyy-MM-dd")}</Td>
|
||||||
|
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
||||||
|
<Td>{format(new Date(expiresAt), "yyyy-MM-dd")}</Td>
|
||||||
|
<Td>
|
||||||
|
<IconButton
|
||||||
|
onClick={async () => {
|
||||||
|
await handleDeleteAPIKeyDataClick(id);
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
|
</IconButton>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{!isLoading && data && data?.length === 0 && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={5}>
|
||||||
|
<EmptyState title="No API Keys on file" icon={faKey} />
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
+185
@@ -0,0 +1,185 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
import { useCreateAPIKey } from "@app/hooks/api";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
const expirations = [
|
||||||
|
{ label: "1 day", value: "1d" },
|
||||||
|
{ label: "7 days", value: "7d" },
|
||||||
|
{ label: "1 month", value: "1mo" },
|
||||||
|
{ label: "6 months", value: "6mo" },
|
||||||
|
{ label: "12 months", value: "12mo" }
|
||||||
|
];
|
||||||
|
|
||||||
|
const expirationMapping: { [key: string]: number } = {
|
||||||
|
"1d": 86400,
|
||||||
|
"7d": 604800,
|
||||||
|
"1mo": 2592000,
|
||||||
|
"6mo": 15552000,
|
||||||
|
"12mo": 31104000
|
||||||
|
};
|
||||||
|
|
||||||
|
const schema = z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
expiresIn: z.string()
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
popUp: UsePopUpState<["addAPIKey"]>;
|
||||||
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addAPIKey"]>, state?: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AddAPIKeyModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||||
|
const [newAPIKey, setNewAPIKey] = useState("");
|
||||||
|
const [isAPIKeyCopied, setIsAPIKeyCopied] = useToggle(false);
|
||||||
|
|
||||||
|
const { mutateAsync, isLoading } = useCreateAPIKey();
|
||||||
|
|
||||||
|
const { control, handleSubmit, reset } = useForm<FormData>({
|
||||||
|
resolver: zodResolver(schema)
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timer: NodeJS.Timeout;
|
||||||
|
|
||||||
|
if (isAPIKeyCopied) {
|
||||||
|
timer = setTimeout(() => setIsAPIKeyCopied.off(), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [setIsAPIKeyCopied]);
|
||||||
|
|
||||||
|
const copyTokenToClipboard = () => {
|
||||||
|
navigator.clipboard.writeText(newAPIKey);
|
||||||
|
setIsAPIKeyCopied.on();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFormSubmit = async ({ name, expiresIn }: FormData) => {
|
||||||
|
try {
|
||||||
|
const { apiKey } = await mutateAsync({
|
||||||
|
name,
|
||||||
|
expiresIn: expirationMapping[expiresIn]
|
||||||
|
});
|
||||||
|
|
||||||
|
setNewAPIKey(apiKey);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully created API key",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
reset();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to create API key",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasAPIKey = Boolean(newAPIKey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp?.addAPIKey?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
handlePopUpToggle("addAPIKey", isOpen);
|
||||||
|
reset();
|
||||||
|
setNewAPIKey("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent title="Create API Key">
|
||||||
|
{!hasAPIKey ? (
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="name"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl label="Name" isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Input {...field} placeholder="My API Key" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="expiresIn"
|
||||||
|
defaultValue="6mo"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl label="Expiration" errorText={error?.message} isError={Boolean(error)}>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{expirations.map(({ label, value }) => (
|
||||||
|
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
||||||
|
{label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="mt-8 flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isLoading}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => handlePopUpToggle("addAPIKey", false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
<div className="mb-3 mr-2 mt-2 flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||||
|
<p className="mr-4 break-all">{newAPIKey}</p>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
onClick={copyTokenToClipboard}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isAPIKeyCopied ? faCheck : faCopy} />
|
||||||
|
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||||
|
Click to copy
|
||||||
|
</span>
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { APIKeySection } from "./APIKeySection";
|
||||||
+132
@@ -0,0 +1,132 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { faGithub, faGitlab, faGoogle, IconDefinition } from "@fortawesome/free-brands-svg-icons";
|
||||||
|
import { faEnvelope } from "@fortawesome/free-regular-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Switch } from "@app/components/v2";
|
||||||
|
import { useServerConfig, useUser } from "@app/context";
|
||||||
|
import { useUpdateUserAuthMethods } from "@app/hooks/api";
|
||||||
|
import { LoginMethod } from "@app/hooks/api/admin/types";
|
||||||
|
import { AuthMethod } from "@app/hooks/api/users/types";
|
||||||
|
|
||||||
|
interface AuthMethodOption {
|
||||||
|
label: string;
|
||||||
|
value: AuthMethod;
|
||||||
|
icon: IconDefinition;
|
||||||
|
loginMethod: LoginMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authMethodOpts: AuthMethodOption[] = [
|
||||||
|
{ label: "Email", value: AuthMethod.EMAIL, icon: faEnvelope, loginMethod: LoginMethod.EMAIL },
|
||||||
|
{ label: "Google", value: AuthMethod.GOOGLE, icon: faGoogle, loginMethod: LoginMethod.GOOGLE },
|
||||||
|
{ label: "GitHub", value: AuthMethod.GITHUB, icon: faGithub, loginMethod: LoginMethod.GITHUB },
|
||||||
|
{ label: "GitLab", value: AuthMethod.GITLAB, icon: faGitlab, loginMethod: LoginMethod.GITLAB }
|
||||||
|
];
|
||||||
|
const schema = z.object({
|
||||||
|
authMethods: z.nativeEnum(AuthMethod).array()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
export const AuthMethodSection = () => {
|
||||||
|
const { user } = useUser();
|
||||||
|
const { config } = useServerConfig();
|
||||||
|
const { mutateAsync } = useUpdateUserAuthMethods();
|
||||||
|
|
||||||
|
const { reset, setValue, watch } = useForm<FormData>({
|
||||||
|
defaultValues: {
|
||||||
|
authMethods: user.authMethods
|
||||||
|
},
|
||||||
|
resolver: zodResolver(schema)
|
||||||
|
});
|
||||||
|
|
||||||
|
const authMethods = watch("authMethods");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
reset({
|
||||||
|
authMethods: user.authMethods
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const onAuthMethodToggle = async (value: boolean, authMethodOpt: AuthMethodOption) => {
|
||||||
|
const newAuthMethods = value
|
||||||
|
? [...authMethods, authMethodOpt.value]
|
||||||
|
: authMethods.filter((auth) => auth !== authMethodOpt.value);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
const newUser = await mutateAsync({
|
||||||
|
authMethods: newAuthMethods
|
||||||
|
});
|
||||||
|
|
||||||
|
setValue("authMethods", newUser.authMethods);
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully enabled authentication method",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newAuthMethods.length === 0) {
|
||||||
|
createNotification({
|
||||||
|
text: "You must keep at least 1 authentication method enabled",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUser = await mutateAsync({
|
||||||
|
authMethods: newAuthMethods
|
||||||
|
});
|
||||||
|
|
||||||
|
setValue("authMethods", newUser.authMethods);
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully disabled authentication method",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<h2 className="mb-8 flex-1 text-xl font-semibold text-mineshaft-100">
|
||||||
|
Authentication methods
|
||||||
|
</h2>
|
||||||
|
<p className="mb-4 text-gray-400">
|
||||||
|
By enabling a SSO provider, you are allowing an account with that provider which uses the
|
||||||
|
same email address as your existing Infisical account to be able to log in to Infisical.
|
||||||
|
</p>
|
||||||
|
<div className="mb-4">
|
||||||
|
{user &&
|
||||||
|
authMethodOpts.map((authMethodOpt) => {
|
||||||
|
// only filter when enabledLoginMethods is explicitly configured by admin
|
||||||
|
if (
|
||||||
|
config.enabledLoginMethods &&
|
||||||
|
!config.enabledLoginMethods.includes(authMethodOpt.loginMethod)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center p-4" key={`auth-method-${authMethodOpt.value}`}>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<FontAwesomeIcon icon={authMethodOpt.icon} className="mr-4" />
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id={`enable-${authMethodOpt.value}-auth`}
|
||||||
|
onCheckedChange={(value) => onAuthMethodToggle(value, authMethodOpt)}
|
||||||
|
isChecked={authMethods?.includes(authMethodOpt.value) ?? false}
|
||||||
|
>
|
||||||
|
<p className="mr-4 w-12">{authMethodOpt.label}</p>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { AuthMethodSection } from "./AuthMethodSection";
|
||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import attemptChangePassword from "@app/components/utilities/attemptChangePassword";
|
||||||
|
import checkPassword from "@app/components/utilities/checks/password/checkPassword";
|
||||||
|
import { Button, FormControl, Input } from "@app/components/v2";
|
||||||
|
import { useUser } from "@app/context";
|
||||||
|
|
||||||
|
type Errors = {
|
||||||
|
tooShort?: string;
|
||||||
|
tooLong?: string;
|
||||||
|
noLetterChar?: string;
|
||||||
|
noNumOrSpecialChar?: string;
|
||||||
|
repeatedChar?: string;
|
||||||
|
escapeChar?: string;
|
||||||
|
lowEntropy?: string;
|
||||||
|
breached?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const schema = z
|
||||||
|
.object({
|
||||||
|
oldPassword: z.string(),
|
||||||
|
newPassword: z.string()
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
export const ChangePasswordSection = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
const { reset, control, handleSubmit } = useForm({
|
||||||
|
defaultValues: {
|
||||||
|
oldPassword: "",
|
||||||
|
newPassword: ""
|
||||||
|
},
|
||||||
|
resolver: zodResolver(schema)
|
||||||
|
});
|
||||||
|
const [errors, setErrors] = useState<Errors>({});
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const onFormSubmit = async ({ oldPassword, newPassword }: FormData) => {
|
||||||
|
try {
|
||||||
|
const errorCheck = await checkPassword({
|
||||||
|
password: newPassword,
|
||||||
|
setErrors
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errorCheck) return;
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
await attemptChangePassword({
|
||||||
|
email: user.username,
|
||||||
|
currentPassword: oldPassword,
|
||||||
|
newPassword
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully changed password",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
reset();
|
||||||
|
window.location.href = "/login";
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setIsLoading(false);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to change password",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
|
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
|
>
|
||||||
|
<h2 className="mb-8 flex-1 text-xl font-semibold text-mineshaft-100">Change password</h2>
|
||||||
|
<div className="max-w-md">
|
||||||
|
<Controller
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Input
|
||||||
|
placeholder="Old password"
|
||||||
|
type="password"
|
||||||
|
{...field}
|
||||||
|
className="bg-mineshaft-800"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="oldPassword"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-w-md">
|
||||||
|
<Controller
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Input
|
||||||
|
placeholder="New password"
|
||||||
|
type="password"
|
||||||
|
{...field}
|
||||||
|
className="bg-mineshaft-800"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="newPassword"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{Object.keys(errors).length > 0 && (
|
||||||
|
<div className="my-4 flex max-w-md flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||||
|
<div className="mb-2 text-sm text-gray-400">{t("section.password.validate-base")}</div>
|
||||||
|
{Object.keys(errors).map((key) => {
|
||||||
|
if (errors[key as keyof Errors]) {
|
||||||
|
return (
|
||||||
|
<div className="items-top ml-1 flex flex-row justify-start" key={key}>
|
||||||
|
<div>
|
||||||
|
<FontAwesomeIcon icon={faXmark} className="text-md ml-0.5 mr-2.5 text-red" />
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-400">{errors[key as keyof Errors]}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button type="submit" colorSchema="secondary" isLoading={isLoading} isDisabled={isLoading}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { ChangePasswordSection } from "./ChangePasswordSection";
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||||
|
import { useDeleteMe } from "@app/hooks/api";
|
||||||
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
export const DeleteAccountSection = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"deleteAccount"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const { mutateAsync: deleteUserMutateAsync, isLoading } = useDeleteMe();
|
||||||
|
|
||||||
|
const handleDeleteAccountSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await deleteUserMutateAsync();
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted account",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate({ to: "/login" });
|
||||||
|
handlePopUpClose("deleteAccount");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to delete account",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Danger Zone</p>
|
||||||
|
<Button
|
||||||
|
isLoading={isLoading}
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="outline_bg"
|
||||||
|
type="submit"
|
||||||
|
onClick={() => handlePopUpOpen("deleteAccount")}
|
||||||
|
>
|
||||||
|
Delete my account
|
||||||
|
</Button>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteAccount.isOpen}
|
||||||
|
title="Are you sure want to delete your account?"
|
||||||
|
subTitle="Permanently remove this account and all of its data. This action is not reversible, so please be careful."
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteAccount", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={handleDeleteAccountSubmit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { DeleteAccountSection } from "./DeleteAccountSection";
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey";
|
||||||
|
import { Button, FormControl, Input } from "@app/components/v2";
|
||||||
|
import { useUser } from "@app/context";
|
||||||
|
|
||||||
|
const schema = z
|
||||||
|
.object({
|
||||||
|
password: z.string().describe("Password is required")
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
export const EmergencyKitSection = () => {
|
||||||
|
const { user } = useUser();
|
||||||
|
const { reset, control, handleSubmit } = useForm({
|
||||||
|
defaultValues: {
|
||||||
|
password: ""
|
||||||
|
},
|
||||||
|
resolver: zodResolver(schema)
|
||||||
|
});
|
||||||
|
|
||||||
|
const onFormSubmit = ({ password }: FormData) => {
|
||||||
|
try {
|
||||||
|
if (!user?.email) return;
|
||||||
|
|
||||||
|
issueBackupKey({
|
||||||
|
email: user.email,
|
||||||
|
password,
|
||||||
|
personalName: `${user.firstName} ${user.lastName}`,
|
||||||
|
setBackupKeyError: () => {},
|
||||||
|
setBackupKeyIssued: () => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
reset();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to download emergency kit",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
|
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
|
>
|
||||||
|
<h2 className="flex-1 text-xl font-semibold text-mineshaft-100">Emergency Kit</h2>
|
||||||
|
<p className="mb-8 text-gray-400">
|
||||||
|
The kit contains information you can use to recover your account.
|
||||||
|
</p>
|
||||||
|
<div className="max-w-md">
|
||||||
|
<Controller
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Input
|
||||||
|
placeholder="Password"
|
||||||
|
type="password"
|
||||||
|
{...field}
|
||||||
|
className="bg-mineshaft-800"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button type="submit" colorSchema="secondary" isLoading={false}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { EmergencyKitSection } from "./EmergencyKitSection";
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import { APIKeySection } from "../APIKeySection";
|
||||||
|
|
||||||
|
export const PersonalAPIKeyTab = () => {
|
||||||
|
return <APIKeySection />;
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { PersonalAPIKeyTab } from "./PersonalAPIKeyTab";
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import { useGetUser } from "@app/hooks/api";
|
||||||
|
import { AuthMethod } from "@app/hooks/api/users/types";
|
||||||
|
|
||||||
|
import { AuthMethodSection } from "../AuthMethodSection";
|
||||||
|
import { ChangePasswordSection } from "../ChangePasswordSection";
|
||||||
|
import { MFASection } from "../SecuritySection";
|
||||||
|
|
||||||
|
export const PersonalAuthTab = () => {
|
||||||
|
const { data: user } = useGetUser();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{user && !user.authMethods.includes(AuthMethod.LDAP) && (
|
||||||
|
<>
|
||||||
|
<MFASection />
|
||||||
|
<AuthMethodSection />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<ChangePasswordSection />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { PersonalAuthTab } from "./PersonalAuthTab";
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import { DeleteAccountSection } from "../DeleteAccountSection";
|
||||||
|
import { EmergencyKitSection } from "../EmergencyKitSection";
|
||||||
|
import { SessionsSection } from "../SessionsSection";
|
||||||
|
import { UserNameSection } from "../UserNameSection";
|
||||||
|
|
||||||
|
export const PersonalGeneralTab = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<UserNameSection />
|
||||||
|
<SessionsSection />
|
||||||
|
<EmergencyKitSection />
|
||||||
|
<DeleteAccountSection />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { PersonalGeneralTab } from "./PersonalGeneralTab";
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { PersonalAPIKeyTab } from "../PersonalAPIKeyTab";
|
||||||
|
import { PersonalAuthTab } from "../PersonalAuthTab";
|
||||||
|
import { PersonalGeneralTab } from "../PersonalGeneralTab";
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ name: "General", key: "tab-account-general" },
|
||||||
|
{ name: "Authentication", key: "tab-account-auth" },
|
||||||
|
{ name: "API Keys", key: "tab-account-api-keys" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const PersonalTabGroup = () => {
|
||||||
|
return (
|
||||||
|
<Tabs defaultValue={tabs[0].key}>
|
||||||
|
<TabList>
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
<Tab value={tab.key}>{tab.name}</Tab>
|
||||||
|
))}
|
||||||
|
</TabList>
|
||||||
|
<TabPanel value={tabs[0].key}>
|
||||||
|
<PersonalGeneralTab />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={tabs[1].key}>
|
||||||
|
<PersonalAuthTab />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={tabs[2].key}>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="mt-4 flex w-full flex-row items-center rounded-md border border-primary-600/70 bg-primary/[.07] p-4 text-base text-white">
|
||||||
|
<FontAwesomeIcon icon={faWarning} className="pr-6 text-4xl text-white/80" />
|
||||||
|
<div className="flex w-full flex-col text-sm">
|
||||||
|
<span className="mb-4 text-lg font-semibold">Deprecation Notice</span>
|
||||||
|
<p>
|
||||||
|
API Keys are being deprecated in favor of Machine Identities.
|
||||||
|
<br />
|
||||||
|
They will be removed in the future in accordance with the deprecation notice and
|
||||||
|
timeline stated{" "}
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/blog/deprecating-api-keys"
|
||||||
|
target="_blank"
|
||||||
|
className="font-semibold text-primary-400"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
<br />
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/documentation/platform/identities/overview"
|
||||||
|
target="_blank"
|
||||||
|
className="font-semibold text-primary-400"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Learn more about Machine Identities
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PersonalAPIKeyTab />
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { PersonalTabGroup } from "./PersonalTabGroup";
|
||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import TotpRegistration from "@app/components/mfa/TotpRegistration";
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ContentLoader,
|
||||||
|
DeleteActionModal,
|
||||||
|
EmailServiceSetupModal,
|
||||||
|
FormControl,
|
||||||
|
Select,
|
||||||
|
SelectItem,
|
||||||
|
Switch
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
import { useGetUser, userKeys, useUpdateUserMfa } from "@app/hooks/api";
|
||||||
|
import { MfaMethod } from "@app/hooks/api/auth/types";
|
||||||
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
|
import {
|
||||||
|
useCreateNewTotpRecoveryCodes,
|
||||||
|
useDeleteUserTotpConfiguration
|
||||||
|
} from "@app/hooks/api/users/mutation";
|
||||||
|
import { useGetUserTotpConfiguration } from "@app/hooks/api/users/queries";
|
||||||
|
import { AuthMethod } from "@app/hooks/api/users/types";
|
||||||
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
export const MFASection = () => {
|
||||||
|
const { data: user } = useGetUser();
|
||||||
|
const { mutateAsync } = useUpdateUserMfa();
|
||||||
|
|
||||||
|
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
|
"setUpEmail",
|
||||||
|
"deleteTotpConfig"
|
||||||
|
] as const);
|
||||||
|
const [shouldShowRecoveryCodes, setShouldShowRecoveryCodes] = useToggle();
|
||||||
|
const { data: totpConfiguration, isLoading: isTotpConfigurationLoading } =
|
||||||
|
useGetUserTotpConfiguration();
|
||||||
|
const { mutateAsync: deleteTotpConfiguration } = useDeleteUserTotpConfiguration();
|
||||||
|
const { mutateAsync: createTotpRecoveryCodes } = useCreateNewTotpRecoveryCodes();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
|
|
||||||
|
const handleTotpDeletion = async () => {
|
||||||
|
try {
|
||||||
|
await deleteTotpConfiguration();
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted mobile authenticator",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("deleteTotpConfig");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
const error = err as any;
|
||||||
|
const text = error?.response?.data?.message ?? "Failed to delete mobile authenticator";
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text,
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerateMoreRecoveryCodes = async () => {
|
||||||
|
try {
|
||||||
|
await createTotpRecoveryCodes();
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully generated new recovery codes",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
const error = err as any;
|
||||||
|
const text = error?.response?.data?.message ?? "Failed to generate new recovery codes";
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text,
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSelectedMfa = async (mfaMethod: MfaMethod) => {
|
||||||
|
try {
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
await mutateAsync({
|
||||||
|
selectedMfaMethod: mfaMethod
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully updated selected 2FA method",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
createNotification({
|
||||||
|
text: "Something went wrong while updating selected 2FA method.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleMfa = async (state: boolean) => {
|
||||||
|
try {
|
||||||
|
if (!user) return;
|
||||||
|
if (user.authMethods.includes(AuthMethod.LDAP)) {
|
||||||
|
createNotification({
|
||||||
|
text: "Two-factor authentication is not available for LDAP users.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUser = await mutateAsync({
|
||||||
|
isMfaEnabled: state
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: `${
|
||||||
|
newUser.isMfaEnabled
|
||||||
|
? "Successfully turned on two-factor authentication."
|
||||||
|
: "Successfully turned off two-factor authentication."
|
||||||
|
}`,
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
createNotification({
|
||||||
|
text: "Something went wrong while toggling the two-factor authentication.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-6 max-w-6xl rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Two-factor Authentication</p>
|
||||||
|
{user && (
|
||||||
|
<Switch
|
||||||
|
className="data-[state=checked]:bg-primary"
|
||||||
|
id="isTwoFAEnabled"
|
||||||
|
isChecked={user?.isMfaEnabled}
|
||||||
|
onCheckedChange={(state) => {
|
||||||
|
if (serverDetails?.emailConfigured) {
|
||||||
|
toggleMfa(state as boolean);
|
||||||
|
} else {
|
||||||
|
handlePopUpOpen("setUpEmail");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Enable 2-factor authentication
|
||||||
|
</Switch>
|
||||||
|
)}
|
||||||
|
{user?.isMfaEnabled && (
|
||||||
|
<FormControl label="Selected 2FA method" className="mt-3">
|
||||||
|
<Select
|
||||||
|
className="min-w-[20rem] border border-mineshaft-500"
|
||||||
|
onValueChange={updateSelectedMfa}
|
||||||
|
defaultValue={user.selectedMfaMethod ?? MfaMethod.EMAIL}
|
||||||
|
>
|
||||||
|
<SelectItem value={MfaMethod.EMAIL} key="mfa-method-email">
|
||||||
|
Email
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={MfaMethod.TOTP} key="mfa-method-totp">
|
||||||
|
Mobile Authenticator
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
<div className="mt-8 text-lg font-semibold text-mineshaft-100">Mobile Authenticator</div>
|
||||||
|
{isTotpConfigurationLoading ? (
|
||||||
|
<ContentLoader />
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
{totpConfiguration?.isVerified ? (
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<Button colorSchema="secondary" onClick={setShouldShowRecoveryCodes.toggle}>
|
||||||
|
{shouldShowRecoveryCodes ? "Hide recovery codes" : "Show recovery codes"}
|
||||||
|
</Button>
|
||||||
|
<Button colorSchema="secondary" onClick={handleGenerateMoreRecoveryCodes}>
|
||||||
|
Generate more codes
|
||||||
|
</Button>
|
||||||
|
<Button colorSchema="danger" onClick={() => handlePopUpOpen("deleteTotpConfig")}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{shouldShowRecoveryCodes && totpConfiguration.recoveryCodes && (
|
||||||
|
<div className="mt-4 bg-mineshaft-600 p-4">
|
||||||
|
{totpConfiguration.recoveryCodes.map((code) => (
|
||||||
|
<div key={code}>{code}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="text-sm text-gray-400">
|
||||||
|
For added security, you can configure a mobile authenticator and set it as your
|
||||||
|
selected 2FA method.
|
||||||
|
</div>
|
||||||
|
<div className="ml-6 mt-6 flex min-w-full">
|
||||||
|
<TotpRegistration
|
||||||
|
onComplete={async () => {
|
||||||
|
await queryClient.invalidateQueries(userKeys.totpConfiguration);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<EmailServiceSetupModal
|
||||||
|
isOpen={popUp.setUpEmail?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("setUpEmail", isOpen)}
|
||||||
|
/>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteTotpConfig.isOpen}
|
||||||
|
title="Are you sure want to delete the configured authenticator?"
|
||||||
|
subTitle="This action is irreversible. You’ll have to go through the setup process to enable it again."
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteTotpConfig", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={handleTotpDeletion}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { MFASection } from "./MFASection";
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { useRevokeMySessions } from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { SessionsTable } from "./SessionsTable";
|
||||||
|
|
||||||
|
export const SessionsSection = () => {
|
||||||
|
const { mutateAsync } = useRevokeMySessions();
|
||||||
|
|
||||||
|
const onRevokeAllSessionsClick = async () => {
|
||||||
|
try {
|
||||||
|
await mutateAsync();
|
||||||
|
window.location.href = "/login";
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-8 flex justify-between">
|
||||||
|
<h2 className="flex-1 text-xl font-semibold text-mineshaft-100">Sessions</h2>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faBan} />}
|
||||||
|
onClick={onRevokeAllSessionsClick}
|
||||||
|
>
|
||||||
|
Revoke all
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="mb-8 text-gray-400">
|
||||||
|
Logging into Infisical via browser or CLI creates a session. Revoking all sessions logs your
|
||||||
|
account out all active sessions across all browsers and CLIs.
|
||||||
|
</p>
|
||||||
|
<SessionsTable />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
import { faServer } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
import {
|
||||||
|
EmptyState,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useGetMySessions } from "@app/hooks/api";
|
||||||
|
|
||||||
|
export const SessionsTable = () => {
|
||||||
|
const { data, isLoading } = useGetMySessions();
|
||||||
|
|
||||||
|
const formatDate = (dateToFormat: string) => {
|
||||||
|
const date = new Date(dateToFormat);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth() + 1;
|
||||||
|
const day = date.getDate();
|
||||||
|
|
||||||
|
const formattedDate = `${day}/${month}/${year}`;
|
||||||
|
|
||||||
|
return formattedDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer className="mt-4">
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Created</Th>
|
||||||
|
<Th>Last active</Th>
|
||||||
|
<Th>IP address</Th>
|
||||||
|
<Th>Device</Th>
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isLoading && <TableSkeleton columns={4} innerKey="sesssions" />}
|
||||||
|
{!isLoading &&
|
||||||
|
data &&
|
||||||
|
data.length > 0 &&
|
||||||
|
data.map(({ id, createdAt, lastUsed, ip, userAgent }) => {
|
||||||
|
return (
|
||||||
|
<Tr className="h-10" key={`session-${id}`}>
|
||||||
|
<Td>{formatDate(createdAt)}</Td>
|
||||||
|
<Td>{formatDate(lastUsed)}</Td>
|
||||||
|
<Td>{ip}</Td>
|
||||||
|
<Td>{userAgent}</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{!isLoading && data && data?.length === 0 && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={4}>
|
||||||
|
<EmptyState title="No sessions on file" icon={faServer} />
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { SessionsSection } from "./SessionsSection";
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, FormControl, Input } from "@app/components/v2";
|
||||||
|
import { useUser } from "@app/context";
|
||||||
|
import { useRenameUser } from "@app/hooks/api/users/queries";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
name: z.string().describe("User Name")
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormData = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const UserNameSection = (): JSX.Element => {
|
||||||
|
const { user } = useUser();
|
||||||
|
|
||||||
|
const { handleSubmit, control, reset } = useForm<FormData>({ resolver: zodResolver(formSchema) });
|
||||||
|
const { mutateAsync, isLoading } = useRenameUser();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
reset({ name: `${user?.firstName}${user?.lastName && " "}${user?.lastName}` });
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const onFormSubmit = async ({ name }: FormData) => {
|
||||||
|
try {
|
||||||
|
if (!user?.id) return;
|
||||||
|
if (name === "") return;
|
||||||
|
|
||||||
|
await mutateAsync({ newName: name });
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully renamed user",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to rename user",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
|
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
|
>
|
||||||
|
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Name</p>
|
||||||
|
<div className="mb-2 max-w-md">
|
||||||
|
<Controller
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Input placeholder={`${user?.firstName} ${user?.lastName}`} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button isLoading={isLoading} colorSchema="primary" variant="outline_bg" type="submit">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { UserNameSection } from "./UserNameSection";
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { PersonalTabGroup } from "./-components/PersonalTabGroup";
|
||||||
|
|
||||||
|
const PersonalSettingsPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full bg-bunker-800 text-white">
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: t("settings.personal.title") })}</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
</Helmet>
|
||||||
|
<div className="flex w-full justify-center bg-bunker-800 px-6 text-white">
|
||||||
|
<div className="w-full max-w-6xl">
|
||||||
|
<div className="mb-6 mt-6">
|
||||||
|
<p className="text-3xl font-semibold text-gray-200">{t("settings.personal.title")}</p>
|
||||||
|
</div>
|
||||||
|
<PersonalTabGroup />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_authenticate/_personal_settings_layout/personal-settings/")(
|
||||||
|
{
|
||||||
|
component: PersonalSettingsPage
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticate/_restrict_login_signup")({
|
|
||||||
beforeLoad: ({ context }) => {
|
|
||||||
if (context.isAuthenticated) {
|
|
||||||
redirect({
|
|
||||||
throw: true,
|
|
||||||
to: "/organization/$organizationId/secret-manager",
|
|
||||||
params: {
|
|
||||||
organizationId: context.organizationId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { IconButton, SecretInput } from "@app/components/v2";
|
||||||
|
import { SessionStorageKeys } from "@app/const";
|
||||||
|
import { useTimedReset } from "@app/hooks";
|
||||||
|
|
||||||
|
const getTerminalCliToken = () => {
|
||||||
|
const cliTerminalTokenInfo = sessionStorage.getItem(SessionStorageKeys.CLI_TERMINAL_TOKEN);
|
||||||
|
if (!cliTerminalTokenInfo) return;
|
||||||
|
|
||||||
|
const { expiry, data } = JSON.parse(cliTerminalTokenInfo);
|
||||||
|
if (new Date() > new Date(expiry)) {
|
||||||
|
sessionStorage.removeItem(SessionStorageKeys.CLI_TERMINAL_TOKEN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
return data as string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CliRedirectPage = () => {
|
||||||
|
const [isUrlCopied, , setIsUrlCopied] = useTimedReset<boolean>({
|
||||||
|
initialState: false
|
||||||
|
});
|
||||||
|
const cliToken = getTerminalCliToken();
|
||||||
|
|
||||||
|
const copyUrlToClipboard = () => {
|
||||||
|
if (cliToken) {
|
||||||
|
navigator.clipboard.writeText(cliToken);
|
||||||
|
setIsUrlCopied(true);
|
||||||
|
sessionStorage.removeItem(SessionStorageKeys.CLI_TERMINAL_TOKEN);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col justify-between bg-bunker-800 md:h-screen">
|
||||||
|
<Helmet>
|
||||||
|
<title>Infisical CLI | Login Successful!</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
</Helmet>
|
||||||
|
<div className="flex h-screen w-screen flex-col items-center justify-center space-y-4 text-gray-200">
|
||||||
|
<div className="mb-4 flex justify-center">
|
||||||
|
<img src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical Logo" />
|
||||||
|
</div>
|
||||||
|
{cliToken ? (
|
||||||
|
<>
|
||||||
|
<div className="pb-4">
|
||||||
|
<p className="bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-3xl font-medium text-transparent">
|
||||||
|
Unable to reach CLI
|
||||||
|
</p>
|
||||||
|
<p className="text-light mb-1 text-center text-lg text-mineshaft-400">
|
||||||
|
Your login was successful but, Infisical couldn't automatically push your login
|
||||||
|
token to the CLI.
|
||||||
|
</p>
|
||||||
|
<p className="text-light mb-1 text-center text-lg text-mineshaft-400">
|
||||||
|
Please copy the token below and manually provide it to your CLI.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="dark relative flex max-h-36 max-w-xl flex-col items-center space-y-4 overflow-y-auto rounded-md border border-mineshaft-700 bg-mineshaft-900 p-3">
|
||||||
|
<SecretInput value={cliToken as string} />
|
||||||
|
<div className="mx-1 flex">
|
||||||
|
<IconButton
|
||||||
|
variant="outline_bg"
|
||||||
|
colorSchema="primary"
|
||||||
|
ariaLabel="copy to clipboard"
|
||||||
|
onClick={copyUrlToClipboard}
|
||||||
|
className="flex items-center rounded py-2"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon className="pr-2" icon={isUrlCopied ? faCheck : faCopy} /> Copy to
|
||||||
|
clipboard
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p className="bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-3xl font-medium text-transparent">
|
||||||
|
Head back to your terminal
|
||||||
|
</p>
|
||||||
|
<p className="text-light mb-1 text-lg text-mineshaft-400">
|
||||||
|
You've successfully logged in to the Infisical CLI
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_authenticate/cli-redirect")({
|
||||||
|
component: CliRedirectPage
|
||||||
|
});
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { authKeys, fetchAuthToken } from "@app/hooks/api/auth/queries";
|
||||||
|
import { setAuthToken } from "@app/hooks/api/reactQuery";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_restrict_login_signup")({
|
||||||
|
beforeLoad: async ({ context }) => {
|
||||||
|
try {
|
||||||
|
const data = await context.queryClient.fetchQuery({
|
||||||
|
queryKey: authKeys.getAuthToken,
|
||||||
|
queryFn: fetchAuthToken
|
||||||
|
});
|
||||||
|
setAuthToken(data.token);
|
||||||
|
if (!data.organizationId) {
|
||||||
|
throw redirect({ to: "/login/select-organization" });
|
||||||
|
}
|
||||||
|
throw redirect({
|
||||||
|
to: "/organization/$organizationId/secret-manager",
|
||||||
|
params: {
|
||||||
|
organizationId: data.organizationId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
const EmailNotVerifiedPage = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col justify-between bg-bunker-800 md:h-screen">
|
||||||
|
<Helmet>
|
||||||
|
<title>Request a New Invite</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
</Helmet>
|
||||||
|
<div className="flex h-screen w-screen flex-col items-center justify-center text-gray-200">
|
||||||
|
<p className="text-6xl">Oops.</p>
|
||||||
|
<p className="mb-1 mt-2 text-xl">Your email was not verified. </p>
|
||||||
|
<p className="text-xl">Please try again.</p>
|
||||||
|
<p className="text-md mt-8 max-w-sm text-center text-gray-600">
|
||||||
|
Note: If it still doesn't work, please reach out to us at support@infisical.com
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_restrict_login_signup/email-not-verified")({
|
||||||
|
component: EmailNotVerifiedPage
|
||||||
|
});
|
||||||
+1
-1
@@ -76,6 +76,6 @@ const LoginPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticate/_restrict_login_signup/login/")({
|
export const Route = createFileRoute("/_restrict_login_signup/login/")({
|
||||||
component: LoginPage
|
component: LoginPage
|
||||||
});
|
});
|
||||||
+1
-1
@@ -153,6 +153,6 @@ const LoginLDAPPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticate/_restrict_login_signup/login/ldap/")({
|
export const Route = createFileRoute("/_restrict_login_signup/login/ldap/")({
|
||||||
component: LoginLDAPPage
|
component: LoginLDAPPage
|
||||||
});
|
});
|
||||||
+1
-1
@@ -10,6 +10,6 @@ const LoginProviderError = () => {
|
|||||||
return <div />;
|
return <div />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticate/_restrict_login_signup/login/provider/error")({
|
export const Route = createFileRoute("/_restrict_login_signup/login/provider/error")({
|
||||||
component: LoginProviderError
|
component: LoginProviderError
|
||||||
});
|
});
|
||||||
+1
-3
@@ -14,8 +14,6 @@ const LoginProviderSuccess = () => {
|
|||||||
return <div />;
|
return <div />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute("/_restrict_login_signup/login/provider/success")({
|
||||||
"/_authenticate/_restrict_login_signup/login/provider/success"
|
|
||||||
)({
|
|
||||||
component: LoginProviderSuccess
|
component: LoginProviderSuccess
|
||||||
});
|
});
|
||||||
+1
-3
@@ -277,8 +277,6 @@ const SelectOrganizationPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute("/_restrict_login_signup/login/select-organization/")({
|
||||||
"/_authenticate/_restrict_login_signup/login/select-organization/"
|
|
||||||
)({
|
|
||||||
component: SelectOrganizationPage
|
component: SelectOrganizationPage
|
||||||
});
|
});
|
||||||
+1
-1
@@ -58,6 +58,6 @@ const LoginSSOPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticate/_restrict_login_signup/login/sso/")({
|
export const Route = createFileRoute("/_restrict_login_signup/login/sso/")({
|
||||||
component: LoginSSOPage
|
component: LoginSSOPage
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,394 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
import { FormEvent, useState } from "react";
|
||||||
|
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { createFileRoute, useNavigate, useSearch } from "@tanstack/react-router";
|
||||||
|
import jsrp from "jsrp";
|
||||||
|
|
||||||
|
import InputField from "@app/components/basic/InputField";
|
||||||
|
import passwordCheck from "@app/components/utilities/checks/password/PasswordCheck";
|
||||||
|
import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
|
||||||
|
import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { useResetPassword, useVerifyPasswordResetCode } from "@app/hooks/api";
|
||||||
|
import { getBackupEncryptedPrivateKey } from "@app/hooks/api/auth/queries";
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const client = new jsrp.client();
|
||||||
|
|
||||||
|
const PasswordResetPage = () => {
|
||||||
|
const [verificationToken, setVerificationToken] = useState("");
|
||||||
|
const [step, setStep] = useState(1);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [backupKey, setBackupKey] = useState("");
|
||||||
|
const [privateKey, setPrivateKey] = useState("");
|
||||||
|
const [newPassword, setNewPassword] = useState("");
|
||||||
|
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||||
|
const [passwordErrorTooShort, setPasswordErrorTooShort] = useState(false);
|
||||||
|
const [passwordErrorTooLong, setPasswordErrorTooLong] = useState(false);
|
||||||
|
const [passwordErrorNoLetterChar, setPasswordErrorNoLetterChar] = useState(false);
|
||||||
|
const [passwordErrorNoNumOrSpecialChar, setPasswordErrorNoNumOrSpecialChar] = useState(false);
|
||||||
|
const [passwordErrorRepeatedChar, setPasswordErrorRepeatedChar] = useState(false);
|
||||||
|
const [passwordErrorEscapeChar, setPasswordErrorEscapeChar] = useState(false);
|
||||||
|
const [passwordErrorLowEntropy, setPasswordErrorLowEntropy] = useState(false);
|
||||||
|
const [passwordErrorBreached, setPasswordErrorBreached] = useState(false);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const search = useSearch({ from: "/_restrict_login_signup/password-reset" });
|
||||||
|
|
||||||
|
const {
|
||||||
|
mutateAsync: verifyPasswordResetCodeMutateAsync,
|
||||||
|
isLoading: isVerifyPasswordResetLoading
|
||||||
|
} = useVerifyPasswordResetCode();
|
||||||
|
const { mutateAsync: resetPasswordMutateAsync } = useResetPassword();
|
||||||
|
|
||||||
|
const parsedUrl = search;
|
||||||
|
const token = parsedUrl.token as string;
|
||||||
|
const email = (parsedUrl.to as string)?.replace(" ", "+").trim();
|
||||||
|
|
||||||
|
// Decrypt the private key with a backup key
|
||||||
|
const getEncryptedKeyHandler = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const result = await getBackupEncryptedPrivateKey({ verificationToken });
|
||||||
|
|
||||||
|
setPrivateKey(
|
||||||
|
Aes256Gcm.decrypt({
|
||||||
|
ciphertext: result.encryptedPrivateKey,
|
||||||
|
iv: result.iv,
|
||||||
|
tag: result.tag,
|
||||||
|
secret: backupKey
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setStep(3);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setBackupKeyError(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If everything is correct, reset the password
|
||||||
|
const resetPasswordHandler = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const errorCheck = await passwordCheck({
|
||||||
|
password: newPassword,
|
||||||
|
setPasswordErrorTooShort,
|
||||||
|
setPasswordErrorTooLong,
|
||||||
|
setPasswordErrorNoLetterChar,
|
||||||
|
setPasswordErrorNoNumOrSpecialChar,
|
||||||
|
setPasswordErrorRepeatedChar,
|
||||||
|
setPasswordErrorEscapeChar,
|
||||||
|
setPasswordErrorLowEntropy,
|
||||||
|
setPasswordErrorBreached
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!errorCheck) {
|
||||||
|
client.init(
|
||||||
|
{
|
||||||
|
username: email,
|
||||||
|
password: newPassword
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
client.createVerifier(async (_err: any, result: { salt: string; verifier: string }) => {
|
||||||
|
const derivedKey = await deriveArgonKey({
|
||||||
|
password: newPassword,
|
||||||
|
salt: result.salt,
|
||||||
|
mem: 65536,
|
||||||
|
time: 3,
|
||||||
|
parallelism: 1,
|
||||||
|
hashLen: 32
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!derivedKey) throw new Error("Failed to derive key from password");
|
||||||
|
|
||||||
|
const key = crypto.randomBytes(32);
|
||||||
|
|
||||||
|
// create encrypted private key by encrypting the private
|
||||||
|
// key with the symmetric key [key]
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedPrivateKey,
|
||||||
|
iv: encryptedPrivateKeyIV,
|
||||||
|
tag: encryptedPrivateKeyTag
|
||||||
|
} = Aes256Gcm.encrypt({
|
||||||
|
text: privateKey,
|
||||||
|
secret: key
|
||||||
|
});
|
||||||
|
|
||||||
|
// create the protected key by encrypting the symmetric key
|
||||||
|
// [key] with the derived key
|
||||||
|
const {
|
||||||
|
ciphertext: protectedKey,
|
||||||
|
iv: protectedKeyIV,
|
||||||
|
tag: protectedKeyTag
|
||||||
|
} = Aes256Gcm.encrypt({
|
||||||
|
text: key.toString("hex"),
|
||||||
|
secret: Buffer.from(derivedKey.hash)
|
||||||
|
});
|
||||||
|
|
||||||
|
await resetPasswordMutateAsync({
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
encryptedPrivateKeyIV,
|
||||||
|
encryptedPrivateKeyTag,
|
||||||
|
salt: result.salt,
|
||||||
|
verifier: result.verifier,
|
||||||
|
verificationToken
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate({ to: "/login" });
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Click a button to confirm email
|
||||||
|
const stepConfirmEmail = (
|
||||||
|
<div className="mx-1 my-32 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 py-6 drop-shadow-xl md:max-w-lg md:px-6">
|
||||||
|
<p className="mb-8 flex justify-center bg-gradient-to-br from-sky-400 to-primary bg-clip-text text-center text-4xl font-semibold text-transparent">
|
||||||
|
Confirm your email
|
||||||
|
</p>
|
||||||
|
<img src="/images/envelope.svg" height={262} width={410} alt="verify email" />
|
||||||
|
<div className="mx-auto mb-2 mt-4 flex max-h-24 max-w-md flex-col items-center justify-center px-4 text-lg md:p-2">
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const response = await verifyPasswordResetCodeMutateAsync({
|
||||||
|
email,
|
||||||
|
code: token
|
||||||
|
});
|
||||||
|
|
||||||
|
setVerificationToken(response.token);
|
||||||
|
setStep(2);
|
||||||
|
} catch (err) {
|
||||||
|
console.log("ERROR", err);
|
||||||
|
navigate({ to: "/email-not-verified" });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
isLoading={isVerifyPasswordResetLoading}
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
Confirm Email
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Input backup key
|
||||||
|
const stepInputBackupKey = (
|
||||||
|
<form
|
||||||
|
onSubmit={getEncryptedKeyHandler}
|
||||||
|
className="mx-1 my-32 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pb-3 pt-6 drop-shadow-xl md:max-w-lg md:px-6"
|
||||||
|
>
|
||||||
|
<p className="mx-auto mb-4 flex w-max justify-center text-2xl font-semibold text-bunker-100 md:text-3xl">
|
||||||
|
Enter your backup key
|
||||||
|
</p>
|
||||||
|
<div className="mt-4 flex flex-row items-center justify-center md:mx-2 md:pb-4">
|
||||||
|
<p className="flex w-max max-w-md justify-center text-sm text-gray-400">
|
||||||
|
You can find it in your emergency kit. You had to download the emergency kit during
|
||||||
|
signup.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex max-h-24 w-full items-center justify-center rounded-lg md:mt-0 md:max-h-28 md:p-2">
|
||||||
|
<InputField
|
||||||
|
label="Backup Key"
|
||||||
|
onChangeHandler={setBackupKey}
|
||||||
|
type="password"
|
||||||
|
value={backupKey}
|
||||||
|
placeholder=""
|
||||||
|
isRequired
|
||||||
|
error={backupKeyError}
|
||||||
|
errorText="Something is wrong with the backup key"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
|
||||||
|
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
|
||||||
|
<Button type="submit" size="lg">
|
||||||
|
Submit Backup Key
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Enter new password
|
||||||
|
const stepEnterNewPassword = (
|
||||||
|
<form
|
||||||
|
onSubmit={resetPasswordHandler}
|
||||||
|
className="mx-1 my-32 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pb-3 pt-6 drop-shadow-xl md:max-w-lg md:px-6"
|
||||||
|
>
|
||||||
|
<p className="mx-auto flex w-max justify-center text-2xl font-semibold text-bunker-100 md:text-3xl">
|
||||||
|
Enter new password
|
||||||
|
</p>
|
||||||
|
<div className="mt-1 flex flex-row items-center justify-center md:mx-2 md:pb-4">
|
||||||
|
<p className="flex w-max max-w-md justify-center text-sm text-gray-400">
|
||||||
|
Make sure you save it somewhere safe.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex max-h-24 w-full items-center justify-center rounded-lg md:mt-0 md:max-h-28 md:p-2">
|
||||||
|
<InputField
|
||||||
|
label="New Password"
|
||||||
|
onChangeHandler={(password) => {
|
||||||
|
setNewPassword(password);
|
||||||
|
passwordCheck({
|
||||||
|
password,
|
||||||
|
setPasswordErrorTooShort,
|
||||||
|
setPasswordErrorTooLong,
|
||||||
|
setPasswordErrorNoLetterChar,
|
||||||
|
setPasswordErrorNoNumOrSpecialChar,
|
||||||
|
setPasswordErrorRepeatedChar,
|
||||||
|
setPasswordErrorEscapeChar,
|
||||||
|
setPasswordErrorLowEntropy,
|
||||||
|
setPasswordErrorBreached
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="password"
|
||||||
|
value={newPassword}
|
||||||
|
isRequired
|
||||||
|
error={
|
||||||
|
passwordErrorTooShort &&
|
||||||
|
passwordErrorTooLong &&
|
||||||
|
passwordErrorNoLetterChar &&
|
||||||
|
passwordErrorNoNumOrSpecialChar &&
|
||||||
|
passwordErrorRepeatedChar &&
|
||||||
|
passwordErrorEscapeChar &&
|
||||||
|
passwordErrorLowEntropy &&
|
||||||
|
passwordErrorBreached
|
||||||
|
}
|
||||||
|
autoComplete="new-password"
|
||||||
|
id="new-password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{passwordErrorTooShort ||
|
||||||
|
passwordErrorTooLong ||
|
||||||
|
passwordErrorNoLetterChar ||
|
||||||
|
passwordErrorNoNumOrSpecialChar ||
|
||||||
|
passwordErrorRepeatedChar ||
|
||||||
|
passwordErrorEscapeChar ||
|
||||||
|
passwordErrorLowEntropy ||
|
||||||
|
passwordErrorBreached ? (
|
||||||
|
<div className="mx-2 mb-2 mt-3 flex w-full max-w-md flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||||
|
<div className="mb-1 text-sm text-gray-400">Password should contain:</div>
|
||||||
|
<div className="ml-1 flex flex-row items-center justify-start">
|
||||||
|
{passwordErrorTooShort ? (
|
||||||
|
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||||
|
) : (
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||||
|
)}
|
||||||
|
<div className={`${passwordErrorTooShort ? "text-gray-400" : "text-gray-600"} text-sm`}>
|
||||||
|
at least 14 characters
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-1 flex flex-row items-center justify-start">
|
||||||
|
{passwordErrorTooLong ? (
|
||||||
|
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||||
|
) : (
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||||
|
)}
|
||||||
|
<div className={`${passwordErrorTooLong ? "text-gray-400" : "text-gray-600"} text-sm`}>
|
||||||
|
at most 100 characters
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-1 flex flex-row items-center justify-start">
|
||||||
|
{passwordErrorNoLetterChar ? (
|
||||||
|
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||||
|
) : (
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`${passwordErrorNoLetterChar ? "text-gray-400" : "text-gray-600"} text-sm`}
|
||||||
|
>
|
||||||
|
at least 1 letter character
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-1 flex flex-row items-center justify-start">
|
||||||
|
{passwordErrorNoNumOrSpecialChar ? (
|
||||||
|
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||||
|
) : (
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
passwordErrorNoNumOrSpecialChar ? "text-gray-400" : "text-gray-600"
|
||||||
|
} text-sm`}
|
||||||
|
>
|
||||||
|
at least 1 number or special character
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-1 flex flex-row items-center justify-start">
|
||||||
|
{passwordErrorRepeatedChar ? (
|
||||||
|
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||||
|
) : (
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`${passwordErrorRepeatedChar ? "text-gray-400" : "text-gray-600"} text-sm`}
|
||||||
|
>
|
||||||
|
at most 3 repeated, consecutive characters
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-1 flex flex-row items-center justify-start">
|
||||||
|
{passwordErrorEscapeChar ? (
|
||||||
|
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||||
|
) : (
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`${passwordErrorEscapeChar ? "text-gray-400" : "text-gray-600"} text-sm`}
|
||||||
|
>
|
||||||
|
No escape characters allowed.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-1 flex flex-row items-center justify-start">
|
||||||
|
{passwordErrorLowEntropy ? (
|
||||||
|
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||||
|
) : (
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`${passwordErrorLowEntropy ? "text-gray-400" : "text-gray-600"} text-sm`}
|
||||||
|
>
|
||||||
|
Password contains personal info.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-1 flex flex-row items-center justify-start">
|
||||||
|
{passwordErrorBreached ? (
|
||||||
|
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||||
|
) : (
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||||
|
)}
|
||||||
|
<div className={`${passwordErrorBreached ? "text-gray-400" : "text-gray-600"} text-sm`}>
|
||||||
|
Password was found in a data breach.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="py-2" />
|
||||||
|
)}
|
||||||
|
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
|
||||||
|
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
|
||||||
|
<Button type="submit" onClick={() => setLoading(true)} size="lg" isLoading={loading}>
|
||||||
|
Submit New Password
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen w-full flex-col items-center justify-center bg-bunker-800">
|
||||||
|
{step === 1 && stepConfirmEmail}
|
||||||
|
{step === 2 && stepInputBackupKey}
|
||||||
|
{step === 3 && stepEnterNewPassword}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_restrict_login_signup/password-reset")({
|
||||||
|
component: PasswordResetPage
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the page that shows up when a user's invitation
|
||||||
|
* to join a project/organization on Infisical has expired
|
||||||
|
*/
|
||||||
|
const RequestNewInvitePage = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col justify-between bg-bunker-700 md:h-screen">
|
||||||
|
<Helmet>
|
||||||
|
<title>Request a New Invite</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
</Helmet>
|
||||||
|
<div className="mt-8 flex h-screen w-screen flex-col items-center justify-center text-bunker-200">
|
||||||
|
<p className="text-4xl text-primary-100">Oops, your invite has expired.</p>
|
||||||
|
<p className="my-4 text-lg">Ask your admin for a new one.</p>
|
||||||
|
<p className="max-w-xs px-7 text-center text-sm leading-tight text-bunker-400">
|
||||||
|
<span className="rounded-md bg-primary-500/40 px-1 text-black">Note:</span> If it still
|
||||||
|
doesn't work, please reach out to us at support@infisical.com
|
||||||
|
</p>
|
||||||
|
<div className="">
|
||||||
|
<img
|
||||||
|
src="/images/invitation-expired.svg"
|
||||||
|
height={500}
|
||||||
|
width={800}
|
||||||
|
alt="invitation expired illustration"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_restrict_login_signup/requestnewinvite")({
|
||||||
|
component: RequestNewInvitePage
|
||||||
|
});
|
||||||
+1
-1
@@ -173,7 +173,7 @@ const SignUpPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticate/_restrict_login_signup/signup/")({
|
export const Route = createFileRoute("/_restrict_login_signup/signup/")({
|
||||||
component: SignUpPage,
|
component: SignUpPage,
|
||||||
loader: ({ context }) => {
|
loader: ({ context }) => {
|
||||||
console.log(context);
|
console.log(context);
|
||||||
+1
-1
@@ -85,6 +85,6 @@ const SignupSSOPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticate/_restrict_login_signup/signup/sso/")({
|
export const Route = createFileRoute("/_restrict_login_signup/signup/sso/")({
|
||||||
component: SignupSSOPage
|
component: SignupSSOPage
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,432 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { faWarning, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { createFileRoute, Link, useNavigate, useSearch } from "@tanstack/react-router";
|
||||||
|
import jsrp from "jsrp";
|
||||||
|
import nacl from "tweetnacl";
|
||||||
|
import { encodeBase64 } from "tweetnacl-util";
|
||||||
|
|
||||||
|
import { Mfa } from "@app/components/auth/Mfa";
|
||||||
|
import InputField from "@app/components/basic/InputField";
|
||||||
|
import checkPassword from "@app/components/utilities/checks/password/checkPassword";
|
||||||
|
import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
|
||||||
|
import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
|
||||||
|
import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey";
|
||||||
|
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
|
||||||
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
import {
|
||||||
|
completeAccountSignupInvite,
|
||||||
|
useSelectOrganization,
|
||||||
|
verifySignupInvite
|
||||||
|
} from "@app/hooks/api/auth/queries";
|
||||||
|
import { MfaMethod } from "@app/hooks/api/auth/types";
|
||||||
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
|
import { navigateUserToOrg } from "./login/-components/Login.utils";
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const client = new jsrp.client();
|
||||||
|
|
||||||
|
type Errors = {
|
||||||
|
tooShort?: string;
|
||||||
|
tooLong?: string;
|
||||||
|
noLetterChar?: string;
|
||||||
|
noNumOrSpecialChar?: string;
|
||||||
|
repeatedChar?: string;
|
||||||
|
escapeChar?: string;
|
||||||
|
lowEntropy?: string;
|
||||||
|
breached?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SignupInvitePage = () => {
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [firstName, setFirstName] = useState("");
|
||||||
|
const [lastName, setLastName] = useState("");
|
||||||
|
const [firstNameError, setFirstNameError] = useState(false);
|
||||||
|
const [lastNameError, setLastNameError] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [step, setStep] = useState(1);
|
||||||
|
const [, setBackupKeyError] = useState(false);
|
||||||
|
const [, setBackupKeyIssued] = useState(false);
|
||||||
|
const [errors, setErrors] = useState<Errors>({});
|
||||||
|
|
||||||
|
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
|
||||||
|
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
|
||||||
|
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const search = useSearch({ from: "/_restrict_login_signup/signupinvite" });
|
||||||
|
const parsedUrl = search;
|
||||||
|
const token = parsedUrl.token as string;
|
||||||
|
const organizationId = parsedUrl.organization_id as string;
|
||||||
|
const email = (parsedUrl.to as string)?.replace(" ", "+").trim();
|
||||||
|
|
||||||
|
const queryParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
const metadata = queryParams.get("metadata") || undefined;
|
||||||
|
|
||||||
|
const { mutateAsync: selectOrganization } = useSelectOrganization();
|
||||||
|
|
||||||
|
// Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria.
|
||||||
|
const signupErrorCheck = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
let errorCheck = await checkPassword({
|
||||||
|
password,
|
||||||
|
setErrors
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!firstName) {
|
||||||
|
setFirstNameError(true);
|
||||||
|
errorCheck = true;
|
||||||
|
} else {
|
||||||
|
setFirstNameError(false);
|
||||||
|
}
|
||||||
|
if (!lastName) {
|
||||||
|
setLastNameError(true);
|
||||||
|
errorCheck = true;
|
||||||
|
} else {
|
||||||
|
setLastNameError(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!errorCheck) {
|
||||||
|
// Generate a random pair of a public and a private key
|
||||||
|
const pair = nacl.box.keyPair();
|
||||||
|
const secretKeyUint8Array = pair.secretKey;
|
||||||
|
const publicKeyUint8Array = pair.publicKey;
|
||||||
|
const privateKey = encodeBase64(secretKeyUint8Array);
|
||||||
|
const publicKey = encodeBase64(publicKeyUint8Array);
|
||||||
|
|
||||||
|
localStorage.setItem("PRIVATE_KEY", privateKey);
|
||||||
|
|
||||||
|
client.init(
|
||||||
|
{
|
||||||
|
username: email,
|
||||||
|
password
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
client.createVerifier(async (_err, result) => {
|
||||||
|
try {
|
||||||
|
const derivedKey = await deriveArgonKey({
|
||||||
|
password,
|
||||||
|
salt: result.salt,
|
||||||
|
mem: 65536,
|
||||||
|
time: 3,
|
||||||
|
parallelism: 1,
|
||||||
|
hashLen: 32
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!derivedKey) throw new Error("Failed to derive key from password");
|
||||||
|
|
||||||
|
const key = crypto.randomBytes(32);
|
||||||
|
|
||||||
|
// create encrypted private key by encrypting the private
|
||||||
|
// key with the symmetric key [key]
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedPrivateKey,
|
||||||
|
iv: encryptedPrivateKeyIV,
|
||||||
|
tag: encryptedPrivateKeyTag
|
||||||
|
} = Aes256Gcm.encrypt({
|
||||||
|
text: privateKey,
|
||||||
|
secret: key
|
||||||
|
});
|
||||||
|
|
||||||
|
// create the protected key by encrypting the symmetric key
|
||||||
|
// [key] with the derived key
|
||||||
|
const {
|
||||||
|
ciphertext: protectedKey,
|
||||||
|
iv: protectedKeyIV,
|
||||||
|
tag: protectedKeyTag
|
||||||
|
} = Aes256Gcm.encrypt({
|
||||||
|
text: key.toString("hex"),
|
||||||
|
secret: Buffer.from(derivedKey.hash)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { token: jwtToken } = await completeAccountSignupInvite({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
encryptedPrivateKeyIV,
|
||||||
|
encryptedPrivateKeyTag,
|
||||||
|
salt: result.salt,
|
||||||
|
verifier: result.verifier,
|
||||||
|
tokenMetadata: metadata
|
||||||
|
});
|
||||||
|
|
||||||
|
// unset temporary signup JWT token and set JWT token
|
||||||
|
SecurityClient.setSignupToken("");
|
||||||
|
SecurityClient.setToken(jwtToken);
|
||||||
|
|
||||||
|
saveTokenToLocalStorage({
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv: encryptedPrivateKeyIV,
|
||||||
|
tag: encryptedPrivateKeyTag,
|
||||||
|
privateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const userOrgs = await fetchOrganizations();
|
||||||
|
|
||||||
|
const orgId = userOrgs[0].id;
|
||||||
|
|
||||||
|
if (!orgId) throw new Error("You are not part of any organization");
|
||||||
|
|
||||||
|
const completeSignupFlow = async () => {
|
||||||
|
const {
|
||||||
|
token: mfaToken,
|
||||||
|
isMfaEnabled,
|
||||||
|
mfaMethod
|
||||||
|
} = await selectOrganization({
|
||||||
|
organizationId: orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isMfaEnabled) {
|
||||||
|
SecurityClient.setMfaToken(mfaToken);
|
||||||
|
if (mfaMethod) {
|
||||||
|
setRequiredMfaMethod(mfaMethod);
|
||||||
|
}
|
||||||
|
toggleShowMfa.on();
|
||||||
|
setMfaSuccessCallback(() => completeSignupFlow);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
|
||||||
|
setStep(3);
|
||||||
|
};
|
||||||
|
|
||||||
|
await completeSignupFlow();
|
||||||
|
} catch (error) {
|
||||||
|
setIsLoading(false);
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||||
|
const stepConfirmEmail = (
|
||||||
|
<div className="h-7/12 mx-1 mb-36 flex w-full max-w-xs flex-col items-center rounded-xl border border-mineshaft-600 bg-mineshaft-800 px-4 py-8 drop-shadow-xl md:mb-16 md:max-w-lg md:px-6">
|
||||||
|
<p className="mb-6 flex justify-center text-center text-4xl font-semibold text-primary-100">
|
||||||
|
Confirm your email
|
||||||
|
</p>
|
||||||
|
<img src="/images/dragon-signupinvite.svg" height={262} width={410} alt="verify email" />
|
||||||
|
<div className="mx-auto mb-2 mt-10 flex max-h-24 max-w-md flex-col items-center justify-center px-4 text-lg md:p-2">
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const response = await verifySignupInvite({
|
||||||
|
email,
|
||||||
|
code: token,
|
||||||
|
organizationId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
// user will have temp token if doesn't have an account
|
||||||
|
// then continue with account setup workflow
|
||||||
|
if (response?.token) {
|
||||||
|
SecurityClient.setSignupToken(response.token);
|
||||||
|
setStep(2);
|
||||||
|
} else {
|
||||||
|
const redirectExistingUser = async () => {
|
||||||
|
const { token: mfaToken, isMfaEnabled } = await selectOrganization({
|
||||||
|
organizationId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isMfaEnabled) {
|
||||||
|
SecurityClient.setMfaToken(mfaToken);
|
||||||
|
toggleShowMfa.on();
|
||||||
|
setMfaSuccessCallback(() => redirectExistingUser);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// user will be redirected to dashboard
|
||||||
|
// if not logged in gets kicked out to login
|
||||||
|
await navigateUserToOrg(navigate, organizationId);
|
||||||
|
};
|
||||||
|
|
||||||
|
await redirectExistingUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
navigate({ to: "/requestnewinvite" });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
Confirm Email
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Because this is the invite signup - we directly go to the last step of signup (email is already verified)
|
||||||
|
const main = (
|
||||||
|
<div className="h-7/12 mx-auto mb-32 w-max rounded-xl border border-mineshaft-600 bg-mineshaft-800 px-8 py-10 drop-shadow-xl md:mb-16">
|
||||||
|
<p className="mx-8 mb-6 flex justify-center bg-gradient-to-tr from-mineshaft-300 to-white bg-clip-text text-4xl font-bold text-transparent md:mx-16">
|
||||||
|
Almost there!
|
||||||
|
</p>
|
||||||
|
<div className="relative z-0 flex max-h-24 w-full items-center justify-end rounded-lg md:p-2">
|
||||||
|
<InputField
|
||||||
|
label="First Name"
|
||||||
|
onChangeHandler={setFirstName}
|
||||||
|
type="name"
|
||||||
|
value={firstName}
|
||||||
|
isRequired
|
||||||
|
errorText="Please input your first name."
|
||||||
|
error={firstNameError}
|
||||||
|
autoComplete="given-name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex max-h-24 w-full items-center justify-center rounded-lg md:p-2">
|
||||||
|
<InputField
|
||||||
|
label="Last Name"
|
||||||
|
onChangeHandler={setLastName}
|
||||||
|
type="name"
|
||||||
|
value={lastName}
|
||||||
|
isRequired
|
||||||
|
errorText="Please input your last name."
|
||||||
|
error={lastNameError}
|
||||||
|
autoComplete="family-name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 flex max-h-60 w-full flex-col items-center justify-center rounded-lg md:p-2">
|
||||||
|
<InputField
|
||||||
|
label="Password"
|
||||||
|
onChangeHandler={(pass) => {
|
||||||
|
setPassword(pass);
|
||||||
|
checkPassword({
|
||||||
|
password: pass,
|
||||||
|
setErrors
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
isRequired
|
||||||
|
error={Object.keys(errors).length > 0}
|
||||||
|
autoComplete="new-password"
|
||||||
|
id="new-password"
|
||||||
|
/>
|
||||||
|
{Object.keys(errors).length > 0 && (
|
||||||
|
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||||
|
<div className="mb-2 text-sm text-gray-400">Password should contain at least:</div>
|
||||||
|
{Object.keys(errors).map((key) => {
|
||||||
|
if (errors[key as keyof Errors]) {
|
||||||
|
return (
|
||||||
|
<div className="items-top ml-1 flex flex-row justify-start" key={key}>
|
||||||
|
<div>
|
||||||
|
<FontAwesomeIcon icon={faXmark} className="text-md ml-0.5 mr-2.5 text-red" />
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-400">{errors[key as keyof Errors]}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto mt-2 flex max-h-24 max-w-max flex-col items-center justify-center px-2 py-3 text-lg md:px-4 md:py-5">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
signupErrorCheck();
|
||||||
|
}}
|
||||||
|
isLoading={isLoading}
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
Sign Up
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||||
|
const step4 = (
|
||||||
|
<div className="h-7/12 mx-1 mb-36 flex w-full max-w-xs flex-col items-center rounded-xl border border-mineshaft-600 bg-mineshaft-800 px-4 pb-6 pt-8 drop-shadow-xl md:mb-16 md:max-w-lg md:px-6">
|
||||||
|
<p className="flex justify-center bg-gradient-to-br from-white to-mineshaft-300 bg-clip-text text-center text-4xl font-semibold text-transparent">
|
||||||
|
Save your Emergency Kit
|
||||||
|
</p>
|
||||||
|
<div className="text-md mt-4 flex w-full max-w-md flex-col items-center justify-center rounded-md px-2 text-gray-400 md:mt-8">
|
||||||
|
<div>
|
||||||
|
If you get locked out of your account, your Emergency Kit is the only way to sign in.
|
||||||
|
</div>
|
||||||
|
<div className="mt-3">We recommend you download it and keep it somewhere safe.</div>
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto mt-4 flex w-full max-w-xs flex-row items-center rounded-md bg-white/10 p-2 text-gray-400 md:max-w-md">
|
||||||
|
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
|
||||||
|
It contains your Secret Key which we cannot access or recover for you if you lose it.
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto mt-4 flex max-h-24 max-w-max flex-col items-center justify-center px-2 py-3 text-lg md:px-4 md:py-5">
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
await issueBackupKey({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
personalName: `${firstName} ${lastName}`,
|
||||||
|
setBackupKeyError,
|
||||||
|
setBackupKeyIssued
|
||||||
|
});
|
||||||
|
navigate({
|
||||||
|
to: `/organization/$organizationId/${ProjectType.SecretManager}` as const,
|
||||||
|
params: {
|
||||||
|
organizationId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
Download PDF
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen flex-col items-center justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700">
|
||||||
|
<Helmet>
|
||||||
|
<title>Sign Up</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
</Helmet>
|
||||||
|
{shouldShowMfa ? (
|
||||||
|
<Mfa
|
||||||
|
email={email}
|
||||||
|
successCallback={mfaSuccessCallback}
|
||||||
|
method={requiredMfaMethod}
|
||||||
|
closeMfa={() => toggleShowMfa.off()}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Link to="/">
|
||||||
|
<div className="mb-4 mt-20 flex justify-center">
|
||||||
|
<img src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical Logo" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
{step === 1 ? stepConfirmEmail : step === 2 ? main : step4}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_restrict_login_signup/signupinvite")({
|
||||||
|
component: SignupInvitePage
|
||||||
|
});
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { FormEvent, useState } from "react";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import InputField from "@app/components/basic/InputField";
|
||||||
|
import { Button, EmailServiceSetupModal } from "@app/components/v2";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import { useSendPasswordResetEmail } from "@app/hooks/api";
|
||||||
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
|
|
||||||
|
const VerifyEmailPage = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [step, setStep] = useState(1);
|
||||||
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
|
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["setUpEmail"] as const);
|
||||||
|
|
||||||
|
const { mutateAsync } = useSendPasswordResetEmail();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function sends the verification email and forwards a user to the next step.
|
||||||
|
*/
|
||||||
|
const sendVerificationEmail = async () => {
|
||||||
|
if (email) {
|
||||||
|
await mutateAsync({ email });
|
||||||
|
setStep(2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
if (serverDetails?.emailConfigured) {
|
||||||
|
sendVerificationEmail();
|
||||||
|
} else {
|
||||||
|
handlePopUpOpen("setUpEmail");
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen flex-col justify-start bg-bunker-800 px-6">
|
||||||
|
<Helmet>
|
||||||
|
<title>Login</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
<meta property="og:image" content="/images/message.png" />
|
||||||
|
<meta property="og:title" content="Verify your email in Infisical" />
|
||||||
|
<meta
|
||||||
|
name="og:description"
|
||||||
|
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
|
||||||
|
/>
|
||||||
|
</Helmet>
|
||||||
|
<Link to="/">
|
||||||
|
<div className="mb-8 mt-20 flex cursor-pointer justify-center">
|
||||||
|
<img src="/images/biglogo.png" height={90} width={120} alt="long logo" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
{step === 1 && (
|
||||||
|
<form
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
className="h-7/12 mx-auto w-full max-w-md rounded-xl bg-bunker px-6 py-4 pt-8 drop-shadow-xl"
|
||||||
|
>
|
||||||
|
<p className="mx-auto mb-6 flex w-max justify-center text-2xl font-semibold text-bunker-100 md:text-3xl">
|
||||||
|
Forgot your password?
|
||||||
|
</p>
|
||||||
|
<div className="mt-4 flex flex-row items-center justify-center md:mx-2 md:pb-4">
|
||||||
|
<p className="flex w-max justify-center text-sm text-gray-400">
|
||||||
|
You will need your emergency kit. Enter your email to start account recovery.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex max-h-24 w-full items-center justify-center rounded-lg md:mt-0 md:max-h-28 md:p-2">
|
||||||
|
<InputField
|
||||||
|
label="Email"
|
||||||
|
onChangeHandler={setEmail}
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
placeholder=""
|
||||||
|
isRequired
|
||||||
|
autoComplete="username"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
|
||||||
|
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
|
||||||
|
<Button type="submit" size="lg" onClick={() => {}} isLoading={loading}>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
{step === 2 && (
|
||||||
|
<div className="h-7/12 mx-auto w-full max-w-md rounded-xl bg-bunker px-6 py-4 pt-8 drop-shadow-xl">
|
||||||
|
<p className="mx-auto mb-6 flex w-max justify-center text-xl font-semibold text-bunker-100 md:text-2xl">
|
||||||
|
Look for an email in your inbox.
|
||||||
|
</p>
|
||||||
|
<div className="mt-4 flex flex-row items-center justify-center md:mx-2 md:pb-4">
|
||||||
|
<p className="flex w-max justify-center text-center text-sm text-gray-400">
|
||||||
|
An email with instructions has been sent to {email}.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<EmailServiceSetupModal
|
||||||
|
isOpen={popUp.setUpEmail?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("setUpEmail", isOpen)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_restrict_login_signup/verify-email")({
|
||||||
|
component: VerifyEmailPage
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user