mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 18:28:12 +00:00
feat: base for signup pages completed
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { useAddUsersToOrg } from "@app/hooks/api";
|
import { useAddUsersToOrg } from "@app/hooks/api";
|
||||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
@@ -13,7 +13,7 @@ import { Button, EmailServiceSetupModal } from "../v2";
|
|||||||
*/
|
*/
|
||||||
export default function TeamInviteStep(): JSX.Element {
|
export default function TeamInviteStep(): JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const navigate = useNavigate();
|
||||||
const [emails, setEmails] = useState("");
|
const [emails, setEmails] = useState("");
|
||||||
const { data: serverDetails } = useFetchServerStatus();
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ export default function TeamInviteStep(): JSX.Element {
|
|||||||
|
|
||||||
// Redirect user to the getting started page
|
// Redirect user to the getting started page
|
||||||
const redirectToHome = async () => {
|
const redirectToHome = async () => {
|
||||||
router.push(`/org/${localStorage.getItem("orgData.id")}/overview`);
|
navigate({ to: `/org/${localStorage.getItem("orgData.id")}/overview` });
|
||||||
};
|
};
|
||||||
|
|
||||||
const inviteUsers = async ({ emails: inviteEmails }: { emails: string }) => {
|
const inviteUsers = async ({ emails: inviteEmails }: { emails: string }) => {
|
||||||
@@ -48,19 +48,19 @@ export default function TeamInviteStep(): JSX.Element {
|
|||||||
<p className="mb-6 mt-4 flex justify-center text-center text-bunker-400 md:mx-8">
|
<p className="mb-6 mt-4 flex justify-center text-center text-bunker-400 md:mx-8">
|
||||||
{t("signup.step5-subtitle")}
|
{t("signup.step5-subtitle")}
|
||||||
</p>
|
</p>
|
||||||
<div className="mx-auto mb-6 w-max rounded-xl border border-mineshaft-500 bg-mineshaft-800 px-8 pt-6 pb-4 drop-shadow-xl">
|
<div className="mx-auto mb-6 w-max rounded-xl border border-mineshaft-500 bg-mineshaft-800 px-8 pb-4 pt-6 drop-shadow-xl">
|
||||||
<div>
|
<div>
|
||||||
<div className="pl-1 pb-1 text-sm font-medium text-bunker-300">
|
<div className="pb-1 pl-1 text-sm font-medium text-bunker-300">
|
||||||
<span>Emails</span>
|
<span>Emails</span>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
className="h-20 w-full min-w-[30rem] rounded-md border border-mineshaft-500 bg-mineshaft-900/70 py-1 px-2 text-sm text-bunker-300 outline-none ring-primary-800 ring-opacity-70 placeholder:text-bunker-400 focus:ring-2"
|
className="h-20 w-full min-w-[30rem] rounded-md border border-mineshaft-500 bg-mineshaft-900/70 px-2 py-1 text-sm text-bunker-300 outline-none ring-primary-800 ring-opacity-70 placeholder:text-bunker-400 focus:ring-2"
|
||||||
value={emails}
|
value={emails}
|
||||||
onChange={(e) => setEmails(e.target.value)}
|
onChange={(e) => setEmails(e.target.value)}
|
||||||
placeholder="[email protected], [email protected]..."
|
placeholder="[email protected], [email protected]..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto mt-2 flex w-full flex-row items-end justify-end text-sm md:mt-4 md:mb-2 md:min-w-[30rem] md:max-w-md">
|
<div className="mx-auto mt-2 flex w-full flex-row items-end justify-end text-sm md:mb-2 md:mt-4 md:min-w-[30rem] md:max-w-md">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (serverDetails?.emailConfigured) {
|
if (serverDetails?.emailConfigured) {
|
||||||
@@ -84,7 +84,7 @@ export default function TeamInviteStep(): JSX.Element {
|
|||||||
onOpenChange={(isOpen) => handlePopUpToggle("setUpEmail", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("setUpEmail", isOpen)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-28 mx-auto mt-4 mb-2 flex max-h-24 min-w-[20rem] max-w-max flex-row items-center justify-center px-4 text-lg md:p-2">
|
<div className="mx-auto mb-2 mt-4 flex max-h-24 min-w-[20rem] max-w-max flex-row items-center justify-center px-4 text-lg md:p-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={redirectToHome}
|
onClick={redirectToHome}
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export default function UserInfoStep({
|
|||||||
password
|
password
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
client.createVerifier(async (err: any, result: { salt: string; verifier: string }) => {
|
client.createVerifier(async (_err: any, result: { salt: string; verifier: string }) => {
|
||||||
try {
|
try {
|
||||||
// TODO: moduralize into KeyService
|
// TODO: moduralize into KeyService
|
||||||
const derivedKey = await deriveArgonKey({
|
const derivedKey = await deriveArgonKey({
|
||||||
@@ -224,7 +224,7 @@ export default function UserInfoStep({
|
|||||||
{t("signup.step3-message")}
|
{t("signup.step3-message")}
|
||||||
</p>
|
</p>
|
||||||
<div className="mx-auto mb-36 h-full w-max rounded-xl py-6 md:mb-16 md:border md:border-mineshaft-600 md:bg-mineshaft-800 md:px-8">
|
<div className="mx-auto mb-36 h-full w-max rounded-xl py-6 md:mb-16 md:border md:border-mineshaft-600 md:bg-mineshaft-800 md:px-8">
|
||||||
<div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
|
<div className="relative z-0 flex w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
|
||||||
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
|
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
|
||||||
Your Name
|
Your Name
|
||||||
</p>
|
</p>
|
||||||
@@ -242,7 +242,7 @@ export default function UserInfoStep({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
|
<div className="relative z-0 flex w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
|
||||||
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
|
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
|
||||||
Organization Name
|
Organization Name
|
||||||
</p>
|
</p>
|
||||||
@@ -260,7 +260,7 @@ export default function UserInfoStep({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
|
<div className="relative z-0 flex w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
|
||||||
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
|
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
|
||||||
Where did you hear about us? <span className="font-light">(optional)</span>
|
Where did you hear about us? <span className="font-light">(optional)</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -271,7 +271,7 @@ export default function UserInfoStep({
|
|||||||
className="h-12"
|
className="h-12"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex max-h-60 w-1/4 w-full min-w-[20rem] flex-col items-center justify-center rounded-lg py-2 lg:w-1/6">
|
<div className="mt-2 flex max-h-60 w-full min-w-[20rem] flex-col items-center justify-center rounded-lg py-2 lg:w-1/6">
|
||||||
<InputField
|
<InputField
|
||||||
label={t("section.password.password")}
|
label={t("section.password.password")}
|
||||||
onChangeHandler={async (pass: string) => {
|
onChangeHandler={async (pass: string) => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
|
|
||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
import { setAuthToken } from "@app/hooks/api/reactQuery";
|
import { setAuthToken } from "../reactQuery";
|
||||||
|
|
||||||
import { organizationKeys } from "../organization/queries";
|
import { organizationKeys } from "../organization/queries";
|
||||||
import { workspaceKeys } from "../workspace";
|
import { workspaceKeys } from "../workspace";
|
||||||
@@ -148,7 +148,7 @@ export const useCompleteAccountSignup = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useSendMfaToken = () => {
|
export const useSendMfaToken = () => {
|
||||||
return useMutation<object, object, SendMfaTokenDTO>({
|
return useMutation<{}, {}, SendMfaTokenDTO>({
|
||||||
mutationFn: async ({ email }) => {
|
mutationFn: async ({ email }) => {
|
||||||
const { data } = await apiRequest.post("/api/v2/auth/mfa/send", { email });
|
const { data } = await apiRequest.post("/api/v2/auth/mfa/send", { email });
|
||||||
return data;
|
return data;
|
||||||
@@ -175,7 +175,7 @@ export const verifyMfaToken = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useVerifyMfaToken = () => {
|
export const useVerifyMfaToken = () => {
|
||||||
return useMutation<VerifyMfaTokenRes, object, VerifyMfaTokenDTO>({
|
return useMutation<VerifyMfaTokenRes, {}, VerifyMfaTokenDTO>({
|
||||||
mutationFn: async ({ email, mfaCode, mfaMethod }) => {
|
mutationFn: async ({ email, mfaCode, mfaMethod }) => {
|
||||||
return verifyMfaToken({
|
return verifyMfaToken({
|
||||||
email,
|
email,
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = {
|
|||||||
[IdentityAuthMethod.GCP_AUTH]: "GCP Auth",
|
[IdentityAuthMethod.GCP_AUTH]: "GCP Auth",
|
||||||
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth",
|
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth",
|
||||||
[IdentityAuthMethod.AZURE_AUTH]: "Azure Auth",
|
[IdentityAuthMethod.AZURE_AUTH]: "Azure Auth",
|
||||||
[IdentityAuthMethod.OIDC_AUTH]: "OIDC Auth"
|
[IdentityAuthMethod.OIDC_AUTH]: "OIDC Auth",
|
||||||
|
[IdentityAuthMethod.JWT_AUTH]: "JWT Auth"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,5 +5,11 @@ export enum IdentityAuthMethod {
|
|||||||
GCP_AUTH = "gcp-auth",
|
GCP_AUTH = "gcp-auth",
|
||||||
AWS_AUTH = "aws-auth",
|
AWS_AUTH = "aws-auth",
|
||||||
AZURE_AUTH = "azure-auth",
|
AZURE_AUTH = "azure-auth",
|
||||||
OIDC_AUTH = "oidc-auth"
|
OIDC_AUTH = "oidc-auth",
|
||||||
|
JWT_AUTH = "jwt-auth"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum IdentityJwtConfigurationType {
|
||||||
|
JWKS = "jwks",
|
||||||
|
STATIC = "static"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export {
|
|||||||
useAddIdentityAwsAuth,
|
useAddIdentityAwsAuth,
|
||||||
useAddIdentityAzureAuth,
|
useAddIdentityAzureAuth,
|
||||||
useAddIdentityGcpAuth,
|
useAddIdentityGcpAuth,
|
||||||
|
useAddIdentityJwtAuth,
|
||||||
useAddIdentityKubernetesAuth,
|
useAddIdentityKubernetesAuth,
|
||||||
useAddIdentityOidcAuth,
|
useAddIdentityOidcAuth,
|
||||||
useAddIdentityTokenAuth,
|
useAddIdentityTokenAuth,
|
||||||
@@ -15,6 +16,7 @@ export {
|
|||||||
useDeleteIdentityAwsAuth,
|
useDeleteIdentityAwsAuth,
|
||||||
useDeleteIdentityAzureAuth,
|
useDeleteIdentityAzureAuth,
|
||||||
useDeleteIdentityGcpAuth,
|
useDeleteIdentityGcpAuth,
|
||||||
|
useDeleteIdentityJwtAuth,
|
||||||
useDeleteIdentityKubernetesAuth,
|
useDeleteIdentityKubernetesAuth,
|
||||||
useDeleteIdentityOidcAuth,
|
useDeleteIdentityOidcAuth,
|
||||||
useDeleteIdentityTokenAuth,
|
useDeleteIdentityTokenAuth,
|
||||||
@@ -25,20 +27,24 @@ export {
|
|||||||
useUpdateIdentityAwsAuth,
|
useUpdateIdentityAwsAuth,
|
||||||
useUpdateIdentityAzureAuth,
|
useUpdateIdentityAzureAuth,
|
||||||
useUpdateIdentityGcpAuth,
|
useUpdateIdentityGcpAuth,
|
||||||
|
useUpdateIdentityJwtAuth,
|
||||||
useUpdateIdentityKubernetesAuth,
|
useUpdateIdentityKubernetesAuth,
|
||||||
useUpdateIdentityOidcAuth,
|
useUpdateIdentityOidcAuth,
|
||||||
useUpdateIdentityTokenAuth,
|
useUpdateIdentityTokenAuth,
|
||||||
useUpdateIdentityTokenAuthToken,
|
useUpdateIdentityTokenAuthToken,
|
||||||
useUpdateIdentityUniversalAuth} from "./mutations";
|
useUpdateIdentityUniversalAuth
|
||||||
|
} from "./mutations";
|
||||||
export {
|
export {
|
||||||
useGetIdentityAwsAuth,
|
useGetIdentityAwsAuth,
|
||||||
useGetIdentityAzureAuth,
|
useGetIdentityAzureAuth,
|
||||||
useGetIdentityById,
|
useGetIdentityById,
|
||||||
useGetIdentityGcpAuth,
|
useGetIdentityGcpAuth,
|
||||||
|
useGetIdentityJwtAuth,
|
||||||
useGetIdentityKubernetesAuth,
|
useGetIdentityKubernetesAuth,
|
||||||
useGetIdentityOidcAuth,
|
useGetIdentityOidcAuth,
|
||||||
useGetIdentityProjectMemberships,
|
useGetIdentityProjectMemberships,
|
||||||
useGetIdentityTokenAuth,
|
useGetIdentityTokenAuth,
|
||||||
useGetIdentityTokensTokenAuth,
|
useGetIdentityTokensTokenAuth,
|
||||||
useGetIdentityUniversalAuth,
|
useGetIdentityUniversalAuth,
|
||||||
useGetIdentityUniversalAuthClientSecrets} from "./queries";
|
useGetIdentityUniversalAuthClientSecrets
|
||||||
|
} from "./queries";
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
AddIdentityAwsAuthDTO,
|
AddIdentityAwsAuthDTO,
|
||||||
AddIdentityAzureAuthDTO,
|
AddIdentityAzureAuthDTO,
|
||||||
AddIdentityGcpAuthDTO,
|
AddIdentityGcpAuthDTO,
|
||||||
|
AddIdentityJwtAuthDTO,
|
||||||
AddIdentityKubernetesAuthDTO,
|
AddIdentityKubernetesAuthDTO,
|
||||||
AddIdentityOidcAuthDTO,
|
AddIdentityOidcAuthDTO,
|
||||||
AddIdentityTokenAuthDTO,
|
AddIdentityTokenAuthDTO,
|
||||||
@@ -22,6 +23,7 @@ import {
|
|||||||
DeleteIdentityAzureAuthDTO,
|
DeleteIdentityAzureAuthDTO,
|
||||||
DeleteIdentityDTO,
|
DeleteIdentityDTO,
|
||||||
DeleteIdentityGcpAuthDTO,
|
DeleteIdentityGcpAuthDTO,
|
||||||
|
DeleteIdentityJwtAuthDTO,
|
||||||
DeleteIdentityKubernetesAuthDTO,
|
DeleteIdentityKubernetesAuthDTO,
|
||||||
DeleteIdentityOidcAuthDTO,
|
DeleteIdentityOidcAuthDTO,
|
||||||
DeleteIdentityTokenAuthDTO,
|
DeleteIdentityTokenAuthDTO,
|
||||||
@@ -32,6 +34,7 @@ import {
|
|||||||
IdentityAwsAuth,
|
IdentityAwsAuth,
|
||||||
IdentityAzureAuth,
|
IdentityAzureAuth,
|
||||||
IdentityGcpAuth,
|
IdentityGcpAuth,
|
||||||
|
IdentityJwtAuth,
|
||||||
IdentityKubernetesAuth,
|
IdentityKubernetesAuth,
|
||||||
IdentityOidcAuth,
|
IdentityOidcAuth,
|
||||||
IdentityTokenAuth,
|
IdentityTokenAuth,
|
||||||
@@ -42,6 +45,7 @@ import {
|
|||||||
UpdateIdentityAzureAuthDTO,
|
UpdateIdentityAzureAuthDTO,
|
||||||
UpdateIdentityDTO,
|
UpdateIdentityDTO,
|
||||||
UpdateIdentityGcpAuthDTO,
|
UpdateIdentityGcpAuthDTO,
|
||||||
|
UpdateIdentityJwtAuthDTO,
|
||||||
UpdateIdentityKubernetesAuthDTO,
|
UpdateIdentityKubernetesAuthDTO,
|
||||||
UpdateIdentityOidcAuthDTO,
|
UpdateIdentityOidcAuthDTO,
|
||||||
UpdateIdentityTokenAuthDTO,
|
UpdateIdentityTokenAuthDTO,
|
||||||
@@ -518,6 +522,118 @@ export const useDeleteIdentityOidcAuth = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
export const useUpdateIdentityJwtAuth = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<IdentityJwtAuth, {}, UpdateIdentityJwtAuthDTO>({
|
||||||
|
mutationFn: async ({
|
||||||
|
identityId,
|
||||||
|
configurationType,
|
||||||
|
jwksUrl,
|
||||||
|
jwksCaCert,
|
||||||
|
publicKeys,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps,
|
||||||
|
boundIssuer,
|
||||||
|
boundAudiences,
|
||||||
|
boundClaims,
|
||||||
|
boundSubject
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
data: { identityJwtAuth }
|
||||||
|
} = await apiRequest.patch<{ identityJwtAuth: IdentityJwtAuth }>(
|
||||||
|
`/api/v1/auth/jwt-auth/identities/${identityId}`,
|
||||||
|
{
|
||||||
|
configurationType,
|
||||||
|
jwksUrl,
|
||||||
|
jwksCaCert,
|
||||||
|
publicKeys,
|
||||||
|
boundIssuer,
|
||||||
|
boundAudiences,
|
||||||
|
boundClaims,
|
||||||
|
boundSubject,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return identityJwtAuth;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { identityId, organizationId }) => {
|
||||||
|
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
|
||||||
|
queryClient.invalidateQueries(identitiesKeys.getIdentityById(identityId));
|
||||||
|
queryClient.invalidateQueries(identitiesKeys.getIdentityJwtAuth(identityId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAddIdentityJwtAuth = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<IdentityJwtAuth, {}, AddIdentityJwtAuthDTO>({
|
||||||
|
mutationFn: async ({
|
||||||
|
identityId,
|
||||||
|
configurationType,
|
||||||
|
jwksUrl,
|
||||||
|
jwksCaCert,
|
||||||
|
publicKeys,
|
||||||
|
boundIssuer,
|
||||||
|
boundAudiences,
|
||||||
|
boundClaims,
|
||||||
|
boundSubject,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
data: { identityJwtAuth }
|
||||||
|
} = await apiRequest.post<{ identityJwtAuth: IdentityJwtAuth }>(
|
||||||
|
`/api/v1/auth/jwt-auth/identities/${identityId}`,
|
||||||
|
{
|
||||||
|
configurationType,
|
||||||
|
jwksUrl,
|
||||||
|
jwksCaCert,
|
||||||
|
publicKeys,
|
||||||
|
boundIssuer,
|
||||||
|
boundAudiences,
|
||||||
|
boundClaims,
|
||||||
|
boundSubject,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return identityJwtAuth;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { identityId, organizationId }) => {
|
||||||
|
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
|
||||||
|
queryClient.invalidateQueries(identitiesKeys.getIdentityById(identityId));
|
||||||
|
queryClient.invalidateQueries(identitiesKeys.getIdentityJwtAuth(identityId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteIdentityJwtAuth = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<IdentityTokenAuth, {}, DeleteIdentityJwtAuthDTO>({
|
||||||
|
mutationFn: async ({ identityId }) => {
|
||||||
|
const {
|
||||||
|
data: { identityJwtAuth }
|
||||||
|
} = await apiRequest.delete(`/api/v1/auth/jwt-auth/identities/${identityId}`);
|
||||||
|
return identityJwtAuth;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { organizationId, identityId }) => {
|
||||||
|
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
|
||||||
|
queryClient.invalidateQueries(identitiesKeys.getIdentityById(identityId));
|
||||||
|
queryClient.invalidateQueries(identitiesKeys.getIdentityJwtAuth(identityId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useAddIdentityAzureAuth = () => {
|
export const useAddIdentityAzureAuth = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
IdentityAwsAuth,
|
IdentityAwsAuth,
|
||||||
IdentityAzureAuth,
|
IdentityAzureAuth,
|
||||||
IdentityGcpAuth,
|
IdentityGcpAuth,
|
||||||
|
IdentityJwtAuth,
|
||||||
IdentityKubernetesAuth,
|
IdentityKubernetesAuth,
|
||||||
IdentityMembership,
|
IdentityMembership,
|
||||||
IdentityMembershipOrg,
|
IdentityMembershipOrg,
|
||||||
@@ -29,6 +30,7 @@ export const identitiesKeys = {
|
|||||||
getIdentityAwsAuth: (identityId: string) => [{ identityId }, "identity-aws-auth"] as const,
|
getIdentityAwsAuth: (identityId: string) => [{ identityId }, "identity-aws-auth"] as const,
|
||||||
getIdentityAzureAuth: (identityId: string) => [{ identityId }, "identity-azure-auth"] as const,
|
getIdentityAzureAuth: (identityId: string) => [{ identityId }, "identity-azure-auth"] as const,
|
||||||
getIdentityTokenAuth: (identityId: string) => [{ identityId }, "identity-token-auth"] as const,
|
getIdentityTokenAuth: (identityId: string) => [{ identityId }, "identity-token-auth"] as const,
|
||||||
|
getIdentityJwtAuth: (identityId: string) => [{ identityId }, "identity-jwt-auth"] as const,
|
||||||
getIdentityTokensTokenAuth: (identityId: string) =>
|
getIdentityTokensTokenAuth: (identityId: string) =>
|
||||||
[{ identityId }, "identity-tokens-token-auth"] as const,
|
[{ identityId }, "identity-tokens-token-auth"] as const,
|
||||||
getIdentityProjectMemberships: (identityId: string) =>
|
getIdentityProjectMemberships: (identityId: string) =>
|
||||||
@@ -276,3 +278,30 @@ export const useGetIdentityOidcAuth = (
|
|||||||
enabled: Boolean(identityId) && (options?.enabled ?? true)
|
enabled: Boolean(identityId) && (options?.enabled ?? true)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetIdentityJwtAuth = (
|
||||||
|
identityId: string,
|
||||||
|
options?: UseQueryOptions<
|
||||||
|
IdentityJwtAuth,
|
||||||
|
unknown,
|
||||||
|
IdentityJwtAuth,
|
||||||
|
ReturnType<typeof identitiesKeys.getIdentityJwtAuth>
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: identitiesKeys.getIdentityJwtAuth(identityId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const {
|
||||||
|
data: { identityJwtAuth }
|
||||||
|
} = await apiRequest.get<{ identityJwtAuth: IdentityJwtAuth }>(
|
||||||
|
`/api/v1/auth/jwt-auth/identities/${identityId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return identityJwtAuth;
|
||||||
|
},
|
||||||
|
staleTime: 0,
|
||||||
|
cacheTime: 0,
|
||||||
|
...options,
|
||||||
|
enabled: Boolean(identityId) && (options?.enabled ?? true)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TOrgRole } from "../roles/types";
|
import { TOrgRole } from "../roles/types";
|
||||||
import { ProjectUserMembershipTemporaryMode, Workspace } from "../workspace/types";
|
import { ProjectUserMembershipTemporaryMode, Workspace } from "../workspace/types";
|
||||||
import { IdentityAuthMethod } from "./enums";
|
import { IdentityAuthMethod, IdentityJwtConfigurationType } from "./enums";
|
||||||
|
|
||||||
export type IdentityTrustedIp = {
|
export type IdentityTrustedIp = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -47,7 +47,7 @@ export type IdentityMembershipOrg = {
|
|||||||
export type IdentityMembership = {
|
export type IdentityMembership = {
|
||||||
id: string;
|
id: string;
|
||||||
identity: Identity;
|
identity: Identity;
|
||||||
project: Pick<Workspace, "id" | "name">;
|
project: Pick<Workspace, "id" | "name" | "type">;
|
||||||
roles: Array<
|
roles: Array<
|
||||||
{
|
{
|
||||||
id: string;
|
id: string;
|
||||||
@@ -446,6 +446,65 @@ export type DeleteIdentityTokenAuthDTO = {
|
|||||||
identityId: string;
|
identityId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type IdentityJwtAuth = {
|
||||||
|
identityId: string;
|
||||||
|
configurationType: IdentityJwtConfigurationType;
|
||||||
|
jwksUrl: string;
|
||||||
|
jwksCaCert: string;
|
||||||
|
publicKeys: string[];
|
||||||
|
boundIssuer: string;
|
||||||
|
boundAudiences: string;
|
||||||
|
boundClaims: Record<string, string>;
|
||||||
|
boundSubject: string;
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTrustedIps: IdentityTrustedIp[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AddIdentityJwtAuthDTO = {
|
||||||
|
organizationId: string;
|
||||||
|
identityId: string;
|
||||||
|
configurationType: string;
|
||||||
|
jwksUrl?: string;
|
||||||
|
jwksCaCert: string;
|
||||||
|
publicKeys?: string[];
|
||||||
|
boundIssuer: string;
|
||||||
|
boundAudiences: string;
|
||||||
|
boundClaims: Record<string, string>;
|
||||||
|
boundSubject: string;
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTrustedIps: {
|
||||||
|
ipAddress: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateIdentityJwtAuthDTO = {
|
||||||
|
organizationId: string;
|
||||||
|
identityId: string;
|
||||||
|
configurationType?: string;
|
||||||
|
jwksUrl?: string;
|
||||||
|
jwksCaCert?: string;
|
||||||
|
publicKeys?: string[];
|
||||||
|
boundIssuer?: string;
|
||||||
|
boundAudiences?: string;
|
||||||
|
boundClaims?: Record<string, string>;
|
||||||
|
boundSubject?: string;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: {
|
||||||
|
ipAddress: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeleteIdentityJwtAuthDTO = {
|
||||||
|
organizationId: string;
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type CreateTokenIdentityTokenAuthDTO = {
|
export type CreateTokenIdentityTokenAuthDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { workspaceKeys } from "../workspace";
|
import { workspaceKeys } from "../workspace";
|
||||||
|
import { ProjectType } from "../workspace/types";
|
||||||
|
|
||||||
export const useImportEnvKey = () => {
|
export const useImportEnvKey = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -20,9 +21,7 @@ export const useImportEnvKey = () => {
|
|||||||
"Content-Type": "multipart/form-data"
|
"Content-Type": "multipart/form-data"
|
||||||
},
|
},
|
||||||
onUploadProgress: (progressEvent) => {
|
onUploadProgress: (progressEvent) => {
|
||||||
const percentCompleted = progressEvent?.total
|
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||||||
? Math.round((progressEvent.loaded * 100) / progressEvent.total)
|
|
||||||
: 0;
|
|
||||||
console.log(`Upload Progress: ${percentCompleted}%`);
|
console.log(`Upload Progress: ${percentCompleted}%`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -33,7 +32,7 @@ export const useImportEnvKey = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { AxiosError } from "axios";
|
|||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
import { SessionStorageKeys } from "@app/const";
|
import { SessionStorageKeys } from "@app/const";
|
||||||
import { setAuthToken } from "@app/hooks/api/reactQuery";
|
|
||||||
|
|
||||||
import { APIKeyDataV2 } from "../apiKeys/types";
|
import { APIKeyDataV2 } from "../apiKeys/types";
|
||||||
import { MfaMethod } from "../auth/types";
|
import { MfaMethod } from "../auth/types";
|
||||||
import { TGroupWithProjectMemberships } from "../groups/types";
|
import { TGroupWithProjectMemberships } from "../groups/types";
|
||||||
|
import { setAuthToken } from "../reactQuery";
|
||||||
import { workspaceKeys } from "../workspace";
|
import { workspaceKeys } from "../workspace";
|
||||||
import { userKeys } from "./query-keys";
|
import { userKeys } from "./query-keys";
|
||||||
import {
|
import {
|
||||||
@@ -80,7 +80,7 @@ export const fetchUserProjectFavorites = async (orgId: string) => {
|
|||||||
export const useRenameUser = () => {
|
export const useRenameUser = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<object, object, RenameUserDTO>({
|
return useMutation<{}, {}, RenameUserDTO>({
|
||||||
mutationFn: ({ newName }) =>
|
mutationFn: ({ newName }) =>
|
||||||
apiRequest.patch("/api/v2/users/me/name", {
|
apiRequest.patch("/api/v2/users/me/name", {
|
||||||
firstName: newName?.split(" ")[0],
|
firstName: newName?.split(" ")[0],
|
||||||
@@ -152,7 +152,7 @@ export const useAddUsersToOrg = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return useMutation<Response, object, AddUserToOrgDTO>({
|
return useMutation<Response, {}, AddUserToOrgDTO>({
|
||||||
mutationFn: (dto) => {
|
mutationFn: (dto) => {
|
||||||
return apiRequest.post("/api/v1/invite-org/signup", dto);
|
return apiRequest.post("/api/v1/invite-org/signup", dto);
|
||||||
},
|
},
|
||||||
@@ -207,7 +207,7 @@ export const useGetOrgMembershipProjectMemberships = (
|
|||||||
export const useDeleteOrgMembership = () => {
|
export const useDeleteOrgMembership = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<object, object, DeletOrgMembershipDTO>({
|
return useMutation<{}, {}, DeletOrgMembershipDTO>({
|
||||||
mutationFn: ({ membershipId, orgId }) => {
|
mutationFn: ({ membershipId, orgId }) => {
|
||||||
return apiRequest.delete(`/api/v2/organizations/${orgId}/memberships/${membershipId}`);
|
return apiRequest.delete(`/api/v2/organizations/${orgId}/memberships/${membershipId}`);
|
||||||
},
|
},
|
||||||
@@ -220,7 +220,7 @@ export const useDeleteOrgMembership = () => {
|
|||||||
export const useDeactivateOrgMembership = () => {
|
export const useDeactivateOrgMembership = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<object, object, DeletOrgMembershipDTO>({
|
return useMutation<{}, {}, DeletOrgMembershipDTO>({
|
||||||
mutationFn: ({ membershipId, orgId }) => {
|
mutationFn: ({ membershipId, orgId }) => {
|
||||||
return apiRequest.post(
|
return apiRequest.post(
|
||||||
`/api/v2/organizations/${orgId}/memberships/${membershipId}/deactivate`
|
`/api/v2/organizations/${orgId}/memberships/${membershipId}/deactivate`
|
||||||
@@ -236,7 +236,7 @@ export const useDeactivateOrgMembership = () => {
|
|||||||
export const useUpdateOrgMembership = () => {
|
export const useUpdateOrgMembership = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<object, object, UpdateOrgMembershipDTO>({
|
return useMutation<{}, {}, UpdateOrgMembershipDTO>({
|
||||||
mutationFn: ({ organizationId, membershipId, role, isActive, metadata }) => {
|
mutationFn: ({ organizationId, membershipId, role, isActive, metadata }) => {
|
||||||
return apiRequest.patch(
|
return apiRequest.patch(
|
||||||
`/api/v2/organizations/${organizationId}/memberships/${membershipId}`,
|
`/api/v2/organizations/${organizationId}/memberships/${membershipId}`,
|
||||||
@@ -261,7 +261,7 @@ export const useUpdateOrgMembership = () => {
|
|||||||
|
|
||||||
export const useRegisterUserAction = () => {
|
export const useRegisterUserAction = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation<object, object, string>({
|
return useMutation<{}, {}, string>({
|
||||||
mutationFn: (action) => apiRequest.post("/api/v1/user-action", { action }),
|
mutationFn: (action) => apiRequest.post("/api/v1/user-action", { action }),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(userKeys.userAction);
|
queryClient.invalidateQueries(userKeys.userAction);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MfaMethod } from "../auth/types";
|
import { MfaMethod } from "../auth/types";
|
||||||
import { UserWsKeyPair } from "../keys/types";
|
import { UserWsKeyPair } from "../keys/types";
|
||||||
import { ProjectUserMembershipTemporaryMode } from "../workspace/types";
|
import { ProjectType, ProjectUserMembershipTemporaryMode } from "../workspace/types";
|
||||||
|
|
||||||
export enum AuthMethod {
|
export enum AuthMethod {
|
||||||
EMAIL = "email",
|
EMAIL = "email",
|
||||||
@@ -95,6 +95,7 @@ export type TWorkspaceUser = {
|
|||||||
project: {
|
project: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
type: ProjectType;
|
||||||
};
|
};
|
||||||
inviteEmail: string;
|
inviteEmail: string;
|
||||||
organization: string;
|
organization: string;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { apiRequest } from "@app/config/request";
|
|||||||
|
|
||||||
import { userKeys } from "../users/query-keys";
|
import { userKeys } from "../users/query-keys";
|
||||||
import { workspaceKeys } from "./query-keys";
|
import { workspaceKeys } from "./query-keys";
|
||||||
import { TUpdateWorkspaceGroupRoleDTO } from "./types";
|
import { ProjectType, TUpdateWorkspaceGroupRoleDTO } from "./types";
|
||||||
|
|
||||||
export const useAddGroupToWorkspace = () => {
|
export const useAddGroupToWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -83,7 +83,7 @@ export const useLeaveProject = () => {
|
|||||||
return apiRequest.delete(`/api/v1/workspace/${workspaceId}/leave`);
|
return apiRequest.delete(`/api/v1/workspace/${workspaceId}/leave`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -95,7 +95,7 @@ export const useMigrateProjectToV3 = () => {
|
|||||||
return apiRequest.post(`/api/v1/workspace/${workspaceId}/migrate-v3`);
|
return apiRequest.post(`/api/v1/workspace/${workspaceId}/migrate-v3`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
DeleteWorkspaceDTO,
|
DeleteWorkspaceDTO,
|
||||||
NameWorkspaceSecretsDTO,
|
NameWorkspaceSecretsDTO,
|
||||||
ProjectIdentityOrderBy,
|
ProjectIdentityOrderBy,
|
||||||
|
ProjectType,
|
||||||
TGetUpgradeProjectStatusDTO,
|
TGetUpgradeProjectStatusDTO,
|
||||||
TListProjectIdentitiesDTO,
|
TListProjectIdentitiesDTO,
|
||||||
ToggleAutoCapitalizationDTO,
|
ToggleAutoCapitalizationDTO,
|
||||||
@@ -82,7 +83,7 @@ export const useUpgradeProject = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -102,10 +103,11 @@ export const useGetUpgradeProjectStatus = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchUserWorkspaces = async (includeRoles?: boolean) => {
|
const fetchUserWorkspaces = async (includeRoles?: boolean, type?: ProjectType | "all") => {
|
||||||
const { data } = await apiRequest.get<{ workspaces: Workspace[] }>("/api/v1/workspace", {
|
const { data } = await apiRequest.get<{ workspaces: Workspace[] }>("/api/v1/workspace", {
|
||||||
params: {
|
params: {
|
||||||
includeRoles
|
includeRoles,
|
||||||
|
type
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return data.workspaces;
|
return data.workspaces;
|
||||||
@@ -139,8 +141,16 @@ export const useGetWorkspaceById = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetUserWorkspaces = (includeRoles?: boolean) =>
|
export const useGetUserWorkspaces = ({
|
||||||
useQuery(workspaceKeys.getAllUserWorkspace, () => fetchUserWorkspaces(includeRoles));
|
includeRoles,
|
||||||
|
type = "all"
|
||||||
|
}: {
|
||||||
|
includeRoles?: boolean;
|
||||||
|
type?: ProjectType | "all";
|
||||||
|
} = {}) =>
|
||||||
|
useQuery(workspaceKeys.getAllUserWorkspace(type || ""), () =>
|
||||||
|
fetchUserWorkspaces(includeRoles, type)
|
||||||
|
);
|
||||||
|
|
||||||
const fetchUserWorkspaceMemberships = async (orgId: string) => {
|
const fetchUserWorkspaceMemberships = async (orgId: string) => {
|
||||||
const { data } = await apiRequest.get<Record<string, Workspace[]>>(
|
const { data } = await apiRequest.get<Record<string, Workspace[]>>(
|
||||||
@@ -206,33 +216,26 @@ export const useGetWorkspaceIntegrations = (workspaceId: string) =>
|
|||||||
refetchInterval: 4000
|
refetchInterval: 4000
|
||||||
});
|
});
|
||||||
|
|
||||||
export const createWorkspace = ({
|
export const createWorkspace = (
|
||||||
projectName,
|
dto: CreateWorkspaceDTO
|
||||||
projectDescription,
|
): Promise<{ data: { project: Workspace } }> => {
|
||||||
kmsKeyId,
|
return apiRequest.post("/api/v2/workspace", dto);
|
||||||
template
|
|
||||||
}: CreateWorkspaceDTO): Promise<{ data: { project: Workspace } }> => {
|
|
||||||
return apiRequest.post("/api/v2/workspace", {
|
|
||||||
projectName,
|
|
||||||
projectDescription,
|
|
||||||
kmsKeyId,
|
|
||||||
template
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCreateWorkspace = () => {
|
export const useCreateWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{ data: { project: Workspace } }, {}, CreateWorkspaceDTO>({
|
return useMutation<{ data: { project: Workspace } }, {}, CreateWorkspaceDTO>({
|
||||||
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template }) =>
|
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template, type }) =>
|
||||||
createWorkspace({
|
createWorkspace({
|
||||||
projectName,
|
projectName,
|
||||||
projectDescription,
|
projectDescription,
|
||||||
kmsKeyId,
|
kmsKeyId,
|
||||||
template
|
template,
|
||||||
|
type
|
||||||
}),
|
}),
|
||||||
onSuccess: () => {
|
onSuccess: (dto) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.data.project.type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -240,15 +243,19 @@ export const useCreateWorkspace = () => {
|
|||||||
export const useUpdateProject = () => {
|
export const useUpdateProject = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, UpdateProjectDTO>({
|
return useMutation<Workspace, {}, UpdateProjectDTO>({
|
||||||
mutationFn: ({ projectID, newProjectName, newProjectDescription }) => {
|
mutationFn: async ({ projectID, newProjectName, newProjectDescription }) => {
|
||||||
return apiRequest.patch(`/api/v1/workspace/${projectID}`, {
|
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
|
||||||
name: newProjectName,
|
`/api/v1/workspace/${projectID}`,
|
||||||
description: newProjectDescription
|
{
|
||||||
});
|
name: newProjectName,
|
||||||
|
description: newProjectDescription
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data.workspace;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: (dto) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -256,13 +263,18 @@ export const useUpdateProject = () => {
|
|||||||
export const useToggleAutoCapitalization = () => {
|
export const useToggleAutoCapitalization = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, ToggleAutoCapitalizationDTO>({
|
return useMutation<Workspace, {}, ToggleAutoCapitalizationDTO>({
|
||||||
mutationFn: ({ workspaceID, state }) =>
|
mutationFn: async ({ workspaceID, state }) => {
|
||||||
apiRequest.post(`/api/v1/workspace/${workspaceID}/auto-capitalization`, {
|
const { data } = await apiRequest.post<{ workspace: Workspace }>(
|
||||||
autoCapitalization: state
|
`/api/v1/workspace/${workspaceID}/auto-capitalization`,
|
||||||
}),
|
{
|
||||||
onSuccess: () => {
|
autoCapitalization: state
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
}
|
||||||
|
);
|
||||||
|
return data.workspace;
|
||||||
|
},
|
||||||
|
onSuccess: (dto) => {
|
||||||
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -270,14 +282,15 @@ export const useToggleAutoCapitalization = () => {
|
|||||||
export const useUpdateWorkspaceVersionLimit = () => {
|
export const useUpdateWorkspaceVersionLimit = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, UpdatePitVersionLimitDTO>({
|
return useMutation<Workspace, {}, UpdatePitVersionLimitDTO>({
|
||||||
mutationFn: ({ projectSlug, pitVersionLimit }) => {
|
mutationFn: async ({ projectSlug, pitVersionLimit }) => {
|
||||||
return apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, {
|
const { data } = await apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, {
|
||||||
pitVersionLimit
|
pitVersionLimit
|
||||||
});
|
});
|
||||||
|
return data.workspace;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: (dto) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -285,14 +298,18 @@ export const useUpdateWorkspaceVersionLimit = () => {
|
|||||||
export const useUpdateWorkspaceAuditLogsRetention = () => {
|
export const useUpdateWorkspaceAuditLogsRetention = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, UpdateAuditLogsRetentionDTO>({
|
return useMutation<Workspace, {}, UpdateAuditLogsRetentionDTO>({
|
||||||
mutationFn: ({ projectSlug, auditLogsRetentionDays }) => {
|
mutationFn: async ({ projectSlug, auditLogsRetentionDays }) => {
|
||||||
return apiRequest.put(`/api/v1/workspace/${projectSlug}/audit-logs-retention`, {
|
const { data } = await apiRequest.put(
|
||||||
auditLogsRetentionDays
|
`/api/v1/workspace/${projectSlug}/audit-logs-retention`,
|
||||||
});
|
{
|
||||||
|
auditLogsRetentionDays
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data.workspace;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: (dto) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -300,12 +317,13 @@ export const useUpdateWorkspaceAuditLogsRetention = () => {
|
|||||||
export const useDeleteWorkspace = () => {
|
export const useDeleteWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<{}, {}, DeleteWorkspaceDTO>({
|
return useMutation<Workspace, {}, DeleteWorkspaceDTO>({
|
||||||
mutationFn: ({ workspaceID }) => {
|
mutationFn: async ({ workspaceID }) => {
|
||||||
return apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
|
const { data } = await apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
|
||||||
|
return data.workspace;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: (dto) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||||
queryClient.invalidateQueries(["org-admin-projects"]);
|
queryClient.invalidateQueries(["org-admin-projects"]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -322,7 +340,7 @@ export const useCreateWsEnvironment = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -339,7 +357,7 @@ export const useUpdateWsEnvironment = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -352,7 +370,7 @@ export const useDeleteWsEnvironment = () => {
|
|||||||
return apiRequest.delete(`/api/v1/workspace/${workspaceId}/environments/${id}`);
|
return apiRequest.delete(`/api/v1/workspace/${workspaceId}/environments/${id}`);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(ProjectType.SecretManager));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ export const workspaceKeys = {
|
|||||||
getWorkspaceMemberships: (orgId: string) => [{ orgId }, "workspace-memberships"],
|
getWorkspaceMemberships: (orgId: string) => [{ orgId }, "workspace-memberships"],
|
||||||
getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"],
|
getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"],
|
||||||
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
|
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
|
||||||
getAllUserWorkspace: ["workspaces"] as const,
|
getAllUserWorkspace: (type?: string) =>
|
||||||
|
type ? ["workspaces", { type }] : (["workspace"] as const),
|
||||||
getWorkspaceAuditLogs: (workspaceId: string) =>
|
getWorkspaceAuditLogs: (workspaceId: string) =>
|
||||||
[{ workspaceId }, "workspace-audit-logs"] as const,
|
[{ workspaceId }, "workspace-audit-logs"] as const,
|
||||||
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,
|
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }, "workspace-users"] as const,
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ export enum ProjectVersion {
|
|||||||
V3 = 3
|
V3 = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectType {
|
||||||
|
SecretManager = "secret-manager",
|
||||||
|
CertificateManager = "cert-manager",
|
||||||
|
KMS = "kms"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectUserMembershipTemporaryMode {
|
export enum ProjectUserMembershipTemporaryMode {
|
||||||
Relative = "relative"
|
Relative = "relative"
|
||||||
}
|
}
|
||||||
@@ -16,6 +22,7 @@ export type Workspace = {
|
|||||||
__v: number;
|
__v: number;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
type: ProjectType;
|
||||||
description?: string;
|
description?: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
version: ProjectVersion;
|
version: ProjectVersion;
|
||||||
@@ -59,6 +66,7 @@ export type CreateWorkspaceDTO = {
|
|||||||
projectDescription?: string;
|
projectDescription?: string;
|
||||||
kmsKeyId?: string;
|
kmsKeyId?: string;
|
||||||
template?: string;
|
template?: string;
|
||||||
|
type: ProjectType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateProjectDTO = {
|
export type UpdateProjectDTO = {
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
|
|
||||||
import { Route as rootRoute } from './routes/__root'
|
import { Route as rootRoute } from './routes/__root'
|
||||||
import { Route as IndexImport } from './routes/index'
|
import { Route as IndexImport } from './routes/index'
|
||||||
|
import { Route as SignupIndexImport } from './routes/signup/index'
|
||||||
import { Route as LoginIndexImport } from './routes/login/index'
|
import { Route as LoginIndexImport } from './routes/login/index'
|
||||||
|
import { Route as SignupSsoIndexImport } from './routes/signup/sso/index'
|
||||||
import { Route as LoginSsoIndexImport } from './routes/login/sso/index'
|
import { Route as LoginSsoIndexImport } from './routes/login/sso/index'
|
||||||
import { Route as LoginSelectOrganizationIndexImport } from './routes/login/select-organization/index'
|
import { Route as LoginSelectOrganizationIndexImport } from './routes/login/select-organization/index'
|
||||||
import { Route as LoginLdapIndexImport } from './routes/login/ldap/index'
|
import { Route as LoginLdapIndexImport } from './routes/login/ldap/index'
|
||||||
@@ -27,12 +29,24 @@ const IndexRoute = IndexImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const SignupIndexRoute = SignupIndexImport.update({
|
||||||
|
id: '/signup/',
|
||||||
|
path: '/signup/',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const LoginIndexRoute = LoginIndexImport.update({
|
const LoginIndexRoute = LoginIndexImport.update({
|
||||||
id: '/login/',
|
id: '/login/',
|
||||||
path: '/login/',
|
path: '/login/',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const SignupSsoIndexRoute = SignupSsoIndexImport.update({
|
||||||
|
id: '/signup/sso/',
|
||||||
|
path: '/signup/sso/',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const LoginSsoIndexRoute = LoginSsoIndexImport.update({
|
const LoginSsoIndexRoute = LoginSsoIndexImport.update({
|
||||||
id: '/login/sso/',
|
id: '/login/sso/',
|
||||||
path: '/login/sso/',
|
path: '/login/sso/',
|
||||||
@@ -82,6 +96,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof LoginIndexImport
|
preLoaderRoute: typeof LoginIndexImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/signup/': {
|
||||||
|
id: '/signup/'
|
||||||
|
path: '/signup'
|
||||||
|
fullPath: '/signup'
|
||||||
|
preLoaderRoute: typeof SignupIndexImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
'/login/provider/error': {
|
'/login/provider/error': {
|
||||||
id: '/login/provider/error'
|
id: '/login/provider/error'
|
||||||
path: '/login/provider/error'
|
path: '/login/provider/error'
|
||||||
@@ -117,6 +138,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof LoginSsoIndexImport
|
preLoaderRoute: typeof LoginSsoIndexImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/signup/sso/': {
|
||||||
|
id: '/signup/sso/'
|
||||||
|
path: '/signup/sso'
|
||||||
|
fullPath: '/signup/sso'
|
||||||
|
preLoaderRoute: typeof SignupSsoIndexImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,32 +153,38 @@ declare module '@tanstack/react-router' {
|
|||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/login': typeof LoginIndexRoute
|
'/login': typeof LoginIndexRoute
|
||||||
|
'/signup': typeof SignupIndexRoute
|
||||||
'/login/provider/error': typeof LoginProviderErrorRoute
|
'/login/provider/error': typeof LoginProviderErrorRoute
|
||||||
'/login/provider/success': typeof LoginProviderSuccessRoute
|
'/login/provider/success': typeof LoginProviderSuccessRoute
|
||||||
'/login/ldap': typeof LoginLdapIndexRoute
|
'/login/ldap': typeof LoginLdapIndexRoute
|
||||||
'/login/select-organization': typeof LoginSelectOrganizationIndexRoute
|
'/login/select-organization': typeof LoginSelectOrganizationIndexRoute
|
||||||
'/login/sso': typeof LoginSsoIndexRoute
|
'/login/sso': typeof LoginSsoIndexRoute
|
||||||
|
'/signup/sso': typeof SignupSsoIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/login': typeof LoginIndexRoute
|
'/login': typeof LoginIndexRoute
|
||||||
|
'/signup': typeof SignupIndexRoute
|
||||||
'/login/provider/error': typeof LoginProviderErrorRoute
|
'/login/provider/error': typeof LoginProviderErrorRoute
|
||||||
'/login/provider/success': typeof LoginProviderSuccessRoute
|
'/login/provider/success': typeof LoginProviderSuccessRoute
|
||||||
'/login/ldap': typeof LoginLdapIndexRoute
|
'/login/ldap': typeof LoginLdapIndexRoute
|
||||||
'/login/select-organization': typeof LoginSelectOrganizationIndexRoute
|
'/login/select-organization': typeof LoginSelectOrganizationIndexRoute
|
||||||
'/login/sso': typeof LoginSsoIndexRoute
|
'/login/sso': typeof LoginSsoIndexRoute
|
||||||
|
'/signup/sso': typeof SignupSsoIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRoute
|
__root__: typeof rootRoute
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/login/': typeof LoginIndexRoute
|
'/login/': typeof LoginIndexRoute
|
||||||
|
'/signup/': typeof SignupIndexRoute
|
||||||
'/login/provider/error': typeof LoginProviderErrorRoute
|
'/login/provider/error': typeof LoginProviderErrorRoute
|
||||||
'/login/provider/success': typeof LoginProviderSuccessRoute
|
'/login/provider/success': typeof LoginProviderSuccessRoute
|
||||||
'/login/ldap/': typeof LoginLdapIndexRoute
|
'/login/ldap/': typeof LoginLdapIndexRoute
|
||||||
'/login/select-organization/': typeof LoginSelectOrganizationIndexRoute
|
'/login/select-organization/': typeof LoginSelectOrganizationIndexRoute
|
||||||
'/login/sso/': typeof LoginSsoIndexRoute
|
'/login/sso/': typeof LoginSsoIndexRoute
|
||||||
|
'/signup/sso/': typeof SignupSsoIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
@@ -158,50 +192,60 @@ export interface FileRouteTypes {
|
|||||||
fullPaths:
|
fullPaths:
|
||||||
| '/'
|
| '/'
|
||||||
| '/login'
|
| '/login'
|
||||||
|
| '/signup'
|
||||||
| '/login/provider/error'
|
| '/login/provider/error'
|
||||||
| '/login/provider/success'
|
| '/login/provider/success'
|
||||||
| '/login/ldap'
|
| '/login/ldap'
|
||||||
| '/login/select-organization'
|
| '/login/select-organization'
|
||||||
| '/login/sso'
|
| '/login/sso'
|
||||||
|
| '/signup/sso'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
| '/login'
|
| '/login'
|
||||||
|
| '/signup'
|
||||||
| '/login/provider/error'
|
| '/login/provider/error'
|
||||||
| '/login/provider/success'
|
| '/login/provider/success'
|
||||||
| '/login/ldap'
|
| '/login/ldap'
|
||||||
| '/login/select-organization'
|
| '/login/select-organization'
|
||||||
| '/login/sso'
|
| '/login/sso'
|
||||||
|
| '/signup/sso'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
| '/login/'
|
| '/login/'
|
||||||
|
| '/signup/'
|
||||||
| '/login/provider/error'
|
| '/login/provider/error'
|
||||||
| '/login/provider/success'
|
| '/login/provider/success'
|
||||||
| '/login/ldap/'
|
| '/login/ldap/'
|
||||||
| '/login/select-organization/'
|
| '/login/select-organization/'
|
||||||
| '/login/sso/'
|
| '/login/sso/'
|
||||||
|
| '/signup/sso/'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
LoginIndexRoute: typeof LoginIndexRoute
|
LoginIndexRoute: typeof LoginIndexRoute
|
||||||
|
SignupIndexRoute: typeof SignupIndexRoute
|
||||||
LoginProviderErrorRoute: typeof LoginProviderErrorRoute
|
LoginProviderErrorRoute: typeof LoginProviderErrorRoute
|
||||||
LoginProviderSuccessRoute: typeof LoginProviderSuccessRoute
|
LoginProviderSuccessRoute: typeof LoginProviderSuccessRoute
|
||||||
LoginLdapIndexRoute: typeof LoginLdapIndexRoute
|
LoginLdapIndexRoute: typeof LoginLdapIndexRoute
|
||||||
LoginSelectOrganizationIndexRoute: typeof LoginSelectOrganizationIndexRoute
|
LoginSelectOrganizationIndexRoute: typeof LoginSelectOrganizationIndexRoute
|
||||||
LoginSsoIndexRoute: typeof LoginSsoIndexRoute
|
LoginSsoIndexRoute: typeof LoginSsoIndexRoute
|
||||||
|
SignupSsoIndexRoute: typeof SignupSsoIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
LoginIndexRoute: LoginIndexRoute,
|
LoginIndexRoute: LoginIndexRoute,
|
||||||
|
SignupIndexRoute: SignupIndexRoute,
|
||||||
LoginProviderErrorRoute: LoginProviderErrorRoute,
|
LoginProviderErrorRoute: LoginProviderErrorRoute,
|
||||||
LoginProviderSuccessRoute: LoginProviderSuccessRoute,
|
LoginProviderSuccessRoute: LoginProviderSuccessRoute,
|
||||||
LoginLdapIndexRoute: LoginLdapIndexRoute,
|
LoginLdapIndexRoute: LoginLdapIndexRoute,
|
||||||
LoginSelectOrganizationIndexRoute: LoginSelectOrganizationIndexRoute,
|
LoginSelectOrganizationIndexRoute: LoginSelectOrganizationIndexRoute,
|
||||||
LoginSsoIndexRoute: LoginSsoIndexRoute,
|
LoginSsoIndexRoute: LoginSsoIndexRoute,
|
||||||
|
SignupSsoIndexRoute: SignupSsoIndexRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const routeTree = rootRoute
|
export const routeTree = rootRoute
|
||||||
@@ -216,11 +260,13 @@ export const routeTree = rootRoute
|
|||||||
"children": [
|
"children": [
|
||||||
"/",
|
"/",
|
||||||
"/login/",
|
"/login/",
|
||||||
|
"/signup/",
|
||||||
"/login/provider/error",
|
"/login/provider/error",
|
||||||
"/login/provider/success",
|
"/login/provider/success",
|
||||||
"/login/ldap/",
|
"/login/ldap/",
|
||||||
"/login/select-organization/",
|
"/login/select-organization/",
|
||||||
"/login/sso/"
|
"/login/sso/",
|
||||||
|
"/signup/sso/"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/": {
|
"/": {
|
||||||
@@ -229,6 +275,9 @@ export const routeTree = rootRoute
|
|||||||
"/login/": {
|
"/login/": {
|
||||||
"filePath": "login/index.tsx"
|
"filePath": "login/index.tsx"
|
||||||
},
|
},
|
||||||
|
"/signup/": {
|
||||||
|
"filePath": "signup/index.tsx"
|
||||||
|
},
|
||||||
"/login/provider/error": {
|
"/login/provider/error": {
|
||||||
"filePath": "login/provider/error.tsx"
|
"filePath": "login/provider/error.tsx"
|
||||||
},
|
},
|
||||||
@@ -243,6 +292,9 @@ export const routeTree = rootRoute
|
|||||||
},
|
},
|
||||||
"/login/sso/": {
|
"/login/sso/": {
|
||||||
"filePath": "login/sso/index.tsx"
|
"filePath": "login/sso/index.tsx"
|
||||||
|
},
|
||||||
|
"/signup/sso/": {
|
||||||
|
"filePath": "signup/sso/index.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import CodeInputStep from "@app/components/signup/CodeInputStep";
|
||||||
|
import DownloadBackupPDF from "@app/components/signup/DonwloadBackupPDFStep";
|
||||||
|
import EnterEmailStep from "@app/components/signup/EnterEmailStep";
|
||||||
|
import InitialSignupStep from "@app/components/signup/InitialSignupStep";
|
||||||
|
import TeamInviteStep from "@app/components/signup/TeamInviteStep";
|
||||||
|
import UserInfoStep from "@app/components/signup/UserInfoStep";
|
||||||
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
|
import { useServerConfig } from "@app/context";
|
||||||
|
import { useVerifySignupEmailVerificationCode } from "@app/hooks/api";
|
||||||
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns the signup page
|
||||||
|
*/
|
||||||
|
const SignUpPage = () => {
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [organizationName, setOrganizationName] = useState("");
|
||||||
|
const [attributionSource, setAttributionSource] = useState("");
|
||||||
|
const [code, setCode] = useState("123456");
|
||||||
|
const [codeError, setCodeError] = useState(false);
|
||||||
|
const [step, setStep] = useState(1);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
|
const [isSignupWithEmail, setIsSignupWithEmail] = useState(false);
|
||||||
|
const [isCodeInputCheckLoading, setIsCodeInputCheckLoading] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { mutateAsync } = useVerifySignupEmailVerificationCode();
|
||||||
|
const { config } = useServerConfig();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!config.allowSignUp) {
|
||||||
|
navigate({ to: "/login" });
|
||||||
|
}
|
||||||
|
}, [config.allowSignUp]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const tryAuth = async () => {
|
||||||
|
try {
|
||||||
|
const userOrgs = await fetchOrganizations();
|
||||||
|
navigate({ to: `/org/${userOrgs[0].id}/${ProjectType.SecretManager}/overview` });
|
||||||
|
} catch {
|
||||||
|
console.log("Error - Not logged in yet");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tryAuth();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Goes to the following step (out of 5) of the signup process.
|
||||||
|
* Step 1 is submitting your email
|
||||||
|
* Step 2 is Verifying your email with the code that you received
|
||||||
|
* Step 3 is asking the final info.
|
||||||
|
* Step 4 is downloading a backup pdf
|
||||||
|
* Step 5 is inviting users
|
||||||
|
*/
|
||||||
|
const incrementStep = async () => {
|
||||||
|
if (step === 1 || step === 3 || step === 4) {
|
||||||
|
setStep(step + 1);
|
||||||
|
} else if (step === 2) {
|
||||||
|
setIsCodeInputCheckLoading(true);
|
||||||
|
// Checking if the code matches the email.
|
||||||
|
try {
|
||||||
|
const { token } = await mutateAsync({ email, code });
|
||||||
|
SecurityClient.setSignupToken(token);
|
||||||
|
setStep(3);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setCodeError(true);
|
||||||
|
}
|
||||||
|
setIsCodeInputCheckLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// when email service is not configured, skip step 2 and 5
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (!serverDetails?.emailConfigured && step === 2) {
|
||||||
|
incrementStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serverDetails?.emailConfigured && step === 5) {
|
||||||
|
const userOrgs = await fetchOrganizations();
|
||||||
|
navigate({ to: `/org/${userOrgs[0].id}/${ProjectType.SecretManager}/overview` });
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [step]);
|
||||||
|
|
||||||
|
const renderView = (registerStep: number) => {
|
||||||
|
if (isSignupWithEmail && registerStep === 1) {
|
||||||
|
return <EnterEmailStep email={email} setEmail={setEmail} incrementStep={incrementStep} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSignupWithEmail && registerStep === 1) {
|
||||||
|
return <InitialSignupStep setIsSignupWithEmail={setIsSignupWithEmail} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registerStep === 2) {
|
||||||
|
return (
|
||||||
|
<CodeInputStep
|
||||||
|
email={email}
|
||||||
|
incrementStep={incrementStep}
|
||||||
|
setCode={setCode}
|
||||||
|
codeError={codeError}
|
||||||
|
isCodeInputCheckLoading={isCodeInputCheckLoading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registerStep === 3) {
|
||||||
|
return (
|
||||||
|
<UserInfoStep
|
||||||
|
incrementStep={incrementStep}
|
||||||
|
email={email}
|
||||||
|
password={password}
|
||||||
|
setPassword={setPassword}
|
||||||
|
name={name}
|
||||||
|
setName={setName}
|
||||||
|
organizationName={organizationName}
|
||||||
|
setOrganizationName={setOrganizationName}
|
||||||
|
attributionSource={attributionSource}
|
||||||
|
setAttributionSource={setAttributionSource}
|
||||||
|
providerAuthToken={undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registerStep === 4) {
|
||||||
|
return (
|
||||||
|
<DownloadBackupPDF
|
||||||
|
incrementStep={incrementStep}
|
||||||
|
email={email}
|
||||||
|
password={password}
|
||||||
|
name={name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverDetails?.emailConfigured) {
|
||||||
|
return <TeamInviteStep />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex max-h-screen min-h-screen flex-col justify-center overflow-y-auto bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28">
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: t("signup.title") })}</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
<meta property="og:image" content="/images/message.png" />
|
||||||
|
<meta property="og:title" content={t("signup.og-title") as string} />
|
||||||
|
<meta name="og:description" content={t("signup.og-description") as string} />
|
||||||
|
</Helmet>
|
||||||
|
<div className="mb-4 mt-20 flex justify-center">
|
||||||
|
<img src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical Logo" />
|
||||||
|
</div>
|
||||||
|
<form onSubmit={(e) => e.preventDefault()}>{renderView(step)}</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/signup/")({
|
||||||
|
component: SignUpPage
|
||||||
|
});
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey";
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
interface DownloadBackupPDFStepProps {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the step of the signup flow where the user downloads the backup pdf
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {function} obj.incrementStep - function that moves the user on to the next stage of signup
|
||||||
|
* @param {string} obj.email - user's email
|
||||||
|
* @param {string} obj.password - user's password
|
||||||
|
* @param {string} obj.name - user's name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const BackupPDFStep = ({ email, password, name }: DownloadBackupPDFStepProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto mb-36 flex h-full w-full flex-col items-center md:mb-16 md:px-6">
|
||||||
|
<p className="flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||||
|
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 text-2xl text-bunker-200" />
|
||||||
|
{t("signup.step4-message")}
|
||||||
|
</p>
|
||||||
|
<div className="text-md mt-8 flex w-full max-w-md flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 pb-2 text-center text-bunker-300 md:min-w-[24rem] lg:w-1/6">
|
||||||
|
<div className="m-2 mx-auto mt-4 flex w-full flex-row items-center rounded-md px-3 text-center text-bunker-300 md:mt-8 md:min-w-[23rem] lg:w-1/6">
|
||||||
|
<span className="mb-2">
|
||||||
|
{t("signup.step4-description1")} {t("signup.step4-description3")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto mb-2 mt-2 flex w-full flex-col items-center justify-center px-3 text-center text-sm md:mb-4 md:mt-4 md:min-w-[20rem] md:max-w-md md:text-left lg:w-1/6">
|
||||||
|
<div className="text-l w-full py-1 text-lg">
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
await issueBackupKey({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
personalName: name,
|
||||||
|
setBackupKeyError: () => {},
|
||||||
|
setBackupKeyIssued: () => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate({
|
||||||
|
to: `/org/${localStorage.getItem("orgData.id")}/${ProjectType.SecretManager}/overview`
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
isFullWidth
|
||||||
|
className="h-12"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
Download PDF{" "}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { BackupPDFStep } from "./BackupPDFStep";
|
||||||
+189
@@ -0,0 +1,189 @@
|
|||||||
|
// confirm email
|
||||||
|
// if same email exists, then trigger fn to merge automatically
|
||||||
|
import { useState } from "react";
|
||||||
|
import ReactCodeInput from "react-code-input";
|
||||||
|
|
||||||
|
import Error from "@app/components/basic/Error";
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { useSendEmailVerificationCode, useVerifyEmailVerificationCode } from "@app/hooks/api";
|
||||||
|
import { UserAliasType } from "@app/hooks/api/users/types";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
authType?: UserAliasType;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
organizationSlug: string;
|
||||||
|
setStep: (step: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The style for the verification code input
|
||||||
|
const props = {
|
||||||
|
inputStyle: {
|
||||||
|
fontFamily: "monospace",
|
||||||
|
margin: "4px",
|
||||||
|
MozAppearance: "textfield",
|
||||||
|
width: "55px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
fontSize: "24px",
|
||||||
|
height: "55px",
|
||||||
|
paddingLeft: "7",
|
||||||
|
backgroundColor: "#0d1117",
|
||||||
|
color: "white",
|
||||||
|
border: "1px solid #2d2f33",
|
||||||
|
textAlign: "center",
|
||||||
|
outlineColor: "#8ca542",
|
||||||
|
borderColor: "#2d2f33"
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
const propsPhone = {
|
||||||
|
inputStyle: {
|
||||||
|
fontFamily: "monospace",
|
||||||
|
margin: "4px",
|
||||||
|
MozAppearance: "textfield",
|
||||||
|
width: "40px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
fontSize: "24px",
|
||||||
|
height: "40px",
|
||||||
|
paddingLeft: "7",
|
||||||
|
backgroundColor: "#0d1117",
|
||||||
|
color: "white",
|
||||||
|
border: "1px solid #2d2f33",
|
||||||
|
textAlign: "center",
|
||||||
|
outlineColor: "#8ca542",
|
||||||
|
borderColor: "#2d2f33"
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const EmailConfirmationStep = ({
|
||||||
|
authType,
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
organizationSlug,
|
||||||
|
setStep
|
||||||
|
}: Props) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [code, setCode] = useState("");
|
||||||
|
const [codeError, setCodeError] = useState(false);
|
||||||
|
const [isResendingVerificationEmail] = useState(false);
|
||||||
|
const [isLoading] = useState(false);
|
||||||
|
|
||||||
|
const { mutateAsync: sendEmailVerificationCode } = useSendEmailVerificationCode();
|
||||||
|
const { mutateAsync: verifyEmailVerificationCode } = useVerifyEmailVerificationCode();
|
||||||
|
|
||||||
|
const checkCode = async () => {
|
||||||
|
try {
|
||||||
|
await verifyEmailVerificationCode({ username, code });
|
||||||
|
setCodeError(false);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully verified code",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (authType) {
|
||||||
|
case UserAliasType.SAML: {
|
||||||
|
window.open(`/api/v1/sso/redirect/saml2/organizations/${organizationSlug}`);
|
||||||
|
window.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UserAliasType.LDAP: {
|
||||||
|
navigate({ to: `/login/ldap?organizationSlug=${organizationSlug}` });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UserAliasType.OIDC: {
|
||||||
|
navigate({ to: `/api/v1/sso/oidc/login?orgSlug=${organizationSlug}` });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
setStep(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to verify code",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setCode("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const resendCode = async () => {
|
||||||
|
try {
|
||||||
|
await sendEmailVerificationCode(username);
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully resent code",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to resend code",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto h-full w-full pb-4 md:px-8">
|
||||||
|
<p className="text-md flex justify-center text-bunker-200">
|
||||||
|
We've sent a verification code to {email}
|
||||||
|
</p>
|
||||||
|
<div className="mx-auto hidden w-max min-w-[20rem] md:block">
|
||||||
|
<ReactCodeInput
|
||||||
|
name=""
|
||||||
|
inputMode="tel"
|
||||||
|
type="text"
|
||||||
|
fields={6}
|
||||||
|
onChange={setCode}
|
||||||
|
{...props}
|
||||||
|
className="mb-2 mt-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto mt-4 block w-max md:hidden">
|
||||||
|
<ReactCodeInput
|
||||||
|
name=""
|
||||||
|
inputMode="tel"
|
||||||
|
type="text"
|
||||||
|
fields={6}
|
||||||
|
onChange={setCode}
|
||||||
|
{...propsPhone}
|
||||||
|
className="mb-2 mt-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{codeError && <Error text="Oops. Your code is wrong. Please try again." />}
|
||||||
|
<div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-[19%]">
|
||||||
|
<div className="text-l w-full py-1 text-lg">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
onClick={checkCode}
|
||||||
|
size="sm"
|
||||||
|
isFullWidth
|
||||||
|
className="h-14"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
Verify
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto flex max-h-24 w-full max-w-md flex-col items-center justify-center pt-2">
|
||||||
|
<div className="flex flex-row items-baseline gap-1 text-sm">
|
||||||
|
<span className="text-bunker-400">Don't see the code?</span>
|
||||||
|
<div className="text-md mt-2 flex flex-row text-bunker-400">
|
||||||
|
<button disabled={isLoading} onClick={resendCode} type="button">
|
||||||
|
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
|
||||||
|
{isResendingVerificationEmail ? "Resending..." : "Resend"}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="pb-2 text-sm text-bunker-400">Make sure to check your spam inbox.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { EmailConfirmationStep } from "./EmailConfirmationStep";
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import jsrp from "jsrp";
|
||||||
|
import nacl from "tweetnacl";
|
||||||
|
import { encodeBase64 } from "tweetnacl-util";
|
||||||
|
|
||||||
|
import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
|
||||||
|
import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
|
||||||
|
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
|
||||||
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
|
import { Button, Input } from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
import { completeAccountSignup, useSelectOrganization } from "@app/hooks/api/auth/queries";
|
||||||
|
import { MfaMethod } from "@app/hooks/api/auth/types";
|
||||||
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
|
import { Mfa } from "@app/routes/login/-components/Mfa";
|
||||||
|
import ProjectService from "@app/services/ProjectService";
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const client = new jsrp.client();
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
setStep: (step: number) => void;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
setPassword: (value: string) => void;
|
||||||
|
name: string;
|
||||||
|
providerOrganizationName: string;
|
||||||
|
providerAuthToken?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the step of the sign up flow where people provife their name/surname and password
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} obj.verificationToken - the token which we use to verify the legitness of a user
|
||||||
|
* @param {string} obj.incrementStep - a function to move to the next signup step
|
||||||
|
* @param {string} obj.email - email of a user who is signing up
|
||||||
|
* @param {string} obj.password - user's password
|
||||||
|
* @param {string} obj.setPassword - function managing the state of user's password
|
||||||
|
* @param {string} obj.firstName - user's first name
|
||||||
|
* @param {string} obj.setFirstName - function managing the state of user's first name
|
||||||
|
* @param {string} obj.lastName - user's lastName
|
||||||
|
* @param {string} obj.setLastName - function managing the state of user's last name
|
||||||
|
*/
|
||||||
|
export const UserInfoSSOStep = ({
|
||||||
|
username,
|
||||||
|
name,
|
||||||
|
providerOrganizationName,
|
||||||
|
password,
|
||||||
|
setPassword,
|
||||||
|
setStep,
|
||||||
|
providerAuthToken
|
||||||
|
}: Props) => {
|
||||||
|
const [nameError, setNameError] = useState(false);
|
||||||
|
const [organizationName, setOrganizationName] = useState("");
|
||||||
|
const [organizationNameError, setOrganizationNameError] = useState(false);
|
||||||
|
const [attributionSource, setAttributionSource] = useState("");
|
||||||
|
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
|
||||||
|
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { mutateAsync: selectOrganization } = useSelectOrganization();
|
||||||
|
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const randomPassword = crypto.randomBytes(32).toString("hex");
|
||||||
|
setPassword(randomPassword);
|
||||||
|
if (providerOrganizationName !== undefined) {
|
||||||
|
setOrganizationName(providerOrganizationName);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Verifies if the information that the users entered (name, workspace)
|
||||||
|
// is there, and if the password matches the criteria.
|
||||||
|
const signupErrorCheck = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
let errorCheck = false;
|
||||||
|
if (!name) {
|
||||||
|
setNameError(true);
|
||||||
|
errorCheck = true;
|
||||||
|
} else {
|
||||||
|
setNameError(false);
|
||||||
|
}
|
||||||
|
if (!organizationName) {
|
||||||
|
setOrganizationNameError(true);
|
||||||
|
errorCheck = true;
|
||||||
|
} else {
|
||||||
|
setOrganizationNameError(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,
|
||||||
|
password
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
client.createVerifier(async (_err: any, result: { salt: string; verifier: string }) => {
|
||||||
|
try {
|
||||||
|
// TODO: moduralize into KeyService
|
||||||
|
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 response = await completeAccountSignup({
|
||||||
|
email: username,
|
||||||
|
password,
|
||||||
|
firstName: name.split(" ")[0],
|
||||||
|
lastName: name.split(" ").slice(1).join(" "),
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
encryptedPrivateKeyIV,
|
||||||
|
encryptedPrivateKeyTag,
|
||||||
|
providerAuthToken,
|
||||||
|
salt: result.salt,
|
||||||
|
verifier: result.verifier,
|
||||||
|
organizationName,
|
||||||
|
attributionSource
|
||||||
|
});
|
||||||
|
|
||||||
|
// unset signup JWT token and set JWT token
|
||||||
|
SecurityClient.setSignupToken("");
|
||||||
|
SecurityClient.setToken(response.token);
|
||||||
|
SecurityClient.setProviderAuthToken("");
|
||||||
|
|
||||||
|
saveTokenToLocalStorage({
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv: encryptedPrivateKeyIV,
|
||||||
|
tag: encryptedPrivateKeyTag,
|
||||||
|
privateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const userOrgs = await fetchOrganizations();
|
||||||
|
const orgId = userOrgs[0]?.id;
|
||||||
|
|
||||||
|
const completeSignupFlow = async () => {
|
||||||
|
try {
|
||||||
|
const { isMfaEnabled, token, mfaMethod } = await selectOrganization({
|
||||||
|
organizationId: orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isMfaEnabled) {
|
||||||
|
SecurityClient.setMfaToken(token);
|
||||||
|
if (mfaMethod) {
|
||||||
|
setRequiredMfaMethod(mfaMethod);
|
||||||
|
}
|
||||||
|
toggleShowMfa.on();
|
||||||
|
setMfaSuccessCallback(() => completeSignupFlow);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only create example project if not joining existing org
|
||||||
|
if (!providerOrganizationName) {
|
||||||
|
const project = await ProjectService.initProject({
|
||||||
|
projectName: "Example Project"
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem("projectData.id", project.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("orgData.id", orgId);
|
||||||
|
setStep(2);
|
||||||
|
} catch (error) {
|
||||||
|
setIsLoading(false);
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await completeSignupFlow();
|
||||||
|
} catch (error) {
|
||||||
|
setIsLoading(false);
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (password && providerOrganizationName) {
|
||||||
|
signupErrorCheck();
|
||||||
|
}
|
||||||
|
}, [providerOrganizationName, password]);
|
||||||
|
|
||||||
|
if (shouldShowMfa) {
|
||||||
|
return (
|
||||||
|
<Mfa
|
||||||
|
hideLogo
|
||||||
|
email={username}
|
||||||
|
successCallback={mfaSuccessCallback}
|
||||||
|
method={requiredMfaMethod}
|
||||||
|
closeMfa={() => toggleShowMfa.off()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto mb-36 h-full w-max rounded-xl md:mb-16 md:px-8">
|
||||||
|
<p className="text-medium mx-8 mb-6 flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-xl font-bold text-transparent md:mx-16">
|
||||||
|
{t("signup.step3-message")}
|
||||||
|
</p>
|
||||||
|
<div className="mx-auto mb-36 h-full w-max rounded-xl py-6 md:mb-16 md:border md:border-mineshaft-600 md:bg-mineshaft-800 md:px-8">
|
||||||
|
<div className="relative z-0 flex w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
|
||||||
|
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
|
||||||
|
Your Name
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
placeholder="Jane Doe"
|
||||||
|
value={name}
|
||||||
|
disabled
|
||||||
|
isRequired
|
||||||
|
autoComplete="given-name"
|
||||||
|
className="h-12"
|
||||||
|
/>
|
||||||
|
{nameError && (
|
||||||
|
<p className="ml-1 mt-1 w-full text-left text-xs text-red-600">
|
||||||
|
Please, specify your name
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{providerOrganizationName === undefined && (
|
||||||
|
<div className="relative z-0 flex w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
|
||||||
|
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
|
||||||
|
Organization Name
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
placeholder="Infisical"
|
||||||
|
value={organizationName}
|
||||||
|
onChange={(e) => setOrganizationName(e.target.value)}
|
||||||
|
isRequired
|
||||||
|
className="h-12"
|
||||||
|
maxLength={64}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
{organizationNameError && (
|
||||||
|
<p className="ml-1 mt-1 w-full text-left text-xs text-red-600">
|
||||||
|
Please, specify your organization name
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{providerOrganizationName === undefined && (
|
||||||
|
<div className="relative z-0 flex w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
|
||||||
|
<p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
|
||||||
|
Where did you hear about us? <span className="font-light">(optional)</span>
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
placeholder=""
|
||||||
|
onChange={(e) => setAttributionSource(e.target.value)}
|
||||||
|
value={attributionSource}
|
||||||
|
className="h-12"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-[19%]">
|
||||||
|
<div className="text-l w-full py-1 text-lg">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
onClick={signupErrorCheck}
|
||||||
|
size="sm"
|
||||||
|
isFullWidth
|
||||||
|
className="h-12"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
isLoading={isLoading}
|
||||||
|
isDisabled={isLoading}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
{String(t("signup.signup"))}{" "}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { UserInfoSSOStep } from "./UserInfoSSOStep";
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { createFileRoute, useSearch } from "@tanstack/react-router";
|
||||||
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
|
||||||
|
import { BackupPDFStep } from "./-components/BackupPDFStep";
|
||||||
|
import { EmailConfirmationStep } from "./-components/EmailConfirmationStep";
|
||||||
|
import { UserInfoSSOStep } from "./-components/UserInfoSSOStep";
|
||||||
|
|
||||||
|
const SignupSSOPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const search = useSearch({ from: "/signup/sso/" });
|
||||||
|
const token = search.token as string;
|
||||||
|
|
||||||
|
const [step, setStep] = useState(0);
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
|
const {
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
organizationName,
|
||||||
|
organizationSlug,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
authType,
|
||||||
|
isEmailVerified
|
||||||
|
} = jwtDecode(token) as any;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isEmailVerified) {
|
||||||
|
setStep(0);
|
||||||
|
} else {
|
||||||
|
setStep(1);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderView = () => {
|
||||||
|
switch (step) {
|
||||||
|
case 0:
|
||||||
|
return (
|
||||||
|
<EmailConfirmationStep
|
||||||
|
authType={authType}
|
||||||
|
username={username}
|
||||||
|
email={email}
|
||||||
|
organizationSlug={organizationSlug}
|
||||||
|
setStep={setStep}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 1:
|
||||||
|
return (
|
||||||
|
<UserInfoSSOStep
|
||||||
|
username={username}
|
||||||
|
name={`${firstName} ${lastName}`}
|
||||||
|
providerOrganizationName={organizationName}
|
||||||
|
password={password}
|
||||||
|
setPassword={setPassword}
|
||||||
|
setStep={setStep}
|
||||||
|
providerAuthToken={token}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case 2:
|
||||||
|
return (
|
||||||
|
<BackupPDFStep email={username} password={password} name={`${firstName} ${lastName}`} />
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28">
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: t("signup.title") })}</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
<meta property="og:image" content="/images/message.png" />
|
||||||
|
<meta property="og:title" content={t("signup.og-title") as string} />
|
||||||
|
<meta name="og:description" content={t("signup.og-description") as string} />
|
||||||
|
</Helmet>
|
||||||
|
<div className="mb-4 mt-20 flex justify-center">
|
||||||
|
<img src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical Logo" />
|
||||||
|
</div>
|
||||||
|
<div>{renderView()}</div>;
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/signup/sso/")({
|
||||||
|
component: SignupSSOPage
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user