{ config, lib, pkgs, ... }:

let cfg = config.sydnix.deertopia.lldap.pam;
in {
  options.sydnix.deertopia.lldap.pam = {
    enable = lib.mkEnableOption "LLDAP PAM integration";
  };

  # See https://github.com/lldap/lldap/blob/main/example_configs/pam/README.md.

  # Required imperative configuration: you will need to add the following custom
  # attributes to the user schema in the LLDAP web UI.
  #
  #   • uidNumber (integer)
  #   • gidNumber (integer, multiple values)
  #   • homeDirectory (string)
  #   • unixShell (string)
  #   • sshPublicKey (string) (only if you’re setting up SSH Public Key Sync)

  config = lib.mkIf cfg.enable {
    sydnix.sops.secrets =
      let
        e = {
          mode = "0440";
          owner = "sssd";
          group = "sssd";
        };
      in {
        sssd-environment = {};
      };

    # TODO: The existence of a dynamic set of users leads to some complexities
    # when combined with Impermanence.  Static users (i.e. those defined in the
    # NixOS config) shall opt in to Impermanence via the Home-manager setting,
    # but "dynamic" users (i.e. those per LDAP/PAM) should be persisted
    # unconditionally.  Implementing this behaviour takes quite the hack:
    #
    #   • /home is persisted.
    #
    #   • A impersistent directory /transient-home is created.
    #
    #   • "Dynamic" users are assigned subdirectories of /home like usual.
    #     Since /home is persisted, so are the users' individual home
    #     directories.
    #
    #   • The home directories of "static" users (that are opted in to
    #     Impermanence) are created at /transient-home/«user» and bind-mounted
    #     to /home/«user».

    security.pam.services."ldap".makeHomeDir = true;

    # For synchronizations of SSH keys.
    services.openssh.settings = {
      AuthorizedKeysCommand = lib.getExe' pkgs.sssd "sss_ssh_authorizedkeys";
      AuthorizedKeysCommandUser = "nobody";
    };

    services.sssd =
      let
        inherit (config.sydnix.deertopia.lldap) baseDN;
        ldaps-port = config.services.lldap.settings.ldaps_options.port;
      in {
        enable = true;
        environmentFile = "/run/secrets/sssd-environment";
        config = ''
          [sssd]
          config_file_version = 2
          # Change the domain below. It must match with the one in the [domain/]
          # part
          domains = deertopia.net

          [nss]

          [pam]

          # Put the same domain here
          [domain/deertopia.net]
          id_provider = ldap
          auth_provider = ldap
          chpass_provider = ldap
          ldap_schema = rfc2307
          # Place your LDAP server url here
          ldap_uri = ldaps://deertopia.net:${builtins.toString ldaps-port}/
          # Put your LDAP dc here
          ldap_search_base = ${baseDN}

          # Bind credentials
          # Bind user username (Should be in group lldap_strict_readonly)
          ldap_default_bind_dn = uid=sssd,ou=people,${baseDN}
          # Bind user password.  Defined by `services.sssd.environmentFile`.
          ldap_default_authtok = $SSSD_LDAP_DEFAULT_AUTHTOK

          # TLS settings
          ldap_tls_reqcert = demand
          # Put the certificate you generate for LDAPS here
          ldap_tls_cacert = ${./cert.pem}

          # User mappings
          # Put your LDAP dc here
          ldap_user_search_base = ou=people,${baseDN}
          ldap_user_object_class = posixAccount
          ldap_user_name = uid
          ldap_user_gecos = uid
          ldap_user_uid_number = uidNumber
          ldap_user_gid_number = gidNumber
          ldap_user_home_directory = homeDirectory
          ldap_user_shell = unixShell

          # Uncomment for SSH Key Sync setup
          ldap_user_ssh_public_key = sshPublicKey

          # Group mappings
          # Put your LDAP dc here
          ldap_group_search_base = ou=groups,${baseDN}
          ldap_group_object_class = groupOfUniqueNames
          ldap_group_name = cn
          ldap_group_member = uniqueMember

          access_provider = permit
          cache_credentials = true
        '';
      };
  };
}
