<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
//   Auth stack endpoint. The single URL behind the auth tab, covering all three services it
//   manages: Nginx Proxy Manager proxy hosts and certificates, lldap users and groups, and
//   Authelia access rules.
//
// OPERATIONAL MODEL
//   Read and write share one URL, split on HTTP method. GET serves the five read actions and
//   is always safe. POST carries an action naming exactly one library call. Anything that is
//   neither GET nor POST is refused with 405 before a parameter is read.
//
//   Three services, one endpoint, because they are one subject. The auth stack is HOST1's
//   source of truth for identity, and a change in one service usually accompanies a change in
//   another — a new lldap user is only useful once an Authelia rule and an NPM host exist for
//   it. Splitting them into three endpoints would split one page's work across three files
//   with three copies of the same dispatch.
//
// DESIGN PRINCIPLES
//   Both dispatches are closed match expressions.
//       Every action maps to one named library call, and an unrecognised action falls to a
//       default arm that returns an error. No part of the request is ever used to construct a
//       function name, so the action list is the complete set of things this endpoint can do.
//
//   The endpoint holds no auth logic of its own.
//       Token acquisition, API dialects, config parsing and the Authelia container restart
//       all live in include/auth.php. This file is dispatch and nothing else, which is what
//       keeps the credential handling in one auditable place.
//
//   Structured payloads arrive as JSON in a form field.
//       Proxy definitions and rule sets are nested, so they are passed as encoded JSON rather
//       than flattened into form keys — a rule set does not survive form encoding intact.
//
// OPERATIONAL SAFEGUARDS
//   Wrong method is refused with a status code, not just a body, so a mistaken caller fails
//   visibly rather than parsing an error object as data.
//
//   Every parameter is optional and typed at the call site.
//       (int) casts on ids, ?? '' on strings, ?? '0' === '1' on flags. A malformed POST
//       reaches the library as zeros and empty strings — which the library rejects — rather
//       than raising undefined-index warnings into the JSON body and corrupting the response.
//
//   Malformed JSON degrades to an empty structure.
//       json_decode with ?: [] on both data and rules. A truncated payload becomes an empty
//       set the library refuses, not a partial one it might act on.
//
//   Credentials pass through, and are never returned.
//       lldap_set_password and lldap_create_user accept a password and hand it straight to
//       the library. No action in either dispatch returns a stored credential, and nothing
//       here writes one to a log.
//
//   The destructive actions are POST-only by construction.
//       Delete of a proxy, a user, or a group exists only in the POST match. The GET arm has
//       five read actions and no others, so no link or prefetch can reach a delete.
//
//   Authelia rule writes are atomic and refuse to create.
//       The library writes .vv.tmp and renames, and returns 'Config not found' rather than
//       authoring a fresh config — a config it created would carry no rules and a default
//       policy, which is an accidental open door.
//
//   The POST actions are CSRF-guarded by the platform, not by this file.
//       Unraid's auto_prepend (webGui/local_prepend.php) validates a token on every POST and
//       terminates the request before any code here runs. That guard is the reason every
//       mutating action lives in the POST arm — this is the highest-value endpoint in the
//       plugin to reach, since it can create a user and open a proxy host. The five GET
//       actions are reads and are deliberately outside it. See README-unraid.md.
//
// REQUEST
//   GET  ?action=npm_proxies | npm_certs | lldap_users | lldap_groups | authelia_rules
//   POST action=npm_create              data=<JSON>
//   POST action=npm_update              id, data=<JSON>
//   POST action=npm_delete              id
//   POST action=npm_toggle              id, enabled=0|1
//   POST action=lldap_create_user       uid, email, display_name, password
//   POST action=lldap_update_user       uid, email, display_name
//   POST action=lldap_delete_user       uid
//   POST action=lldap_set_password      uid, password
//   POST action=lldap_create_group      name
//   POST action=lldap_delete_group      id
//   POST action=lldap_add_to_group      uid, gid
//   POST action=lldap_remove_from_group uid, gid
//   POST action=authelia_save           rules=<JSON>, default_policy
//
// RESPONSE
//   Whatever the invoked library call returns — ['ok' => bool] with a payload or an error,
//   or an _err key on a failed remote call. npm_certs is wrapped as {"ok":true,"certs":[…]}.
//   {"ok":false,"error":"Unknown action: …"} for anything outside the lists above.
//
// DEPENDS ON
//   include/auth.php   vv_npm_*(), vv_lldap_*(), vv_authelia_read_rules(),
//                      vv_authelia_write_rules()
// ═══════════════════════════════════════════════════════════════════════════════════════════════
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/auth.php';

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $action = $_GET['action'] ?? '';
    $result = match ($action) {
        'npm_proxies'    => vv_npm_list_proxies(),
        'npm_certs'      => ['ok' => true, 'certs' => vv_npm_list_certs()],
        'lldap_users'    => vv_lldap_list_users(),
        'lldap_groups'   => vv_lldap_list_groups(),
        'authelia_rules' => vv_authelia_read_rules(),
        default          => ['ok' => false, 'error' => 'Unknown action: ' . $action],
    };
    echo json_encode($result);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode(['ok' => false, 'error' => 'GET or POST only']);
    exit;
}

$action = trim($_POST['action'] ?? '');

$result = match ($action) {
    // NPM
    'npm_create'              => vv_npm_create_proxy(json_decode($_POST['data'] ?? '{}', true) ?: []),
    'npm_update'              => vv_npm_update_proxy((int)($_POST['id'] ?? 0), json_decode($_POST['data'] ?? '{}', true) ?: []),
    'npm_delete'              => vv_npm_delete_proxy((int)($_POST['id'] ?? 0)),
    'npm_toggle'              => vv_npm_toggle_proxy((int)($_POST['id'] ?? 0), ($_POST['enabled'] ?? '0') === '1'),
    // lldap
    'lldap_create_user'       => vv_lldap_create_user($_POST['uid'] ?? '', $_POST['email'] ?? '', $_POST['display_name'] ?? '', $_POST['password'] ?? ''),
    'lldap_update_user'       => vv_lldap_update_user($_POST['uid'] ?? '', $_POST['email'] ?? '', $_POST['display_name'] ?? ''),
    'lldap_delete_user'       => vv_lldap_delete_user($_POST['uid'] ?? ''),
    'lldap_set_password'      => vv_lldap_set_password($_POST['uid'] ?? '', $_POST['password'] ?? ''),
    'lldap_create_group'      => vv_lldap_create_group($_POST['name'] ?? ''),
    'lldap_delete_group'      => vv_lldap_delete_group((int)($_POST['id'] ?? 0)),
    'lldap_add_to_group'      => vv_lldap_add_to_group($_POST['uid'] ?? '', (int)($_POST['gid'] ?? 0)),
    'lldap_remove_from_group' => vv_lldap_remove_from_group($_POST['uid'] ?? '', (int)($_POST['gid'] ?? 0)),
    // Authelia
    'authelia_save'           => vv_authelia_write_rules(json_decode($_POST['rules'] ?? '[]', true) ?: [], $_POST['default_policy'] ?? 'deny'),
    default                   => ['ok' => false, 'error' => 'Unknown action: ' . $action],
};

echo json_encode($result);
