#!/bin/sh
# Gestion des ayants droit de la bibliotheque.
#
# users.json est la SEULE source de verite ; la map nginx en est deduite et
# se reecrit entierement a chaque geste. Retirer quelqu un de ce fichier et
# recharger, c est tuer tous ses liens dans la seconde : c est la revocation.
# L expiration inscrite dans les URL n est qu un filet.
#
# Configuration : /etc/li/li.env (ou le fichier nomme par LI_CONF), partage
# avec li-gen. Voir li.env.example.
set -eu

LI_CONF=${LI_CONF:-/etc/li/li.env}
# shellcheck disable=SC1090
[ -f "$LI_CONF" ] && . "$LI_CONF"

: "${LI_BASE_URL:?LI_BASE_URL n est pas defini (voir li.env.example)}"
LI_DATA=${LI_DATA:-/srv/appdata/li}
LI_EXPIRES=${LI_EXPIRES:-2145916800}   # 2038 — doit valoir celui de li-gen
LI_NGINX=${LI_NGINX:-docker exec li nginx}
# li-gen lit les memes variables : on les lui transmet telles que chargees.
export LI_CONF LI_BASE_URL LI_DATA LI_EXPIRES

BASE=${LI_BASE_URL%/}
AYANTS=$LI_DATA/users.json
MAP=$LI_DATA/conf/users.conf
LI_GEN=${LI_GEN:-$(dirname "$0")/li-gen}

usage() {
    echo "usage: li-user add|del|lien|list [nom]" >&2
    exit 2
}

verifie_nom() {
    # Le nom voyage dans l URL et devient un nom de fichier : on le tient
    # court et sans surprise plutot que d avoir a l echapper partout.
    case "$1" in
        *[!A-Za-z0-9_-]*|"") echo "Nom invalide : lettres, chiffres, - et _ seulement." >&2; exit 1 ;;
    esac
}

rendre_map() {
    mkdir -p "$(dirname "$MAP")"
    umask 077
    {
        echo "# Engendre par li-user — ne pas editer a la main."
        echo "# Un identifiant absent d ici obtient un secret VIDE, et le vhost"
        echo "# refuse avant meme de calculer la moindre signature."
        echo 'map $arg_u $li_secret {'
        echo '    default "";'
        jq -r 'to_entries | sort_by(.key)[] | "    \"\(.key)\"  \"\(.value)\";"' "$AYANTS"
        echo '}'
    } > "$MAP.tmp"
    mv "$MAP.tmp" "$MAP"
    chmod 644 "$MAP"   # lu par le conteneur, qui le monte en lecture seule

    # shellcheck disable=SC2086 # LI_NGINX est une commande, mots compris
    $LI_NGINX -t >/dev/null 2>&1 || {
        echo "La configuration nginx ne passe plus le controle — rien recharge." >&2
        $LI_NGINX -t
        exit 1
    }
    # shellcheck disable=SC2086
    $LI_NGINX -s reload
}

signe_uri() {   # $1 = uri, $2 = secret
    printf '%s' "$LI_EXPIRES$1$2" \
      | openssl md5 -binary | openssl base64 | tr '+/' '-_' | tr -d '='
}

lien() {
    secret=$(jq -er --arg n "$1" '.[$n]' "$AYANTS") || {
        echo "$1 : inconnu." >&2; exit 1
    }
    # Les deux documents portent les MEMES liens de telechargement : la
    # signature ne depend que du chemin et du secret, jamais du document
    # qui la transporte.
    for ext in html xspf; do
        uri="/playlists/$1.$ext"
        printf '  %-5s %s\n' "$ext" "$BASE$uri?u=$1&e=$LI_EXPIRES&s=$(signe_uri "$uri" "$secret")"
    done
}

[ -f "$AYANTS" ] || { mkdir -p "$LI_DATA"; umask 077; echo '{}' > "$AYANTS"; }

case "${1:-}" in
    add)
        [ $# -eq 2 ] || usage
        verifie_nom "$2"
        if jq -e --arg n "$2" 'has($n)' "$AYANTS" >/dev/null; then
            echo "$2 existe deja — « li-user lien $2 » pour retrouver son URL." >&2
            exit 1
        fi
        secret=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 24)
        tmp=$(mktemp)
        jq --arg n "$2" --arg s "$secret" '.[$n] = $s' "$AYANTS" > "$tmp"
        mv "$tmp" "$AYANTS"
        chmod 600 "$AYANTS"
        rendre_map
        "$LI_GEN"
        echo
        echo "Liens a transmettre a $2 :"
        lien "$2"
        ;;
    del)
        [ $# -eq 2 ] || usage
        jq -e --arg n "$2" 'has($n)' "$AYANTS" >/dev/null || {
            echo "$2 : inconnu." >&2; exit 1
        }
        tmp=$(mktemp)
        jq --arg n "$2" 'del(.[$n])' "$AYANTS" > "$tmp"
        mv "$tmp" "$AYANTS"
        chmod 600 "$AYANTS"
        # Les documents partent AVANT le rechargement : un worker en cours de
        # vidange a encore l ancienne map, et servirait un 404 la ou la
        # reponse juste est 403.
        rm -f "$LI_DATA/playlists/$2.xspf" "$LI_DATA/playlists/$2.html"
        rendre_map
        echo "$2 revoque — tous ses liens sont morts."
        ;;
    lien)
        [ $# -eq 2 ] || usage
        lien "$2"
        ;;
    list)
        jq -r 'to_entries | sort_by(.key)[] | .key' "$AYANTS"
        ;;
    *)
        usage
        ;;
esac
