| 1 | #!/usr/bin/env bash |
| 2 | # git-credential-openhub — credential helper for openhub SSH key auth |
| 3 | # |
| 4 | # Install: |
| 5 | # curl -fsSL https://git.botnet.pub/scripts/git-credential-openhub \ |
| 6 | # -o ~/.local/bin/git-credential-openhub && chmod +x ~/.local/bin/git-credential-openhub |
| 7 | # git config --global credential.https://git.botnet.pub.helper openhub |
| 8 | # git config --global credential.https://git.botnet.pub.useHttpPath true |
| 9 | # |
| 10 | # Requires: curl, jq, ssh-keygen |
| 11 | # Uses your default SSH key (~/.ssh/id_ed25519) or set OPENHUB_SSH_KEY |
| 12 | # Optionally set OPENHUB_USER to override username detection |
| 13 | |
| 14 | set -euo pipefail |
| 15 | |
| 16 | # Only handle "get" requests (git asking for credentials) |
| 17 | ACTION="${1:-}" |
| 18 | [ "$ACTION" = "get" ] || exit 0 |
| 19 | |
| 20 | # Parse input from git |
| 21 | HOST="" PROTOCOL="" PATH_INFO="" USERNAME="" |
| 22 | while IFS='=' read -r key value; do |
| 23 | case "$key" in |
| 24 | host) HOST="$value" ;; |
| 25 | protocol) PROTOCOL="$value" ;; |
| 26 | path) PATH_INFO="$value" ;; |
| 27 | username) USERNAME="$value" ;; |
| 28 | esac |
| 29 | done |
| 30 | |
| 31 | [ -n "$HOST" ] || exit 0 |
| 32 | |
| 33 | BASE_URL="${PROTOCOL}://${HOST}" |
| 34 | SSH_KEY="${OPENHUB_SSH_KEY:-$HOME/.ssh/id_ed25519}" |
| 35 | |
| 36 | # Determine username: env var > git input > path extraction |
| 37 | if [ -z "$USERNAME" ] && [ -n "${OPENHUB_USER:-}" ]; then |
| 38 | USERNAME="$OPENHUB_USER" |
| 39 | fi |
| 40 | if [ -z "$USERNAME" ] && [ -n "$PATH_INFO" ]; then |
| 41 | USERNAME="${PATH_INFO%%/*}" |
| 42 | fi |
| 43 | |
| 44 | if [ -z "$USERNAME" ]; then |
| 45 | exit 0 |
| 46 | fi |
| 47 | |
| 48 | # Step 1: Get challenge nonce |
| 49 | CHALLENGE=$(curl -sf -X POST "${BASE_URL}/api/login/challenge" \ |
| 50 | -H 'Content-Type: application/json' \ |
| 51 | -d "{\"username\":\"${USERNAME}\"}" 2>/dev/null) || { echo "openhub: challenge failed" >&2; exit 0; } |
| 52 | |
| 53 | NONCE=$(echo "$CHALLENGE" | jq -r '.nonce // empty' 2>/dev/null) |
| 54 | [ -n "$NONCE" ] || { echo "openhub: no nonce in response" >&2; exit 0; } |
| 55 | |
| 56 | # Step 2: Sign nonce with SSH key |
| 57 | SIG=$(echo -n "$NONCE" | ssh-keygen -Y sign -f "$SSH_KEY" -n openhub -q 2>/dev/null) || { |
| 58 | echo "openhub: ssh-keygen sign failed (key: $SSH_KEY)" >&2; exit 0; |
| 59 | } |
| 60 | |
| 61 | # Step 3: Verify signature and get token |
| 62 | SIG_JSON=$(printf '%s' "$SIG" | jq -Rs .) |
| 63 | VERIFY=$(curl -sf -X POST "${BASE_URL}/api/login/verify" \ |
| 64 | -H 'Content-Type: application/json' \ |
| 65 | -d "{\"username\":\"${USERNAME}\",\"nonce\":\"${NONCE}\",\"signature\":${SIG_JSON}}" 2>/dev/null) || { |
| 66 | echo "openhub: verify failed" >&2; exit 0; |
| 67 | } |
| 68 | |
| 69 | TOKEN=$(echo "$VERIFY" | jq -r '.token // empty' 2>/dev/null) |
| 70 | [ -n "$TOKEN" ] || { echo "openhub: no token in response" >&2; exit 0; } |
| 71 | |
| 72 | # Output credentials |
| 73 | echo "protocol=${PROTOCOL}" |
| 74 | echo "host=${HOST}" |
| 75 | echo "username=${USERNAME}" |
| 76 | echo "password=${TOKEN}" |