{ config, lib, ... }:

let
  inherit (lib)
    genAttrs
    mkAliasOptionModule
    mkEnableOption
    mkIf
    mkOption
    types
    ;
  cfg = config.services.nginx.tailscaleAuth;
  cfgAuth = config.services.tailscaleAuth;
in
{
  imports = [
    (mkAliasOptionModule
      [ "services" "nginx" "tailscaleAuth" "package" ]
      [ "services" "tailscaleAuth" "package" ]
    )
    (mkAliasOptionModule
      [ "services" "nginx" "tailscaleAuth" "user" ]
      [ "services" "tailscaleAuth" "user" ]
    )
    (mkAliasOptionModule
      [ "services" "nginx" "tailscaleAuth" "group" ]
      [ "services" "tailscaleAuth" "group" ]
    )
    (mkAliasOptionModule
      [ "services" "nginx" "tailscaleAuth" "socketPath" ]
      [ "services" "tailscaleAuth" "socketPath" ]
    )
  ];

  options.services.nginx.tailscaleAuth = {
    enable = mkEnableOption "tailscale.nginx-auth, to authenticate nginx users via tailscale";

    expectedTailnet = mkOption {
      default = "";
      type = types.nullOr types.str;
      example = "tailnet012345.ts.net";
      description = ''
        If you want to prevent node sharing from allowing users to access services
        across tailnets, declare your expected tailnets domain here.
      '';
    };

    virtualHosts = mkOption {
      type = types.listOf types.str;
      default = [ ];
      description = ''
        A list of nginx virtual hosts to put behind tailscale.nginx-auth
      '';
    };
  };

  config = mkIf cfg.enable {
    services.tailscaleAuth.enable = true;
    services.nginx.enable = true;

    users.users.${config.services.nginx.user}.extraGroups = [ cfgAuth.group ];

    systemd.services.tailscale-nginx-auth = {
      after = [ "nginx.service" ];
      wants = [ "nginx.service" ];
    };

    services.nginx.virtualHosts = genAttrs cfg.virtualHosts (vhost: {
      locations."/auth" = {
        extraConfig = ''
          internal;

          proxy_pass http://unix:${cfgAuth.socketPath};
          proxy_pass_request_body off;
          proxy_set_header Content-Length "";

          # Upstream uses $http_host here, but we are using gixy to check nginx configurations
          # gixy wants us to use $host: https://github.com/yandex/gixy/blob/master/docs/en/plugins/hostspoofing.md
          proxy_set_header Host $host;
          proxy_set_header Remote-Addr $remote_addr;
          proxy_set_header Remote-Port $remote_port;
          proxy_set_header Original-URI $request_uri;
          proxy_set_header X-Scheme                $scheme;
          proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
        '';
      };
      locations."/".extraConfig = ''
        auth_request /auth;
        auth_request_set $auth_user $upstream_http_tailscale_user;
        auth_request_set $auth_name $upstream_http_tailscale_name;
        auth_request_set $auth_login $upstream_http_tailscale_login;
        auth_request_set $auth_tailnet $upstream_http_tailscale_tailnet;
        auth_request_set $auth_profile_picture $upstream_http_tailscale_profile_picture;

        proxy_set_header X-Webauth-User "$auth_user";
        proxy_set_header X-Webauth-Name "$auth_name";
        proxy_set_header X-Webauth-Login "$auth_login";
        proxy_set_header X-Webauth-Tailnet "$auth_tailnet";
        proxy_set_header X-Webauth-Profile-Picture "$auth_profile_picture";

        ${lib.optionalString (
          cfg.expectedTailnet != ""
        ) ''proxy_set_header Expected-Tailnet "${cfg.expectedTailnet}";''}
      '';
    });
  };

  meta.maintainers = [ ];

}
