Skip to content

Add Active Directory (LDAP) auth provider with role/group#2223

Open
unocelli wants to merge 3 commits intomasterfrom
feat/auth-ad
Open

Add Active Directory (LDAP) auth provider with role/group#2223
unocelli wants to merge 3 commits intomasterfrom
feat/auth-ad

Conversation

@unocelli
Copy link
Member

Add Active Directory (LDAP) auth provider with role/group mapping and settings persistence safeguards

Summary

This PR adds a pluggable AD authentication provider (ad / ad-ldap) to FUXA, keeps local auth fallback, normalizes mapped roles to FUXA role IDs in userRole mode.

What’s included

  1. Provider-based auth flow
    • Added provider registry in server/runtime/auth.
    • Supported providers:
      • local
      • ad (ad-ldap)
    • Configurable fallback to local (auth.fallbackLocal).
  2. AD/LDAP authentication
    • Service bind (optional but recommended) for user lookup.
    • User search via configurable filter.
    • Credential validation via bind with resolved user DN.
    • Group extraction and mapping to:
      • FUXA roles
      • legacy bitmask groups
  3. Role normalization in userRole mode
    • Mapped role labels/names are resolved to FUXA role IDs using configured roles.
    • Prevents runtime permission mismatch where objects store role IDs.
  4. AD validation endpoint
    • Added admin-only endpoint:
      • POST /api/settings/auth/test-ad
    • Verifies AD auth/mapping and returns diagnostic payload including adGroups.
  5. Tests
    • supertest API tests for /api/settings auth persistence and authorization checks.
    • Role mapping test for role-name -> role-id conversion.

New/updated settings

{
  "auth": {
    "provider": "local",
    "fallbackLocal": true,
    "adminRoleNames": ["admin", "administrator"],
    "ad": {
      "enabled": false,
      "url": "",
      "baseDN": "",
      "bindDN": "",
      "bindPassword": "",
      "userFilter": "(&(objectClass=user)(|(sAMAccountName={{username}})(userPrincipalName={{username}})))",
      "userAttributes": ["dn", "cn", "displayName", "memberOf", "mail", "userPrincipalName", "sAMAccountName"],
      "groupAttribute": "memberOf",
      "refreshIdentity": false,
      "rejectUnauthorized": true,
      "timeoutMs": 5000,
      "connectTimeoutMs": 5000,
      "groupToRoles": {},
      "groupToMask": {},
      "defaultRoles": [],
      "defaultGroup": 0
    }
  }
}

Settings meaning (AD block)

  • enabled: enables AD provider config.
  • url: LDAP/LDAPS endpoint (ldap://...:389 or ldaps://...:636).
  • baseDN: root search DN.
  • bindDN / bindPassword: service account for user lookup.
  • userFilter: LDAP filter; supports {{username}}.
  • userAttributes: attributes fetched from LDAP.
  • groupAttribute: group attribute name (usually memberOf).
  • refreshIdentity: if true, /api/refresh re-resolves identity from AD.
  • rejectUnauthorized: TLS certificate validation for ldaps://.
  • timeoutMs / connectTimeoutMs: LDAP operation/connect timeouts.
  • groupToRoles: map AD group (DN or CN) -> role name(s)/id(s).
  • groupToMask: map AD group (DN or CN) -> legacy group mask number(s).
  • defaultRoles: roles always added when no match.
  • defaultGroup: fallback mask when no groupToMask match.
  • provider: selected provider (local, ad).
  • fallbackLocal: use local auth when selected provider fails.
  • adminRoleNames: role names treated as admin for legacy checks.

Example (roles + groups mapping)

{
  "userRole": true,
  "auth": {
    "provider": "ad",
    "fallbackLocal": true,
    "ad": {
      "enabled": true,
      "url": "ldap://dc01.lab.local:389",
      "baseDN": "DC=lab,DC=local",
      "bindDN": "svc_fuxa_ldap@lab.local",
      "bindPassword": "********",
      "groupToRoles": {
        "CN=GG_Admins,OU=Groups,DC=lab,DC=local": ["admin"],
        "GG_Admins": ["admin"],
        "CN=GG_Operators,OU=Groups,DC=lab,DC=local": ["operator"],
        "GG_Operators": ["operator"]
      },
      "groupToMask": {
        "CN=GG_Admins,OU=Groups,DC=lab,DC=local": -1,
        "GG_Admins": -1,
        "CN=GG_Operators,OU=Groups,DC=lab,DC=local": 2,
        "GG_Operators": 2
      },
      "defaultRoles": [],
      "defaultGroup": 0
    }
  }
}

Expected results:

  • user in GG_Operators -> roles=["operator"], groups=2
  • user in GG_Admins -> roles=["admin"], groups=-1

Security note

  • Lab: ldap://...:389 is acceptable for testing.
  • Production: prefer ldaps://...:636 with valid certs and rejectUnauthorized: true.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant