mirror of
https://github.com/docmost/docmost.git
synced 2026-05-16 05:44:04 +08:00
feat(EE): LDAP integration (#1515)
* LDAP - WIP * WIP * add hasGeneratedPassword * fix jotai atom * - don't require password confirmation for MFA is user has auto generated password (LDAP) - cleanups * fix * reorder * update migration * update default * fix type error
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import React, { useState } from "react";
|
||||
import { Modal, TextInput, PasswordInput, Button, Stack } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { zodResolver } from "mantine-form-zod-resolver";
|
||||
import { z } from "zod";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IAuthProvider } from "@/ee/security/types/security.types";
|
||||
import APP_ROUTE from "@/lib/app-route";
|
||||
import { ldapLogin } from "@/ee/security/services/ldap-auth-service";
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string().min(1, { message: "Username is required" }),
|
||||
password: z.string().min(1, { message: "Password is required" }),
|
||||
});
|
||||
|
||||
interface LdapLoginModalProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
provider: IAuthProvider;
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
export function LdapLoginModal({
|
||||
opened,
|
||||
onClose,
|
||||
provider,
|
||||
workspaceId,
|
||||
}: LdapLoginModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const form = useForm({
|
||||
validate: zodResolver(formSchema),
|
||||
initialValues: {
|
||||
username: "",
|
||||
password: "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = async (values: {
|
||||
username: string;
|
||||
password: string;
|
||||
}) => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await ldapLogin({
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
providerId: provider.id,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
// Handle MFA like the regular login
|
||||
if (response?.userHasMfa) {
|
||||
onClose();
|
||||
navigate(APP_ROUTE.AUTH.MFA_CHALLENGE);
|
||||
} else if (response?.requiresMfaSetup) {
|
||||
onClose();
|
||||
navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED);
|
||||
} else {
|
||||
onClose();
|
||||
navigate(APP_ROUTE.HOME);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setIsLoading(false);
|
||||
const errorMessage =
|
||||
err.response?.data?.message || "Authentication failed";
|
||||
setError(errorMessage);
|
||||
|
||||
notifications.show({
|
||||
message: errorMessage,
|
||||
color: "red",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
form.reset();
|
||||
setError(null);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={handleClose}
|
||||
title={`LDAP Login - ${provider.name}`}
|
||||
size="md"
|
||||
>
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
id="ldap-username"
|
||||
type="text"
|
||||
label={t("LDAP username")}
|
||||
placeholder="Enter your LDAP username"
|
||||
variant="filled"
|
||||
disabled={isLoading}
|
||||
data-autofocus
|
||||
{...form.getInputProps("username")}
|
||||
/>
|
||||
|
||||
<PasswordInput
|
||||
label={t("LDAP password")}
|
||||
placeholder={t("Enter your LDAP password")}
|
||||
variant="filled"
|
||||
disabled={isLoading}
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
|
||||
<Button type="submit" fullWidth mt="md" loading={isLoading}>
|
||||
{t("Sign in with LDAP")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +1,62 @@
|
||||
import { useState } from "react";
|
||||
import { useWorkspacePublicDataQuery } from "@/features/workspace/queries/workspace-query.ts";
|
||||
import { Button, Divider, Stack } from "@mantine/core";
|
||||
import { IconLock } from "@tabler/icons-react";
|
||||
import { IconLock, IconServer } from "@tabler/icons-react";
|
||||
import { IAuthProvider } from "@/ee/security/types/security.types.ts";
|
||||
import { buildSsoLoginUrl } from "@/ee/security/sso.utils.ts";
|
||||
import { SSO_PROVIDER } from "@/ee/security/contants.ts";
|
||||
import { GoogleIcon } from "@/components/icons/google-icon.tsx";
|
||||
import { isCloud } from "@/lib/config.ts";
|
||||
import { LdapLoginModal } from "@/ee/components/ldap-login-modal.tsx";
|
||||
|
||||
export default function SsoLogin() {
|
||||
const { data, isLoading } = useWorkspacePublicDataQuery();
|
||||
const [ldapModalOpened, setLdapModalOpened] = useState(false);
|
||||
const [selectedLdapProvider, setSelectedLdapProvider] = useState<IAuthProvider | null>(null);
|
||||
|
||||
if (!data?.authProviders || data?.authProviders?.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleSsoLogin = (provider: IAuthProvider) => {
|
||||
window.location.href = buildSsoLoginUrl({
|
||||
providerId: provider.id,
|
||||
type: provider.type,
|
||||
workspaceId: data.id,
|
||||
});
|
||||
if (provider.type === SSO_PROVIDER.LDAP) {
|
||||
// Open modal for LDAP instead of redirecting
|
||||
setSelectedLdapProvider(provider);
|
||||
setLdapModalOpened(true);
|
||||
} else {
|
||||
// Redirect for other SSO providers
|
||||
window.location.href = buildSsoLoginUrl({
|
||||
providerId: provider.id,
|
||||
type: provider.type,
|
||||
workspaceId: data.id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getProviderIcon = (provider: IAuthProvider) => {
|
||||
if (provider.type === SSO_PROVIDER.GOOGLE) {
|
||||
return <GoogleIcon size={16} />;
|
||||
} else if (provider.type === SSO_PROVIDER.LDAP) {
|
||||
return <IconServer size={16} />;
|
||||
} else {
|
||||
return <IconLock size={16} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedLdapProvider && (
|
||||
<LdapLoginModal
|
||||
opened={ldapModalOpened}
|
||||
onClose={() => {
|
||||
setLdapModalOpened(false);
|
||||
setSelectedLdapProvider(null);
|
||||
}}
|
||||
provider={selectedLdapProvider}
|
||||
workspaceId={data.id}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(isCloud() || data.hasLicenseKey) && (
|
||||
<>
|
||||
<Stack align="stretch" justify="center" gap="sm">
|
||||
@@ -31,13 +64,7 @@ export default function SsoLogin() {
|
||||
<div key={provider.id}>
|
||||
<Button
|
||||
onClick={() => handleSsoLogin(provider)}
|
||||
leftSection={
|
||||
provider.type === SSO_PROVIDER.GOOGLE ? (
|
||||
<GoogleIcon size={16} />
|
||||
) : (
|
||||
<IconLock size={16} />
|
||||
)
|
||||
}
|
||||
leftSection={getProviderIcon(provider)}
|
||||
variant="default"
|
||||
fullWidth
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user