feat: add LDAP authentication settings and toggle
Add an admin-only LDAP configuration UI with an enable toggle and full sign-in integration. Backend: - ldapSettings model + migration (single-row config) - GET/PUT/test routes under /ldap (admin-gated; bind password masked) - shared ldapClient with RFC 4515 filter escaping and empty-password guard - signin tries local auth first, then LDAP when enabled (find-or-create local user) so the bootstrap admin is never locked out Frontend: - LDAP settings page (Switch + form + test connection) under /admin/ldap - AdminNav tabs between user management and LDAP - ldapControl util, types, and Ldap i18n namespace for all 6 locales Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -149,6 +149,7 @@
|
||||
},
|
||||
"Admin": {
|
||||
"user_management": "Benutzerverwaltung",
|
||||
"ldap": "LDAP",
|
||||
"avatar": "Avatar",
|
||||
"id": "ID",
|
||||
"email": "E-Mail",
|
||||
@@ -434,5 +435,27 @@
|
||||
"tag_error_create": "Tag konnte nicht erstellt werden. Bitte versuche es erneut.",
|
||||
"tag_error_update": "Tag konnte nicht aktualisiert werden. Bitte versuche es erneut.",
|
||||
"tag_error_delete": "Tag konnte nicht gelöscht werden. Bitte versuche es erneut."
|
||||
},
|
||||
"Ldap": {
|
||||
"ldap_settings": "LDAP-Einstellungen",
|
||||
"ldap_description": "Benutzer über ein LDAP-Verzeichnis authentifizieren.",
|
||||
"enable_ldap": "LDAP aktivieren",
|
||||
"enable_ldap_desc": "Benutzern erlauben, sich mit ihren Verzeichnis-Anmeldedaten anzumelden.",
|
||||
"server_url": "Server-URL",
|
||||
"bind_dn": "Bind-DN",
|
||||
"bind_credentials": "Bind-Passwort",
|
||||
"bind_credentials_placeholder": "Leer lassen, um das aktuelle Passwort beizubehalten",
|
||||
"search_base": "Suchbasis",
|
||||
"search_filter": "Suchfilter",
|
||||
"email_attribute": "E-Mail-Attribut",
|
||||
"username_attribute": "Benutzername-Attribut",
|
||||
"save": "Speichern",
|
||||
"saved": "LDAP-Einstellungen gespeichert",
|
||||
"save_error": "LDAP-Einstellungen konnten nicht gespeichert werden",
|
||||
"test_connection": "Verbindung testen",
|
||||
"test_username": "Test-Benutzername",
|
||||
"test_password": "Test-Passwort",
|
||||
"test_success": "Verbindung erfolgreich",
|
||||
"test_failed": "Verbindung fehlgeschlagen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@
|
||||
},
|
||||
"Admin": {
|
||||
"user_management": "User Management",
|
||||
"ldap": "LDAP",
|
||||
"avatar": "Avatar",
|
||||
"id": "ID",
|
||||
"email": "Email",
|
||||
@@ -434,5 +435,27 @@
|
||||
"tag_error_create": "Failed to create tag. Please try again.",
|
||||
"tag_error_update": "Failed to update tag. Please try again.",
|
||||
"tag_error_delete": "Failed to delete tag. Please try again."
|
||||
},
|
||||
"Ldap": {
|
||||
"ldap_settings": "LDAP Settings",
|
||||
"ldap_description": "Authenticate users against an LDAP directory.",
|
||||
"enable_ldap": "Enable LDAP",
|
||||
"enable_ldap_desc": "Allow users to sign in with their directory credentials.",
|
||||
"server_url": "Server URL",
|
||||
"bind_dn": "Bind DN",
|
||||
"bind_credentials": "Bind Password",
|
||||
"bind_credentials_placeholder": "Leave blank to keep current password",
|
||||
"search_base": "Search Base",
|
||||
"search_filter": "Search Filter",
|
||||
"email_attribute": "Email Attribute",
|
||||
"username_attribute": "Username Attribute",
|
||||
"save": "Save",
|
||||
"saved": "LDAP settings saved",
|
||||
"save_error": "Failed to save LDAP settings",
|
||||
"test_connection": "Test Connection",
|
||||
"test_username": "Test Username",
|
||||
"test_password": "Test Password",
|
||||
"test_success": "Connection successful",
|
||||
"test_failed": "Connection failed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@
|
||||
},
|
||||
"Admin": {
|
||||
"user_management": "ユーザー管理",
|
||||
"ldap": "LDAP",
|
||||
"avatar": "アバター",
|
||||
"id": "ID",
|
||||
"email": "メールアドレス",
|
||||
@@ -434,5 +435,27 @@
|
||||
"tag_error_create": "タグの作成に失敗しました。もう一度お試しください。",
|
||||
"tag_error_update": "タグの更新に失敗しました。もう一度お試しください。",
|
||||
"tag_error_delete": "タグの削除に失敗しました。もう一度お試しください。"
|
||||
},
|
||||
"Ldap": {
|
||||
"ldap_settings": "LDAP 設定",
|
||||
"ldap_description": "LDAP ディレクトリでユーザーを認証します。",
|
||||
"enable_ldap": "LDAP を有効化",
|
||||
"enable_ldap_desc": "ユーザーがディレクトリの資格情報でサインインできるようにします。",
|
||||
"server_url": "サーバー URL",
|
||||
"bind_dn": "バインド DN",
|
||||
"bind_credentials": "バインドパスワード",
|
||||
"bind_credentials_placeholder": "現在のパスワードを保持する場合は空欄",
|
||||
"search_base": "検索ベース",
|
||||
"search_filter": "検索フィルター",
|
||||
"email_attribute": "メール属性",
|
||||
"username_attribute": "ユーザー名属性",
|
||||
"save": "保存",
|
||||
"saved": "LDAP 設定を保存しました",
|
||||
"save_error": "LDAP 設定の保存に失敗しました",
|
||||
"test_connection": "接続テスト",
|
||||
"test_username": "テストユーザー名",
|
||||
"test_password": "テストパスワード",
|
||||
"test_success": "接続に成功しました",
|
||||
"test_failed": "接続に失敗しました"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@
|
||||
},
|
||||
"Admin": {
|
||||
"user_management": "Gerenciamento de Usuários",
|
||||
"ldap": "LDAP",
|
||||
"avatar": "Avatar",
|
||||
"id": "ID",
|
||||
"email": "E-mail",
|
||||
@@ -434,5 +435,27 @@
|
||||
"tag_error_create": "Falha ao criar tag. Por favor, tente novamente.",
|
||||
"tag_error_update": "Falha ao atualizar tag. Por favor, tente novamente.",
|
||||
"tag_error_delete": "Falha ao excluir tag. Por favor, tente novamente."
|
||||
},
|
||||
"Ldap": {
|
||||
"ldap_settings": "Configurações de LDAP",
|
||||
"ldap_description": "Autenticar usuários em um diretório LDAP.",
|
||||
"enable_ldap": "Ativar LDAP",
|
||||
"enable_ldap_desc": "Permitir que os usuários entrem com suas credenciais do diretório.",
|
||||
"server_url": "URL do servidor",
|
||||
"bind_dn": "Bind DN",
|
||||
"bind_credentials": "Senha de Bind",
|
||||
"bind_credentials_placeholder": "Deixe em branco para manter a senha atual",
|
||||
"search_base": "Base de busca",
|
||||
"search_filter": "Filtro de busca",
|
||||
"email_attribute": "Atributo de e-mail",
|
||||
"username_attribute": "Atributo de nome de usuário",
|
||||
"save": "Salvar",
|
||||
"saved": "Configurações de LDAP salvas",
|
||||
"save_error": "Falha ao salvar as configurações de LDAP",
|
||||
"test_connection": "Testar conexão",
|
||||
"test_username": "Usuário de teste",
|
||||
"test_password": "Senha de teste",
|
||||
"test_success": "Conexão bem-sucedida",
|
||||
"test_failed": "Falha na conexão"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@
|
||||
},
|
||||
"Admin": {
|
||||
"user_management": "用户管理",
|
||||
"ldap": "LDAP",
|
||||
"avatar": "头像",
|
||||
"id": "ID",
|
||||
"email": "邮箱",
|
||||
@@ -434,5 +435,27 @@
|
||||
"tag_error_create": "标签创建失败,请重试。",
|
||||
"tag_error_update": "标签更新失败,请重试。",
|
||||
"tag_error_delete": "标签删除失败,请重试。"
|
||||
},
|
||||
"Ldap": {
|
||||
"ldap_settings": "LDAP 设置",
|
||||
"ldap_description": "通过 LDAP 目录验证用户身份。",
|
||||
"enable_ldap": "启用 LDAP",
|
||||
"enable_ldap_desc": "允许用户使用目录凭据登录。",
|
||||
"server_url": "服务器地址",
|
||||
"bind_dn": "绑定 DN",
|
||||
"bind_credentials": "绑定密码",
|
||||
"bind_credentials_placeholder": "留空以保留当前密码",
|
||||
"search_base": "搜索基准",
|
||||
"search_filter": "搜索过滤器",
|
||||
"email_attribute": "邮箱属性",
|
||||
"username_attribute": "用户名属性",
|
||||
"save": "保存",
|
||||
"saved": "LDAP 设置已保存",
|
||||
"save_error": "保存 LDAP 设置失败",
|
||||
"test_connection": "测试连接",
|
||||
"test_username": "测试用户名",
|
||||
"test_password": "测试密码",
|
||||
"test_success": "连接成功",
|
||||
"test_failed": "连接失败"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@
|
||||
},
|
||||
"Admin": {
|
||||
"user_management": "使用者管理",
|
||||
"ldap": "LDAP",
|
||||
"avatar": "頭像",
|
||||
"id": "ID",
|
||||
"email": "電子郵件",
|
||||
@@ -434,5 +435,27 @@
|
||||
"tag_error_create": "標籤建立失敗,請重試。",
|
||||
"tag_error_update": "標籤更新失敗,請重試。",
|
||||
"tag_error_delete": "標籤刪除失敗,請重試。"
|
||||
},
|
||||
"Ldap": {
|
||||
"ldap_settings": "LDAP 設定",
|
||||
"ldap_description": "透過 LDAP 目錄驗證使用者身分。",
|
||||
"enable_ldap": "啟用 LDAP",
|
||||
"enable_ldap_desc": "允許使用者使用目錄憑證登入。",
|
||||
"server_url": "伺服器位址",
|
||||
"bind_dn": "繫結 DN",
|
||||
"bind_credentials": "繫結密碼",
|
||||
"bind_credentials_placeholder": "留空以保留目前密碼",
|
||||
"search_base": "搜尋基準",
|
||||
"search_filter": "搜尋過濾器",
|
||||
"email_attribute": "電子郵件屬性",
|
||||
"username_attribute": "使用者名稱屬性",
|
||||
"save": "儲存",
|
||||
"saved": "已儲存 LDAP 設定",
|
||||
"save_error": "儲存 LDAP 設定失敗",
|
||||
"test_connection": "測試連線",
|
||||
"test_username": "測試使用者名稱",
|
||||
"test_password": "測試密碼",
|
||||
"test_success": "連線成功",
|
||||
"test_failed": "連線失敗"
|
||||
}
|
||||
}
|
||||
|
||||
30
frontend/src/app/[locale]/admin/AdminNav.tsx
Normal file
30
frontend/src/app/[locale]/admin/AdminNav.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
'use client';
|
||||
import { Tabs, Tab } from '@heroui/react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useRouter } from '@/src/i18n/routing';
|
||||
import { LocaleCodeType } from '@/types/locale';
|
||||
|
||||
type Props = {
|
||||
locale: LocaleCodeType;
|
||||
userManagementLabel: string;
|
||||
ldapLabel: string;
|
||||
};
|
||||
|
||||
export default function AdminNav({ locale, userManagementLabel, ldapLabel }: Props) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const selected = pathname.endsWith('/admin/ldap') ? 'ldap' : 'users';
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
aria-label="Admin sections"
|
||||
selectedKey={selected}
|
||||
onSelectionChange={(key) => {
|
||||
router.push(key === 'ldap' ? '/admin/ldap' : '/admin', { locale });
|
||||
}}
|
||||
>
|
||||
<Tab key="users" title={userManagementLabel} />
|
||||
<Tab key="ldap" title={ldapLabel} />
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { useState, useEffect, useContext } from 'react';
|
||||
import { Button, addToast } from '@heroui/react';
|
||||
import UsersTable from './UsersTable';
|
||||
import PasswordResetDialog from './PasswordResetDialog';
|
||||
import AdminNav from './AdminNav';
|
||||
import DeleteConfirmDialog from '@/components/DeleteConfirmDialog';
|
||||
import { UserType, AdminMessages } from '@/types/user';
|
||||
import { TokenContext } from '@/utils/TokenProvider';
|
||||
@@ -149,6 +150,7 @@ export default function AdminPage({ messages, locale }: Props) {
|
||||
return (
|
||||
<>
|
||||
<div className="container mx-auto max-w-3xl pt-16 px-6 flex-grow">
|
||||
<AdminNav locale={locale} userManagementLabel={messages.userManagement} ldapLabel={messages.ldap} />
|
||||
<div className="w-full p-3 flex items-center justify-between">
|
||||
<h3 className="font-bold">{messages.userManagement}</h3>
|
||||
</div>
|
||||
|
||||
190
frontend/src/app/[locale]/admin/ldap/LdapSettingsPage.tsx
Normal file
190
frontend/src/app/[locale]/admin/ldap/LdapSettingsPage.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
'use client';
|
||||
import { useState, useEffect, useContext } from 'react';
|
||||
import { Button, Input, Switch, Card, CardHeader, CardBody, CardFooter, addToast } from '@heroui/react';
|
||||
import AdminNav from '../AdminNav';
|
||||
import { TokenContext } from '@/utils/TokenProvider';
|
||||
import { LocaleCodeType } from '@/types/locale';
|
||||
import { LdapSettingsType, LdapMessages } from '@/types/ldap';
|
||||
import { fetchLdapSettings, updateLdapSettings, testLdapConnection } from '@/utils/ldapControl';
|
||||
import { logError } from '@/utils/errorHandler';
|
||||
|
||||
type Props = {
|
||||
messages: LdapMessages;
|
||||
locale: LocaleCodeType;
|
||||
};
|
||||
|
||||
const emptySettings: LdapSettingsType = {
|
||||
enabled: false,
|
||||
url: '',
|
||||
bindDn: '',
|
||||
bindCredentials: '',
|
||||
bindCredentialsSet: false,
|
||||
searchBase: '',
|
||||
searchFilter: '(mail={{username}})',
|
||||
emailAttribute: 'mail',
|
||||
usernameAttribute: 'cn',
|
||||
};
|
||||
|
||||
export default function LdapSettingsPage({ messages, locale }: Props) {
|
||||
const context = useContext(TokenContext);
|
||||
const [settings, setSettings] = useState<LdapSettingsType>(emptySettings);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
const [testUsername, setTestUsername] = useState('');
|
||||
const [testPassword, setTestPassword] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
if (!context.isAdmin()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const data = await fetchLdapSettings(context.token.access_token);
|
||||
setSettings(data);
|
||||
} catch (error: unknown) {
|
||||
logError('Error fetching LDAP settings:', error);
|
||||
}
|
||||
}
|
||||
load();
|
||||
}, [context]);
|
||||
|
||||
const update = (patch: Partial<LdapSettingsType>) => setSettings((prev) => ({ ...prev, ...patch }));
|
||||
|
||||
const handleSave = async () => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const saved = await updateLdapSettings(context.token.access_token, settings);
|
||||
setSettings(saved);
|
||||
addToast({ title: 'Success', color: 'success', description: messages.saved });
|
||||
} catch (error: unknown) {
|
||||
logError('Error saving LDAP settings:', error);
|
||||
addToast({ title: 'Error', color: 'danger', description: messages.saveError });
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTest = async () => {
|
||||
setIsTesting(true);
|
||||
try {
|
||||
const result = await testLdapConnection(context.token.access_token, settings, testUsername, testPassword);
|
||||
if (result.ok) {
|
||||
addToast({ title: 'Success', color: 'success', description: messages.testSuccess });
|
||||
} else {
|
||||
addToast({ title: 'Error', color: 'danger', description: `${messages.testFailed}: ${result.message ?? ''}` });
|
||||
}
|
||||
} finally {
|
||||
setIsTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!context.isAdmin()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto max-w-3xl pt-16 px-6 flex-grow">
|
||||
<AdminNav locale={locale} userManagementLabel={messages.userManagement} ldapLabel={messages.ldap} />
|
||||
|
||||
<Card className="my-6">
|
||||
<CardHeader className="flex justify-between items-center">
|
||||
<div>
|
||||
<h2 className="text-large font-semibold">{messages.ldapSettings}</h2>
|
||||
<p className="text-small text-default-500">{messages.ldapDescription}</p>
|
||||
</div>
|
||||
<Switch isSelected={settings.enabled} onValueChange={(value) => update({ enabled: value })}>
|
||||
{messages.enableLdap}
|
||||
</Switch>
|
||||
</CardHeader>
|
||||
<CardBody className="space-y-4">
|
||||
<Input
|
||||
size="sm"
|
||||
label={messages.serverUrl}
|
||||
placeholder="ldap://localhost:389"
|
||||
value={settings.url}
|
||||
onChange={(e) => update({ url: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
size="sm"
|
||||
label={messages.bindDn}
|
||||
placeholder="cn=admin,dc=example,dc=com"
|
||||
value={settings.bindDn}
|
||||
onChange={(e) => update({ bindDn: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
size="sm"
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
label={messages.bindCredentials}
|
||||
placeholder={settings.bindCredentialsSet ? messages.bindCredentialsPlaceholder : ''}
|
||||
value={settings.bindCredentials}
|
||||
onChange={(e) => update({ bindCredentials: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
size="sm"
|
||||
label={messages.searchBase}
|
||||
placeholder="ou=users,dc=example,dc=com"
|
||||
value={settings.searchBase}
|
||||
onChange={(e) => update({ searchBase: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
size="sm"
|
||||
label={messages.searchFilter}
|
||||
placeholder="(mail={{username}})"
|
||||
value={settings.searchFilter}
|
||||
onChange={(e) => update({ searchFilter: e.target.value })}
|
||||
/>
|
||||
<div className="flex gap-4">
|
||||
<Input
|
||||
size="sm"
|
||||
label={messages.emailAttribute}
|
||||
value={settings.emailAttribute}
|
||||
onChange={(e) => update({ emailAttribute: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
size="sm"
|
||||
label={messages.usernameAttribute}
|
||||
value={settings.usernameAttribute}
|
||||
onChange={(e) => update({ usernameAttribute: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
<CardFooter className="flex justify-end">
|
||||
<Button size="sm" color="primary" onPress={handleSave} isLoading={isSaving}>
|
||||
{messages.save}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<h2 className="text-large font-semibold">{messages.testConnection}</h2>
|
||||
</CardHeader>
|
||||
<CardBody className="space-y-4">
|
||||
<div className="flex gap-4">
|
||||
<Input
|
||||
size="sm"
|
||||
label={messages.testUsername}
|
||||
autoComplete="off"
|
||||
value={testUsername}
|
||||
onChange={(e) => setTestUsername(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
size="sm"
|
||||
type="password"
|
||||
label={messages.testPassword}
|
||||
autoComplete="off"
|
||||
value={testPassword}
|
||||
onChange={(e) => setTestPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
<CardFooter className="flex justify-end">
|
||||
<Button size="sm" color="secondary" onPress={handleTest} isLoading={isTesting}>
|
||||
{messages.testConnection}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
49
frontend/src/app/[locale]/admin/ldap/page.tsx
Normal file
49
frontend/src/app/[locale]/admin/ldap/page.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import LdapSettingsPage from './LdapSettingsPage';
|
||||
import { PageType } from '@/types/base';
|
||||
import { LocaleCodeType } from '@/types/locale';
|
||||
import { LdapMessages } from '@/types/ldap';
|
||||
|
||||
export async function generateMetadata({ params: { locale } }: { params: { locale: LocaleCodeType } }) {
|
||||
const t = await getTranslations({ locale, namespace: 'Ldap' });
|
||||
return {
|
||||
title: `${t('ldap_settings')} | UnitTCMS`,
|
||||
robots: { index: false, follow: false },
|
||||
};
|
||||
}
|
||||
|
||||
export default function Page({ params }: PageType) {
|
||||
const t = useTranslations('Ldap');
|
||||
const tAdmin = useTranslations('Admin');
|
||||
const messages: LdapMessages = {
|
||||
userManagement: tAdmin('user_management'),
|
||||
ldap: tAdmin('ldap'),
|
||||
ldapSettings: t('ldap_settings'),
|
||||
ldapDescription: t('ldap_description'),
|
||||
enableLdap: t('enable_ldap'),
|
||||
enableLdapDesc: t('enable_ldap_desc'),
|
||||
serverUrl: t('server_url'),
|
||||
bindDn: t('bind_dn'),
|
||||
bindCredentials: t('bind_credentials'),
|
||||
bindCredentialsPlaceholder: t('bind_credentials_placeholder'),
|
||||
searchBase: t('search_base'),
|
||||
searchFilter: t('search_filter'),
|
||||
emailAttribute: t('email_attribute'),
|
||||
usernameAttribute: t('username_attribute'),
|
||||
save: t('save'),
|
||||
saved: t('saved'),
|
||||
saveError: t('save_error'),
|
||||
testConnection: t('test_connection'),
|
||||
testUsername: t('test_username'),
|
||||
testPassword: t('test_password'),
|
||||
testSuccess: t('test_success'),
|
||||
testFailed: t('test_failed'),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full flex items-center justify-center">
|
||||
<LdapSettingsPage messages={messages} locale={params.locale as LocaleCodeType} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export default function Page({ params }: PageType) {
|
||||
const t = useTranslations('Admin');
|
||||
const messages: AdminMessages = {
|
||||
userManagement: t('user_management'),
|
||||
ldap: t('ldap'),
|
||||
avatar: t('avatar'),
|
||||
id: t('id'),
|
||||
email: t('email'),
|
||||
|
||||
36
frontend/types/ldap.ts
Normal file
36
frontend/types/ldap.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export type LdapSettingsType = {
|
||||
enabled: boolean;
|
||||
url: string;
|
||||
bindDn: string;
|
||||
bindCredentials: string;
|
||||
bindCredentialsSet?: boolean;
|
||||
searchBase: string;
|
||||
searchFilter: string;
|
||||
emailAttribute: string;
|
||||
usernameAttribute: string;
|
||||
};
|
||||
|
||||
export type LdapMessages = {
|
||||
userManagement: string;
|
||||
ldap: string;
|
||||
ldapSettings: string;
|
||||
ldapDescription: string;
|
||||
enableLdap: string;
|
||||
enableLdapDesc: string;
|
||||
serverUrl: string;
|
||||
bindDn: string;
|
||||
bindCredentials: string;
|
||||
bindCredentialsPlaceholder: string;
|
||||
searchBase: string;
|
||||
searchFilter: string;
|
||||
emailAttribute: string;
|
||||
usernameAttribute: string;
|
||||
save: string;
|
||||
saved: string;
|
||||
saveError: string;
|
||||
testConnection: string;
|
||||
testUsername: string;
|
||||
testPassword: string;
|
||||
testSuccess: string;
|
||||
testFailed: string;
|
||||
};
|
||||
@@ -70,6 +70,7 @@ export type AuthMessages = {
|
||||
|
||||
export type AdminMessages = {
|
||||
userManagement: string;
|
||||
ldap: string;
|
||||
avatar: string;
|
||||
id: string;
|
||||
email: string;
|
||||
|
||||
57
frontend/utils/ldapControl.ts
Normal file
57
frontend/utils/ldapControl.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { LdapSettingsType } from '@/types/ldap';
|
||||
import Config from '@/config/config';
|
||||
import { logError } from '@/utils/errorHandler';
|
||||
const apiServer = Config.apiServer;
|
||||
|
||||
async function fetchLdapSettings(jwt: string): Promise<LdapSettingsType> {
|
||||
const response = await fetch(`${apiServer}/ldap`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function updateLdapSettings(jwt: string, settings: LdapSettingsType): Promise<LdapSettingsType> {
|
||||
const response = await fetch(`${apiServer}/ldap`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function testLdapConnection(
|
||||
jwt: string,
|
||||
settings: LdapSettingsType,
|
||||
testUsername: string,
|
||||
testPassword: string
|
||||
): Promise<{ ok: boolean; message?: string }> {
|
||||
try {
|
||||
const response = await fetch(`${apiServer}/ldap/test`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
},
|
||||
body: JSON.stringify({ ...settings, testUsername, testPassword }),
|
||||
});
|
||||
return response.json();
|
||||
} catch (error: unknown) {
|
||||
logError('Error testing LDAP connection:', error);
|
||||
return { ok: false, message: 'Request failed' };
|
||||
}
|
||||
}
|
||||
|
||||
export { fetchLdapSettings, updateLdapSettings, testLdapConnection };
|
||||
Reference in New Issue
Block a user