# Test Authelia as an auth server for Traefik as a reverse proxy of a local web service
{ lib, ... }:
{
  name = "authelia";
  meta.maintainers = with lib.maintainers; [ jk ];

  nodes = {
    authelia =
      {
        pkgs,
        ...
      }:
      {
        services.authelia.instances.testing = {
          enable = true;
          secrets.storageEncryptionKeyFile = "/etc/authelia/storageEncryptionKeyFile";
          secrets.jwtSecretFile = "/etc/authelia/jwtSecretFile";
          settings = {
            authentication_backend.file.path = "/etc/authelia/users_database.yml";
            access_control.default_policy = "one_factor";
            session.domain = "example.com";
            storage.local.path = "/tmp/db.sqlite3";
            notifier.filesystem.filename = "/tmp/notifications.txt";
          };
        };

        # These should not be set from nix but through other means to not leak the secret!
        # This is purely for testing purposes!
        environment.etc."authelia/storageEncryptionKeyFile" = {
          mode = "0400";
          user = "authelia-testing";
          text = "you_must_generate_a_random_string_of_more_than_twenty_chars_and_configure_this";
        };
        environment.etc."authelia/jwtSecretFile" = {
          mode = "0400";
          user = "authelia-testing";
          text = "a_very_important_secret";
        };
        environment.etc."authelia/users_database.yml" = {
          mode = "0400";
          user = "authelia-testing";
          text = ''
            users:
              bob:
                disabled: false
                displayname: bob
                # password of password
                password: $argon2id$v=19$m=65536,t=3,p=4$2ohUAfh9yetl+utr4tLcCQ$AsXx0VlwjvNnCsa70u4HKZvFkC8Gwajr2pHGKcND/xs
                email: bob@jim.com
                groups:
                  - admin
                  - dev
          '';
        };

        services.traefik = {
          enable = true;

          dynamicConfigOptions = {
            tls.certificates =
              let
                certDir = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
                  openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=example.com/CN=auth.example.com/CN=static.example.com' -days 36500
                  mkdir -p $out
                  cp key.pem cert.pem $out
                '';
              in
              [
                {
                  certFile = "${certDir}/cert.pem";
                  keyFile = "${certDir}/key.pem";
                }
              ];
            http.middlewares.authelia.forwardAuth = {
              address = "http://localhost:9091/api/verify?rd=https%3A%2F%2Fauth.example.com%2F";
              trustForwardHeader = true;
              authResponseHeaders = [
                "Remote-User"
                "Remote-Groups"
                "Remote-Email"
                "Remote-Name"
              ];
            };
            http.middlewares.authelia-basic.forwardAuth = {
              address = "http://localhost:9091/api/verify?auth=basic";
              trustForwardHeader = true;
              authResponseHeaders = [
                "Remote-User"
                "Remote-Groups"
                "Remote-Email"
                "Remote-Name"
              ];
            };

            http.routers.simplehttp = {
              rule = "Host(`static.example.com`)";
              tls = true;
              entryPoints = "web";
              service = "simplehttp";
            };
            http.routers.simplehttp-basic-auth = {
              rule = "Host(`static-basic-auth.example.com`)";
              tls = true;
              entryPoints = "web";
              service = "simplehttp";
              middlewares = [ "authelia-basic@file" ];
            };

            http.services.simplehttp = {
              loadBalancer.servers = [
                {
                  url = "http://localhost:8000";
                }
              ];
            };

            http.routers.authelia = {
              rule = "Host(`auth.example.com`)";
              tls = true;
              entryPoints = "web";
              service = "authelia@file";
            };

            http.services.authelia = {
              loadBalancer.servers = [
                {
                  url = "http://localhost:9091";
                }
              ];
            };
          };

          staticConfigOptions = {
            global = {
              checkNewVersion = false;
              sendAnonymousUsage = false;
            };

            entryPoints.web.address = ":443";
          };
        };

        systemd.services.simplehttp =
          let
            fakeWebPageDir = pkgs.writeTextDir "index.html" "hello";
          in
          {
            script = "${pkgs.python3}/bin/python -m http.server --directory ${fakeWebPageDir} 8000";
            serviceConfig.Type = "simple";
            wantedBy = [ "multi-user.target" ];
          };
      };
  };

  testScript = ''
    start_all()

    authelia.wait_for_unit("simplehttp.service")
    authelia.wait_for_unit("traefik.service")
    authelia.wait_for_unit("authelia-testing.service")
    authelia.wait_for_open_port(443)
    authelia.wait_for_unit("multi-user.target")

    with subtest("Check for authelia"):
      # expect the login page
      assert "Login - Authelia", "could not reach authelia" in \
        authelia.succeed("curl --insecure -sSf -H Host:auth.example.com https://authelia:443/")

    with subtest("Check contacting basic http server via traefik with https works"):
      assert "hello", "could not reach raw static site" in \
        authelia.succeed("curl --insecure -sSf -H Host:static.example.com https://authelia:443/")

    with subtest("Test traefik and authelia"):
      with subtest("No details fail"):
        authelia.fail("curl --insecure -sSf -H Host:static-basic-auth.example.com https://authelia:443/")
      with subtest("Incorrect details fail"):
        authelia.fail("curl --insecure -sSf -u 'bob:wordpass' -H Host:static-basic-auth.example.com https://authelia:443/")
        authelia.fail("curl --insecure -sSf -u 'alice:password' -H Host:static-basic-auth.example.com https://authelia:443/")
      with subtest("Correct details pass"):
        assert "hello", "could not reach authed static site with valid credentials" in \
          authelia.succeed("curl --insecure -sSf -u 'bob:password' -H Host:static-basic-auth.example.com https://authelia:443/")
  '';
}
