fix: make Admin LDAP sign-in work against GLAuth
Three bugs blocked sign-in entirely: - Login.razor is static-SSR but its form model lacked [SupplyParameterFromForm], so the posted username/password never bound — SignInAsync saw empty fields and bailed before LDAP was contacted. Annotate the model; seed it in OnInitialized since BL0008 forbids an initializer on a [SupplyParameterFromForm] property. - appsettings.json ServiceAccountDn used ou=svcaccts, which GLAuth reads as a (non-existent) group — the service-account bind failed with "Group not found". Use cn=serviceaccount,dc=lmxopcua,dc=local. - LdapAuthService resolved the user DN by searching (uid=...), but GLAuth keys users by cn. Add an LdapOptions.UserNameAttribute knob (default cn for GLAuth; set sAMAccountName for Active Directory) and use it for the search filter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,10 +47,17 @@
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private Input _input = new();
|
||||
// Static-SSR form post: the model must be [SupplyParameterFromForm] or the
|
||||
// submitted username/password never bind back onto _input. The property
|
||||
// cannot carry an initializer (BL0008) — seed it in OnInitialized instead.
|
||||
[SupplyParameterFromForm]
|
||||
private Input _input { get; set; } = default!;
|
||||
|
||||
private string? _error;
|
||||
private bool _busy;
|
||||
|
||||
protected override void OnInitialized() => _input ??= new();
|
||||
|
||||
private async Task SignInAsync()
|
||||
{
|
||||
_error = null;
|
||||
|
||||
@@ -108,7 +108,7 @@ public sealed class LdapAuthService(IOptions<LdapOptions> options, ILogger<LdapA
|
||||
await Task.Run(() =>
|
||||
conn.Bind(_options.ServiceAccountDn, _options.ServiceAccountPassword), ct);
|
||||
|
||||
var filter = $"(uid={EscapeLdapFilter(username)})";
|
||||
var filter = $"({_options.UserNameAttribute}={EscapeLdapFilter(username)})";
|
||||
var results = await Task.Run(() =>
|
||||
conn.Search(_options.SearchBase, LdapConnection.ScopeSub, filter, ["dn"], false), ct);
|
||||
|
||||
@@ -116,7 +116,7 @@ public sealed class LdapAuthService(IOptions<LdapOptions> options, ILogger<LdapA
|
||||
return results.Next().Dn;
|
||||
|
||||
throw new LdapException("User not found", LdapException.NoSuchObject,
|
||||
$"No entry for uid={username}");
|
||||
$"No entry for {filter}");
|
||||
}
|
||||
|
||||
return string.IsNullOrWhiteSpace(_options.SearchBase)
|
||||
|
||||
@@ -29,6 +29,13 @@ public sealed class LdapOptions
|
||||
public string DisplayNameAttribute { get; set; } = "cn";
|
||||
public string GroupAttribute { get; set; } = "memberOf";
|
||||
|
||||
/// <summary>
|
||||
/// Attribute the service-account search matches the login name against to resolve the
|
||||
/// user's DN. <c>cn</c> for GLAuth (the dev default); set <c>sAMAccountName</c> for
|
||||
/// Active Directory.
|
||||
/// </summary>
|
||||
public string UserNameAttribute { get; set; } = "cn";
|
||||
|
||||
/// <summary>
|
||||
/// Maps LDAP group name → Admin role. Group match is case-insensitive. A user gets every
|
||||
/// role whose source group is in their membership list. Example dev mapping:
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"UseTls": false,
|
||||
"AllowInsecureLdap": true,
|
||||
"SearchBase": "dc=lmxopcua,dc=local",
|
||||
"ServiceAccountDn": "cn=serviceaccount,ou=svcaccts,dc=lmxopcua,dc=local",
|
||||
"ServiceAccountDn": "cn=serviceaccount,dc=lmxopcua,dc=local",
|
||||
"ServiceAccountPassword": "serviceaccount123",
|
||||
"DisplayNameAttribute": "cn",
|
||||
"GroupAttribute": "memberOf",
|
||||
|
||||
Reference in New Issue
Block a user