Luigit
repositories / bugabinga.net

bugabinga.net

personal infrastructure for bugabinga!

owned by admin

scripts/local/auth-user.sh

Raw
#!/usr/bin/env bash
set -euo pipefail
umask 077

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
file="${repo_root}/auth.auto.tfvars.json"
lock="${repo_root}/.auth.auto.tfvars.lock"

usage() {
  echo "usage: auth-user.sh zot|basic USER [--password-stdin]" >&2
  exit 2
}

kind="${1:-}"
username="${2:-}"
[[ "$#" -ge 2 && "$#" -le 3 ]] || usage
[[ "${username}" =~ ^[A-Za-z0-9._-]+$ ]] || {
  echo "username may only contain letters, digits, '.', '_' and '-'" >&2
  exit 2
}

password_stdin=false
if [[ "$#" -eq 3 ]]; then
  [[ "$3" == "--password-stdin" ]] || usage
  password_stdin=true
fi

case "${kind}" in
  zot)
    variable="zot_htpasswd_content"
    separator=:
    cost=12
    ;;
  basic)
    variable="hallucygenie_basic_auth_users"
    separator=' '
    cost=14
    ;;
  *)
    usage
    ;;
esac

if ${password_stdin}; then
  IFS= read -r password
  [[ -n "${password}" ]] || {
    echo "empty password from stdin" >&2
    exit 1
  }
else
  IFS= read -r -s -p "password: " password
  printf '\n'
  IFS= read -r -s -p "confirm password: " confirmation
  printf '\n'
  [[ "${password}" == "${confirmation}" ]] || {
    echo "passwords do not match" >&2
    exit 1
  }
  [[ -n "${password}" ]] || {
    echo "empty password" >&2
    exit 1
  }
  unset confirmation
fi

hash="$(printf '%s\n' "${password}" | caddy hash-password --algorithm bcrypt --bcrypt-cost "${cost}")"
unset password
line="${username}${separator}${hash}"
prefix="${username}${separator}"

exec 9>"${lock}"
flock -x 9

input="${file}"
temporary_input=""
if [[ ! -e "${file}" ]]; then
  temporary_input="$(mktemp "${repo_root}/.auth-input.XXXXXX")"
  printf '{}\n' >"${temporary_input}"
  input="${temporary_input}"
fi

temporary_output="$(mktemp "${repo_root}/.auth-output.XXXXXX")"
trap 'rm -f -- "${temporary_input}" "${temporary_output}"' EXIT

if jq -e --arg variable "${variable}" --arg prefix "${prefix}" '
  (.[$variable] // "") | split("\n") | any(startswith($prefix))
' "${input}" >/dev/null; then
  action=updated
else
  action=added
fi

jq --arg variable "${variable}" --arg prefix "${prefix}" --arg line "${line}" '
  .[$variable] = (
    ((.[$variable] // "") | split("\n") | map(select(length > 0 and (startswith($prefix) | not))))
    + [$line]
    | join("\n") + "\n"
  )
' "${input}" >"${temporary_output}"
chmod 600 "${temporary_output}"
mv "${temporary_output}" "${file}"
printf "%s %s user '%s' in %s\n" "${action}" "${kind}" "${username}" "${file}"