LDAP admin GUI + group filter

- LDAP settings configurable from Admin panel (no restart needed)
- Required group filter: only users in specified group can login
- Supports both memberOf attribute and groupOfNames search
- Settings stored in DB (settings table), env vars as fallback
- SLDAP supported via ldaps:// URL
- Bind password masked in UI
This commit is contained in:
2026-05-27 00:08:00 +02:00
parent f58ac04069
commit 8a7b0e18ed
6 changed files with 161 additions and 18 deletions
+42 -1
View File
@@ -255,6 +255,26 @@
</tbody>
</table>
</div>
<div class="panel-section">
<h3>LDAP / SLDAP Authentication</h3>
<form @submit.prevent="saveLdapSettings" class="admin-form">
<label>Server URL</label>
<input v-model="ldapSettings.ldap_url" placeholder="ldap://host:389 or ldaps://host:636" />
<label>Bind DN</label>
<input v-model="ldapSettings.ldap_bind_dn" placeholder="cn=service,dc=example,dc=com" />
<label>Bind Password</label>
<input v-model="ldapSettings.ldap_bind_pass" type="password" placeholder="Service account password" />
<label>Base DN</label>
<input v-model="ldapSettings.ldap_base_dn" placeholder="dc=example,dc=com" />
<label>User Filter</label>
<input v-model="ldapSettings.ldap_user_filter" placeholder="(&(objectClass=inetOrgPerson)(uid=%s))" />
<label>Required Group (DN or CN)</label>
<input v-model="ldapSettings.ldap_group_filter" placeholder="cn=markdownhub-users,ou=groups,dc=..." />
<label><input type="checkbox" v-model="ldapSkipTLS" /> Skip TLS verification</label>
<button type="submit">Save LDAP Settings</button>
</form>
<p v-if="ldapMsg" class="admin-msg">{{ ldapMsg }}</p>
</div>
</main>
<!-- About -->
<main class="panel" v-if="view === 'about'" style="text-align:center;padding-top:80px">
@@ -380,6 +400,9 @@ const newRemote = ref({ name: '', url: '' })
const users = ref([])
const newUser = ref({ username: '', email: '', password: '', isAdmin: false })
const adminMsg = ref('')
const ldapSettings = ref({ ldap_url: '', ldap_bind_dn: '', ldap_bind_pass: '', ldap_base_dn: '', ldap_user_filter: '', ldap_group_filter: '' })
const ldapSkipTLS = ref(false)
const ldapMsg = ref('')
const rendered = computed(() => renderMarkdown(content.value))
@@ -404,7 +427,7 @@ async function login() {
loadShared()
loadRemotes()
syncPending()
if (isAdmin.value) loadUsers()
if (isAdmin.value) { loadUsers(); loadLdapSettings() }
// Sync pending changes when coming back online
window.addEventListener('online', syncPending)
} catch (e) {
@@ -777,6 +800,24 @@ async function adminCreateUser() {
}
}
async function loadLdapSettings() {
try {
ldapSettings.value = await api('/api/admin/settings/get', {})
ldapSkipTLS.value = ldapSettings.value.ldap_skip_tls === 'true'
} catch {}
}
async function saveLdapSettings() {
const data = { ...ldapSettings.value, ldap_skip_tls: ldapSkipTLS.value ? 'true' : 'false' }
try {
await api('/api/admin/settings/save', data)
ldapMsg.value = 'LDAP settings saved'
toast('LDAP settings saved', 'success')
} catch {
ldapMsg.value = 'Failed to save'
}
}
function formatDate(d) {
if (!d) return ''
try {