feat: enhance LDAP integration with new configuration options and browsing capabilities

This commit is contained in:
Björn Blomberg
2026-04-14 16:07:52 +02:00
parent d6fd50e511
commit 61f33d350a
6 changed files with 631 additions and 163 deletions

View File

@@ -5,11 +5,9 @@ import FolderPicker from '@/components/FolderPicker.vue'
const storagePath = ref('')
const ldapEnabled = ref(false)
const ldapHost = ref('')
const ldapPort = ref(389)
const ldapBaseDN = ref('')
const ldapBindDN = ref('')
const ldapBindPassword = ref('')
const ldapUrl = ref('')
const ldapAdminUser = ref('')
const ldapAdminPassword = ref('')
const oldPass = ref('')
const newPass = ref('')
@@ -22,17 +20,20 @@ const showGitPrompt = ref(false)
const gitCommitMessage = ref('Initial commit')
const gitCommitting = ref(false)
// LDAP Browser State
const ldapUsers = ref<string[]>([])
const ldapGroups = ref<string[]>([])
const ldapBrowsing = ref(false)
onMounted(async () => {
try {
const data = await gql<{ config: any }>(`{ config { storagePath ldap { host port baseDN bindDN } } }`)
const data = await gql<{ config: any }>(`{ config { storagePath ldap { url adminUser } } }`)
storagePath.value = data.config.storagePath || ''
if (data.config.ldap) {
ldapEnabled.value = true
ldapHost.value = data.config.ldap.host
ldapPort.value = data.config.ldap.port
ldapBaseDN.value = data.config.ldap.baseDN
ldapBindDN.value = data.config.ldap.bindDN
ldapUrl.value = data.config.ldap.url
ldapAdminUser.value = data.config.ldap.adminUser
}
} catch (err: any) {
status.value = { msg: err.message, isError: true }
@@ -83,11 +84,9 @@ async function saveLdap() {
let input = null
if (ldapEnabled.value) {
input = {
host: ldapHost.value,
port: ldapPort.value,
baseDN: ldapBaseDN.value,
bindDN: ldapBindDN.value,
bindPassword: ldapBindPassword.value
url: ldapUrl.value,
adminUser: ldapAdminUser.value,
adminPass: ldapAdminPassword.value
}
}
@@ -98,6 +97,52 @@ async function saveLdap() {
}
}
async function testLdap() {
try {
const data = await gql<{ testLdapConnection: { success: boolean, message: string } }>(
`mutation TestLdap($url: String!, $adminUser: String, $adminPass: String) {
testLdapConnection(url: $url, adminUser: $adminUser, adminPass: $adminPass) { success message }
}`,
{ url: ldapUrl.value, adminUser: ldapAdminUser.value, adminPass: ldapAdminPassword.value }
)
if (data.testLdapConnection.success) {
showStatus('LDAP connection successful.', false)
} else {
showStatus('LDAP connection failed: ' + data.testLdapConnection.message, true)
}
} catch (err: any) {
showStatus(err.message, true)
}
}
async function browseLdap() {
try {
ldapBrowsing.value = true
const data = await gql<{ ldapBrowse: { users: string[], groups: string[] } }>(
`query LdapBrowse($url: String, $adminUser: String, $adminPass: String) {
ldapBrowse(url: $url, adminUser: $adminUser, adminPass: $adminPass) { users groups }
}`,
{ url: ldapUrl.value || undefined, adminUser: ldapAdminUser.value || undefined, adminPass: ldapAdminPassword.value || undefined }
)
ldapUsers.value = data.ldapBrowse.users
ldapGroups.value = data.ldapBrowse.groups
showStatus('LDAP structure loaded.')
} catch (err: any) {
showStatus(err.message, true)
} finally {
ldapBrowsing.value = false
}
}
async function importSubject(type: 'user' | 'group', name: string) {
try {
await gql(`mutation ImportSubj($type: String!, $name: String!) { importLdapSubject(type: $type, name: $name) }`, { type, name })
showStatus(`Imported ${type} ${name} successfully.`)
} catch (err: any) {
showStatus(err.message, true)
}
}
async function savePassword() {
if (newPass.value.length < 8) {
showStatus('New password must be at least 8 characters.', true)
@@ -217,33 +262,58 @@ async function savePassword() {
</div>
<div v-if="ldapEnabled" class="grid gap-4 sm:grid-cols-2 mb-4">
<div class="sm:col-span-2 md:col-span-1">
<label class="label">Host</label>
<input v-model="ldapHost" class="input" placeholder="ldap.example.com" />
</div>
<div class="sm:col-span-2 md:col-span-1">
<label class="label">Port</label>
<input v-model.number="ldapPort" type="number" class="input" placeholder="389" />
</div>
<div class="sm:col-span-2">
<label class="label">Base DN <span class="text-slate-400 text-xs font-normal">(e.g. dc=example,dc=com)</span></label>
<input v-model="ldapBaseDN" class="input" placeholder="dc=example,dc=com" />
<label class="label">LDAP URL</label>
<input v-model="ldapUrl" class="input" placeholder="ldap://ldap.example.com:389" />
</div>
<div class="sm:col-span-2 md:col-span-1">
<label class="label">Bind DN (Service Account)</label>
<input v-model="ldapBindDN" class="input" placeholder="cn=reader,dc=example,dc=com" />
<label class="label">Admin User (Optional Service Account)</label>
<input v-model="ldapAdminUser" class="input" placeholder="cn=reader,dc=example,dc=com" />
</div>
<div class="sm:col-span-2 md:col-span-1">
<label class="label">Bind Password</label>
<input v-model="ldapBindPassword" type="password" class="input" />
<label class="label">Admin Password</label>
<input v-model="ldapAdminPassword" type="password" class="input" />
</div>
</div>
<div class="flex justify-end">
<div class="flex justify-end gap-2 mb-6">
<button class="btn-secondary" v-if="ldapEnabled" @click="testLdap">Test Connection</button>
<button class="btn-primary" @click="saveLdap">
{{ ldapEnabled ? 'Save LDAP Config' : 'Save & Disable LDAP' }}
</button>
</div>
<!-- LDAP Browser -->
<div v-if="ldapEnabled" class="border-t border-slate-200 dark:border-slate-700 pt-6 mt-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-md font-semibold">LDAP Browser</h3>
<button class="btn-secondary text-sm py-1 px-3" @click="browseLdap" :disabled="ldapBrowsing">
{{ ldapBrowsing ? 'Loading...' : 'Browse Users & Groups' }}
</button>
</div>
<div v-if="ldapUsers.length || ldapGroups.length" class="grid sm:grid-cols-2 gap-6">
<div class="card p-4">
<h4 class="font-medium text-slate-800 dark:text-slate-200 mb-2 border-b pb-1">Users</h4>
<ul class="max-h-48 overflow-y-auto space-y-1">
<li v-for="u in ldapUsers" :key="u" class="flex justify-between items-center text-sm p-1 hover:bg-slate-50 dark:hover:bg-slate-800 rounded">
<span class="truncate">{{ u }}</span>
<button class="text-accent-600 hover:text-accent-700 text-xs" @click="importSubject('user', u)">Import</button>
</li>
</ul>
</div>
<div class="card p-4">
<h4 class="font-medium text-slate-800 dark:text-slate-200 mb-2 border-b pb-1">Groups</h4>
<ul class="max-h-48 overflow-y-auto space-y-1">
<li v-for="g in ldapGroups" :key="g" class="flex justify-between items-center text-sm p-1 hover:bg-slate-50 dark:hover:bg-slate-800 rounded">
<span class="truncate">{{ g }}</span>
<button class="text-accent-600 hover:text-accent-700 text-xs" @click="importSubject('group', g)">Import</button>
</li>
</ul>
</div>
</div>
<p v-else-if="!ldapBrowsing" class="text-sm text-slate-500 italic">Click browse to load LDAP hierarchy.</p>
</div>
</section>
</div>