diff --git a/frontend-v2/src/layouts/OrganizationLayout/components/SidebarFooter/SidebarFooter.tsx b/frontend-v2/src/layouts/OrganizationLayout/components/SidebarFooter/SidebarFooter.tsx index d71a70263..cb113e66a 100644 --- a/frontend-v2/src/layouts/OrganizationLayout/components/SidebarFooter/SidebarFooter.tsx +++ b/frontend-v2/src/layouts/OrganizationLayout/components/SidebarFooter/SidebarFooter.tsx @@ -20,7 +20,7 @@ import { import { useOrganization, useSubscription } from "@app/context"; import { useGetOrgTrialUrl } from "@app/hooks/api"; -const supportOptions = [ +export const INFISICAL_SUPPORT_OPTIONS = [ [ , "Support Forum", @@ -84,7 +84,7 @@ export const SidebarFooter = () => { - {supportOptions.map(([icon, text, url]) => ( + {INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => ( { + const { organizationId } = useRouteContext({ from: "/_authenticate/_personal_settings_layout" }); + const { t } = useTranslation(); + + const infisicalPlatformVersion = import.meta.env.VITE_INFISICAL_PLATFORM_VERSION; + + return ( + <> +
+ {!window.isSecureContext && } +
+ +
+ +
+
+
+
+ +

+ {` ${t("common.no-mobile")} `} +

+
+ + ); +}; diff --git a/frontend-v2/src/layouts/PersonalSettingsLayout/index.tsx b/frontend-v2/src/layouts/PersonalSettingsLayout/index.tsx new file mode 100644 index 000000000..2ce9140ff --- /dev/null +++ b/frontend-v2/src/layouts/PersonalSettingsLayout/index.tsx @@ -0,0 +1 @@ +export { PersonalSettingsLayout } from "./PersonalSettingsLayout"; diff --git a/frontend-v2/src/routeTree.gen.ts b/frontend-v2/src/routeTree.gen.ts index 4f1743fde..ff12f273a 100644 --- a/frontend-v2/src/routeTree.gen.ts +++ b/frontend-v2/src/routeTree.gen.ts @@ -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" diff --git a/frontend-v2/src/routes/_authenticate.tsx b/frontend-v2/src/routes/_authenticate.tsx index 3e4df0e27..d7826e610 100644 --- a/frontend-v2/src/routes/_authenticate.tsx +++ b/frontend-v2/src/routes/_authenticate.tsx @@ -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", diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout.tsx new file mode 100644 index 000000000..9e524a4f1 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout.tsx @@ -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 +}); diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/APIKeySection.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/APIKeySection.tsx new file mode 100644 index 000000000..a0f033049 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/APIKeySection.tsx @@ -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 ( +
+
+

+ {t("settings.personal.api-keys.title")} +

+ + + +
+ + +
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/APIKeyTable.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/APIKeyTable.tsx new file mode 100644 index 000000000..07531dbc0 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/APIKeyTable.tsx @@ -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 ( + + + + + + + + + + + + {isLoading && } + {!isLoading && + data && + data.length > 0 && + data.map(({ id, name, createdAt, expiresAt, lastUsed }) => { + return ( + + + + + + + + ); + })} + {!isLoading && data && data?.length === 0 && ( + + + + )} + +
NameLast activeCreatedExpiration +
{name}{format(new Date(lastUsed), "yyyy-MM-dd")}{format(new Date(createdAt), "yyyy-MM-dd")}{format(new Date(expiresAt), "yyyy-MM-dd")} + { + await handleDeleteAPIKeyDataClick(id); + }} + size="lg" + colorSchema="danger" + variant="plain" + ariaLabel="update" + > + + +
+ +
+
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/AddAPIKeyModal.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/AddAPIKeyModal.tsx new file mode 100644 index 000000000..595da20f2 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/AddAPIKeyModal.tsx @@ -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; + +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({ + 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 ( + { + handlePopUpToggle("addAPIKey", isOpen); + reset(); + setNewAPIKey(""); + }} + > + + {!hasAPIKey ? ( +
+ ( + + + + )} + /> + ( + + + + )} + /> +
+ + +
+ + ) : ( +
+

{newAPIKey}

+ + + + Click to copy + + +
+ )} +
+
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/index.tsx new file mode 100644 index 000000000..f4bdf430e --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/APIKeySection/index.tsx @@ -0,0 +1 @@ +export { APIKeySection } from "./APIKeySection"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/AuthMethodSection/AuthMethodSection.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/AuthMethodSection/AuthMethodSection.tsx new file mode 100644 index 000000000..91080a73e --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/AuthMethodSection/AuthMethodSection.tsx @@ -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; + +export const AuthMethodSection = () => { + const { user } = useUser(); + const { config } = useServerConfig(); + const { mutateAsync } = useUpdateUserAuthMethods(); + + const { reset, setValue, watch } = useForm({ + 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 ( +
+

+ Authentication methods +

+

+ 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. +

+
+ {user && + authMethodOpts.map((authMethodOpt) => { + // only filter when enabledLoginMethods is explicitly configured by admin + if ( + config.enabledLoginMethods && + !config.enabledLoginMethods.includes(authMethodOpt.loginMethod) + ) { + return null; + } + + return ( +
+
+ +
+ onAuthMethodToggle(value, authMethodOpt)} + isChecked={authMethods?.includes(authMethodOpt.value) ?? false} + > +

{authMethodOpt.label}

+
+
+ ); + })} +
+
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/AuthMethodSection/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/AuthMethodSection/index.tsx new file mode 100644 index 000000000..3f6ad3da6 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/AuthMethodSection/index.tsx @@ -0,0 +1 @@ +export { AuthMethodSection } from "./AuthMethodSection"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/ChangePasswordSection/ChangePasswordSection.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/ChangePasswordSection/ChangePasswordSection.tsx new file mode 100644 index 000000000..efe07961e --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/ChangePasswordSection/ChangePasswordSection.tsx @@ -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; + +export const ChangePasswordSection = () => { + const { t } = useTranslation(); + + const { user } = useUser(); + const { reset, control, handleSubmit } = useForm({ + defaultValues: { + oldPassword: "", + newPassword: "" + }, + resolver: zodResolver(schema) + }); + const [errors, setErrors] = useState({}); + 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 ( +
+

Change password

+
+ ( + + + + )} + control={control} + name="oldPassword" + /> +
+
+ ( + + + + )} + control={control} + name="newPassword" + /> +
+ {Object.keys(errors).length > 0 && ( +
+
{t("section.password.validate-base")}
+ {Object.keys(errors).map((key) => { + if (errors[key as keyof Errors]) { + return ( +
+
+ +
+

{errors[key as keyof Errors]}

+
+ ); + } + + return null; + })} +
+ )} + +
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/ChangePasswordSection/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/ChangePasswordSection/index.tsx new file mode 100644 index 000000000..c2d2dfd80 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/ChangePasswordSection/index.tsx @@ -0,0 +1 @@ +export { ChangePasswordSection } from "./ChangePasswordSection"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/DeleteAccountSection/DeleteAccountSection.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/DeleteAccountSection/DeleteAccountSection.tsx new file mode 100644 index 000000000..74e2f83fe --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/DeleteAccountSection/DeleteAccountSection.tsx @@ -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 ( +
+

Danger Zone

+ + handlePopUpToggle("deleteAccount", isOpen)} + deleteKey="confirm" + onDeleteApproved={handleDeleteAccountSubmit} + /> +
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/DeleteAccountSection/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/DeleteAccountSection/index.tsx new file mode 100644 index 000000000..a4354d533 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/DeleteAccountSection/index.tsx @@ -0,0 +1 @@ +export { DeleteAccountSection } from "./DeleteAccountSection"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/EmergencyKitSection/EmergencyKitSection.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/EmergencyKitSection/EmergencyKitSection.tsx new file mode 100644 index 000000000..1cf0d336e --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/EmergencyKitSection/EmergencyKitSection.tsx @@ -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; + +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 ( +
+

Emergency Kit

+

+ The kit contains information you can use to recover your account. +

+
+ ( + + + + )} + control={control} + name="password" + /> +
+ +
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/EmergencyKitSection/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/EmergencyKitSection/index.tsx new file mode 100644 index 000000000..c817666b1 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/EmergencyKitSection/index.tsx @@ -0,0 +1 @@ +export { EmergencyKitSection } from "./EmergencyKitSection"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAPIKeyTab/PersonalAPIKeyTab.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAPIKeyTab/PersonalAPIKeyTab.tsx new file mode 100644 index 000000000..ca1fa130e --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAPIKeyTab/PersonalAPIKeyTab.tsx @@ -0,0 +1,5 @@ +import { APIKeySection } from "../APIKeySection"; + +export const PersonalAPIKeyTab = () => { + return ; +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAPIKeyTab/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAPIKeyTab/index.tsx new file mode 100644 index 000000000..50c77ba0a --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAPIKeyTab/index.tsx @@ -0,0 +1 @@ +export { PersonalAPIKeyTab } from "./PersonalAPIKeyTab"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAuthTab/PersonalAuthTab.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAuthTab/PersonalAuthTab.tsx new file mode 100644 index 000000000..b87d87dbb --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAuthTab/PersonalAuthTab.tsx @@ -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 ( +
+ {user && !user.authMethods.includes(AuthMethod.LDAP) && ( + <> + + + + )} + +
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAuthTab/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAuthTab/index.tsx new file mode 100644 index 000000000..2a042aaf5 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalAuthTab/index.tsx @@ -0,0 +1 @@ +export { PersonalAuthTab } from "./PersonalAuthTab"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalGeneralTab/PersonalGeneralTab.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalGeneralTab/PersonalGeneralTab.tsx new file mode 100644 index 000000000..ac4c5bea2 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalGeneralTab/PersonalGeneralTab.tsx @@ -0,0 +1,15 @@ +import { DeleteAccountSection } from "../DeleteAccountSection"; +import { EmergencyKitSection } from "../EmergencyKitSection"; +import { SessionsSection } from "../SessionsSection"; +import { UserNameSection } from "../UserNameSection"; + +export const PersonalGeneralTab = () => { + return ( +
+ + + + +
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalGeneralTab/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalGeneralTab/index.tsx new file mode 100644 index 000000000..97c1cb39a --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalGeneralTab/index.tsx @@ -0,0 +1 @@ +export { PersonalGeneralTab } from "./PersonalGeneralTab"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalTabGroup/PersonalTabGroup.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalTabGroup/PersonalTabGroup.tsx new file mode 100644 index 000000000..bff53c3c6 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalTabGroup/PersonalTabGroup.tsx @@ -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.map((tab) => ( + {tab.name} + ))} + + + + + + + + +
+
+ +
+ Deprecation Notice +

+ API Keys are being deprecated in favor of Machine Identities. +
+ They will be removed in the future in accordance with the deprecation notice and + timeline stated{" "} + + here + + . +
+ + Learn more about Machine Identities + +

+
+
+ +
+
+
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalTabGroup/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalTabGroup/index.tsx new file mode 100644 index 000000000..5bc002597 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/PersonalTabGroup/index.tsx @@ -0,0 +1 @@ +export { PersonalTabGroup } from "./PersonalTabGroup"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SecuritySection/MFASection.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SecuritySection/MFASection.tsx new file mode 100644 index 000000000..f27bf65dd --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SecuritySection/MFASection.tsx @@ -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 ( + <> +
+

Two-factor Authentication

+ {user && ( + { + if (serverDetails?.emailConfigured) { + toggleMfa(state as boolean); + } else { + handlePopUpOpen("setUpEmail"); + } + }} + > + Enable 2-factor authentication + + )} + {user?.isMfaEnabled && ( + + + + )} +
Mobile Authenticator
+ {isTotpConfigurationLoading ? ( + + ) : ( +
+ {totpConfiguration?.isVerified ? ( +
+
+ + + +
+ {shouldShowRecoveryCodes && totpConfiguration.recoveryCodes && ( +
+ {totpConfiguration.recoveryCodes.map((code) => ( +
{code}
+ ))} +
+ )} +
+ ) : ( + <> +
+ For added security, you can configure a mobile authenticator and set it as your + selected 2FA method. +
+
+ { + await queryClient.invalidateQueries(userKeys.totpConfiguration); + }} + /> +
+ + )} +
+ )} +
+ handlePopUpToggle("setUpEmail", isOpen)} + /> + handlePopUpToggle("deleteTotpConfig", isOpen)} + deleteKey="confirm" + onDeleteApproved={handleTotpDeletion} + /> + + ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SecuritySection/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SecuritySection/index.tsx new file mode 100644 index 000000000..1284d0848 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SecuritySection/index.tsx @@ -0,0 +1 @@ +export { MFASection } from "./MFASection"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SessionsSection/SessionsSection.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SessionsSection/SessionsSection.tsx new file mode 100644 index 000000000..91369d060 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SessionsSection/SessionsSection.tsx @@ -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 ( +
+
+

Sessions

+ +
+

+ 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. +

+ +
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SessionsSection/SessionsTable.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SessionsSection/SessionsTable.tsx new file mode 100644 index 000000000..dfb657640 --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SessionsSection/SessionsTable.tsx @@ -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 ( + + + + + + + + + + + + {isLoading && } + {!isLoading && + data && + data.length > 0 && + data.map(({ id, createdAt, lastUsed, ip, userAgent }) => { + return ( + + + + + + + ); + })} + {!isLoading && data && data?.length === 0 && ( + + + + )} + +
CreatedLast activeIP addressDevice
{formatDate(createdAt)}{formatDate(lastUsed)}{ip}{userAgent}
+ +
+
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SessionsSection/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SessionsSection/index.tsx new file mode 100644 index 000000000..cc0fe376b --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/SessionsSection/index.tsx @@ -0,0 +1 @@ +export { SessionsSection } from "./SessionsSection"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/UserNameSection/UserNameSection.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/UserNameSection/UserNameSection.tsx new file mode 100644 index 000000000..d9653fa2a --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/UserNameSection/UserNameSection.tsx @@ -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; + +export const UserNameSection = (): JSX.Element => { + const { user } = useUser(); + + const { handleSubmit, control, reset } = useForm({ 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 ( +
+

Name

+
+ ( + + + + )} + control={control} + name="name" + /> +
+ +
+ ); +}; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/UserNameSection/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/UserNameSection/index.tsx new file mode 100644 index 000000000..a81ecba8f --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/-components/UserNameSection/index.tsx @@ -0,0 +1 @@ +export { UserNameSection } from "./UserNameSection"; diff --git a/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/index.tsx b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/index.tsx new file mode 100644 index 000000000..41677bafe --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/_personal_settings_layout/personal-settings/index.tsx @@ -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 ( +
+ + {t("common.head-title", { title: t("settings.personal.title") })} + + +
+
+
+

{t("settings.personal.title")}

+
+ +
+
+
+ ); +}; + +export const Route = createFileRoute("/_authenticate/_personal_settings_layout/personal-settings/")( + { + component: PersonalSettingsPage + } +); diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup.tsx b/frontend-v2/src/routes/_authenticate/_restrict_login_signup.tsx deleted file mode 100644 index 470fe6d9e..000000000 --- a/frontend-v2/src/routes/_authenticate/_restrict_login_signup.tsx +++ /dev/null @@ -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 - } - }); - } - } -}); diff --git a/frontend-v2/src/routes/_authenticate/cli-redirect.tsx b/frontend-v2/src/routes/_authenticate/cli-redirect.tsx new file mode 100644 index 000000000..7f3baf1cf --- /dev/null +++ b/frontend-v2/src/routes/_authenticate/cli-redirect.tsx @@ -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({ + initialState: false + }); + const cliToken = getTerminalCliToken(); + + const copyUrlToClipboard = () => { + if (cliToken) { + navigator.clipboard.writeText(cliToken); + setIsUrlCopied(true); + sessionStorage.removeItem(SessionStorageKeys.CLI_TERMINAL_TOKEN); + } + }; + + return ( +
+ + Infisical CLI | Login Successful! + + +
+
+ Infisical Logo +
+ {cliToken ? ( + <> +
+

+ Unable to reach CLI +

+

+ Your login was successful but, Infisical couldn't automatically push your login + token to the CLI. +

+

+ Please copy the token below and manually provide it to your CLI. +

+
+
+ +
+ + Copy to + clipboard + +
+
+ + ) : ( + <> +

+ Head back to your terminal +

+

+ You've successfully logged in to the Infisical CLI +

+ + )} +
+
+ ); +}; + +export const Route = createFileRoute("/_authenticate/cli-redirect")({ + component: CliRedirectPage +}); diff --git a/frontend-v2/src/routes/_restrict_login_signup.tsx b/frontend-v2/src/routes/_restrict_login_signup.tsx new file mode 100644 index 000000000..2de490f59 --- /dev/null +++ b/frontend-v2/src/routes/_restrict_login_signup.tsx @@ -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); + } + } +}); diff --git a/frontend-v2/src/routes/_restrict_login_signup/email-not-verified.tsx b/frontend-v2/src/routes/_restrict_login_signup/email-not-verified.tsx new file mode 100644 index 000000000..2918e9e92 --- /dev/null +++ b/frontend-v2/src/routes/_restrict_login_signup/email-not-verified.tsx @@ -0,0 +1,25 @@ +import { Helmet } from "react-helmet"; +import { createFileRoute } from "@tanstack/react-router"; + +const EmailNotVerifiedPage = () => { + return ( +
+ + Request a New Invite + + +
+

Oops.

+

Your email was not verified.

+

Please try again.

+

+ Note: If it still doesn't work, please reach out to us at support@infisical.com +

+
+
+ ); +}; + +export const Route = createFileRoute("/_restrict_login_signup/email-not-verified")({ + component: EmailNotVerifiedPage +}); diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/InitialStep/InitialStep.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/-components/InitialStep/InitialStep.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/InitialStep/InitialStep.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/-components/InitialStep/InitialStep.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/InitialStep/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/-components/InitialStep/index.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/InitialStep/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/-components/InitialStep/index.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/Login.utils.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/-components/Login.utils.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/Login.utils.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/-components/Login.utils.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/LoginSSO.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/-components/LoginSSO.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/LoginSSO.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/-components/LoginSSO.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/PasswordStep/PasswordStep.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/-components/PasswordStep/PasswordStep.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/PasswordStep/PasswordStep.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/-components/PasswordStep/PasswordStep.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/PasswordStep/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/-components/PasswordStep/index.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/PasswordStep/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/-components/PasswordStep/index.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/SSOStep/SSOStep.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/-components/SSOStep/SSOStep.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/SSOStep/SSOStep.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/-components/SSOStep/SSOStep.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/SSOStep/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/-components/SSOStep/index.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/SSOStep/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/-components/SSOStep/index.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/-components/index.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/-components/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/-components/index.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/index.tsx similarity index 96% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/index.tsx index 4e1f74e80..36a1a0ab2 100644 --- a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/index.tsx +++ b/frontend-v2/src/routes/_restrict_login_signup/login/index.tsx @@ -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 }); diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/ldap/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/ldap/index.tsx similarity index 98% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/ldap/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/ldap/index.tsx index 588352259..a004cff7d 100644 --- a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/ldap/index.tsx +++ b/frontend-v2/src/routes/_restrict_login_signup/login/ldap/index.tsx @@ -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 }); diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/provider/error.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/provider/error.tsx similarity index 74% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/provider/error.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/provider/error.tsx index 9ebad83c4..37212eb07 100644 --- a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/provider/error.tsx +++ b/frontend-v2/src/routes/_restrict_login_signup/login/provider/error.tsx @@ -10,6 +10,6 @@ const LoginProviderError = () => { return
; }; -export const Route = createFileRoute("/_authenticate/_restrict_login_signup/login/provider/error")({ +export const Route = createFileRoute("/_restrict_login_signup/login/provider/error")({ component: LoginProviderError }); diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/provider/success.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/provider/success.tsx similarity index 80% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/provider/success.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/provider/success.tsx index 14394aeed..55c491753 100644 --- a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/provider/success.tsx +++ b/frontend-v2/src/routes/_restrict_login_signup/login/provider/success.tsx @@ -14,8 +14,6 @@ const LoginProviderSuccess = () => { return
; }; -export const Route = createFileRoute( - "/_authenticate/_restrict_login_signup/login/provider/success" -)({ +export const Route = createFileRoute("/_restrict_login_signup/login/provider/success")({ component: LoginProviderSuccess }); diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/select-organization/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/select-organization/index.tsx similarity index 98% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/select-organization/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/select-organization/index.tsx index 35cd23bf7..0844a71e9 100644 --- a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/select-organization/index.tsx +++ b/frontend-v2/src/routes/_restrict_login_signup/login/select-organization/index.tsx @@ -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 }); diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/sso/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/login/sso/index.tsx similarity index 95% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/sso/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/login/sso/index.tsx index 130df38c3..46190472d 100644 --- a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/login/sso/index.tsx +++ b/frontend-v2/src/routes/_restrict_login_signup/login/sso/index.tsx @@ -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 }); diff --git a/frontend-v2/src/routes/_restrict_login_signup/password-reset.tsx b/frontend-v2/src/routes/_restrict_login_signup/password-reset.tsx new file mode 100644 index 000000000..696ce5e6b --- /dev/null +++ b/frontend-v2/src/routes/_restrict_login_signup/password-reset.tsx @@ -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) => { + 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) => { + 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 = ( +
+

+ Confirm your email +

+ verify email +
+ +
+
+ ); + + // Input backup key + const stepInputBackupKey = ( +
+

+ Enter your backup key +

+
+

+ You can find it in your emergency kit. You had to download the emergency kit during + signup. +

+
+
+ +
+
+
+ +
+
+
+ ); + + // Enter new password + const stepEnterNewPassword = ( +
+

+ Enter new password +

+
+

+ Make sure you save it somewhere safe. +

+
+
+ { + 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" + /> +
+ {passwordErrorTooShort || + passwordErrorTooLong || + passwordErrorNoLetterChar || + passwordErrorNoNumOrSpecialChar || + passwordErrorRepeatedChar || + passwordErrorEscapeChar || + passwordErrorLowEntropy || + passwordErrorBreached ? ( +
+
Password should contain:
+
+ {passwordErrorTooShort ? ( + + ) : ( + + )} +
+ at least 14 characters +
+
+
+ {passwordErrorTooLong ? ( + + ) : ( + + )} +
+ at most 100 characters +
+
+
+ {passwordErrorNoLetterChar ? ( + + ) : ( + + )} +
+ at least 1 letter character +
+
+
+ {passwordErrorNoNumOrSpecialChar ? ( + + ) : ( + + )} +
+ at least 1 number or special character +
+
+
+ {passwordErrorRepeatedChar ? ( + + ) : ( + + )} +
+ at most 3 repeated, consecutive characters +
+
+
+ {passwordErrorEscapeChar ? ( + + ) : ( + + )} +
+ No escape characters allowed. +
+
+
+ {passwordErrorLowEntropy ? ( + + ) : ( + + )} +
+ Password contains personal info. +
+
+
+ {passwordErrorBreached ? ( + + ) : ( + + )} +
+ Password was found in a data breach. +
+
+
+ ) : ( +
+ )} +
+
+ +
+
+ + ); + + return ( +
+ {step === 1 && stepConfirmEmail} + {step === 2 && stepInputBackupKey} + {step === 3 && stepEnterNewPassword} +
+ ); +}; + +export const Route = createFileRoute("/_restrict_login_signup/password-reset")({ + component: PasswordResetPage +}); diff --git a/frontend-v2/src/routes/_restrict_login_signup/requestnewinvite.tsx b/frontend-v2/src/routes/_restrict_login_signup/requestnewinvite.tsx new file mode 100644 index 000000000..62e8dfa05 --- /dev/null +++ b/frontend-v2/src/routes/_restrict_login_signup/requestnewinvite.tsx @@ -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 ( +
+ + Request a New Invite + + +
+

Oops, your invite has expired.

+

Ask your admin for a new one.

+

+ Note: If it still + doesn't work, please reach out to us at support@infisical.com +

+
+ invitation expired illustration +
+
+
+ ); +}; + +export const Route = createFileRoute("/_restrict_login_signup/requestnewinvite")({ + component: RequestNewInvitePage +}); diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/signup/index.tsx similarity index 98% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/signup/index.tsx index 7a9911b3d..94c28c372 100644 --- a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/index.tsx +++ b/frontend-v2/src/routes/_restrict_login_signup/signup/index.tsx @@ -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); diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/BackupPDFStep/BackupPDFStep.tsx b/frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/BackupPDFStep/BackupPDFStep.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/BackupPDFStep/BackupPDFStep.tsx rename to frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/BackupPDFStep/BackupPDFStep.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/BackupPDFStep/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/BackupPDFStep/index.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/BackupPDFStep/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/BackupPDFStep/index.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/EmailConfirmationStep/EmailConfirmationStep.tsx b/frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/EmailConfirmationStep/EmailConfirmationStep.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/EmailConfirmationStep/EmailConfirmationStep.tsx rename to frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/EmailConfirmationStep/EmailConfirmationStep.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/EmailConfirmationStep/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/EmailConfirmationStep/index.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/EmailConfirmationStep/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/EmailConfirmationStep/index.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/UserInfoSSOStep/UserInfoSSOStep.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/UserInfoSSOStep/UserInfoSSOStep.tsx rename to frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/UserInfoSSOStep/UserInfoSSOStep.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/UserInfoSSOStep/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/UserInfoSSOStep/index.tsx similarity index 100% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/-components/UserInfoSSOStep/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/signup/sso/-components/UserInfoSSOStep/index.tsx diff --git a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/index.tsx b/frontend-v2/src/routes/_restrict_login_signup/signup/sso/index.tsx similarity index 96% rename from frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/index.tsx rename to frontend-v2/src/routes/_restrict_login_signup/signup/sso/index.tsx index 80d877fdc..94fb17b82 100644 --- a/frontend-v2/src/routes/_authenticate/_restrict_login_signup/signup/sso/index.tsx +++ b/frontend-v2/src/routes/_restrict_login_signup/signup/sso/index.tsx @@ -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 }); diff --git a/frontend-v2/src/routes/_restrict_login_signup/signupinvite.tsx b/frontend-v2/src/routes/_restrict_login_signup/signupinvite.tsx new file mode 100644 index 000000000..2405bffce --- /dev/null +++ b/frontend-v2/src/routes/_restrict_login_signup/signupinvite.tsx @@ -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({}); + + 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 = ( +
+

+ Confirm your email +

+ verify email +
+ +
+
+ ); + + // Because this is the invite signup - we directly go to the last step of signup (email is already verified) + const main = ( +
+

+ Almost there! +

+
+ +
+
+ +
+
+ { + 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 && ( +
+
Password should contain at least:
+ {Object.keys(errors).map((key) => { + if (errors[key as keyof Errors]) { + return ( +
+
+ +
+

{errors[key as keyof Errors]}

+
+ ); + } + + return null; + })} +
+ )} +
+
+ +
+
+ ); + + // Step 4 of the sign up process (download the emergency kit pdf) + const step4 = ( +
+

+ Save your Emergency Kit +

+
+
+ If you get locked out of your account, your Emergency Kit is the only way to sign in. +
+
We recommend you download it and keep it somewhere safe.
+
+
+ + It contains your Secret Key which we cannot access or recover for you if you lose it. +
+
+ +
+
+ ); + + return ( +
+ + Sign Up + + + {shouldShowMfa ? ( + toggleShowMfa.off()} + /> + ) : ( + <> + +
+ Infisical Logo +
+ + {step === 1 ? stepConfirmEmail : step === 2 ? main : step4} + + )} +
+ ); +}; + +export const Route = createFileRoute("/_restrict_login_signup/signupinvite")({ + component: SignupInvitePage +}); diff --git a/frontend-v2/src/routes/_restrict_login_signup/verify-email.tsx b/frontend-v2/src/routes/_restrict_login_signup/verify-email.tsx new file mode 100644 index 000000000..c79d28d13 --- /dev/null +++ b/frontend-v2/src/routes/_restrict_login_signup/verify-email.tsx @@ -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) => { + e.preventDefault(); + setLoading(true); + + if (serverDetails?.emailConfigured) { + sendVerificationEmail(); + } else { + handlePopUpOpen("setUpEmail"); + setLoading(false); + } + }; + + return ( +
+ + Login + + + + + + +
+ long logo +
+ + {step === 1 && ( +
+

+ Forgot your password? +

+
+

+ You will need your emergency kit. Enter your email to start account recovery. +

+
+
+ +
+
+
+ +
+
+
+ )} + {step === 2 && ( +
+

+ Look for an email in your inbox. +

+
+

+ An email with instructions has been sent to {email}. +

+
+
+ )} + + handlePopUpToggle("setUpEmail", isOpen)} + /> +
+ ); +}; + +export const Route = createFileRoute("/_restrict_login_signup/verify-email")({ + component: VerifyEmailPage +});