Codumentor logo Codumentor

Authentication

Codumentor supports multiple authentication providers to control access to the API. Authentication is configured through the auth section of codumentor.yaml.

Overview

The auth.provider setting selects which authentication backend to use:

ProviderValueDescription
No authentication"none"Default. No credentials required. Suitable for local development only.
Gitea"gitea"Authenticate using Gitea personal access tokens.
Local"local"Username/password users defined directly in the config file. For small teams and self-hosted deployments without an external directory.
LDAP"ldap"Authenticate against an LDAP or Active Directory server.
auth:
  provider: "none"  # "none", "gitea", "local", or "ldap"

Regardless of provider, set jwt_secret in any deployment that uses persistent login sessions or the per-user secrets / model-profiles / OAuth plugins — without it sessions reset on restart and those stores are not encrypted at rest.

Separate UI and API providers (advanced). auth.provider applies to both the web UI and the API when no per-surface overrides are set. To use different methods for each — e.g. LDAP for interactive UI login and static_key for OpenAI-compatible API clients — set ui_provider and/or api_provider. When api_provider is omitted it inherits ui_provider (not provider alone), so hardening the UI also protects the API unless you explicitly set api_provider (including to "none"). The per-provider config blocks (ldap:, local:, …) are shared between them.

No Authentication (provider: "none")

The default configuration. All API requests are accepted without credentials. This is appropriate for local development and single-user setups where the API is bound to 127.0.0.1.

auth:
  provider: "none"

Do not use this setting in production when the API is accessible over a network. See the Security guide for recommendations.

Gitea (provider: "gitea")

Users authenticate by providing a Gitea personal access token. Codumentor validates the token against the Gitea API on each request and caches successful validations to reduce API calls.

Configuration

auth:
  provider: "gitea"
  gitea:
    base_url: "https://gitea.example.com"
    token_cache_ttl_minutes: 30
SettingRequiredDefaultDescription
base_urlYes--Base URL of the Gitea instance
token_cache_ttl_minutesNo30How long (in minutes) to cache a successful token validation. Set to 0 to disable caching. Recommended range for production: 15--120 minutes.

How It Works

  1. The client sends a Gitea personal access token in the Authorization header.
  2. Codumentor calls the Gitea API (/api/v1/user) to validate the token.
  3. On success, the result is cached for token_cache_ttl_minutes to avoid repeated API calls.
  4. Invalid tokens are never cached.

Generating a Token

Users create personal access tokens in their Gitea account under Settings > Applications > Generate New Token. The token only needs the read:user scope.

Local (provider: "local")

A fixed set of username/password users defined directly in codumentor.yaml. This is the simplest way to give a small team real logins without standing up Gitea or LDAP — it is what the self-hosted reference configs use.

Configuration

auth:
  provider: "local"
  jwt_secret: "${CODUMENTOR_JWT_SECRET}"   # see below — needed for persistent sessions
  local:
    users:
      - username: admin
        password: "${CODUMENTOR_ADMIN_PASSWORD}"
        email: admin@example.com
        display_name: "Site Admin"
        roles: ["admin", "user", "power_user"]
      - username: dev
        password: "${CODUMENTOR_DEV_PASSWORD}"
        roles: ["user"]
FieldRequiredDefaultDescription
usernameYes--Login name. Must be unique across the users list (duplicates fail config validation).
passwordYes--Plaintext or a pbkdf2_sha256$... hash (see below). Supports ${ENV_VAR} substitution so the real secret stays out of the committed file.
emailNonullUsed for identity matching and notifications.
display_nameNonullShown in the UI. Falls back to username.
rolesNo["user"]Codumentor roles granted on login. Common roles: user, power_user, admin.

Passwords: env injection and hashing

Never commit a plaintext password. Use ${ENV_VAR} substitution and provide the value at runtime (systemd EnvironmentFile, .env, secrets manager):

export CODUMENTOR_ADMIN_PASSWORD='s3cret'
export CODUMENTOR_DEV_PASSWORD='…'

For defence in depth you can store a PBKDF2 hash instead of a plaintext value (the provider accepts either, auto-detected by the pbkdf2_sha256$ prefix). Generate one with:

python -c "from codumentor.auth.providers.local import hash_password; print(hash_password('s3cret'))"

Paste the resulting pbkdf2_sha256$<iterations>$<salt>$<hash> string as the password value (this can be committed safely).

Security note. As with every provider, do not expose a provider: "none"/local instance on a public network without a reverse proxy and TLS. See Security. Local auth has no lockout or rate limiting of its own — put rate limiting on the reverse proxy.

Session Persistence and At-Rest Encryption (jwt_secret)

auth.jwt_secret is a single deployment-wide secret that does two things:

  1. Signs login session tokens. Without it, sessions are signed with an ephemeral key generated at startup — so every restart invalidates all logins and forces users to sign in again.
  2. Seeds the at-rest encryption key (Fernet) for the per-user stores written by the user_secrets, model_profiles, and oauth plugins. Without it, those credentials are stored unencrypted on disk.
auth:
  jwt_secret: "${CODUMENTOR_JWT_SECRET}"
# Generate a strong value once and keep it stable for the life of the deployment:
export CODUMENTOR_JWT_SECRET="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')"

If the referenced environment variable is unset, Codumentor refuses to use the literal ${CODUMENTOR_JWT_SECRET} placeholder as the key (it would be a guessable, committed value). It drops the secret, logs a prominent ERROR, and degrades to ephemeral sessions + unencrypted secret stores. Treat that log line as a misconfiguration to fix, not a warning to ignore.

Rotation. Changing jwt_secret logs everyone out and makes existing encrypted rows unreadable — unless you let the affected plugins decrypt under the old key while re-encrypting under the new one. Pass the prior value(s) to each store plugin via its previous_jwt_secrets arg; rotated rows are re-encrypted lazily on next read:

plugins:
  - module: codumentor.plugins.user_secrets
    class: UserSecretsPlugin
    args:
      enabled: true
      previous_jwt_secrets: ["${CODUMENTOR_JWT_SECRET_OLD}"]

(The same previous_jwt_secrets arg is accepted by the model_profiles and oauth plugins.)

LDAP (provider: "ldap")

For enterprise environments, Codumentor can authenticate users against an LDAP directory or Active Directory server. This provider supports user search, group membership checks, and role mapping.

Configuration

auth:
  provider: "ldap"
  ldap:
    # Connection (required)
    server_url: "ldaps://ldap.example.com"
    bind_dn: "cn=codumentor-svc,ou=services,dc=example,dc=com"
    bind_password: "${LDAP_BIND_PASSWORD}"
    base_dn: "dc=example,dc=com"

    # Server type: "active_directory", "openldap", or "generic"
    server_type: "openldap"

    # Security: "ldaps", "starttls", or "none"
    security_mode: "ldaps"

    # User search
    user_search_filter: "(&(objectClass=inetOrgPerson)(uid={username}))"
    user_search_base: "ou=users,dc=example,dc=com"
    username_attribute: "uid"
    email_attribute: "mail"
    display_name_attribute: "displayName"

    # Group settings
    group_search_base: "ou=groups,dc=example,dc=com"
    group_member_attribute: "memberOf"

    # Role mapping
    group_role_mapping:
      "cn=admins,ou=groups,dc=example,dc=com": ["admin"]
      "cn=developers,ou=groups,dc=example,dc=com": ["developer"]
    default_roles: ["user"]

    # Access restriction (optional)
    require_group: "cn=codumentor-users,ou=groups,dc=example,dc=com"

    # SSL certificate validation
    validate_cert: true
    ca_cert_file: "/etc/ssl/certs/ca-certificates.crt"

Connection Settings

SettingRequiredDefaultDescription
server_urlYes--LDAP server URL. Use ldaps:// for SSL or ldap:// for plain/STARTTLS.
bind_dnYes--Distinguished name of the service account used for searching.
bind_passwordYes--Password for the bind DN. Use ${LDAP_BIND_PASSWORD} to read from an environment variable.
base_dnYes--Base DN for all LDAP searches.

Server Type

The server_type setting applies sensible defaults for common directory servers:

Typeusername_attributeuser_search_filterNotes
active_directorysAMAccountName(&(objectClass=user)(sAMAccountName={username}))Uses AD-specific nested group resolution
openldapuid(&(objectClass=inetOrgPerson)(uid={username}))Standard LDAP schema, no nested group resolution
genericuid(&(objectClass=person)(uid={username}))Minimal defaults, configure manually

You can override any of the defaults by specifying the corresponding setting explicitly.

Security Mode

ModeDescription
ldapsSSL/TLS on connect (port 636). Recommended for production.
starttlsUpgrade plain connection to TLS after connect (port 389).
noneNo encryption. Only use for testing on a trusted network.

User Search Settings

SettingDefaultDescription
user_search_filterDepends on server_typeLDAP filter to locate the user. {username} is replaced with the login name.
user_search_baseValue of base_dnSubtree to search for users.
username_attributeDepends on server_typeLDAP attribute containing the username.
email_attributemailLDAP attribute containing the email address.
display_name_attributedisplayNameLDAP attribute containing the display name.

Group and Role Mapping

SettingDefaultDescription
group_search_baseValue of base_dnSubtree to search for groups.
group_member_attributememberOfLDAP attribute on the user entry listing group membership.
group_role_mapping{}Dictionary mapping LDAP group DNs to lists of Codumentor roles.
default_roles["user"]Roles assigned when the user does not match any group mapping.
require_groupnullIf set, only users who are members of this group can authenticate. All others are rejected.

SSL Certificate Settings

SettingDefaultDescription
validate_certtrueWhether to validate the server's TLS certificate. Set to false only for testing.
ca_cert_filenullPath to a custom CA certificate file. If not set, the system certificate store is used.

Full LDAP Example (Active Directory)

auth:
  provider: "ldap"
  ldap:
    server_url: "ldaps://dc01.corp.example.com"
    bind_dn: "CN=Codumentor Service,OU=Service Accounts,DC=corp,DC=example,DC=com"
    bind_password: "${LDAP_BIND_PASSWORD}"
    base_dn: "DC=corp,DC=example,DC=com"
    server_type: "active_directory"
    security_mode: "ldaps"

    user_search_base: "OU=Employees,DC=corp,DC=example,DC=com"
    # sAMAccountName and AD-specific filter are applied automatically

    group_search_base: "OU=Groups,DC=corp,DC=example,DC=com"
    group_member_attribute: "memberOf"

    group_role_mapping:
      "CN=Codumentor Admins,OU=Groups,DC=corp,DC=example,DC=com": ["admin"]
      "CN=Engineering,OU=Groups,DC=corp,DC=example,DC=com": ["developer"]
    default_roles: ["user"]

    require_group: "CN=Codumentor Users,OU=Groups,DC=corp,DC=example,DC=com"

    validate_cert: true
    ca_cert_file: "/etc/ssl/certs/corp-ca.pem"

Full LDAP Example (OpenLDAP)

auth:
  provider: "ldap"
  ldap:
    server_url: "ldaps://ldap.example.com"
    bind_dn: "cn=codumentor,ou=services,dc=example,dc=com"
    bind_password: "${LDAP_BIND_PASSWORD}"
    base_dn: "dc=example,dc=com"
    server_type: "openldap"
    security_mode: "ldaps"

    user_search_filter: "(&(objectClass=inetOrgPerson)(uid={username}))"
    user_search_base: "ou=people,dc=example,dc=com"
    username_attribute: "uid"
    email_attribute: "mail"
    display_name_attribute: "cn"

    group_search_base: "ou=groups,dc=example,dc=com"
    group_member_attribute: "memberOf"

    group_role_mapping:
      "cn=admins,ou=groups,dc=example,dc=com": ["admin"]
      "cn=dev-team,ou=groups,dc=example,dc=com": ["developer"]
    default_roles: ["user"]

    validate_cert: true

Multiple LDAP Directories

When users live in more than one independent directory (separate domains, an
employee directory plus a contractor directory, partner organisations, etc.),
configure them as a list under ldap_directories instead of the single
ldap: block. The two forms are mutually exclusive.

auth:
  provider: "ldap"
  ldap_directories:
    - name: "corp"
      server_url: "ldaps://corp.example.com"
      bind_dn: "cn=svc,dc=corp,dc=example,dc=com"
      bind_password: "${LDAP_CORP_BIND_PASSWORD}"
      base_dn: "dc=corp,dc=example,dc=com"
      server_type: "active_directory"
      group_role_mapping:
        "CN=Codumentor Admins,OU=Groups,DC=corp,DC=example,DC=com": ["admin"]

    - name: "contractors"
      server_url: "ldaps://contractors.example.com"
      bind_dn: "cn=svc,dc=contractors,dc=example,dc=com"
      bind_password: "${LDAP_CONTRACTORS_BIND_PASSWORD}"
      base_dn: "dc=contractors,dc=example,dc=com"
      server_type: "openldap"
      # No admin mapping — contractors get the default role only.

Each entry accepts the full set of LDAP options listed above. Each entry must
have a unique, non-empty name.

How login routing works

On every login Codumentor tries each directory in declared order and stops at
the first directory that authenticates the user successfully:

The first directory whose bind succeeds wins, so if the same username exists
in two directories, the one listed first authenticates. Order matters.

Identity across directories

User.provider remains "ldap" regardless of which directory handled the
login. With the default trust_email behaviour, a user who appears in two
directories with the same verified email is treated as the same person — they
land on a single user record.

Audit logs

Each per-directory attempt is logged with provider="ldap:<name>" (e.g.
ldap:corp, ldap:contractors) so administrators can see which directory
authenticated a given login or which one rejected it.

See Also