mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 11:29:42 +00:00
Merge remote-tracking branch 'origin' into aws-iam-auth
This commit is contained in:
@@ -46,14 +46,6 @@ export const SecretPathInput = ({
|
||||
setInputValue(propValue ?? "/");
|
||||
}, [propValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (environment) {
|
||||
setInputValue("/");
|
||||
setSecretPath("/");
|
||||
onChange?.("/");
|
||||
}
|
||||
}, [environment]);
|
||||
|
||||
useEffect(() => {
|
||||
// update secret path if input is valid
|
||||
if (
|
||||
@@ -158,9 +150,8 @@ export const SecretPathInput = ({
|
||||
key={`secret-reference-secret-${i + 1}`}
|
||||
>
|
||||
<div
|
||||
className={`${
|
||||
highlightedIndex === i ? "bg-gray-600" : ""
|
||||
} text-md relative mb-0.5 flex w-full cursor-pointer select-none items-center justify-between rounded-md px-2 py-1 outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-500`}
|
||||
className={`${highlightedIndex === i ? "bg-gray-600" : ""
|
||||
} text-md relative mb-0.5 flex w-full cursor-pointer select-none items-center justify-between rounded-md px-2 py-1 outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-500`}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-center text-yellow-700">
|
||||
|
||||
@@ -6,6 +6,7 @@ export const publicPaths = [
|
||||
"/signup",
|
||||
"/signup/sso",
|
||||
"/login",
|
||||
"/login/ldap",
|
||||
"/blog",
|
||||
"/docs",
|
||||
"/changelog",
|
||||
|
||||
@@ -3,6 +3,8 @@ export type TServerConfig = {
|
||||
allowSignUp: boolean;
|
||||
allowedSignUpDomain?: string | null;
|
||||
isMigrationModeOn?: boolean;
|
||||
trustSamlEmails: boolean;
|
||||
trustLdapEmails: boolean;
|
||||
};
|
||||
|
||||
export type TCreateAdminUserDTO = {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export {
|
||||
useCreateAuditLogStream,
|
||||
useDeleteAuditLogStream,
|
||||
useUpdateAuditLogStream
|
||||
} from "./mutations";
|
||||
export { useGetAuditLogStreamDetails, useGetAuditLogStreams } from "./queries";
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { auditLogStreamKeys } from "./queries";
|
||||
import {
|
||||
TAuditLogStream,
|
||||
TCreateAuditLogStreamDTO,
|
||||
TDeleteAuditLogStreamDTO,
|
||||
TUpdateAuditLogStreamDTO
|
||||
} from "./types";
|
||||
|
||||
export const useCreateAuditLogStream = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ auditLogStream: TAuditLogStream }, {}, TCreateAuditLogStreamDTO>({
|
||||
mutationFn: async (dto) => {
|
||||
const { data } = await apiRequest.post<{ auditLogStream: TAuditLogStream }>(
|
||||
"/api/v1/audit-log-streams",
|
||||
dto
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { orgId }) => {
|
||||
queryClient.invalidateQueries(auditLogStreamKeys.list(orgId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateAuditLogStream = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ auditLogStream: TAuditLogStream }, {}, TUpdateAuditLogStreamDTO>({
|
||||
mutationFn: async (dto) => {
|
||||
const { data } = await apiRequest.patch<{ auditLogStream: TAuditLogStream }>(
|
||||
`/api/v1/audit-log-streams/${dto.id}`,
|
||||
dto
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { orgId }) => {
|
||||
queryClient.invalidateQueries(auditLogStreamKeys.list(orgId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteAuditLogStream = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ auditLogStream: TAuditLogStream }, {}, TDeleteAuditLogStreamDTO>({
|
||||
mutationFn: async (dto) => {
|
||||
const { data } = await apiRequest.delete<{ auditLogStream: TAuditLogStream }>(
|
||||
`/api/v1/audit-log-streams/${dto.id}`
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { orgId }) => {
|
||||
queryClient.invalidateQueries(auditLogStreamKeys.list(orgId));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TAuditLogStream } from "./types";
|
||||
|
||||
export const auditLogStreamKeys = {
|
||||
list: (orgId: string) => ["audit-log-stream", { orgId }],
|
||||
getById: (id: string) => ["audit-log-stream-details", { id }]
|
||||
};
|
||||
|
||||
const fetchAuditLogStreams = async () => {
|
||||
const { data } = await apiRequest.get<{ auditLogStreams: TAuditLogStream[] }>(
|
||||
"/api/v1/audit-log-streams"
|
||||
);
|
||||
|
||||
return data.auditLogStreams;
|
||||
};
|
||||
|
||||
export const useGetAuditLogStreams = (orgId: string) =>
|
||||
useQuery({
|
||||
queryKey: auditLogStreamKeys.list(orgId),
|
||||
queryFn: () => fetchAuditLogStreams(),
|
||||
enabled: Boolean(orgId)
|
||||
});
|
||||
|
||||
const fetchAuditLogStreamDetails = async (id: string) => {
|
||||
const { data } = await apiRequest.get<{ auditLogStream: TAuditLogStream }>(
|
||||
`/api/v1/audit-log-streams/${id}`
|
||||
);
|
||||
|
||||
return data.auditLogStream;
|
||||
};
|
||||
|
||||
export const useGetAuditLogStreamDetails = (id: string) =>
|
||||
useQuery({
|
||||
queryKey: auditLogStreamKeys.getById(id),
|
||||
queryFn: () => fetchAuditLogStreamDetails(id),
|
||||
enabled: Boolean(id)
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
export type LogStreamHeaders = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type TAuditLogStream = {
|
||||
id: string;
|
||||
url: string;
|
||||
headers?: LogStreamHeaders[];
|
||||
};
|
||||
|
||||
export type TCreateAuditLogStreamDTO = {
|
||||
url: string;
|
||||
headers?: LogStreamHeaders[];
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type TUpdateAuditLogStreamDTO = {
|
||||
id: string;
|
||||
url?: string;
|
||||
headers?: LogStreamHeaders[];
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type TDeleteAuditLogStreamDTO = {
|
||||
id: string;
|
||||
orgId: string;
|
||||
};
|
||||
@@ -5,7 +5,6 @@ export {
|
||||
useSendMfaToken,
|
||||
useSendPasswordResetEmail,
|
||||
useSendVerificationEmail,
|
||||
useVerifyEmailVerificationCode,
|
||||
useVerifyMfaToken,
|
||||
useVerifyPasswordResetCode
|
||||
} from "./queries";
|
||||
useVerifyPasswordResetCode,
|
||||
useVerifySignupEmailVerificationCode} from "./queries";
|
||||
|
||||
@@ -164,7 +164,7 @@ export const useSendVerificationEmail = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useVerifyEmailVerificationCode = () => {
|
||||
export const useVerifySignupEmailVerificationCode = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ email, code }: { email: string; code: string }) => {
|
||||
const { data } = await apiRequest.post("/api/v3/signup/email/verify", {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { PackRule, unpackRules } from "@casl/ability/extra";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TProjectPermission } from "../roles/types";
|
||||
import {
|
||||
TGetIdentityProejctPrivilegeDetails as TGetIdentityProjectPrivilegeDetails,
|
||||
TIdentityProjectPrivilege,
|
||||
@@ -36,17 +34,14 @@ export const useGetIdentityProjectPrivilegeDetails = ({
|
||||
const {
|
||||
data: { privilege }
|
||||
} = await apiRequest.get<{
|
||||
privilege: Omit<TIdentityProjectPrivilege, "permissions"> & { permissions: unknown };
|
||||
privilege: TIdentityProjectPrivilege;
|
||||
}>(`/api/v1/additional-privilege/identity/${privilegeSlug}`, {
|
||||
params: {
|
||||
identityId,
|
||||
projectSlug
|
||||
}
|
||||
});
|
||||
return {
|
||||
...privilege,
|
||||
permissions: unpackRules(privilege.permissions as PackRule<TProjectPermission>[])
|
||||
};
|
||||
return privilege;
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -62,16 +57,11 @@ export const useListIdentityProjectPrivileges = ({
|
||||
const {
|
||||
data: { privileges }
|
||||
} = await apiRequest.get<{
|
||||
privileges: Array<
|
||||
Omit<TIdentityProjectPrivilege, "permissions"> & { permissions: unknown }
|
||||
>;
|
||||
privileges: Array<TIdentityProjectPrivilege>;
|
||||
}>("/api/v1/additional-privilege/identity", {
|
||||
params: { identityId, projectSlug, unpacked: false }
|
||||
params: { identityId, projectSlug }
|
||||
});
|
||||
return privileges.map((el) => ({
|
||||
...el,
|
||||
permissions: unpackRules(el.permissions as PackRule<TProjectPermission>[])
|
||||
}));
|
||||
return privileges;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from "./admin";
|
||||
export * from "./apiKeys";
|
||||
export * from "./auditLogs";
|
||||
export * from "./auditLogStreams";
|
||||
export * from "./auth";
|
||||
export * from "./bots";
|
||||
export * from "./dynamicSecret";
|
||||
|
||||
@@ -41,7 +41,7 @@ export type TPermission = {
|
||||
export type TProjectPermission = {
|
||||
conditions?: Record<string, any>;
|
||||
action: string;
|
||||
subject: [string];
|
||||
subject: string | string[];
|
||||
};
|
||||
|
||||
export type TGetUserOrgPermissionsDTO = {
|
||||
|
||||
@@ -5,6 +5,8 @@ export type SubscriptionPlan = {
|
||||
auditLogs: boolean;
|
||||
dynamicSecret: boolean;
|
||||
auditLogsRetentionDays: number;
|
||||
auditLogStreamLimit: number;
|
||||
auditLogStreams: boolean;
|
||||
customAlerts: boolean;
|
||||
customRateLimits: boolean;
|
||||
pitRecovery: boolean;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ZodIssue } from "zod";
|
||||
|
||||
export type { TAuditLogStream } from "./auditLogStreams/types";
|
||||
export type { GetAuthTokenAPI } from "./auth/types";
|
||||
export type { IncidentContact } from "./incidentContacts/types";
|
||||
export type { IntegrationAuth } from "./integrationAuth/types";
|
||||
@@ -48,13 +49,13 @@ export enum ApiErrorTypes {
|
||||
|
||||
export type TApiErrors =
|
||||
| {
|
||||
error: ApiErrorTypes.ValidationError;
|
||||
message: ZodIssue[];
|
||||
statusCode: 403;
|
||||
}
|
||||
error: ApiErrorTypes.ValidationError;
|
||||
message: ZodIssue[];
|
||||
statusCode: 403;
|
||||
}
|
||||
| { error: ApiErrorTypes.ForbiddenError; message: string; statusCode: 401 }
|
||||
| {
|
||||
statusCode: 400;
|
||||
message: string;
|
||||
error: ApiErrorTypes.BadRequestError;
|
||||
};
|
||||
statusCode: 400;
|
||||
message: string;
|
||||
error: ApiErrorTypes.BadRequestError;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export { useAddUserToWsE2EE, useAddUserToWsNonE2EE } from "./mutation";
|
||||
export {
|
||||
useAddUserToWsE2EE,
|
||||
useAddUserToWsNonE2EE,
|
||||
useSendEmailVerificationCode,
|
||||
useVerifyEmailVerificationCode
|
||||
} from "./mutation";
|
||||
export {
|
||||
fetchOrgUsers,
|
||||
useAddUserToOrg,
|
||||
|
||||
@@ -61,3 +61,30 @@ export const useAddUserToWsNonE2EE = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const sendEmailVerificationCode = async (username: string) => {
|
||||
return apiRequest.post("/api/v2/users/me/emails/code", {
|
||||
username
|
||||
});
|
||||
};
|
||||
|
||||
export const useSendEmailVerificationCode = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (username: string) => {
|
||||
await sendEmailVerificationCode(username);
|
||||
return {};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useVerifyEmailVerificationCode = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ username, code }: { username: string; code: string }) => {
|
||||
await apiRequest.post("/api/v2/users/me/emails/verify", {
|
||||
username,
|
||||
code
|
||||
});
|
||||
return {};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -27,6 +27,11 @@ export type User = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export enum UserAliasType {
|
||||
LDAP = "ldap",
|
||||
SAML = "saml"
|
||||
}
|
||||
|
||||
export type UserEnc = {
|
||||
encryptionVersion?: number;
|
||||
protectedKey?: string;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export const debounce = <F extends (...args: any[]) => any>(
|
||||
func: F,
|
||||
delay: number
|
||||
): ((...args: Parameters<F>) => void) => {
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null;
|
||||
return function debounced(...args: Parameters<F>) {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = setTimeout(() => {
|
||||
func(...args);
|
||||
timeoutId = null;
|
||||
}, delay);
|
||||
};
|
||||
};
|
||||
@@ -7,7 +7,6 @@ import { Login } from "@app/views/Login";
|
||||
|
||||
export default function LoginPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
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">
|
||||
<Head>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
import { LoginLDAP } from "@app/views/Login";
|
||||
|
||||
export default function LoginLDAPPage() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28 ">
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("login.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content={t("login.og-title") ?? ""} />
|
||||
<meta name="og:description" content={t("login.og-description") ?? ""} />
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="mb-4 mt-20 flex justify-center">
|
||||
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical logo" />
|
||||
</div>
|
||||
</Link>
|
||||
<LoginLDAP />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -594,7 +594,7 @@ const OrganizationPage = withPermission(
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
|
||||
{window.location.origin.includes("https://app.infisical.com") || window.location.origin.includes("http://localhost:8080") && (
|
||||
{(window.location.origin.includes("https://app.infisical.com") || window.location.origin.includes("http://localhost:8080")) && (
|
||||
<div
|
||||
className={`${
|
||||
!updateClosed ? "block" : "hidden"
|
||||
@@ -602,10 +602,10 @@ const OrganizationPage = withPermission(
|
||||
>
|
||||
<FontAwesomeIcon icon={faWarning} className="p-6 text-4xl text-primary" />
|
||||
<div className="text-sm">
|
||||
<span className="text-lg font-semibold">Scheduled maintenance on April 13th 2024 </span>{" "}
|
||||
<span className="text-lg font-semibold">Scheduled maintenance on May 11th 2024 </span>{" "}
|
||||
<br />
|
||||
Infisical will undergo scheduled maintenance for approximately 1 hour on Saturday, April 13th, 11am EST. During these hours, read
|
||||
operations will continue to function normally but no resources will be editable.
|
||||
Infisical will undergo scheduled maintenance for approximately 2 hour on Saturday, May 11th, 11am EST. During these hours, read
|
||||
operations to Infisical will continue to function normally but no resources will be editable.
|
||||
No action is required on your end — your applications will continue to fetch secrets.
|
||||
<br />
|
||||
</div>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
|
||||
import { SecretMainPage } from "@app/views/SecretMainPage";
|
||||
|
||||
const Dashboard = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{t("common.head-title", { title: t("dashboard.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content={String(t("dashboard.og-title"))} />
|
||||
<meta name="og:description" content={String(t("dashboard.og-description"))} />
|
||||
</Head>
|
||||
<div className="h-full">
|
||||
<SecretMainPage />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
|
||||
Dashboard.requireAuth = true;
|
||||
@@ -13,7 +13,7 @@ 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 { useVerifyEmailVerificationCode } from "@app/hooks/api";
|
||||
import { useVerifySignupEmailVerificationCode } from "@app/hooks/api";
|
||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function SignUp() {
|
||||
const [isSignupWithEmail, setIsSignupWithEmail] = useState(false);
|
||||
const [isCodeInputCheckLoading, setIsCodeInputCheckLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const { mutateAsync } = useVerifyEmailVerificationCode();
|
||||
const { mutateAsync } = useVerifySignupEmailVerificationCode();
|
||||
const { config } = useServerConfig();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
||||
|
||||
import { isLoggedIn } from "@app/reactQuery";
|
||||
|
||||
import { InitialStep, LDAPStep, MFAStep, SAMLSSOStep } from "./components";
|
||||
import { InitialStep, MFAStep, SAMLSSOStep } from "./components";
|
||||
import { navigateUserToSelectOrg } from "./Login.utils";
|
||||
|
||||
export const Login = () => {
|
||||
@@ -58,8 +58,6 @@ export const Login = () => {
|
||||
);
|
||||
case 2:
|
||||
return <SAMLSSOStep setStep={setStep} />;
|
||||
case 3:
|
||||
return <LDAPStep setStep={setStep} />;
|
||||
default:
|
||||
return <div />;
|
||||
}
|
||||
|
||||
+11
-11
@@ -1,24 +1,23 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, Input } from "@app/components/v2";
|
||||
import { loginLDAPRedirect } from "@app/hooks/api/auth/queries";
|
||||
|
||||
type Props = {
|
||||
setStep: (step: number) => void;
|
||||
};
|
||||
export const LoginLDAP = () => {
|
||||
const router = useRouter();
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
const passedOrgSlug = queryParams.get("organizationSlug");
|
||||
const passedUsername = queryParams.get("username");
|
||||
|
||||
export const LDAPStep = ({ setStep }: Props) => {
|
||||
|
||||
const [organizationSlug, setOrganizationSlug] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [organizationSlug, setOrganizationSlug] = useState(passedOrgSlug || "");
|
||||
const [username, setUsername] = useState(passedUsername || "");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// const queryParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const handleSubmission = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
@@ -42,7 +41,6 @@ export const LDAPStep = ({ setStep }: Props) => {
|
||||
type: "success"
|
||||
});
|
||||
|
||||
// redirects either to /login/sso or /signup/sso
|
||||
window.open(nextUrl);
|
||||
window.close();
|
||||
} catch (err) {
|
||||
@@ -76,6 +74,7 @@ export const LDAPStep = ({ setStep }: Props) => {
|
||||
autoComplete="email"
|
||||
id="email"
|
||||
className="h-12"
|
||||
isDisabled={passedOrgSlug !== null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,6 +89,7 @@ export const LDAPStep = ({ setStep }: Props) => {
|
||||
autoComplete="email"
|
||||
id="email"
|
||||
className="h-12"
|
||||
isDisabled={passedUsername !== null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,7 +122,7 @@ export const LDAPStep = ({ setStep }: Props) => {
|
||||
<div className="mt-4 flex flex-row items-center justify-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep(0);
|
||||
router.push("/login");
|
||||
}}
|
||||
type="button"
|
||||
className="mt-2 cursor-pointer text-sm text-bunker-300 duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4"
|
||||
@@ -25,7 +25,7 @@ type Props = {
|
||||
|
||||
export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: Props) => {
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [loginError, setLoginError] = useState(false);
|
||||
@@ -33,7 +33,10 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
|
||||
useEffect(() => {
|
||||
if (process.env.NEXT_PUBLIC_SAML_ORG_SLUG && process.env.NEXT_PUBLIC_SAML_ORG_SLUG !== "saml-org-slug-default") {
|
||||
if (
|
||||
process.env.NEXT_PUBLIC_SAML_ORG_SLUG &&
|
||||
process.env.NEXT_PUBLIC_SAML_ORG_SLUG !== "saml-org-slug-default"
|
||||
) {
|
||||
const callbackPort = queryParams.get("callback_port");
|
||||
window.open(
|
||||
`/api/v1/sso/redirect/saml2/organizations/${process.env.NEXT_PUBLIC_SAML_ORG_SLUG}${
|
||||
@@ -42,7 +45,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
||||
);
|
||||
window.close();
|
||||
}
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
@@ -196,7 +199,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
setStep(3);
|
||||
router.push("/login/ldap");
|
||||
}}
|
||||
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
||||
className="mx-0 h-10 w-full"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { LDAPStep } from "./LDAPStep";
|
||||
@@ -1,5 +1,4 @@
|
||||
export { InitialStep } from "./InitialStep";
|
||||
export { LDAPStep } from "./LDAPStep";
|
||||
export { MFAStep } from "./MFAStep";
|
||||
export { SAMLSSOStep } from "./SAMLSSOStep";
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { Login } from "./Login";
|
||||
export { LoginLDAP } from "./LoginLDAP";
|
||||
export { LoginSSO } from "./LoginSSO";
|
||||
|
||||
+1
-2
@@ -49,7 +49,6 @@ type Props = {
|
||||
};
|
||||
|
||||
export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Props) => {
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { user } = useUser();
|
||||
@@ -218,7 +217,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
|
||||
variant="outline_bg"
|
||||
onClick={() => onResendInvite(email)}
|
||||
>
|
||||
Resend Invite
|
||||
Resend invite
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
|
||||
+3
-2
@@ -142,7 +142,7 @@ const SpecificPrivilegeSecretForm = ({
|
||||
.filter(({ allowed }) => allowed)
|
||||
.map(({ action }) => ({
|
||||
action,
|
||||
subject: [ProjectPermissionSub.Secrets],
|
||||
subject: ProjectPermissionSub.Secrets,
|
||||
conditions
|
||||
}))
|
||||
},
|
||||
@@ -477,7 +477,7 @@ export const SpecificPrivilegeSection = ({ identityId }: Props) => {
|
||||
permissions: [
|
||||
{
|
||||
action: ProjectPermissionActions.Read,
|
||||
subject: [ProjectPermissionSub.Secrets],
|
||||
subject: ProjectPermissionSub.Secrets,
|
||||
conditions: {
|
||||
environment: currentWorkspace?.environments?.[0].slug
|
||||
}
|
||||
@@ -512,6 +512,7 @@ export const SpecificPrivilegeSection = ({ identityId }: Props) => {
|
||||
?.filter(({ permissions }) =>
|
||||
permissions?.[0]?.subject?.includes(ProjectPermissionSub.Secrets)
|
||||
)
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
?.map((privilege) => (
|
||||
<SpecificPrivilegeSecretForm
|
||||
privilege={privilege as TProjectUserPrivilege}
|
||||
|
||||
+1
@@ -495,6 +495,7 @@ export const SpecificPrivilegeSection = ({ membershipId }: Props) => {
|
||||
?.filter(({ permissions }) =>
|
||||
permissions?.[0]?.subject?.includes(ProjectPermissionSub.Secrets)
|
||||
)
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
?.map((privilege) => (
|
||||
<SpecificPrivilegeSecretForm
|
||||
privilege={privilege as TProjectUserPrivilege}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { subject } from "@casl/ability";
|
||||
import {
|
||||
faAngleDown,
|
||||
@@ -45,6 +46,7 @@ import { ProjectPermissionActions, ProjectPermissionSub, useSubscription } from
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useCreateFolder, useDeleteSecretBatch } from "@app/hooks/api";
|
||||
import { DecryptedSecret, TImportedSecrets, WsTag } from "@app/hooks/api/types";
|
||||
import { debounce } from "@app/lib/fn/debounce";
|
||||
|
||||
import {
|
||||
PopUpNames,
|
||||
@@ -106,6 +108,7 @@ export const ActionBar = ({
|
||||
] as const);
|
||||
const { subscription } = useSubscription();
|
||||
const { openPopUp } = usePopUpAction();
|
||||
const [search, setSearch] = useState(filter.searchFilter);
|
||||
|
||||
const { mutateAsync: createFolder } = useCreateFolder();
|
||||
const { mutateAsync: deleteBatchSecretV3 } = useDeleteSecretBatch();
|
||||
@@ -114,6 +117,8 @@ export const ActionBar = ({
|
||||
const { reset: resetSelectedSecret } = useSelectedSecretActions();
|
||||
const isMultiSelectActive = Boolean(Object.keys(selectedSecrets).length);
|
||||
|
||||
const debouncedOnSearch = debounce(onSearchChange, 500);
|
||||
|
||||
const handleFolderCreate = async (folderName: string) => {
|
||||
try {
|
||||
await createFolder({
|
||||
@@ -199,8 +204,11 @@ export const ActionBar = ({
|
||||
className="bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
||||
placeholder="Search by folder name, key name, comment..."
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
value={filter.searchFilter}
|
||||
onChange={(evt) => onSearchChange(evt.target.value)}
|
||||
value={search}
|
||||
onChange={(evt) => {
|
||||
setSearch(evt.target.value);
|
||||
debouncedOnSearch(evt.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
SecretInput,
|
||||
Spinner,
|
||||
TextArea,
|
||||
Tooltip
|
||||
@@ -48,7 +49,6 @@ import { memo, useEffect } from "react";
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||
import { CreateReminderForm } from "./CreateReminderForm";
|
||||
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
||||
|
||||
@@ -263,12 +263,10 @@ export const SecretItem = memo(
|
||||
key="value-overriden"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<InfisicalSecretInput
|
||||
<SecretInput
|
||||
key="value-overriden"
|
||||
isVisible={isVisible}
|
||||
isReadOnly={isReadOnly}
|
||||
environment={environment}
|
||||
secretPath={secretPath}
|
||||
{...field}
|
||||
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
|
||||
/>
|
||||
@@ -280,12 +278,10 @@ export const SecretItem = memo(
|
||||
key="secret-value"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<InfisicalSecretInput
|
||||
<SecretInput
|
||||
isReadOnly={isReadOnly}
|
||||
key="secret-value"
|
||||
isVisible={isVisible}
|
||||
environment={environment}
|
||||
secretPath={secretPath}
|
||||
{...field}
|
||||
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
|
||||
/>
|
||||
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, FormLabel, IconButton, Input, Spinner } from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import {
|
||||
useCreateAuditLogStream,
|
||||
useGetAuditLogStreamDetails,
|
||||
useUpdateAuditLogStream
|
||||
} from "@app/hooks/api";
|
||||
|
||||
type Props = {
|
||||
id?: string;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
url: z.string().url().min(1),
|
||||
headers: z
|
||||
.object({
|
||||
key: z.string(),
|
||||
value: z.string()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
});
|
||||
type TForm = z.infer<typeof formSchema>;
|
||||
|
||||
export const AuditLogStreamForm = ({ id = "", onClose }: Props) => {
|
||||
const isEdit = Boolean(id);
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
|
||||
const auditLogStream = useGetAuditLogStreamDetails(id);
|
||||
const createAuditLogStream = useCreateAuditLogStream();
|
||||
const updateAuditLogStream = useUpdateAuditLogStream();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
setValue,
|
||||
getValues,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<TForm>({
|
||||
values: auditLogStream?.data,
|
||||
defaultValues: {
|
||||
headers: [{ key: "", value: "" }]
|
||||
}
|
||||
});
|
||||
|
||||
const headerFields = useFieldArray({
|
||||
control,
|
||||
name: "headers"
|
||||
});
|
||||
|
||||
const handleAuditLogStreamEdit = async ({ headers, url }: TForm) => {
|
||||
if (!id) return;
|
||||
try {
|
||||
await updateAuditLogStream.mutateAsync({
|
||||
id,
|
||||
orgId,
|
||||
headers,
|
||||
url
|
||||
});
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully updated stream"
|
||||
});
|
||||
onClose();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Failed to update stream"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleFormSubmit = async ({ headers = [], url }: TForm) => {
|
||||
if (isSubmitting) return;
|
||||
const sanitizedHeaders = headers.filter(({ key, value }) => Boolean(key) && Boolean(value));
|
||||
const streamHeaders = sanitizedHeaders.length ? sanitizedHeaders : undefined;
|
||||
if (isEdit) {
|
||||
await handleAuditLogStreamEdit({ headers: streamHeaders, url });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await createAuditLogStream.mutateAsync({
|
||||
orgId,
|
||||
headers: streamHeaders,
|
||||
url
|
||||
});
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully created stream"
|
||||
});
|
||||
onClose();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Failed to create stream"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (isEdit && auditLogStream.isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<Spinner size="lg" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)} autoComplete="off">
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="url"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Endpoint URL"
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<FormLabel label="Headers" isOptional />
|
||||
{headerFields.fields.map(({ id: headerFieldId }, i) => (
|
||||
<div key={headerFieldId} className="flex space-x-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name={`headers.${i}.key`}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
className="w-1/3"
|
||||
>
|
||||
<Input {...field} placeholder="Authorization" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`headers.${i}.value`}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
className="flex-grow"
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
type="password"
|
||||
placeholder="Bearer <token>"
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<IconButton
|
||||
ariaLabel="delete key"
|
||||
className="h-9"
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
const header = getValues("headers");
|
||||
if (header && header?.length > 1) {
|
||||
headerFields.remove(i);
|
||||
} else {
|
||||
setValue("headers", [{ key: "", value: "" }]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</div>
|
||||
))}
|
||||
<div>
|
||||
<Button
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
size="xs"
|
||||
variant="outline_bg"
|
||||
onClick={() => headerFields.append({ value: "", key: "" })}
|
||||
>
|
||||
Add Key
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button className="mr-4" type="submit" isLoading={isSubmitting}>
|
||||
{isEdit ? "Save" : "Create"}
|
||||
</Button>
|
||||
<Button variant="plain" colorSchema="secondary" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
import { faPlug, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
EmptyState,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
THead,
|
||||
Tr,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useSubscription
|
||||
} from "@app/context";
|
||||
import { withPermission } from "@app/hoc";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteAuditLogStream, useGetAuditLogStreams } from "@app/hooks/api";
|
||||
|
||||
import { AuditLogStreamForm } from "./AuditLogStreamForm";
|
||||
|
||||
export const AuditLogStreamsTab = withPermission(
|
||||
() => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||
"auditLogStreamForm",
|
||||
"deleteAuditLogStream",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { data: auditLogStreams, isLoading: isAuditLogStreamsLoading } =
|
||||
useGetAuditLogStreams(orgId);
|
||||
|
||||
// mutation
|
||||
const { mutateAsync: deleteAuditLogStream } = useDeleteAuditLogStream();
|
||||
|
||||
const handleAuditLogStreamDelete = async () => {
|
||||
try {
|
||||
const auditLogStreamId = popUp?.deleteAuditLogStream?.data as string;
|
||||
await deleteAuditLogStream({
|
||||
id: auditLogStreamId,
|
||||
orgId
|
||||
});
|
||||
handlePopUpClose("deleteAuditLogStream");
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully deleted stream"
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "Failed to delete stream"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Audit Log Streams</p>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Settings}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (subscription && !subscription?.auditLogStreams) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
return;
|
||||
}
|
||||
handlePopUpOpen("auditLogStreamForm");
|
||||
}}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<p className="mb-8 text-gray-400">
|
||||
Send audit logs from Infisical to external logging providers via HTTP
|
||||
</p>
|
||||
<div>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Td>URL</Td>
|
||||
<Td className="text-right">Action</Td>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isAuditLogStreamsLoading && (
|
||||
<TableSkeleton columns={2} innerKey="stream-loading" />
|
||||
)}
|
||||
{!isAuditLogStreamsLoading && auditLogStreams && auditLogStreams?.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<EmptyState title="No audit log streams found" icon={faPlug} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
{!isAuditLogStreamsLoading &&
|
||||
auditLogStreams?.map(({ id, url }) => (
|
||||
<Tr key={id}>
|
||||
<Td className="max-w-xs overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
|
||||
{url}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.Settings}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
size="xs"
|
||||
isDisabled={!isAllowed}
|
||||
onClick={() => handlePopUpOpen("auditLogStreamForm", id)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Delete}
|
||||
a={OrgPermissionSubjects.Settings}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
className="border-red-800 bg-red-800 hover:border-red-700 hover:bg-red-700"
|
||||
colorSchema="danger"
|
||||
size="xs"
|
||||
isDisabled={!isAllowed}
|
||||
onClick={() => handlePopUpOpen("deleteAuditLogStream", id)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={popUp.auditLogStreamForm.isOpen}
|
||||
onOpenChange={(isModalOpen) => {
|
||||
handlePopUpToggle("auditLogStreamForm", isModalOpen);
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title={`${popUp?.auditLogStreamForm?.data ? "Update" : "Create"} Audit Log Stream `}
|
||||
subTitle="Continuously stream logs from Infisical to third-party logging providers."
|
||||
>
|
||||
<AuditLogStreamForm
|
||||
id={popUp?.auditLogStreamForm?.data as string}
|
||||
onClose={() => handlePopUpToggle("auditLogStreamForm")}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can add audit log streams if you switch to Infisical's Enterprise plan."
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteAuditLogStream.isOpen}
|
||||
deleteKey="delete"
|
||||
title="Are you sure you want to remove this stream?"
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteAuditLogStream", isOpen)}
|
||||
onClose={() => handlePopUpClose("deleteAuditLogStream")}
|
||||
onDeleteApproved={handleAuditLogStreamDelete}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Settings }
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export { AuditLogStreamsTab } from "./AuditLogStreamTab";
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Fragment } from "react";
|
||||
import { Tab } from "@headlessui/react";
|
||||
|
||||
import { AuditLogStreamsTab } from "../AuditLogStreamTab";
|
||||
import { OrgAuthTab } from "../OrgAuthTab";
|
||||
import { OrgGeneralTab } from "../OrgGeneralTab";
|
||||
|
||||
const tabs = [
|
||||
{ name: "General", key: "tab-org-general" },
|
||||
{ name: "Security", key: "tab-org-security" }
|
||||
{ name: "Security", key: "tab-org-security" },
|
||||
{ name: "Audit Log Streams", key: "tag-audit-log-streams" }
|
||||
];
|
||||
export const OrgTabGroup = () => {
|
||||
return (
|
||||
@@ -17,9 +19,8 @@ export const OrgTabGroup = () => {
|
||||
{({ selected }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`w-30 mx-2 mr-4 py-2 text-sm font-medium outline-none ${
|
||||
selected ? "border-b border-white text-white" : "text-mineshaft-400"
|
||||
}`}
|
||||
className={`w-30 mx-2 mr-4 py-2 text-sm font-medium outline-none ${selected ? "border-b border-white text-white" : "text-mineshaft-400"
|
||||
}`}
|
||||
>
|
||||
{tab.name}
|
||||
</button>
|
||||
@@ -34,6 +35,9 @@ export const OrgTabGroup = () => {
|
||||
<Tab.Panel>
|
||||
<OrgAuthTab />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<AuditLogStreamsTab />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { WebhooksTab } from "./components/WebhooksTab";
|
||||
|
||||
const tabs = [
|
||||
{ name: "General", key: "tab-project-general" },
|
||||
{ name: "Webhooks", key: "tab-project-webhooks" }
|
||||
{ name: "Webhooks", key: "tab-project-webhooks" },
|
||||
];
|
||||
|
||||
export const ProjectSettingsPage = () => {
|
||||
@@ -25,9 +25,8 @@ export const ProjectSettingsPage = () => {
|
||||
{({ selected }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`w-30 mx-2 mr-4 py-2 text-sm font-medium outline-none ${
|
||||
selected ? "border-b border-white text-white" : "text-mineshaft-400"
|
||||
}`}
|
||||
className={`w-30 mx-2 mr-4 py-2 text-sm font-medium outline-none ${selected ? "border-b border-white text-white" : "text-mineshaft-400"
|
||||
}`}
|
||||
>
|
||||
{tab.name}
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import jwt_decode from "jwt-decode";
|
||||
|
||||
import { BackupPDFStep, UserInfoSSOStep } from "./components";
|
||||
import { BackupPDFStep, EmailConfirmationStep, UserInfoSSOStep } from "./components";
|
||||
|
||||
type Props = {
|
||||
providerAuthToken: string;
|
||||
@@ -11,11 +11,38 @@ export const SignupSSO = ({ providerAuthToken }: Props) => {
|
||||
const [step, setStep] = useState(0);
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const { username, organizationName, firstName, lastName } = jwt_decode(providerAuthToken) as any;
|
||||
const {
|
||||
username,
|
||||
email,
|
||||
organizationName,
|
||||
organizationSlug,
|
||||
firstName,
|
||||
lastName,
|
||||
authType,
|
||||
isEmailVerified
|
||||
} = jwt_decode(providerAuthToken) 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}
|
||||
@@ -27,7 +54,7 @@ export const SignupSSO = ({ providerAuthToken }: Props) => {
|
||||
providerAuthToken={providerAuthToken}
|
||||
/>
|
||||
);
|
||||
case 1:
|
||||
case 2:
|
||||
return (
|
||||
<BackupPDFStep email={username} password={password} name={`${firstName} ${lastName}`} />
|
||||
);
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// confirm email
|
||||
// if same email exists, then trigger fn to merge automatically
|
||||
import { useState } from "react";
|
||||
import ReactCodeInput from "react-code-input";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
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";
|
||||
|
||||
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 router = useRouter();
|
||||
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: {
|
||||
router.push(`/login/ldap?organizationSlug=${organizationSlug}`);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
setStep(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
createNotification({
|
||||
text: "Failed to verify code",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
setCode("");
|
||||
};
|
||||
|
||||
const resendCode = async () => {
|
||||
try {
|
||||
await sendEmailVerificationCode(username);
|
||||
createNotification({
|
||||
text: "Successfully resent code",
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
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="mt-6 mb-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-auto mt-4 block w-max md:hidden">
|
||||
<ReactCodeInput
|
||||
name=""
|
||||
inputMode="tel"
|
||||
type="text"
|
||||
fields={6}
|
||||
onChange={setCode}
|
||||
{...propsPhone}
|
||||
className="mt-2 mb-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";
|
||||
@@ -201,7 +201,7 @@ export const UserInfoSSOStep = ({
|
||||
localStorage.setItem("orgData.id", orgId);
|
||||
localStorage.setItem("projectData.id", project.id);
|
||||
|
||||
setStep(1);
|
||||
setStep(2);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
console.error(error);
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { BackupPDFStep } from "./BackupPDFStep";
|
||||
export { EmailConfirmationStep } from "./EmailConfirmationStep";
|
||||
export { UserInfoSSOStep } from "./UserInfoSSOStep";
|
||||
|
||||
@@ -14,11 +14,11 @@ import {
|
||||
Input,
|
||||
Select,
|
||||
SelectItem,
|
||||
Switch,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
Tabs
|
||||
} from "@app/components/v2";
|
||||
Tabs} from "@app/components/v2";
|
||||
import { useOrganization, useServerConfig, useUser } from "@app/context";
|
||||
import { useUpdateServerConfig } from "@app/hooks/api";
|
||||
|
||||
@@ -33,7 +33,9 @@ enum SignUpModes {
|
||||
|
||||
const formSchema = z.object({
|
||||
signUpMode: z.nativeEnum(SignUpModes),
|
||||
allowedSignUpDomain: z.string().optional().nullable()
|
||||
allowedSignUpDomain: z.string().optional().nullable(),
|
||||
trustSamlEmails: z.boolean(),
|
||||
trustLdapEmails: z.boolean()
|
||||
});
|
||||
|
||||
type TDashboardForm = z.infer<typeof formSchema>;
|
||||
@@ -52,7 +54,9 @@ export const AdminDashboardPage = () => {
|
||||
values: {
|
||||
// eslint-disable-next-line
|
||||
signUpMode: config.allowSignUp ? SignUpModes.Anyone : SignUpModes.Disabled,
|
||||
allowedSignUpDomain: config.allowedSignUpDomain
|
||||
allowedSignUpDomain: config.allowedSignUpDomain,
|
||||
trustSamlEmails: config.trustSamlEmails,
|
||||
trustLdapEmails: config.trustLdapEmails
|
||||
}
|
||||
});
|
||||
|
||||
@@ -62,8 +66,6 @@ export const AdminDashboardPage = () => {
|
||||
const { orgs } = useOrganization();
|
||||
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
||||
|
||||
|
||||
|
||||
const isNotAllowed = !user?.superAdmin;
|
||||
|
||||
// TODO(akhilmhdh): on nextjs 14 roadmap this will be properly addressed with context split
|
||||
@@ -78,10 +80,13 @@ export const AdminDashboardPage = () => {
|
||||
|
||||
const onFormSubmit = async (formData: TDashboardForm) => {
|
||||
try {
|
||||
const { signUpMode, allowedSignUpDomain } = formData;
|
||||
const { signUpMode, allowedSignUpDomain, trustSamlEmails, trustLdapEmails } = formData;
|
||||
|
||||
await updateServerConfig({
|
||||
allowSignUp: signUpMode !== SignUpModes.Disabled,
|
||||
allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null
|
||||
allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null,
|
||||
trustSamlEmails,
|
||||
trustLdapEmails
|
||||
});
|
||||
createNotification({
|
||||
text: "Successfully changed sign up setting.",
|
||||
@@ -123,8 +128,9 @@ export const AdminDashboardPage = () => {
|
||||
<div className="mb-2 text-xl font-semibold text-mineshaft-100">
|
||||
Allow user signups
|
||||
</div>
|
||||
<div className="mb-4 text-sm max-w-sm text-mineshaft-400">
|
||||
Select if you want users to be able to signup freely into your Infisical instance.
|
||||
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
|
||||
Select if you want users to be able to signup freely into your Infisical
|
||||
instance.
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -176,6 +182,48 @@ export const AdminDashboardPage = () => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-8 mb-8 flex flex-col justify-start">
|
||||
<div className="mb-2 text-xl font-semibold text-mineshaft-100">Trust emails</div>
|
||||
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
|
||||
Select if you want Infisical to trust external emails from SAML/LDAP identity
|
||||
providers. If set to false, then Infisical will prompt SAML/LDAP provisioned
|
||||
users to verify their email upon their first login.
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="trustSamlEmails"
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||
<Switch
|
||||
id="trust-saml-emails"
|
||||
onCheckedChange={(value) => field.onChange(value)}
|
||||
isChecked={field.value}
|
||||
>
|
||||
<p className="w-full">Trust SAML emails</p>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="trustLdapEmails"
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||
<Switch
|
||||
id="trust-ldap-emails"
|
||||
onCheckedChange={(value) => field.onChange(value)}
|
||||
isChecked={field.value}
|
||||
>
|
||||
<p className="w-full">Trust LDAP emails</p>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
|
||||
Reference in New Issue
Block a user