ldpa commit 2

This commit is contained in:
Mateusz Gruszczyński
2026-07-25 20:55:30 +02:00
parent fcf7910f28
commit c24ebc4be7
7 changed files with 48 additions and 29 deletions
+1
View File
@@ -100,4 +100,5 @@ AUTHORIZATION_TYPE=local
# LDAP_USERNAME_ATTRIBUTE=sAMAccountName # LDAP_USERNAME_ATTRIBUTE=sAMAccountName
# LDAP_USER_FILTER=(uid={username}) # LDAP_USER_FILTER=(uid={username})
# LDAP_USERNAME_ATTRIBUTE=uid # LDAP_USERNAME_ATTRIBUTE=uid
# email auth: LDAP_EMAIL_ATTRIBUTE=mail
+8 -20
View File
@@ -1,27 +1,15 @@
dn: ou=people,dc=example,dc=org dn: ou=people,dc=organization,dc=local
objectClass: organizationalUnit objectClass: organizationalUnit
ou: people ou: people
dn: uid=mateusz,ou=people,dc=example,dc=org dn: uid=admin,ou=people,dc=organization,dc=local
objectClass: inetOrgPerson objectClass: inetOrgPerson
objectClass: organizationalPerson objectClass: organizationalPerson
objectClass: person objectClass: person
objectClass: top objectClass: top
uid: mateusz uid: admin
cn: Mateusz Testowy cn: Organization Administrator
sn: Testowy sn: Administrator
displayName: Mateusz Testowy displayName: Organization Administrator
mail: mateusz@example.org mail: admin@organization.local
userPassword: test1234 userPassword: test1234!
dn: uid=anna,ou=people,dc=example,dc=org
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: anna
cn: Anna Testowa
sn: Testowa
displayName: Anna Testowa
mail: anna@example.org
userPassword: test1234
+2 -2
View File
@@ -10,7 +10,7 @@ services:
LDAP_CONFIG_PASSWORD: config LDAP_CONFIG_PASSWORD: config
LDAP_TLS: "false" LDAP_TLS: "false"
ports: ports:
- "12389:389" - "10.87.2.6:389:389"
volumes: volumes:
- ./bootstrap:/container/service/slapd/assets/config/bootstrap/ldif/custom:ro - ./bootstrap:/container/service/slapd/assets/config/bootstrap/ldif/custom:ro
command: --copy-service command: --copy-service
@@ -23,6 +23,6 @@ services:
PHPLDAPADMIN_LDAP_HOSTS: ldap PHPLDAPADMIN_LDAP_HOSTS: ldap
PHPLDAPADMIN_HTTPS: "false" PHPLDAPADMIN_HTTPS: "false"
ports: ports:
- "12390:80" - "10.87.2.6:8088:80"
depends_on: depends_on:
- ldap - ldap
+5
View File
@@ -180,6 +180,7 @@ async fn home(State(state): State<SharedState>) -> Response {
include_str!("../static/home.html"), include_str!("../static/home.html"),
&state.asset_version, &state.asset_version,
state.registration_enabled, state.registration_enabled,
state.ldap.is_some(),
&state.frontend_log_level, &state.frontend_log_level,
state.upload_max_size_bytes, state.upload_max_size_bytes,
"home", "home",
@@ -195,6 +196,7 @@ async fn pad(State(state): State<SharedState>, Path(slug): Path<String>) -> Resp
&html, &html,
&state.asset_version, &state.asset_version,
state.registration_enabled, state.registration_enabled,
state.ldap.is_some(),
&state.frontend_log_level, &state.frontend_log_level,
state.upload_max_size_bytes, state.upload_max_size_bytes,
"pad", "pad",
@@ -222,6 +224,7 @@ async fn public_page(State(state): State<SharedState>, Path(token): Path<String>
include_str!("../static/public.html"), include_str!("../static/public.html"),
&state.asset_version, &state.asset_version,
state.registration_enabled, state.registration_enabled,
state.ldap.is_some(),
&state.frontend_log_level, &state.frontend_log_level,
state.upload_max_size_bytes, state.upload_max_size_bytes,
"public", "public",
@@ -254,6 +257,7 @@ async fn workspace(
&html, &html,
&state.asset_version, &state.asset_version,
state.registration_enabled, state.registration_enabled,
state.ldap.is_some(),
&state.frontend_log_level, &state.frontend_log_level,
state.upload_max_size_bytes, state.upload_max_size_bytes,
"workspace", "workspace",
@@ -308,6 +312,7 @@ async fn note(
&html, &html,
&state.asset_version, &state.asset_version,
state.registration_enabled, state.registration_enabled,
state.ldap.is_some(),
&state.frontend_log_level, &state.frontend_log_level,
state.upload_max_size_bytes, state.upload_max_size_bytes,
"note", "note",
+5 -3
View File
@@ -27,12 +27,13 @@ pub fn render_html(
template: &str, template: &str,
asset_version: &str, asset_version: &str,
registration_enabled: bool, registration_enabled: bool,
external_auth: bool,
frontend_log_level: &str, frontend_log_level: &str,
upload_max_size_bytes: usize, upload_max_size_bytes: usize,
entrypoint: &str, entrypoint: &str,
) -> Response { ) -> Response {
let urls = AssetUrls::new(asset_version); let urls = AssetUrls::new(asset_version);
let frontend_config = frontend_config(frontend_log_level, upload_max_size_bytes); let frontend_config = frontend_config(frontend_log_level, upload_max_size_bytes, external_auth);
let html = template let html = template
.replace("__APP_STYLESHEET__", &urls.stylesheet("styles")) .replace("__APP_STYLESHEET__", &urls.stylesheet("styles"))
.replace("__APP_IMPORT_MAP__", &urls.import_map()) .replace("__APP_IMPORT_MAP__", &urls.import_map())
@@ -59,11 +60,12 @@ pub fn stylesheet_tag(asset_version: &str, name: &str) -> String {
AssetUrls::new(asset_version).stylesheet(name) AssetUrls::new(asset_version).stylesheet(name)
} }
fn frontend_config(frontend_log_level: &str, upload_max_size_bytes: usize) -> String { fn frontend_config(frontend_log_level: &str, upload_max_size_bytes: usize, external_auth: bool) -> String {
format!( format!(
r#"<script>window.__RUSTPAD_CONFIG__=Object.freeze({{frontendLogLevel:"{}",uploadMaxSizeBytes:{}}});</script>"#, r#"<script>window.__RUSTPAD_CONFIG__=Object.freeze({{frontendLogLevel:"{}",uploadMaxSizeBytes:{},externalAuth:{}}});</script>"#,
escape_js_string(frontend_log_level), escape_js_string(frontend_log_level),
upload_max_size_bytes, upload_max_size_bytes,
external_auth,
) )
} }
+15 -2
View File
@@ -53,13 +53,26 @@ pub async fn authenticate(
config.email_attribute.as_str(), config.email_attribute.as_str(),
config.display_name_attribute.as_str(), config.display_name_attribute.as_str(),
]; ];
let (entries, _) = ldap let (mut entries, _) = ldap
.search(&config.base_dn, Scope::Subtree, &filter, attributes) .search(&config.base_dn, Scope::Subtree, &filter, attributes.clone())
.await .await
.map_err(|error| format!("LDAP search failed: {error}"))? .map_err(|error| format!("LDAP search failed: {error}"))?
.success() .success()
.map_err(|error| format!("LDAP search rejected: {error}"))?; .map_err(|error| format!("LDAP search rejected: {error}"))?;
// Allow users to sign in with their directory e-mail even when the configured
// primary filter searches by uid/sAMAccountName only.
if entries.is_empty() && login.trim().contains('@') {
let email_filter = format!("({}={})", config.email_attribute, escaped);
let (email_entries, _) = ldap
.search(&config.base_dn, Scope::Subtree, &email_filter, attributes)
.await
.map_err(|error| format!("LDAP e-mail search failed: {error}"))?
.success()
.map_err(|error| format!("LDAP e-mail search rejected: {error}"))?;
entries = email_entries;
}
if entries.len() != 1 { if entries.len() != 1 {
let _ = ldap.unbind().await; let _ = ldap.unbind().await;
return Ok(None); return Ok(None);
+12 -2
View File
@@ -24,6 +24,7 @@ export function bindIdentityDialog({ dialog, onIdentity, initialMode = "login" }
const resetButton = dialog.querySelector("#show-reset"); const resetButton = dialog.querySelector("#show-reset");
const backButton = dialog.querySelector("#reset-back"); const backButton = dialog.querySelector("#reset-back");
const registrationEnabled = document.body.dataset.registrationEnabled === "true"; const registrationEnabled = document.body.dataset.registrationEnabled === "true";
const externalAuth = window.__RUSTPAD_CONFIG__?.externalAuth === true;
let mode = initialMode; let mode = initialMode;
const setMode = (nextMode) => { const setMode = (nextMode) => {
@@ -47,9 +48,12 @@ export function bindIdentityDialog({ dialog, onIdentity, initialMode = "login" }
submit.textContent = resetting ? "Send reset link" : registering ? "Create account" : "Log in"; submit.textContent = resetting ? "Send reset link" : registering ? "Create account" : "Log in";
switchMode.hidden = resetting || !registrationEnabled; switchMode.hidden = resetting || !registrationEnabled;
switchMode.textContent = registering ? "Already registered? Log in" : "Create an account"; switchMode.textContent = registering ? "Already registered? Log in" : "Create an account";
resetButton.hidden = resetting || registering; resetButton.hidden = resetting || registering || externalAuth;
backButton.hidden = !resetting; backButton.hidden = !resetting;
const loginMode = mode === "login"; const loginMode = mode === "login";
email.type = externalAuth && loginMode ? "text" : "email";
if (externalAuth && loginMode) password.removeAttribute("minlength");
else password.minLength = 8;
form.autocomplete = loginMode ? "on" : "off"; form.autocomplete = loginMode ? "on" : "off";
nickname.autocomplete = "off"; nickname.autocomplete = "off";
nickname.dataset.bwignore = "true"; nickname.dataset.bwignore = "true";
@@ -143,6 +147,7 @@ function bindLegacyIdentityDialog({ dialog, onIdentity }) {
const backButton = dialog.querySelector("#auth-back"); const backButton = dialog.querySelector("#auth-back");
const logoutButton = dialog.querySelector("#logout-account"); const logoutButton = dialog.querySelector("#logout-account");
const registrationEnabled = document.body.dataset.registrationEnabled === "true"; const registrationEnabled = document.body.dataset.registrationEnabled === "true";
const externalAuth = window.__RUSTPAD_CONFIG__?.externalAuth === true;
let mode = "login"; let mode = "login";
const updateActions = () => { const updateActions = () => {
@@ -170,6 +175,9 @@ function bindLegacyIdentityDialog({ dialog, onIdentity }) {
modeTitle.textContent = mode === "register" ? "Register nickname" : "Log in"; modeTitle.textContent = mode === "register" ? "Register nickname" : "Log in";
authSubmit.textContent = mode === "register" ? "Register and continue" : "Log in and continue"; authSubmit.textContent = mode === "register" ? "Register and continue" : "Log in and continue";
const loginMode = mode === "login"; const loginMode = mode === "login";
email.type = externalAuth && loginMode ? "text" : "email";
if (externalAuth && loginMode) password.removeAttribute("minlength");
else password.minLength = 8;
form.autocomplete = loginMode ? "on" : "off"; form.autocomplete = loginMode ? "on" : "off";
nickname.autocomplete = "off"; nickname.autocomplete = "off";
nickname.dataset.bwignore = "true"; nickname.dataset.bwignore = "true";
@@ -194,7 +202,9 @@ function bindLegacyIdentityDialog({ dialog, onIdentity }) {
dialog.addEventListener("close", collapse); dialog.addEventListener("close", collapse);
dialog.addEventListener("cancel", collapse); dialog.addEventListener("cancel", collapse);
dialog.querySelector("#show-reset")?.addEventListener("click", async () => { const legacyResetButton = dialog.querySelector("#show-reset");
if (legacyResetButton) legacyResetButton.hidden = externalAuth;
legacyResetButton?.addEventListener("click", async () => {
const value = email.value.trim() || await askInput({ const value = email.value.trim() || await askInput({
title: "Reset password", title: "Reset password",
message: "Enter the e-mail address assigned to your local account.", message: "Enter the e-mail address assigned to your local account.",