#!/bin/sh # Git credential helper for Polar (RFC 8628 device flow). # # Usage: # git config --global credential.helper '!sh /path/to/polar/scripts/git-credential-polar.sh' # # On "get" the helper reuses a cached token for the server, otherwise it starts # a device authorization flow, opens the verification page in the browser and # waits until the request is approved. Tokens are cached in # ~/.polar/credentials.json. op="${1:-get}" base="${POLAR_URL:-}" cache_dir="${HOME}/.polar" cache_file="${cache_dir}/credentials.json" client_id="git-credential-polar" input="$(cat)" if [ -z "$base" ]; then host="$(printf '%s\n' "$input" | sed -n 's/^host=//p' | head -n1)" proto="$(printf '%s\n' "$input" | sed -n 's/^protocol=//p' | head -n1)" [ -z "$proto" ] && proto="https" if [ -n "$host" ]; then base="${proto}://${host}" else base="http://localhost:3100" fi fi base="${base%/}" json_field() { # $1 = json, $2 = field printf '%s' "$1" | sed -n 's/.*"'"$2"'"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1 } case "$op" in get) if [ -f "$cache_file" ]; then cached="$(grep -F "\"${base}\":" "$cache_file" | head -n1)" if [ -n "$cached" ]; then token="$(json_field "$cached" token)" username="$(json_field "$cached" username)" if [ -n "$token" ] && [ -n "$username" ]; then printf 'username=%s\npassword=%s\n' "$username" "$token" exit 0 fi fi fi code="$(curl -fsS -X POST "${base}/api/v1/device/code" -d "client_id=${client_id}" 2>/dev/null)" || { echo "polar: could not reach ${base}/api/v1/device/code" >&2 exit 1 } device_code="$(json_field "$code" device_code)" user_code="$(json_field "$code" user_code)" verify_uri="$(json_field "$code" verification_uri)" verify_full="$(json_field "$code" verification_uri_complete)" interval="$(json_field "$code" interval)" [ -z "$interval" ] && interval=5 [ "$interval" -lt 3 ] && interval=3 echo "" >&2 echo "polar: open ${verify_uri} and enter code ${user_code}" >&2 echo "polar: waiting for approval..." >&2 if command -v xdg-open >/dev/null 2>&1; then xdg-open "$verify_full" >/dev/null 2>&1 & elif command -v open >/dev/null 2>&1; then open "$verify_full" >/dev/null 2>&1 & fi elapsed=0 while [ "$elapsed" -lt 900 ]; do sleep "$interval" elapsed=$((elapsed + interval)) resp="$(curl -sS -X POST "${base}/api/v1/device/token" -d "device_code=${device_code}&client_id=${client_id}" 2>/dev/null)" token="$(json_field "$resp" access_token)" if [ -n "$token" ]; then username="$(json_field "$resp" username)" [ -z "$username" ] && username="x-access-token" mkdir -p "$cache_dir" printf '{"%s":{"username":"%s","token":"%s"}}\n' "$base" "$username" "$token" >>"$cache_file" printf 'username=%s\npassword=%s\n' "$username" "$token" exit 0 fi case "$resp" in *authorization_pending*) continue ;; *access_denied*) echo "polar: access denied" >&2 exit 1 ;; *expired_token*) echo "polar: device code expired, run the command again" >&2 exit 1 ;; *) echo "polar: authorization failed: $resp" >&2 exit 1 ;; esac done echo "polar: timed out waiting for approval" >&2 exit 1 ;; store|erase) exit 0 ;; *) exit 0 ;; esac