mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 00:27:30 +00:00
feat: allowed toggling login options as admin
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.SuperAdmin, "enabledLoginMethods"))) {
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (tb) => {
|
||||||
|
tb.specificType("enabledLoginMethods", "text[]");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.SuperAdmin, "enabledLoginMethods")) {
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||||
|
t.dropColumn("enabledLoginMethods");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,8 @@ export const SuperAdminSchema = z.object({
|
|||||||
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()
|
defaultAuthOrgId: z.string().uuid().nullable().optional(),
|
||||||
|
enabledLoginMethods: z.string().array().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
|
import { LoginMethod } from "@app/services/super-admin/super-admin-types";
|
||||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
||||||
@@ -54,7 +55,14 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
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()
|
defaultAuthOrgId: z.string().optional().nullable(),
|
||||||
|
enabledLoginMethods: z
|
||||||
|
.nativeEnum(LoginMethod)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.refine((methods) => !methods || methods.length > 0, {
|
||||||
|
message: "At least one login method should be enabled."
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -15,3 +15,13 @@ export type TAdminSignUpDTO = {
|
|||||||
ip: string;
|
ip: string;
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum LoginMethod {
|
||||||
|
EMAIL = "email",
|
||||||
|
GOOGLE = "google",
|
||||||
|
GITHUB = "github",
|
||||||
|
GITLAB = "gitlab",
|
||||||
|
SAML = "saml",
|
||||||
|
LDAP = "ldap",
|
||||||
|
OIDC = "oidc"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
export enum LoginMethod {
|
||||||
|
EMAIL = "email",
|
||||||
|
GOOGLE = "google",
|
||||||
|
GITHUB = "github",
|
||||||
|
GITLAB = "gitlab",
|
||||||
|
SAML = "saml",
|
||||||
|
LDAP = "ldap",
|
||||||
|
OIDC = "oidc"
|
||||||
|
}
|
||||||
|
|
||||||
export type TServerConfig = {
|
export type TServerConfig = {
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
allowSignUp: boolean;
|
allowSignUp: boolean;
|
||||||
@@ -9,6 +19,7 @@ export type TServerConfig = {
|
|||||||
isSecretScanningDisabled: boolean;
|
isSecretScanningDisabled: boolean;
|
||||||
defaultAuthOrgSlug: string | null;
|
defaultAuthOrgSlug: string | null;
|
||||||
defaultAuthOrgId: string | null;
|
defaultAuthOrgId: string | null;
|
||||||
|
enabledLoginMethods: LoginMethod[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateAdminUserDTO = {
|
export type TCreateAdminUserDTO = {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { CAPTCHA_SITE_KEY } from "@app/components/utilities/config";
|
|||||||
import { Button, Input } from "@app/components/v2";
|
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 { LoginMethod } from "@app/hooks/api/admin/types";
|
||||||
|
|
||||||
import { useNavigateToSelectOrganization } from "../../Login.utils";
|
import { useNavigateToSelectOrganization } from "../../Login.utils";
|
||||||
|
|
||||||
@@ -162,154 +163,188 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
|
|||||||
<h1 className="mb-8 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
<h1 className="mb-8 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||||
Login to Infisical
|
Login to Infisical
|
||||||
</h1>
|
</h1>
|
||||||
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
{!config.enabledLoginMethods ||
|
||||||
<Button
|
(config.enabledLoginMethods.includes(LoginMethod.GOOGLE) && (
|
||||||
colorSchema="primary"
|
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
variant="outline_bg"
|
<Button
|
||||||
onClick={() => {
|
colorSchema="primary"
|
||||||
const callbackPort = queryParams.get("callback_port");
|
variant="outline_bg"
|
||||||
|
onClick={() => {
|
||||||
|
const callbackPort = queryParams.get("callback_port");
|
||||||
|
|
||||||
window.open(
|
window.open(
|
||||||
`/api/v1/sso/redirect/google${callbackPort ? `?callback_port=${callbackPort}` : ""}`
|
`/api/v1/sso/redirect/google${
|
||||||
);
|
callbackPort ? `?callback_port=${callbackPort}` : ""
|
||||||
window.close();
|
}`
|
||||||
}}
|
);
|
||||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
window.close();
|
||||||
className="mx-0 h-10 w-full"
|
}}
|
||||||
>
|
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
||||||
{t("login.continue-with-google")}
|
className="mx-0 h-10 w-full"
|
||||||
</Button>
|
>
|
||||||
</div>
|
{t("login.continue-with-google")}
|
||||||
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
</Button>
|
||||||
<Button
|
</div>
|
||||||
colorSchema="primary"
|
))}
|
||||||
variant="outline_bg"
|
{!config.enabledLoginMethods ||
|
||||||
onClick={() => {
|
(config.enabledLoginMethods.includes(LoginMethod.GITHUB) && (
|
||||||
const callbackPort = queryParams.get("callback_port");
|
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
|
<Button
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() => {
|
||||||
|
const callbackPort = queryParams.get("callback_port");
|
||||||
|
|
||||||
window.open(
|
window.open(
|
||||||
`/api/v1/sso/redirect/github${callbackPort ? `?callback_port=${callbackPort}` : ""}`
|
`/api/v1/sso/redirect/github${
|
||||||
);
|
callbackPort ? `?callback_port=${callbackPort}` : ""
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
|
||||||
window.close();
|
window.close();
|
||||||
}}
|
}}
|
||||||
leftIcon={<FontAwesomeIcon icon={faGithub} className="mr-2" />}
|
leftIcon={<FontAwesomeIcon icon={faGithub} className="mr-2" />}
|
||||||
className="mx-0 h-10 w-full"
|
className="mx-0 h-10 w-full"
|
||||||
>
|
>
|
||||||
Continue with GitHub
|
Continue with GitHub
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
))}
|
||||||
<Button
|
{!config.enabledLoginMethods ||
|
||||||
colorSchema="primary"
|
(config.enabledLoginMethods.includes(LoginMethod.GITLAB) && (
|
||||||
variant="outline_bg"
|
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
onClick={() => {
|
<Button
|
||||||
const callbackPort = queryParams.get("callback_port");
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() => {
|
||||||
|
const callbackPort = queryParams.get("callback_port");
|
||||||
|
|
||||||
window.open(
|
window.open(
|
||||||
`/api/v1/sso/redirect/gitlab${callbackPort ? `?callback_port=${callbackPort}` : ""}`
|
`/api/v1/sso/redirect/gitlab${
|
||||||
);
|
callbackPort ? `?callback_port=${callbackPort}` : ""
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
|
||||||
window.close();
|
window.close();
|
||||||
}}
|
}}
|
||||||
leftIcon={<FontAwesomeIcon icon={faGitlab} className="mr-2" />}
|
leftIcon={<FontAwesomeIcon icon={faGitlab} className="mr-2" />}
|
||||||
className="mx-0 h-10 w-full"
|
className="mx-0 h-10 w-full"
|
||||||
>
|
>
|
||||||
Continue with GitLab
|
Continue with GitLab
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
))}
|
||||||
<Button
|
{!config.enabledLoginMethods ||
|
||||||
colorSchema="primary"
|
(config.enabledLoginMethods.includes(LoginMethod.SAML) && (
|
||||||
variant="outline_bg"
|
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
onClick={() => {
|
<Button
|
||||||
handleSaml(2);
|
colorSchema="primary"
|
||||||
}}
|
variant="outline_bg"
|
||||||
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
onClick={() => {
|
||||||
className="mx-0 h-10 w-full"
|
handleSaml(2);
|
||||||
>
|
}}
|
||||||
Continue with SAML
|
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
||||||
</Button>
|
className="mx-0 h-10 w-full"
|
||||||
</div>
|
>
|
||||||
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
Continue with SAML
|
||||||
<Button
|
</Button>
|
||||||
colorSchema="primary"
|
</div>
|
||||||
variant="outline_bg"
|
))}
|
||||||
onClick={() => {
|
{!config.enabledLoginMethods ||
|
||||||
setStep(3);
|
(config.enabledLoginMethods.includes(LoginMethod.OIDC) && (
|
||||||
}}
|
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
<Button
|
||||||
className="mx-0 h-10 w-full"
|
colorSchema="primary"
|
||||||
>
|
variant="outline_bg"
|
||||||
Continue with OIDC
|
onClick={() => {
|
||||||
</Button>
|
setStep(3);
|
||||||
</div>
|
}}
|
||||||
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
||||||
<Button
|
className="mx-0 h-10 w-full"
|
||||||
colorSchema="primary"
|
>
|
||||||
variant="outline_bg"
|
Continue with OIDC
|
||||||
onClick={() => {
|
</Button>
|
||||||
router.push("/login/ldap");
|
</div>
|
||||||
}}
|
))}
|
||||||
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
{!config.enabledLoginMethods ||
|
||||||
className="mx-0 h-10 w-full"
|
(config.enabledLoginMethods.includes(LoginMethod.LDAP) && (
|
||||||
>
|
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
Continue with LDAP
|
<Button
|
||||||
</Button>
|
colorSchema="primary"
|
||||||
</div>
|
variant="outline_bg"
|
||||||
<div className="my-4 flex w-1/4 min-w-[20rem] flex-row items-center py-2 lg:w-1/6">
|
onClick={() => {
|
||||||
<div className="w-full border-t border-mineshaft-400/60" />
|
router.push("/login/ldap");
|
||||||
<span className="mx-2 text-xs text-mineshaft-200">or</span>
|
}}
|
||||||
<div className="w-full border-t border-mineshaft-400/60" />
|
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
|
||||||
</div>
|
className="mx-0 h-10 w-full"
|
||||||
<div className="w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
>
|
||||||
<Input
|
Continue with LDAP
|
||||||
value={email}
|
</Button>
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
</div>
|
||||||
type="email"
|
))}
|
||||||
placeholder="Enter your email..."
|
|
||||||
isRequired
|
{!config.enabledLoginMethods ||
|
||||||
autoComplete="username"
|
(config.enabledLoginMethods.length > 1 &&
|
||||||
className="h-10"
|
config.enabledLoginMethods.includes(LoginMethod.EMAIL) && (
|
||||||
/>
|
<div className="my-4 flex w-1/4 min-w-[20rem] flex-row items-center py-2 lg:w-1/6">
|
||||||
</div>
|
<div className="w-full border-t border-mineshaft-400/60" />
|
||||||
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
<span className="mx-2 text-xs text-mineshaft-200">or</span>
|
||||||
<Input
|
<div className="w-full border-t border-mineshaft-400/60" />
|
||||||
value={password}
|
</div>
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
))}
|
||||||
type="password"
|
{!config.enabledLoginMethods ||
|
||||||
placeholder="Enter your password..."
|
(config.enabledLoginMethods.includes(LoginMethod.EMAIL) && (
|
||||||
isRequired
|
<>
|
||||||
autoComplete="current-password"
|
<div className="w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
id="current-password"
|
<Input
|
||||||
className="select:-webkit-autofill:focus h-10"
|
value={email}
|
||||||
/>
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
</div>
|
type="email"
|
||||||
{shouldShowCaptcha && (
|
placeholder="Enter your email..."
|
||||||
<div className="mt-4">
|
isRequired
|
||||||
<HCaptcha
|
autoComplete="username"
|
||||||
theme="dark"
|
className="h-10"
|
||||||
sitekey={CAPTCHA_SITE_KEY}
|
/>
|
||||||
onVerify={(token) => setCaptchaToken(token)}
|
</div>
|
||||||
ref={captchaRef}
|
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
/>
|
<Input
|
||||||
</div>
|
value={password}
|
||||||
)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
<div className="mt-3 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
type="password"
|
||||||
<Button
|
placeholder="Enter your password..."
|
||||||
disabled={shouldShowCaptcha && captchaToken === ""}
|
isRequired
|
||||||
type="submit"
|
autoComplete="current-password"
|
||||||
size="sm"
|
id="current-password"
|
||||||
isFullWidth
|
className="select:-webkit-autofill:focus h-10"
|
||||||
className="h-10"
|
/>
|
||||||
colorSchema="primary"
|
</div>
|
||||||
variant="solid"
|
{shouldShowCaptcha && (
|
||||||
isLoading={isLoading}
|
<div className="mt-4">
|
||||||
>
|
<HCaptcha
|
||||||
{" "}
|
theme="dark"
|
||||||
Continue with Email{" "}
|
sitekey={CAPTCHA_SITE_KEY}
|
||||||
</Button>
|
onVerify={(token) => setCaptchaToken(token)}
|
||||||
</div>
|
ref={captchaRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="mt-3 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||||
|
<Button
|
||||||
|
disabled={shouldShowCaptcha && captchaToken === ""}
|
||||||
|
type="submit"
|
||||||
|
size="sm"
|
||||||
|
isFullWidth
|
||||||
|
className="h-10"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="solid"
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
Continue with Email{" "}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
|
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
|
||||||
{config.allowSignUp ? (
|
{config.allowSignUp ? (
|
||||||
<div className="mt-6 flex flex-row text-sm text-bunker-400">
|
<div className="mt-6 flex flex-row text-sm text-bunker-400">
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, FormControl, Switch } from "@app/components/v2";
|
||||||
|
import { useServerConfig } from "@app/context";
|
||||||
|
import { useUpdateServerConfig } from "@app/hooks/api";
|
||||||
|
import { LoginMethod } from "@app/hooks/api/admin/types";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
isEmailEnabled: z.boolean(),
|
||||||
|
isGoogleEnabled: z.boolean(),
|
||||||
|
isGithubEnabled: z.boolean(),
|
||||||
|
isGitlabEnabled: z.boolean(),
|
||||||
|
isSamlEnabled: z.boolean(),
|
||||||
|
isLdapEnabled: z.boolean(),
|
||||||
|
isOidcEnabled: z.boolean()
|
||||||
|
});
|
||||||
|
|
||||||
|
type TAuthForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const AuthPanel = () => {
|
||||||
|
const { config } = useServerConfig();
|
||||||
|
const { enabledLoginMethods } = config;
|
||||||
|
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isSubmitting, isDirty }
|
||||||
|
} = useForm<TAuthForm>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
values: enabledLoginMethods
|
||||||
|
? {
|
||||||
|
isEmailEnabled: enabledLoginMethods.includes(LoginMethod.EMAIL),
|
||||||
|
isGoogleEnabled: enabledLoginMethods.includes(LoginMethod.GOOGLE),
|
||||||
|
isGithubEnabled: enabledLoginMethods.includes(LoginMethod.GITHUB),
|
||||||
|
isGitlabEnabled: enabledLoginMethods.includes(LoginMethod.GITLAB),
|
||||||
|
isSamlEnabled: enabledLoginMethods.includes(LoginMethod.SAML),
|
||||||
|
isLdapEnabled: enabledLoginMethods.includes(LoginMethod.LDAP),
|
||||||
|
isOidcEnabled: enabledLoginMethods.includes(LoginMethod.OIDC)
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
isEmailEnabled: true,
|
||||||
|
isGoogleEnabled: true,
|
||||||
|
isGithubEnabled: true,
|
||||||
|
isGitlabEnabled: true,
|
||||||
|
isSamlEnabled: true,
|
||||||
|
isLdapEnabled: true,
|
||||||
|
isOidcEnabled: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onAuthFormSubmit = async (formData: TAuthForm) => {
|
||||||
|
try {
|
||||||
|
const enabledMethods: LoginMethod[] = [];
|
||||||
|
if (formData.isEmailEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.EMAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isGoogleEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.GOOGLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isGithubEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.GITHUB);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isGitlabEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.GITLAB);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isSamlEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.SAML);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isLdapEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.LDAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isOidcEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.OIDC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enabledMethods.length) {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "At least one login method should be enabled."
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateServerConfig({
|
||||||
|
enabledLoginMethods: enabledMethods
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Login methods have been successfully updated.",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update login methods."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
|
onSubmit={handleSubmit(onAuthFormSubmit)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<div className="mb-2 text-xl font-semibold text-mineshaft-100">Login Methods</div>
|
||||||
|
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
|
||||||
|
Select the login methods available to users
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isEmailEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="email-enabled"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">Email</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isGoogleEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="google-enabled"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">Google SSO</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isGithubEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="enable-github"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">Github SSO</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isGitlabEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="enable-gitlab"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">Gitlab SSO</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isSamlEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="enable-saml"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">SAML SSO</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isOidcEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="enable-oidc"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">OIDC SSO</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isLdapEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="enable-ldap"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">LDAP</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className="mt-2"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting || !isDirty}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -24,10 +24,12 @@ import {
|
|||||||
import { useOrganization, useServerConfig, useUser } from "@app/context";
|
import { useOrganization, useServerConfig, useUser } from "@app/context";
|
||||||
import { useGetOrganizations, useUpdateServerConfig } from "@app/hooks/api";
|
import { useGetOrganizations, useUpdateServerConfig } from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { AuthPanel } from "./AuthPanel";
|
||||||
import { RateLimitPanel } from "./RateLimitPanel";
|
import { RateLimitPanel } from "./RateLimitPanel";
|
||||||
|
|
||||||
enum TabSections {
|
enum TabSections {
|
||||||
Settings = "settings",
|
Settings = "settings",
|
||||||
|
Auth = "auth",
|
||||||
RateLimit = "rate-limit"
|
RateLimit = "rate-limit"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +133,7 @@ export const AdminDashboardPage = () => {
|
|||||||
<TabList>
|
<TabList>
|
||||||
<div className="flex w-full flex-row border-b border-mineshaft-600">
|
<div className="flex w-full flex-row border-b border-mineshaft-600">
|
||||||
<Tab value={TabSections.Settings}>General</Tab>
|
<Tab value={TabSections.Settings}>General</Tab>
|
||||||
|
<Tab value={TabSections.Auth}>Auth</Tab>
|
||||||
<Tab value={TabSections.RateLimit}>Rate Limit</Tab>
|
<Tab value={TabSections.RateLimit}>Rate Limit</Tab>
|
||||||
</div>
|
</div>
|
||||||
</TabList>
|
</TabList>
|
||||||
@@ -203,7 +206,8 @@ export const AdminDashboardPage = () => {
|
|||||||
Default organization
|
Default organization
|
||||||
</div>
|
</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 the default organization you want to set for SAML/LDAP based logins. When selected, user logins will be automatically scoped to the selected organization.
|
Select the default organization you want to set for SAML/LDAP based logins. When
|
||||||
|
selected, user logins will be automatically scoped to the selected organization.
|
||||||
</div>
|
</div>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
@@ -310,6 +314,9 @@ export const AdminDashboardPage = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.Auth}>
|
||||||
|
<AuthPanel />
|
||||||
|
</TabPanel>
|
||||||
<TabPanel value={TabSections.RateLimit}>
|
<TabPanel value={TabSections.RateLimit}>
|
||||||
<RateLimitPanel />
|
<RateLimitPanel />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|||||||
Reference in New Issue
Block a user