mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 06:28:11 +00:00
Merge pull request #2000 from Infisical/daniel/default-org
Feat: Default organization slug for LDAP/SAML
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
const DEFAULT_AUTH_ORG_ID_FIELD = "defaultAuthOrgId";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasDefaultOrgColumn = await knex.schema.hasColumn(TableName.SuperAdmin, DEFAULT_AUTH_ORG_ID_FIELD);
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||||
|
if (!hasDefaultOrgColumn) {
|
||||||
|
t.uuid(DEFAULT_AUTH_ORG_ID_FIELD).nullable();
|
||||||
|
t.foreign(DEFAULT_AUTH_ORG_ID_FIELD).references("id").inTable(TableName.Organization).onDelete("SET NULL");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasDefaultOrgColumn = await knex.schema.hasColumn(TableName.SuperAdmin, DEFAULT_AUTH_ORG_ID_FIELD);
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||||
|
if (hasDefaultOrgColumn) {
|
||||||
|
t.dropForeign([DEFAULT_AUTH_ORG_ID_FIELD]);
|
||||||
|
t.dropColumn(DEFAULT_AUTH_ORG_ID_FIELD);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -17,7 +17,8 @@ export const SuperAdminSchema = z.object({
|
|||||||
instanceId: z.string().uuid().default("00000000-0000-0000-0000-000000000000"),
|
instanceId: z.string().uuid().default("00000000-0000-0000-0000-000000000000"),
|
||||||
trustSamlEmails: z.boolean().default(false).nullable().optional(),
|
trustSamlEmails: z.boolean().default(false).nullable().optional(),
|
||||||
trustLdapEmails: z.boolean().default(false).nullable().optional(),
|
trustLdapEmails: z.boolean().default(false).nullable().optional(),
|
||||||
trustOidcEmails: z.boolean().default(false).nullable().optional()
|
trustOidcEmails: z.boolean().default(false).nullable().optional(),
|
||||||
|
defaultAuthOrgId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
config: SuperAdminSchema.omit({ createdAt: true, updatedAt: true }).extend({
|
config: SuperAdminSchema.omit({ createdAt: true, updatedAt: true }).extend({
|
||||||
isMigrationModeOn: z.boolean(),
|
isMigrationModeOn: z.boolean(),
|
||||||
|
defaultAuthOrgSlug: z.string().nullable(),
|
||||||
isSecretScanningDisabled: z.boolean()
|
isSecretScanningDisabled: z.boolean()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -52,11 +53,14 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
allowedSignUpDomain: z.string().optional().nullable(),
|
allowedSignUpDomain: z.string().optional().nullable(),
|
||||||
trustSamlEmails: z.boolean().optional(),
|
trustSamlEmails: z.boolean().optional(),
|
||||||
trustLdapEmails: z.boolean().optional(),
|
trustLdapEmails: z.boolean().optional(),
|
||||||
trustOidcEmails: z.boolean().optional()
|
trustOidcEmails: z.boolean().optional(),
|
||||||
|
defaultAuthOrgId: z.string().optional().nullable()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
config: SuperAdminSchema
|
config: SuperAdminSchema.extend({
|
||||||
|
defaultAuthOrgSlug: z.string().nullable()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,57 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { TableName, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TSuperAdminDALFactory = ReturnType<typeof superAdminDALFactory>;
|
export type TSuperAdminDALFactory = ReturnType<typeof superAdminDALFactory>;
|
||||||
|
|
||||||
export const superAdminDALFactory = (db: TDbClient) => ormify(db, TableName.SuperAdmin, {});
|
export const superAdminDALFactory = (db: TDbClient) => {
|
||||||
|
const superAdminOrm = ormify(db, TableName.SuperAdmin);
|
||||||
|
|
||||||
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
|
const config = await (tx || db)(TableName.SuperAdmin)
|
||||||
|
.where(`${TableName.SuperAdmin}.id`, id)
|
||||||
|
.leftJoin(TableName.Organization, `${TableName.SuperAdmin}.defaultAuthOrgId`, `${TableName.Organization}.id`)
|
||||||
|
.select(
|
||||||
|
db.ref("*").withSchema(TableName.SuperAdmin) as unknown as keyof TSuperAdmin,
|
||||||
|
db.ref("slug").withSchema(TableName.Organization).as("defaultAuthOrgSlug")
|
||||||
|
)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
defaultAuthOrgSlug: config?.defaultAuthOrgSlug || null
|
||||||
|
} as TSuperAdmin & { defaultAuthOrgSlug: string | null };
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateById = async (id: string, data: TSuperAdminUpdate, tx?: Knex) => {
|
||||||
|
const updatedConfig = await (superAdminOrm || tx).transaction(async (trx: Knex) => {
|
||||||
|
await superAdminOrm.updateById(id, data, trx);
|
||||||
|
const config = await findById(id, trx);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
throw new DatabaseError({
|
||||||
|
error: "Failed to find updated super admin config",
|
||||||
|
message: "Failed to update super admin config",
|
||||||
|
name: "UpdateById"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...superAdminOrm,
|
||||||
|
findById,
|
||||||
|
updateById
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ type TSuperAdminServiceFactoryDep = {
|
|||||||
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
|
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
export let getServerCfg: () => Promise<TSuperAdmin>;
|
export let getServerCfg: () => Promise<TSuperAdmin & { defaultAuthOrgSlug: string | null }>;
|
||||||
|
|
||||||
const ADMIN_CONFIG_KEY = "infisical-admin-cfg";
|
const ADMIN_CONFIG_KEY = "infisical-admin-cfg";
|
||||||
const ADMIN_CONFIG_KEY_EXP = 60; // 60s
|
const ADMIN_CONFIG_KEY_EXP = 60; // 60s
|
||||||
@@ -42,16 +42,20 @@ export const superAdminServiceFactory = ({
|
|||||||
// TODO(akhilmhdh): bad pattern time less change this later to me itself
|
// TODO(akhilmhdh): bad pattern time less change this later to me itself
|
||||||
getServerCfg = async () => {
|
getServerCfg = async () => {
|
||||||
const config = await keyStore.getItem(ADMIN_CONFIG_KEY);
|
const config = await keyStore.getItem(ADMIN_CONFIG_KEY);
|
||||||
|
|
||||||
// missing in keystore means fetch from db
|
// missing in keystore means fetch from db
|
||||||
if (!config) {
|
if (!config) {
|
||||||
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||||
if (serverCfg) {
|
|
||||||
await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(serverCfg)); // insert it back to keystore
|
if (!serverCfg) {
|
||||||
|
throw new BadRequestError({ name: "Admin config", message: "Admin config not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(serverCfg)); // insert it back to keystore
|
||||||
return serverCfg;
|
return serverCfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyStoreServerCfg = JSON.parse(config) as TSuperAdmin;
|
const keyStoreServerCfg = JSON.parse(config) as TSuperAdmin & { defaultAuthOrgSlug: string | null };
|
||||||
return {
|
return {
|
||||||
...keyStoreServerCfg,
|
...keyStoreServerCfg,
|
||||||
// this is to allow admin router to work
|
// this is to allow admin router to work
|
||||||
@@ -65,14 +69,21 @@ export const superAdminServiceFactory = ({
|
|||||||
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||||
if (serverCfg) return;
|
if (serverCfg) return;
|
||||||
|
|
||||||
// @ts-expect-error id is kept as fixed for idempotence and to avoid race condition
|
const newCfg = await serverCfgDAL.create({
|
||||||
const newCfg = await serverCfgDAL.create({ initialized: false, allowSignUp: true, id: ADMIN_CONFIG_DB_UUID });
|
// @ts-expect-error id is kept as fixed for idempotence and to avoid race condition
|
||||||
|
id: ADMIN_CONFIG_DB_UUID,
|
||||||
|
initialized: false,
|
||||||
|
allowSignUp: true,
|
||||||
|
defaultAuthOrgId: null
|
||||||
|
});
|
||||||
return newCfg;
|
return newCfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateServerCfg = async (data: TSuperAdminUpdate) => {
|
const updateServerCfg = async (data: TSuperAdminUpdate) => {
|
||||||
const updatedServerCfg = await serverCfgDAL.updateById(ADMIN_CONFIG_DB_UUID, data);
|
const updatedServerCfg = await serverCfgDAL.updateById(ADMIN_CONFIG_DB_UUID, data);
|
||||||
|
|
||||||
await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(updatedServerCfg));
|
await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(updatedServerCfg));
|
||||||
|
|
||||||
return updatedServerCfg;
|
return updatedServerCfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,61 +36,73 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
|
|||||||
ref
|
ref
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Root {...props} disabled={isDisabled}>
|
<div className="flex items-center space-x-2">
|
||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Root
|
||||||
ref={ref}
|
{...props}
|
||||||
className={twMerge(
|
onValueChange={(value) => {
|
||||||
`inline-flex items-center justify-between rounded-md
|
if (!props.onValueChange) return;
|
||||||
bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none focus:bg-mineshaft-700/80 data-[placeholder]:text-mineshaft-200`,
|
|
||||||
className,
|
|
||||||
isDisabled && "cursor-not-allowed opacity-50"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<SelectPrimitive.Value placeholder={placeholder}>
|
|
||||||
{props.icon ? <FontAwesomeIcon icon={props.icon} /> : placeholder}
|
|
||||||
</SelectPrimitive.Value>
|
|
||||||
|
|
||||||
<SelectPrimitive.Icon className="ml-3">
|
const newValue = value === "EMPTY-VALUE" ? "" : value;
|
||||||
<FontAwesomeIcon
|
props.onValueChange(newValue);
|
||||||
icon={faCaretDown}
|
}}
|
||||||
size="sm"
|
disabled={isDisabled}
|
||||||
className={twMerge(isDisabled && "opacity-30")}
|
>
|
||||||
/>
|
<SelectPrimitive.Trigger
|
||||||
</SelectPrimitive.Icon>
|
ref={ref}
|
||||||
</SelectPrimitive.Trigger>
|
|
||||||
<SelectPrimitive.Portal>
|
|
||||||
<SelectPrimitive.Content
|
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"relative top-1 z-[100] overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 font-inter text-bunker-100 shadow-md",
|
`inline-flex items-center justify-between rounded-md
|
||||||
position === "popper" && "max-h-72",
|
bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none focus:bg-mineshaft-700/80 data-[placeholder]:text-mineshaft-200`,
|
||||||
dropdownContainerClassName
|
className,
|
||||||
|
isDisabled && "cursor-not-allowed opacity-50"
|
||||||
)}
|
)}
|
||||||
position={position}
|
|
||||||
style={{ width: "var(--radix-select-trigger-width)" }}
|
|
||||||
>
|
>
|
||||||
<SelectPrimitive.ScrollUpButton>
|
<div className="flex items-center space-x-2">
|
||||||
<div className="flex items-center justify-center">
|
{props.icon && <FontAwesomeIcon icon={props.icon} />}
|
||||||
<FontAwesomeIcon icon={faCaretUp} size="sm" />
|
<SelectPrimitive.Value placeholder={placeholder} />
|
||||||
</div>
|
</div>
|
||||||
</SelectPrimitive.ScrollUpButton>
|
|
||||||
<SelectPrimitive.Viewport className="p-1">
|
<SelectPrimitive.Icon className="ml-3">
|
||||||
{isLoading ? (
|
<FontAwesomeIcon
|
||||||
<div className="flex items-center justify-center">
|
icon={faCaretDown}
|
||||||
<Spinner size="xs" />
|
size="sm"
|
||||||
<span className="ml-2 text-xs text-gray-500">Loading...</span>
|
className={twMerge(isDisabled && "opacity-30")}
|
||||||
</div>
|
/>
|
||||||
) : (
|
</SelectPrimitive.Icon>
|
||||||
children
|
</SelectPrimitive.Trigger>
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
className={twMerge(
|
||||||
|
"relative top-1 z-[100] overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 font-inter text-bunker-100 shadow-md",
|
||||||
|
position === "popper" && "max-h-72",
|
||||||
|
dropdownContainerClassName
|
||||||
)}
|
)}
|
||||||
</SelectPrimitive.Viewport>
|
position={position}
|
||||||
<SelectPrimitive.ScrollDownButton>
|
style={{ width: "var(--radix-select-trigger-width)" }}
|
||||||
<div className="flex items-center justify-center">
|
>
|
||||||
<FontAwesomeIcon icon={faCaretDown} size="sm" />
|
<SelectPrimitive.ScrollUpButton>
|
||||||
</div>
|
<div className="flex items-center justify-center">
|
||||||
</SelectPrimitive.ScrollDownButton>
|
<FontAwesomeIcon icon={faCaretUp} size="sm" />
|
||||||
</SelectPrimitive.Content>
|
</div>
|
||||||
</SelectPrimitive.Portal>
|
</SelectPrimitive.ScrollUpButton>
|
||||||
</SelectPrimitive.Root>
|
<SelectPrimitive.Viewport className="p-1">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Spinner size="xs" />
|
||||||
|
<span className="ml-2 text-xs text-gray-500">Loading...</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectPrimitive.ScrollDownButton>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<FontAwesomeIcon icon={faCaretDown} size="sm" />
|
||||||
|
</div>
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
</SelectPrimitive.Root>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -114,7 +126,7 @@ export const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
|
|||||||
outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-700/80`,
|
outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-700/80`,
|
||||||
isSelected && "bg-primary",
|
isSelected && "bg-primary",
|
||||||
isDisabled &&
|
isDisabled &&
|
||||||
"cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-mineshaft-600",
|
"cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-mineshaft-600",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
@@ -129,3 +141,45 @@ export const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
SelectItem.displayName = "SelectItem";
|
SelectItem.displayName = "SelectItem";
|
||||||
|
|
||||||
|
export type SelectClearProps = Omit<SelectItemProps, "disabled" | "value"> & {
|
||||||
|
onClear: () => void;
|
||||||
|
selectValue: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SelectClear = forwardRef<HTMLDivElement, SelectClearProps>(
|
||||||
|
(
|
||||||
|
{ children, className, isSelected, isDisabled, onClear, selectValue, ...props },
|
||||||
|
forwardedRef
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
{...props}
|
||||||
|
value="EMPTY-VALUE"
|
||||||
|
onSelect={() => onClear()}
|
||||||
|
onClick={() => onClear()}
|
||||||
|
className={twMerge(
|
||||||
|
`relative mb-0.5 flex
|
||||||
|
cursor-pointer select-none items-center rounded-md py-2 pl-10 pr-4 text-sm
|
||||||
|
outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-700/80`,
|
||||||
|
isSelected && "bg-primary",
|
||||||
|
isDisabled &&
|
||||||
|
"cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-mineshaft-600",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={forwardedRef}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
"absolute left-3.5 text-primary",
|
||||||
|
selectValue === "" ? "visible" : "hidden"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faCheck} />
|
||||||
|
</div>
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
SelectClear.displayName = "SelectClear";
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export type { SelectItemProps, SelectProps } from "./Select";
|
export type { SelectItemProps, SelectProps } from "./Select";
|
||||||
export { Select, SelectItem } from "./Select";
|
export { Select, SelectClear, SelectItem } from "./Select";
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export type TServerConfig = {
|
|||||||
trustLdapEmails: boolean;
|
trustLdapEmails: boolean;
|
||||||
trustOidcEmails: boolean;
|
trustOidcEmails: boolean;
|
||||||
isSecretScanningDisabled: boolean;
|
isSecretScanningDisabled: boolean;
|
||||||
|
defaultAuthOrgSlug: string | null;
|
||||||
|
defaultAuthOrgId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateAdminUserDTO = {
|
export type TCreateAdminUserDTO = {
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ export type ServerStatus = {
|
|||||||
emailConfigured: boolean;
|
emailConfigured: boolean;
|
||||||
secretScanningConfigured: boolean;
|
secretScanningConfigured: boolean;
|
||||||
redisConfigured: boolean;
|
redisConfigured: boolean;
|
||||||
samlDefaultOrgSlug: boolean
|
samlDefaultOrgSlug: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
import { isLoggedIn } from "@app/reactQuery";
|
import { isLoggedIn } from "@app/reactQuery";
|
||||||
|
|
||||||
import { InitialStep, MFAStep, SSOStep } from "./components";
|
import { InitialStep, MFAStep, SSOStep } from "./components";
|
||||||
import { navigateUserToSelectOrg } from "./Login.utils";
|
import { useNavigateToSelectOrganization } from "./Login.utils";
|
||||||
|
|
||||||
export const Login = () => {
|
export const Login = () => {
|
||||||
const router = useRouter();
|
|
||||||
const [step, setStep] = useState(0);
|
const [step, setStep] = useState(0);
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
const { navigateToSelectOrganization } = useNavigateToSelectOrganization();
|
||||||
|
|
||||||
const queryParams = new URLSearchParams(window.location.search);
|
const queryParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
@@ -21,10 +20,10 @@ export const Login = () => {
|
|||||||
const callbackPort = queryParams?.get("callback_port");
|
const callbackPort = queryParams?.get("callback_port");
|
||||||
// case: a callback port is set, meaning it's a cli login request: redirect to select org with callback port
|
// case: a callback port is set, meaning it's a cli login request: redirect to select org with callback port
|
||||||
if (callbackPort) {
|
if (callbackPort) {
|
||||||
navigateUserToSelectOrg(router, callbackPort);
|
navigateToSelectOrganization(callbackPort);
|
||||||
} else {
|
} else {
|
||||||
// case: no callback port, meaning it's a regular login request: redirect to select org
|
// case: no callback port, meaning it's a regular login request: redirect to select org
|
||||||
navigateUserToSelectOrg(router);
|
navigateToSelectOrganization();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error - Not logged in yet");
|
console.log("Error - Not logged in yet");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { NextRouter } from "next/router";
|
import { NextRouter, useRouter } from "next/router";
|
||||||
|
|
||||||
|
import { useServerConfig } from "@app/context";
|
||||||
|
import { useSelectOrganization } from "@app/hooks/api";
|
||||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import { userKeys } from "@app/hooks/api/users/queries";
|
import { userKeys } from "@app/hooks/api/users/queries";
|
||||||
import { queryClient } from "@app/reactQuery";
|
import { queryClient } from "@app/reactQuery";
|
||||||
@@ -27,14 +29,29 @@ export const navigateUserToOrg = async (router: NextRouter, organizationId?: str
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const navigateUserToSelectOrg = (router: NextRouter, cliCallbackPort?: string) => {
|
export const useNavigateToSelectOrganization = () => {
|
||||||
queryClient.invalidateQueries(userKeys.getUser);
|
const { config } = useServerConfig();
|
||||||
|
const selectOrganization = useSelectOrganization();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
let redirectTo = "/login/select-organization";
|
const navigate = async (cliCallbackPort?: string) => {
|
||||||
|
if (config.defaultAuthOrgId) {
|
||||||
|
await selectOrganization.mutateAsync({
|
||||||
|
organizationId: config.defaultAuthOrgId
|
||||||
|
});
|
||||||
|
|
||||||
if (cliCallbackPort) {
|
await navigateUserToOrg(router, config.defaultAuthOrgId);
|
||||||
redirectTo += `?callback_port=${cliCallbackPort}`;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
router.push(redirectTo, undefined, { shallow: true });
|
queryClient.invalidateQueries(userKeys.getUser);
|
||||||
|
let redirectTo = "/login/select-organization";
|
||||||
|
|
||||||
|
if (cliCallbackPort) {
|
||||||
|
redirectTo += `?callback_port=${cliCallbackPort}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push(redirectTo, undefined, { shallow: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
return { navigateToSelectOrganization: navigate };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,15 +4,19 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, Input } from "@app/components/v2";
|
import { Button, Input } from "@app/components/v2";
|
||||||
|
import { useServerConfig } from "@app/context";
|
||||||
import { loginLDAPRedirect } from "@app/hooks/api/auth/queries";
|
import { loginLDAPRedirect } from "@app/hooks/api/auth/queries";
|
||||||
|
|
||||||
export const LoginLDAP = () => {
|
export const LoginLDAP = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { config } = useServerConfig();
|
||||||
const queryParams = new URLSearchParams(window.location.search);
|
const queryParams = new URLSearchParams(window.location.search);
|
||||||
const passedOrgSlug = queryParams.get("organizationSlug");
|
const passedOrgSlug = queryParams.get("organizationSlug");
|
||||||
const passedUsername = queryParams.get("username");
|
const passedUsername = queryParams.get("username");
|
||||||
|
|
||||||
const [organizationSlug, setOrganizationSlug] = useState(passedOrgSlug || "");
|
const [organizationSlug, setOrganizationSlug] = useState(
|
||||||
|
config.defaultAuthOrgSlug || passedOrgSlug || ""
|
||||||
|
);
|
||||||
const [username, setUsername] = useState(passedUsername || "");
|
const [username, setUsername] = useState(passedUsername || "");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
@@ -63,21 +67,22 @@ export const LoginLDAP = () => {
|
|||||||
What's your LDAP Login?
|
What's your LDAP Login?
|
||||||
</p>
|
</p>
|
||||||
<form onSubmit={handleSubmission}>
|
<form onSubmit={handleSubmission}>
|
||||||
<div className="relative mx-auto flex max-h-24 w-1/4 w-full min-w-[20rem] items-center justify-center rounded-lg md:max-h-28 md:min-w-[22rem] lg:w-1/6">
|
{!config.defaultAuthOrgSlug && !passedOrgSlug && (
|
||||||
<div className="flex max-h-24 w-full items-center justify-center rounded-lg md:max-h-28">
|
<div className="relative mx-auto flex max-h-24 w-1/4 w-full min-w-[20rem] items-center justify-center rounded-lg md:max-h-28 md:min-w-[22rem] lg:w-1/6">
|
||||||
<Input
|
<div className="flex max-h-24 w-full items-center justify-center rounded-lg md:max-h-28">
|
||||||
value={organizationSlug}
|
<Input
|
||||||
onChange={(e) => setOrganizationSlug(e.target.value)}
|
value={organizationSlug}
|
||||||
type="text"
|
onChange={(e) => setOrganizationSlug(e.target.value)}
|
||||||
placeholder="Enter your organization slug..."
|
type="text"
|
||||||
isRequired
|
placeholder="Enter your organization slug..."
|
||||||
autoComplete="email"
|
isRequired
|
||||||
id="email"
|
autoComplete="email"
|
||||||
className="h-12"
|
id="email"
|
||||||
isDisabled={passedOrgSlug !== null}
|
className="h-12"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
<div className="relative mx-auto mt-2 flex max-h-24 w-1/4 w-full min-w-[20rem] items-center justify-center rounded-lg md:max-h-28 md:min-w-[22rem] lg:w-1/6">
|
<div className="relative mx-auto mt-2 flex max-h-24 w-1/4 w-full min-w-[20rem] items-center justify-center rounded-lg md:max-h-28 md:min-w-[22rem] lg:w-1/6">
|
||||||
<div className="flex max-h-24 w-full items-center justify-center rounded-lg md:max-h-28">
|
<div className="flex max-h-24 w-full items-center justify-center rounded-lg md:max-h-28">
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FormEvent, useEffect, useRef, useState } from "react";
|
import { FormEvent, useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
@@ -16,7 +16,7 @@ import { Button, Input } from "@app/components/v2";
|
|||||||
import { useServerConfig } from "@app/context";
|
import { useServerConfig } from "@app/context";
|
||||||
import { useFetchServerStatus } from "@app/hooks/api";
|
import { useFetchServerStatus } from "@app/hooks/api";
|
||||||
|
|
||||||
import { navigateUserToSelectOrg } from "../../Login.utils";
|
import { useNavigateToSelectOrganization } from "../../Login.utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setStep: (step: number) => void;
|
setStep: (step: number) => void;
|
||||||
@@ -39,16 +39,28 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
|||||||
const captchaRef = useRef<HCaptcha>(null);
|
const captchaRef = useRef<HCaptcha>(null);
|
||||||
const { data: serverDetails } = useFetchServerStatus();
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
|
|
||||||
|
const { navigateToSelectOrganization } = useNavigateToSelectOrganization();
|
||||||
|
|
||||||
|
const redirectToSaml = (orgSlug: string) => {
|
||||||
|
const callbackPort = queryParams.get("callback_port");
|
||||||
|
const redirectUrl = `/api/v1/sso/redirect/saml2/organizations/${orgSlug}${
|
||||||
|
callbackPort ? `?callback_port=${callbackPort}` : ""
|
||||||
|
}`;
|
||||||
|
router.push(redirectUrl);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (serverDetails?.samlDefaultOrgSlug) {
|
if (serverDetails?.samlDefaultOrgSlug) redirectToSaml(serverDetails.samlDefaultOrgSlug);
|
||||||
const callbackPort = queryParams.get("callback_port");
|
|
||||||
const redirectUrl = `/api/v1/sso/redirect/saml2/organizations/${
|
|
||||||
serverDetails?.samlDefaultOrgSlug
|
|
||||||
}${callbackPort ? `?callback_port=${callbackPort}` : ""}`;
|
|
||||||
router.push(redirectUrl);
|
|
||||||
}
|
|
||||||
}, [serverDetails?.samlDefaultOrgSlug]);
|
}, [serverDetails?.samlDefaultOrgSlug]);
|
||||||
|
|
||||||
|
const handleSaml = useCallback((step: number) => {
|
||||||
|
if (config.defaultAuthOrgSlug) {
|
||||||
|
redirectToSaml(config.defaultAuthOrgSlug);
|
||||||
|
} else {
|
||||||
|
setStep(step);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
|
const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
@@ -75,7 +87,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateUserToSelectOrg(router, callbackPort!);
|
navigateToSelectOrganization(callbackPort!);
|
||||||
} else {
|
} else {
|
||||||
setLoginError(true);
|
setLoginError(true);
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -100,7 +112,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateUserToSelectOrg(router);
|
navigateToSelectOrganization();
|
||||||
|
|
||||||
// case: login does not require MFA step
|
// case: login does not require MFA step
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -211,7 +223,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
|||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setStep(2);
|
handleSaml(2);
|
||||||
}}
|
}}
|
||||||
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
||||||
className="mx-0 h-10 w-full"
|
className="mx-0 h-10 w-full"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { useSelectOrganization, verifyMfaToken } from "@app/hooks/api/auth/queri
|
|||||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import { fetchMyPrivateKey } from "@app/hooks/api/users/queries";
|
import { fetchMyPrivateKey } from "@app/hooks/api/users/queries";
|
||||||
|
|
||||||
import { navigateUserToOrg, navigateUserToSelectOrg } from "../../Login.utils";
|
import { navigateUserToOrg, useNavigateToSelectOrganization } from "../../Login.utils";
|
||||||
|
|
||||||
// The style for the verification code input
|
// The style for the verification code input
|
||||||
const props = {
|
const props = {
|
||||||
@@ -50,6 +50,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isLoadingResend, setIsLoadingResend] = useState(false);
|
const [isLoadingResend, setIsLoadingResend] = useState(false);
|
||||||
const [mfaCode, setMfaCode] = useState("");
|
const [mfaCode, setMfaCode] = useState("");
|
||||||
|
const { navigateToSelectOrganization } = useNavigateToSelectOrganization();
|
||||||
const [triesLeft, setTriesLeft] = useState<number | undefined>(undefined);
|
const [triesLeft, setTriesLeft] = useState<number | undefined>(undefined);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -93,7 +94,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => {
|
|||||||
|
|
||||||
// case: user has orgs, so we navigate the user to select an org
|
// case: user has orgs, so we navigate the user to select an org
|
||||||
if (userOrgs.length > 0) {
|
if (userOrgs.length > 0) {
|
||||||
navigateUserToSelectOrg(router, callbackPort);
|
navigateToSelectOrganization(callbackPort);
|
||||||
}
|
}
|
||||||
// case: no orgs found, so we navigate the user to create an org
|
// case: no orgs found, so we navigate the user to create an org
|
||||||
// cli login will fail in this case
|
// cli login will fail in this case
|
||||||
@@ -166,7 +167,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => {
|
|||||||
|
|
||||||
// case: user has orgs, so we navigate the user to select an org
|
// case: user has orgs, so we navigate the user to select an org
|
||||||
if (userOrgs.length > 0) {
|
if (userOrgs.length > 0) {
|
||||||
navigateUserToSelectOrg(router, callbackPort);
|
navigateToSelectOrganization(callbackPort);
|
||||||
}
|
}
|
||||||
// case: no orgs found, so we navigate the user to create an org
|
// case: no orgs found, so we navigate the user to create an org
|
||||||
// cli login will fail in this case
|
// cli login will fail in this case
|
||||||
@@ -195,7 +196,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => {
|
|||||||
if (organizationId) {
|
if (organizationId) {
|
||||||
await navigateUserToOrg(router, organizationId);
|
await navigateUserToOrg(router, organizationId);
|
||||||
} else {
|
} else {
|
||||||
navigateUserToSelectOrg(router);
|
navigateToSelectOrganization();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
createNotification({
|
createNotification({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef,useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
@@ -16,7 +16,7 @@ import { useOauthTokenExchange, useSelectOrganization } from "@app/hooks/api";
|
|||||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import { fetchMyPrivateKey } from "@app/hooks/api/users/queries";
|
import { fetchMyPrivateKey } from "@app/hooks/api/users/queries";
|
||||||
|
|
||||||
import { navigateUserToOrg, navigateUserToSelectOrg } from "../../Login.utils";
|
import { navigateUserToOrg, useNavigateToSelectOrganization } from "../../Login.utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
providerAuthToken: string;
|
providerAuthToken: string;
|
||||||
@@ -39,8 +39,11 @@ export const PasswordStep = ({
|
|||||||
const { mutateAsync: selectOrganization } = useSelectOrganization();
|
const { mutateAsync: selectOrganization } = useSelectOrganization();
|
||||||
const { mutateAsync: oauthTokenExchange } = useOauthTokenExchange();
|
const { mutateAsync: oauthTokenExchange } = useOauthTokenExchange();
|
||||||
|
|
||||||
const { callbackPort, organizationId, hasExchangedPrivateKey } =
|
const { navigateToSelectOrganization } = useNavigateToSelectOrganization();
|
||||||
jwt_decode(providerAuthToken) as any;
|
|
||||||
|
const { callbackPort, organizationId, hasExchangedPrivateKey } = jwt_decode(
|
||||||
|
providerAuthToken
|
||||||
|
) as any;
|
||||||
|
|
||||||
const handleExchange = async () => {
|
const handleExchange = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -92,7 +95,7 @@ export const PasswordStep = ({
|
|||||||
|
|
||||||
// case: user has orgs, so we navigate the user to select an org
|
// case: user has orgs, so we navigate the user to select an org
|
||||||
if (userOrgs.length > 0) {
|
if (userOrgs.length > 0) {
|
||||||
navigateUserToSelectOrg(router, callbackPort);
|
navigateToSelectOrganization(callbackPort);
|
||||||
}
|
}
|
||||||
// case: no orgs found, so we navigate the user to create an org
|
// case: no orgs found, so we navigate the user to create an org
|
||||||
else {
|
else {
|
||||||
@@ -176,7 +179,7 @@ export const PasswordStep = ({
|
|||||||
|
|
||||||
// case: user has orgs, so we navigate the user to select an org
|
// case: user has orgs, so we navigate the user to select an org
|
||||||
if (userOrgs.length > 0) {
|
if (userOrgs.length > 0) {
|
||||||
navigateUserToSelectOrg(router, callbackPort);
|
navigateToSelectOrganization(callbackPort);
|
||||||
}
|
}
|
||||||
// case: no orgs found, so we navigate the user to create an org
|
// case: no orgs found, so we navigate the user to create an org
|
||||||
else {
|
else {
|
||||||
@@ -220,7 +223,7 @@ export const PasswordStep = ({
|
|||||||
const userOrgs = await fetchOrganizations();
|
const userOrgs = await fetchOrganizations();
|
||||||
|
|
||||||
if (userOrgs.length > 0) {
|
if (userOrgs.length > 0) {
|
||||||
navigateUserToSelectOrg(router);
|
navigateToSelectOrganization();
|
||||||
} else {
|
} else {
|
||||||
await navigateUserToOrg(router);
|
await navigateUserToOrg(router);
|
||||||
}
|
}
|
||||||
@@ -270,7 +273,7 @@ export const PasswordStep = ({
|
|||||||
<form onSubmit={handleLogin} className="mx-auto h-full w-full max-w-md px-6 pt-8">
|
<form onSubmit={handleLogin} className="mx-auto h-full w-full max-w-md px-6 pt-8">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<p className="mx-auto mb-4 flex w-max justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
<p className="mx-auto mb-4 flex w-max justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||||
What's your Infisical password?
|
What's your Infisical password?
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative mx-auto flex max-h-24 w-1/4 w-full min-w-[22rem] items-center justify-center rounded-lg md:max-h-28 lg:w-1/6">
|
<div className="relative mx-auto flex max-h-24 w-1/4 w-full min-w-[22rem] items-center justify-center rounded-lg md:max-h-28 lg:w-1/6">
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
FormControl,
|
FormControl,
|
||||||
Input,
|
Input,
|
||||||
Select,
|
Select,
|
||||||
|
SelectClear,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
Switch,
|
Switch,
|
||||||
Tab,
|
Tab,
|
||||||
@@ -21,7 +22,7 @@ import {
|
|||||||
Tabs
|
Tabs
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useOrganization, useServerConfig, useUser } from "@app/context";
|
import { useOrganization, useServerConfig, useUser } from "@app/context";
|
||||||
import { useUpdateServerConfig } from "@app/hooks/api";
|
import { useGetOrganizations, useUpdateServerConfig } from "@app/hooks/api";
|
||||||
|
|
||||||
import { RateLimitPanel } from "./RateLimitPanel";
|
import { RateLimitPanel } from "./RateLimitPanel";
|
||||||
|
|
||||||
@@ -40,7 +41,8 @@ const formSchema = z.object({
|
|||||||
allowedSignUpDomain: z.string().optional().nullable(),
|
allowedSignUpDomain: z.string().optional().nullable(),
|
||||||
trustSamlEmails: z.boolean(),
|
trustSamlEmails: z.boolean(),
|
||||||
trustLdapEmails: z.boolean(),
|
trustLdapEmails: z.boolean(),
|
||||||
trustOidcEmails: z.boolean()
|
trustOidcEmails: z.boolean(),
|
||||||
|
defaultAuthOrgId: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
type TDashboardForm = z.infer<typeof formSchema>;
|
type TDashboardForm = z.infer<typeof formSchema>;
|
||||||
@@ -62,16 +64,20 @@ export const AdminDashboardPage = () => {
|
|||||||
allowedSignUpDomain: config.allowedSignUpDomain,
|
allowedSignUpDomain: config.allowedSignUpDomain,
|
||||||
trustSamlEmails: config.trustSamlEmails,
|
trustSamlEmails: config.trustSamlEmails,
|
||||||
trustLdapEmails: config.trustLdapEmails,
|
trustLdapEmails: config.trustLdapEmails,
|
||||||
trustOidcEmails: config.trustOidcEmails
|
trustOidcEmails: config.trustOidcEmails,
|
||||||
|
defaultAuthOrgId: config.defaultAuthOrgId ?? ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const signupMode = watch("signUpMode");
|
const signUpMode = watch("signUpMode");
|
||||||
|
const defaultAuthOrgId = watch("defaultAuthOrgId");
|
||||||
|
|
||||||
const { user, isLoading: isUserLoading } = useUser();
|
const { user, isLoading: isUserLoading } = useUser();
|
||||||
const { orgs } = useOrganization();
|
const { orgs } = useOrganization();
|
||||||
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
||||||
|
|
||||||
|
const organizations = useGetOrganizations();
|
||||||
|
|
||||||
const isNotAllowed = !user?.superAdmin;
|
const isNotAllowed = !user?.superAdmin;
|
||||||
|
|
||||||
// TODO(akhilmhdh): on nextjs 14 roadmap this will be properly addressed with context split
|
// TODO(akhilmhdh): on nextjs 14 roadmap this will be properly addressed with context split
|
||||||
@@ -86,10 +92,10 @@ export const AdminDashboardPage = () => {
|
|||||||
|
|
||||||
const onFormSubmit = async (formData: TDashboardForm) => {
|
const onFormSubmit = async (formData: TDashboardForm) => {
|
||||||
try {
|
try {
|
||||||
const { signUpMode, allowedSignUpDomain, trustSamlEmails, trustLdapEmails, trustOidcEmails } =
|
const { allowedSignUpDomain, trustSamlEmails, trustLdapEmails, trustOidcEmails } = formData;
|
||||||
formData;
|
|
||||||
|
|
||||||
await updateServerConfig({
|
await updateServerConfig({
|
||||||
|
defaultAuthOrgId: defaultAuthOrgId || null,
|
||||||
allowSignUp: signUpMode !== SignUpModes.Disabled,
|
allowSignUp: signUpMode !== SignUpModes.Disabled,
|
||||||
allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null,
|
allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null,
|
||||||
trustSamlEmails,
|
trustSamlEmails,
|
||||||
@@ -130,7 +136,7 @@ export const AdminDashboardPage = () => {
|
|||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel value={TabSections.Settings}>
|
<TabPanel value={TabSections.Settings}>
|
||||||
<form
|
<form
|
||||||
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
className="mb-6 space-y-8 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
onSubmit={handleSubmit(onFormSubmit)}
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col justify-start">
|
<div className="flex flex-col justify-start">
|
||||||
@@ -146,13 +152,13 @@ export const AdminDashboardPage = () => {
|
|||||||
name="signUpMode"
|
name="signUpMode"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
className="max-w-72 w-72"
|
className="max-w-sm"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
className="w-72 bg-mineshaft-700"
|
className="w-full bg-mineshaft-700"
|
||||||
dropdownContainerClassName="bg-mineshaft-700"
|
dropdownContainerClassName="bg-mineshaft-800"
|
||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
onValueChange={(e) => onChange(e)}
|
onValueChange={(e) => onChange(e)}
|
||||||
{...field}
|
{...field}
|
||||||
@@ -164,8 +170,8 @@ export const AdminDashboardPage = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{signupMode === "anyone" && (
|
{signUpMode === "anyone" && (
|
||||||
<div className="mt-8 mb-8 flex flex-col justify-start">
|
<div className="flex flex-col justify-start">
|
||||||
<div className="mb-4 text-xl font-semibold text-mineshaft-100">
|
<div className="mb-4 text-xl font-semibold text-mineshaft-100">
|
||||||
Restrict signup by email domain(s)
|
Restrict signup by email domain(s)
|
||||||
</div>
|
</div>
|
||||||
@@ -191,7 +197,54 @@ export const AdminDashboardPage = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mt-8 mb-8 flex flex-col justify-start">
|
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<div className="mb-2 text-xl font-semibold text-mineshaft-100">
|
||||||
|
Default organization
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
|
||||||
|
Select the slug of the organization you want to set as default for SAML/LDAP
|
||||||
|
logins. When this field is set, users will also skip the organization selection
|
||||||
|
page and automatically be logged into the default organization.
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="defaultAuthOrgId"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
className="max-w-sm"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder="Allow all organizations"
|
||||||
|
className="w-full bg-mineshaft-700"
|
||||||
|
dropdownContainerClassName="bg-mineshaft-800"
|
||||||
|
defaultValue={field.value ?? " "}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
{...field}
|
||||||
|
>
|
||||||
|
<SelectClear
|
||||||
|
selectValue={defaultAuthOrgId}
|
||||||
|
onClear={() => {
|
||||||
|
console.log("clearing");
|
||||||
|
onChange("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Allow all organizations
|
||||||
|
</SelectClear>
|
||||||
|
{organizations.data?.map((org) => (
|
||||||
|
<SelectItem key={org.id} value={org.id}>
|
||||||
|
{org.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
<div className="mb-2 text-xl font-semibold text-mineshaft-100">Trust emails</div>
|
<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">
|
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
|
||||||
Select if you want Infisical to trust external emails from SAML/LDAP/OIDC
|
Select if you want Infisical to trust external emails from SAML/LDAP/OIDC
|
||||||
|
|||||||
Reference in New Issue
Block a user