feat: completed signup and personal settings ui

This commit is contained in:
=
2024-12-16 00:23:50 +05:30
parent 0435305a68
commit c442c8483a
65 changed files with 2965 additions and 240 deletions

View File

@@ -20,7 +20,7 @@ import {
import { useOrganization, useSubscription } from "@app/context";
import { useGetOrgTrialUrl } from "@app/hooks/api";
const supportOptions = [
export const INFISICAL_SUPPORT_OPTIONS = [
[
<FontAwesomeIcon key={1} className="pr-4 text-sm" icon={faSlack} />,
"Support Forum",
@@ -84,7 +84,7 @@ export const SidebarFooter = () => {
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
{supportOptions.map(([icon, text, url]) => (
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
<DropdownMenuItem key={url as string}>
<a
target="_blank"

View File

@@ -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>
</>
);
};

View File

@@ -0,0 +1 @@
export { PersonalSettingsLayout } from "./PersonalSettingsLayout";

View File

@@ -11,25 +11,38 @@
// Import Routes
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 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 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 AuthenticateRestrictloginsignupSignupIndexImport } from './routes/_authenticate/_restrict_login_signup/signup/index'
import { Route as AuthenticateRestrictloginsignupLoginIndexImport } from './routes/_authenticate/_restrict_login_signup/login/index'
import { Route as AuthenticateRestrictloginsignupSignupSsoIndexImport } from './routes/_authenticate/_restrict_login_signup/signup/sso/index'
import { Route as AuthenticateRestrictloginsignupLoginSsoIndexImport } from './routes/_authenticate/_restrict_login_signup/login/sso/index'
import { Route as AuthenticateRestrictloginsignupLoginSelectOrganizationIndexImport } from './routes/_authenticate/_restrict_login_signup/login/select-organization/index'
import { Route as AuthenticateRestrictloginsignupLoginLdapIndexImport } from './routes/_authenticate/_restrict_login_signup/login/ldap/index'
import { Route as RestrictloginsignupSignupSsoIndexImport } from './routes/_restrict_login_signup/signup/sso/index'
import { Route as RestrictloginsignupLoginSsoIndexImport } from './routes/_restrict_login_signup/login/sso/index'
import { Route as RestrictloginsignupLoginSelectOrganizationIndexImport } from './routes/_restrict_login_signup/login/select-organization/index'
import { Route as RestrictloginsignupLoginLdapIndexImport } from './routes/_restrict_login_signup/login/ldap/index'
import { Route as AuthenticatePersonalsettingslayoutPersonalSettingsIndexImport } from './routes/_authenticate/_personal_settings_layout/personal-settings/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 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 AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdSecretManagerImport } from './routes/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager'
// Create/Update Routes
const RestrictloginsignupRoute = RestrictloginsignupImport.update({
id: '/_restrict_login_signup',
getParentRoute: () => rootRoute,
} as any)
const AuthenticateRoute = AuthenticateImport.update({
id: '/_authenticate',
getParentRoute: () => rootRoute,
@@ -41,9 +54,50 @@ const IndexRoute = IndexImport.update({
getParentRoute: () => rootRoute,
} as any)
const AuthenticateRestrictloginsignupRoute =
AuthenticateRestrictloginsignupImport.update({
id: '/_restrict_login_signup',
const RestrictloginsignupVerifyEmailRoute =
RestrictloginsignupVerifyEmailImport.update({
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,
} as any)
@@ -52,52 +106,73 @@ const AuthenticateOrgdetailsRoute = AuthenticateOrgdetailsImport.update({
getParentRoute: () => AuthenticateRoute,
} 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 =
AuthenticateOrgdetailsOrganizationlayoutImport.update({
id: '/_organization_layout',
getParentRoute: () => AuthenticateOrgdetailsRoute,
} as any)
const AuthenticateRestrictloginsignupSignupIndexRoute =
AuthenticateRestrictloginsignupSignupIndexImport.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({
const RestrictloginsignupSignupSsoIndexRoute =
RestrictloginsignupSignupSsoIndexImport.update({
id: '/signup/sso/',
path: '/signup/sso/',
getParentRoute: () => AuthenticateRestrictloginsignupRoute,
getParentRoute: () => RestrictloginsignupRoute,
} as any)
const AuthenticateRestrictloginsignupLoginSsoIndexRoute =
AuthenticateRestrictloginsignupLoginSsoIndexImport.update({
const RestrictloginsignupLoginSsoIndexRoute =
RestrictloginsignupLoginSsoIndexImport.update({
id: '/login/sso/',
path: '/login/sso/',
getParentRoute: () => AuthenticateRestrictloginsignupRoute,
getParentRoute: () => RestrictloginsignupRoute,
} as any)
const AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute =
AuthenticateRestrictloginsignupLoginSelectOrganizationIndexImport.update({
const RestrictloginsignupLoginSelectOrganizationIndexRoute =
RestrictloginsignupLoginSelectOrganizationIndexImport.update({
id: '/login/select-organization/',
path: '/login/select-organization/',
getParentRoute: () => AuthenticateRestrictloginsignupRoute,
getParentRoute: () => RestrictloginsignupRoute,
} as any)
const AuthenticateRestrictloginsignupLoginLdapIndexRoute =
AuthenticateRestrictloginsignupLoginLdapIndexImport.update({
const RestrictloginsignupLoginLdapIndexRoute =
RestrictloginsignupLoginLdapIndexImport.update({
id: '/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)
const AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexRoute =
@@ -107,20 +182,6 @@ const AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexRoute =
getParentRoute: () => AuthenticateOrgdetailsOrganizationlayoutRoute,
} 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 =
AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexImport.update(
{
@@ -157,6 +218,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticateImport
parentRoute: typeof rootRoute
}
'/_restrict_login_signup': {
id: '/_restrict_login_signup'
path: ''
fullPath: ''
preLoaderRoute: typeof RestrictloginsignupImport
parentRoute: typeof rootRoute
}
'/_authenticate/_org_details': {
id: '/_authenticate/_org_details'
path: ''
@@ -164,13 +232,55 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticateOrgdetailsImport
parentRoute: typeof AuthenticateImport
}
'/_authenticate/_restrict_login_signup': {
id: '/_authenticate/_restrict_login_signup'
'/_authenticate/_personal_settings_layout': {
id: '/_authenticate/_personal_settings_layout'
path: ''
fullPath: ''
preLoaderRoute: typeof AuthenticateRestrictloginsignupImport
preLoaderRoute: typeof AuthenticatePersonalsettingslayoutImport
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': {
id: '/_authenticate/_org_details/_organization_layout'
path: ''
@@ -178,33 +288,68 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticateOrgdetailsOrganizationlayoutImport
parentRoute: typeof AuthenticateOrgdetailsImport
}
'/_authenticate/_restrict_login_signup/login/': {
id: '/_authenticate/_restrict_login_signup/login/'
'/_restrict_login_signup/login/': {
id: '/_restrict_login_signup/login/'
path: '/login'
fullPath: '/login'
preLoaderRoute: typeof AuthenticateRestrictloginsignupLoginIndexImport
parentRoute: typeof AuthenticateRestrictloginsignupImport
preLoaderRoute: typeof RestrictloginsignupLoginIndexImport
parentRoute: typeof RestrictloginsignupImport
}
'/_authenticate/_restrict_login_signup/signup/': {
id: '/_authenticate/_restrict_login_signup/signup/'
'/_restrict_login_signup/signup/': {
id: '/_restrict_login_signup/signup/'
path: '/signup'
fullPath: '/signup'
preLoaderRoute: typeof AuthenticateRestrictloginsignupSignupIndexImport
parentRoute: typeof AuthenticateRestrictloginsignupImport
preLoaderRoute: typeof RestrictloginsignupSignupIndexImport
parentRoute: typeof RestrictloginsignupImport
}
'/_authenticate/_restrict_login_signup/login/provider/error': {
id: '/_authenticate/_restrict_login_signup/login/provider/error'
'/_restrict_login_signup/login/provider/error': {
id: '/_restrict_login_signup/login/provider/error'
path: '/login/provider/error'
fullPath: '/login/provider/error'
preLoaderRoute: typeof AuthenticateRestrictloginsignupLoginProviderErrorImport
parentRoute: typeof AuthenticateRestrictloginsignupImport
preLoaderRoute: typeof RestrictloginsignupLoginProviderErrorImport
parentRoute: typeof RestrictloginsignupImport
}
'/_authenticate/_restrict_login_signup/login/provider/success': {
id: '/_authenticate/_restrict_login_signup/login/provider/success'
'/_restrict_login_signup/login/provider/success': {
id: '/_restrict_login_signup/login/provider/success'
path: '/login/provider/success'
fullPath: '/login/provider/success'
preLoaderRoute: typeof AuthenticateRestrictloginsignupLoginProviderSuccessImport
parentRoute: typeof AuthenticateRestrictloginsignupImport
preLoaderRoute: typeof RestrictloginsignupLoginProviderSuccessImport
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/': {
id: '/_authenticate/_org_details/_organization_layout/organization/'
@@ -213,34 +358,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationIndexImport
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': {
id: '/_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager'
path: '/organization/$organizationId/secret-manager'
@@ -296,69 +413,99 @@ const AuthenticateOrgdetailsRouteWithChildren =
AuthenticateOrgdetailsRouteChildren,
)
interface AuthenticateRestrictloginsignupRouteChildren {
AuthenticateRestrictloginsignupLoginIndexRoute: typeof AuthenticateRestrictloginsignupLoginIndexRoute
AuthenticateRestrictloginsignupSignupIndexRoute: typeof AuthenticateRestrictloginsignupSignupIndexRoute
AuthenticateRestrictloginsignupLoginProviderErrorRoute: typeof AuthenticateRestrictloginsignupLoginProviderErrorRoute
AuthenticateRestrictloginsignupLoginProviderSuccessRoute: typeof AuthenticateRestrictloginsignupLoginProviderSuccessRoute
AuthenticateRestrictloginsignupLoginLdapIndexRoute: typeof AuthenticateRestrictloginsignupLoginLdapIndexRoute
AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute: typeof AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute
AuthenticateRestrictloginsignupLoginSsoIndexRoute: typeof AuthenticateRestrictloginsignupLoginSsoIndexRoute
AuthenticateRestrictloginsignupSignupSsoIndexRoute: typeof AuthenticateRestrictloginsignupSignupSsoIndexRoute
interface AuthenticatePersonalsettingslayoutRouteChildren {
AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute: typeof AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute
}
const AuthenticateRestrictloginsignupRouteChildren: AuthenticateRestrictloginsignupRouteChildren =
const AuthenticatePersonalsettingslayoutRouteChildren: AuthenticatePersonalsettingslayoutRouteChildren =
{
AuthenticateRestrictloginsignupLoginIndexRoute:
AuthenticateRestrictloginsignupLoginIndexRoute,
AuthenticateRestrictloginsignupSignupIndexRoute:
AuthenticateRestrictloginsignupSignupIndexRoute,
AuthenticateRestrictloginsignupLoginProviderErrorRoute:
AuthenticateRestrictloginsignupLoginProviderErrorRoute,
AuthenticateRestrictloginsignupLoginProviderSuccessRoute:
AuthenticateRestrictloginsignupLoginProviderSuccessRoute,
AuthenticateRestrictloginsignupLoginLdapIndexRoute:
AuthenticateRestrictloginsignupLoginLdapIndexRoute,
AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute:
AuthenticateRestrictloginsignupLoginSelectOrganizationIndexRoute,
AuthenticateRestrictloginsignupLoginSsoIndexRoute:
AuthenticateRestrictloginsignupLoginSsoIndexRoute,
AuthenticateRestrictloginsignupSignupSsoIndexRoute:
AuthenticateRestrictloginsignupSignupSsoIndexRoute,
AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute:
AuthenticatePersonalsettingslayoutPersonalSettingsIndexRoute,
}
const AuthenticateRestrictloginsignupRouteWithChildren =
AuthenticateRestrictloginsignupRoute._addFileChildren(
AuthenticateRestrictloginsignupRouteChildren,
const AuthenticatePersonalsettingslayoutRouteWithChildren =
AuthenticatePersonalsettingslayoutRoute._addFileChildren(
AuthenticatePersonalsettingslayoutRouteChildren,
)
interface AuthenticateRouteChildren {
AuthenticateOrgdetailsRoute: typeof AuthenticateOrgdetailsRouteWithChildren
AuthenticateRestrictloginsignupRoute: typeof AuthenticateRestrictloginsignupRouteWithChildren
AuthenticatePersonalsettingslayoutRoute: typeof AuthenticatePersonalsettingslayoutRouteWithChildren
AuthenticateCliRedirectRoute: typeof AuthenticateCliRedirectRoute
}
const AuthenticateRouteChildren: AuthenticateRouteChildren = {
AuthenticateOrgdetailsRoute: AuthenticateOrgdetailsRouteWithChildren,
AuthenticateRestrictloginsignupRoute:
AuthenticateRestrictloginsignupRouteWithChildren,
AuthenticatePersonalsettingslayoutRoute:
AuthenticatePersonalsettingslayoutRouteWithChildren,
AuthenticateCliRedirectRoute: AuthenticateCliRedirectRoute,
}
const AuthenticateRouteWithChildren = AuthenticateRoute._addFileChildren(
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 {
'/': typeof IndexRoute
'': typeof AuthenticateOrgdetailsOrganizationlayoutRouteWithChildren
'/login': typeof AuthenticateRestrictloginsignupLoginIndexRoute
'/signup': typeof AuthenticateRestrictloginsignupSignupIndexRoute
'/login/provider/error': typeof AuthenticateRestrictloginsignupLoginProviderErrorRoute
'/login/provider/success': typeof AuthenticateRestrictloginsignupLoginProviderSuccessRoute
'/cli-redirect': typeof AuthenticateCliRedirectRoute
'/email-not-verified': typeof RestrictloginsignupEmailNotVerifiedRoute
'/password-reset': typeof RestrictloginsignupPasswordResetRoute
'/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
'/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': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexRoute
}
@@ -366,15 +513,22 @@ export interface FileRoutesByFullPath {
export interface FileRoutesByTo {
'/': typeof IndexRoute
'': typeof AuthenticateOrgdetailsOrganizationlayoutRouteWithChildren
'/login': typeof AuthenticateRestrictloginsignupLoginIndexRoute
'/signup': typeof AuthenticateRestrictloginsignupSignupIndexRoute
'/login/provider/error': typeof AuthenticateRestrictloginsignupLoginProviderErrorRoute
'/login/provider/success': typeof AuthenticateRestrictloginsignupLoginProviderSuccessRoute
'/cli-redirect': typeof AuthenticateCliRedirectRoute
'/email-not-verified': typeof RestrictloginsignupEmailNotVerifiedRoute
'/password-reset': typeof RestrictloginsignupPasswordResetRoute
'/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
'/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': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexRoute
}
@@ -383,18 +537,26 @@ export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/_authenticate': typeof AuthenticateRouteWithChildren
'/_restrict_login_signup': typeof RestrictloginsignupRouteWithChildren
'/_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/_restrict_login_signup/login/': typeof AuthenticateRestrictloginsignupLoginIndexRoute
'/_authenticate/_restrict_login_signup/signup/': typeof AuthenticateRestrictloginsignupSignupIndexRoute
'/_authenticate/_restrict_login_signup/login/provider/error': typeof AuthenticateRestrictloginsignupLoginProviderErrorRoute
'/_authenticate/_restrict_login_signup/login/provider/success': typeof AuthenticateRestrictloginsignupLoginProviderSuccessRoute
'/_restrict_login_signup/login/': typeof RestrictloginsignupLoginIndexRoute
'/_restrict_login_signup/signup/': typeof RestrictloginsignupSignupIndexRoute
'/_restrict_login_signup/login/provider/error': typeof RestrictloginsignupLoginProviderErrorRoute
'/_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/_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/': typeof AuthenticateOrgdetailsOrganizationlayoutOrganizationOrganizationIdIndexRoute
}
@@ -404,48 +566,70 @@ export interface FileRouteTypes {
fullPaths:
| '/'
| ''
| '/cli-redirect'
| '/email-not-verified'
| '/password-reset'
| '/requestnewinvite'
| '/signupinvite'
| '/verify-email'
| '/login'
| '/signup'
| '/login/provider/error'
| '/login/provider/success'
| '/organization'
| '/personal-settings'
| '/login/ldap'
| '/login/select-organization'
| '/login/sso'
| '/signup/sso'
| '/organization'
| '/organization/$organizationId/secret-manager'
| '/organization/$organizationId'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| ''
| '/cli-redirect'
| '/email-not-verified'
| '/password-reset'
| '/requestnewinvite'
| '/signupinvite'
| '/verify-email'
| '/login'
| '/signup'
| '/login/provider/error'
| '/login/provider/success'
| '/organization'
| '/personal-settings'
| '/login/ldap'
| '/login/select-organization'
| '/login/sso'
| '/signup/sso'
| '/organization'
| '/organization/$organizationId/secret-manager'
| '/organization/$organizationId'
id:
| '__root__'
| '/'
| '/_authenticate'
| '/_restrict_login_signup'
| '/_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/_restrict_login_signup/login/'
| '/_authenticate/_restrict_login_signup/signup/'
| '/_authenticate/_restrict_login_signup/login/provider/error'
| '/_authenticate/_restrict_login_signup/login/provider/success'
| '/_restrict_login_signup/login/'
| '/_restrict_login_signup/signup/'
| '/_restrict_login_signup/login/provider/error'
| '/_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/_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/'
fileRoutesById: FileRoutesById
@@ -454,11 +638,13 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AuthenticateRoute: typeof AuthenticateRouteWithChildren
RestrictloginsignupRoute: typeof RestrictloginsignupRouteWithChildren
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AuthenticateRoute: AuthenticateRouteWithChildren,
RestrictloginsignupRoute: RestrictloginsignupRouteWithChildren,
}
export const routeTree = rootRoute
@@ -472,7 +658,8 @@ export const routeTree = rootRoute
"filePath": "__root.tsx",
"children": [
"/",
"/_authenticate"
"/_authenticate",
"/_restrict_login_signup"
]
},
"/": {
@@ -482,7 +669,26 @@ export const routeTree = rootRoute
"filePath": "_authenticate.tsx",
"children": [
"/_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": {
@@ -492,20 +698,37 @@ export const routeTree = rootRoute
"/_authenticate/_org_details/_organization_layout"
]
},
"/_authenticate/_restrict_login_signup": {
"filePath": "_authenticate/_restrict_login_signup.tsx",
"/_authenticate/_personal_settings_layout": {
"filePath": "_authenticate/_personal_settings_layout.tsx",
"parent": "/_authenticate",
"children": [
"/_authenticate/_restrict_login_signup/login/",
"/_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/_personal_settings_layout/personal-settings/"
]
},
"/_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": {
"filePath": "_authenticate/_org_details/_organization_layout.tsx",
"parent": "/_authenticate/_org_details",
@@ -515,42 +738,46 @@ export const routeTree = rootRoute
"/_authenticate/_org_details/_organization_layout/organization/$organizationId/"
]
},
"/_authenticate/_restrict_login_signup/login/": {
"filePath": "_authenticate/_restrict_login_signup/login/index.tsx",
"parent": "/_authenticate/_restrict_login_signup"
"/_restrict_login_signup/login/": {
"filePath": "_restrict_login_signup/login/index.tsx",
"parent": "/_restrict_login_signup"
},
"/_authenticate/_restrict_login_signup/signup/": {
"filePath": "_authenticate/_restrict_login_signup/signup/index.tsx",
"parent": "/_authenticate/_restrict_login_signup"
"/_restrict_login_signup/signup/": {
"filePath": "_restrict_login_signup/signup/index.tsx",
"parent": "/_restrict_login_signup"
},
"/_authenticate/_restrict_login_signup/login/provider/error": {
"filePath": "_authenticate/_restrict_login_signup/login/provider/error.tsx",
"parent": "/_authenticate/_restrict_login_signup"
"/_restrict_login_signup/login/provider/error": {
"filePath": "_restrict_login_signup/login/provider/error.tsx",
"parent": "/_restrict_login_signup"
},
"/_authenticate/_restrict_login_signup/login/provider/success": {
"filePath": "_authenticate/_restrict_login_signup/login/provider/success.tsx",
"parent": "/_authenticate/_restrict_login_signup"
"/_restrict_login_signup/login/provider/success": {
"filePath": "_restrict_login_signup/login/provider/success.tsx",
"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/": {
"filePath": "_authenticate/_org_details/_organization_layout/organization/index.tsx",
"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": {
"filePath": "_authenticate/_org_details/_organization_layout/organization/$organizationId/secret-manager.tsx",
"parent": "/_authenticate/_org_details/_organization_layout"

View File

@@ -7,9 +7,7 @@ import { setAuthToken } from "@app/hooks/api/reactQuery";
import { fetchUserDetails } from "@app/hooks/api/users/queries";
export const Route = createFileRoute("/_authenticate")({
beforeLoad: async ({ context, location }) => {
const isLoginRoute = location.pathname.startsWith("/login");
const isSignupRoute = location.pathname.startsWith("/signup");
beforeLoad: async ({ context }) => {
try {
const data = await context.queryClient.fetchQuery({
queryKey: authKeys.getAuthToken,
@@ -27,7 +25,6 @@ export const Route = createFileRoute("/_authenticate")({
return { organizationId: data.organizationId as string, isAuthenticated: true, user };
} catch {
if (isLoginRoute || isSignupRoute) return {};
createNotification({
type: "error",
title: "Access Restricted",

View File

@@ -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
});

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -0,0 +1 @@
export { APIKeySection } from "./APIKeySection";

View File

@@ -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>
);
};

View File

@@ -0,0 +1 @@
export { AuthMethodSection } from "./AuthMethodSection";

View File

@@ -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>
);
};

View File

@@ -0,0 +1 @@
export { ChangePasswordSection } from "./ChangePasswordSection";

View File

@@ -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>
);
};

View File

@@ -0,0 +1 @@
export { DeleteAccountSection } from "./DeleteAccountSection";

View File

@@ -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>
);
};

View File

@@ -0,0 +1 @@
export { EmergencyKitSection } from "./EmergencyKitSection";

View File

@@ -0,0 +1,5 @@
import { APIKeySection } from "../APIKeySection";
export const PersonalAPIKeyTab = () => {
return <APIKeySection />;
};

View File

@@ -0,0 +1 @@
export { PersonalAPIKeyTab } from "./PersonalAPIKeyTab";

View File

@@ -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>
);
};

View File

@@ -0,0 +1 @@
export { PersonalAuthTab } from "./PersonalAuthTab";

View File

@@ -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>
);
};

View File

@@ -0,0 +1 @@
export { PersonalGeneralTab } from "./PersonalGeneralTab";

View File

@@ -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>
);
};

View File

@@ -0,0 +1 @@
export { PersonalTabGroup } from "./PersonalTabGroup";

View File

@@ -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}
/>
</>
);
};

View File

@@ -0,0 +1 @@
export { MFASection } from "./MFASection";

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -0,0 +1 @@
export { SessionsSection } from "./SessionsSection";

View File

@@ -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>
);
};

View File

@@ -0,0 +1 @@
export { UserNameSection } from "./UserNameSection";

View File

@@ -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
}
);

View File

@@ -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
}
});
}
}
});

View File

@@ -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&apos;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&apos;ve successfully logged in to the Infisical CLI
</p>
</>
)}
</div>
</div>
);
};
export const Route = createFileRoute("/_authenticate/cli-redirect")({
component: CliRedirectPage
});

View File

@@ -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);
}
}
});

View File

@@ -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&apos;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
});

View File

@@ -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
});

View File

@@ -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
});

View File

@@ -10,6 +10,6 @@ const LoginProviderError = () => {
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
});

View File

@@ -14,8 +14,6 @@ const LoginProviderSuccess = () => {
return <div />;
};
export const Route = createFileRoute(
"/_authenticate/_restrict_login_signup/login/provider/success"
)({
export const Route = createFileRoute("/_restrict_login_signup/login/provider/success")({
component: LoginProviderSuccess
});

View File

@@ -277,8 +277,6 @@ const SelectOrganizationPage = () => {
);
};
export const Route = createFileRoute(
"/_authenticate/_restrict_login_signup/login/select-organization/"
)({
export const Route = createFileRoute("/_restrict_login_signup/login/select-organization/")({
component: SelectOrganizationPage
});

View File

@@ -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
});

View File

@@ -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
});

View File

@@ -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&apos;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
});

View File

@@ -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,
loader: ({ context }) => {
console.log(context);

View File

@@ -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
});

View File

@@ -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
});

View File

@@ -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
});