{
  config,
  pkgs,
  lib,
  ...
}:
with lib;
 
let
  postProcess = pkgs.writeShellScript "post-process-pam-service.sh" ''
    set -eu
 
    IN="$1"
    OUT="$2"
    USE_2FACTOR="$3"
    U2F_ARGS="$4"
 
    if [ -n "$USE_2FACTOR" ]; then
        sed -n "/^auth.*pam_u2f/{
            # add pattern space to hold space
            h
            :loop
            # print pattern space and fetch next line
            n
            # if we find pam_unix as sufficient we must change it to required
            # because this rule will go above pam_u2f in that case pam_u2f must be
            # sufficient as it will go after pam_unix
            /^auth sufficient.*pam_unix/{
                        s/auth sufficient/auth required/
                        p
                        # exchange pattern space with hold space
                        x
                        s#\(auth sufficient .*pam_u2f.so\)#\1 $U2F_ARGS#
                        bexit
            }
            # pam_unix was required, this means that there will be a followup rule
            # which is sufficient in that case pam_u2f must be required as it will
            # go before a sufficient rule
            /^auth required.*pam_unix/{
                        p
                        # exchange pattern space with hold space
                        x
                        s#auth sufficient \(.*pam_u2f.so\)#auth required \1 $U2F_ARGS#
                        bexit
            }
            # if we haven't found a pam_unix rule processing is aborted
            \$q1
 
            # append line to hold space with newline prepended
            H
            # exchange pattern space with hold space
            x
            # bubble the first matching rule to the bottom of the hold space
            s/\([^\n]*\)\n\([^\n]*\)\$/\2\n\1/
            # exchange pattern space with hold space
            x
            bloop
            }
            # print remaining contents
            :exit
            p" $IN > $OUT || cp $IN $OUT
    else
        sed "s#\(^auth .*pam_u2f.so\)#\1 $U2F_ARGS#" $IN > $OUT
    fi
  '';
 
  parentConfig = config;
  overrideServices =
    { name, config, ... }:
    {
      options = {
        use2Factor = mkOption {
          description = "If set to true u2f is used as 2nd factor.";
          default = parentConfig.security.pam.use2Factor;
        };
        u2fModuleArgs = mkOption {
          description = "Additional arguments to pass to pam_u2f.so";
          default = parentConfig.security.pam.u2fModuleArgs;
        };
        text = mkOption {
          apply =
            svc:
            builtins.readFile (
              pkgs.runCommand "pam-${name}-u2f"
                {
                  inherit svc;
                  passAsFile = [ "svc" ];
                }
                ''
                  ${postProcess} \
                    $svcPath \
                    $out \
                    ${
                      escapeShellArgs [
                        config.use2Factor
                        config.u2fModuleArgs
                      ]
                    }
                ''
            );
        };
      };
    };
in
{
  options = {
    security.pam.services = mkOption {
      type = with types; attrsOf (submodule overrideServices);
    };
    security.pam.u2fModuleArgs = mkOption {
      description = ''
        Additional arguments to pass to pam_u2f.so in all pam services.
        A service definition may override this setting.
      '';
      example = ''"cue"'';
      default = "cue";
    };
    security.pam.use2Factor = mkOption {
      description = ''
        If set to true u2f is used as 2nd factor in all pam services.
        A service definition may override this setting.
      '';
      default = true;
    };
  };
 
  config = {
    environment.systemPackages = with pkgs; [
      yubikey-personalization
      yubioath-flutter
    ];
 
    services.pcscd.enable = true;
    services.udev.packages = with pkgs; [
      yubikey-personalization
    ];
 
    security.pam.u2f.enable = true;
    security.pam.use2Factor = false;
    security.pam.services."polkit-1".use2Factor = false;
    security.pam.services."sudo".use2Factor = false;
  };
}