#!/bin/bash # Git credential helper for 1Password CLI # This script integrates with Git's credential system to fetch credentials from 1Password set -euo pipefail # Source utilities for logging SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/utils.sh" ]; then source "$SCRIPT_DIR/utils.sh" fi # Function to check if 1Password CLI is available and signed in check_op_cli() { if ! command -v op >/dev/null 2>&1; then echo "Error: 1Password CLI (op) not found. Please install it first." >&2 return 1 fi # Check if signed in if ! op account list >/dev/null 2>&1; then echo "Error: Not signed in to 1Password CLI. Please run 'op signin' first." >&2 return 1 fi return 0 } # Parse Git credential input parse_input() { while IFS= read -r line; do if [ -z "$line" ]; then break fi case "$line" in protocol=*) protocol="${line#protocol=}" ;; host=*) host="${line#host=}" ;; username=*) username="${line#username=}" ;; password=*) password="${line#password=}" ;; esac done } # Get credentials from 1Password get_credentials() { local search_term="$1" # Try to find item by URL/host first local item_uuid if ! item_uuid=$(op item list --format=json 2>/dev/null | jq -r --arg host "$search_term" ' .[] | select( ((.urls // [])[] | select(.href != null and (.href | type) == "string") | .href | test($host; "i")) or ((.title // "") | type == "string" and test($host; "i")) or ((.additional_information // "") | type == "string" and test($host; "i")) ) | .id' | head -1 2>/dev/null); then # If the complex search fails, try a simpler approach item_uuid="" fi if [ -z "$item_uuid" ]; then # Fallback: simple search by title containing the host if ! item_uuid=$(op item list --format=json 2>/dev/null | jq -r --arg host "$search_term" ' .[] | select((.title // "") != "" and ((.title // "") | type == "string") and ((.title // "") | test($host; "i"))) | .id' | head -1 2>/dev/null); then item_uuid="" fi fi if [ -z "$item_uuid" ]; then echo "No matching item found in 1Password for: $search_term" >&2 return 1 fi # Get the item details local item_json if ! item_json=$(op item get "$item_uuid" --format=json 2>/dev/null); then echo "Failed to retrieve item from 1Password" >&2 return 1 fi # Extract username and password local op_username op_password op_username=$(echo "$item_json" | jq -r '(.fields // [])[] | select((.id // "") == "username" or ((.label // "") | type == "string" and test("username"; "i"))) | .value // empty' | head -1 2>/dev/null) op_password=$(echo "$item_json" | jq -r '(.fields // [])[] | select((.id // "") == "password" or ((.label // "") | type == "string" and test("password|token"; "i"))) | .value // empty' | head -1 2>/dev/null) # If standard fields don't work, try common field names if [ -z "$op_username" ]; then op_username=$(echo "$item_json" | jq -r '(.fields // [])[] | select((.label // "") | type == "string" and test("user|login"; "i")) | .value // empty' | head -1 2>/dev/null) fi if [ -z "$op_password" ]; then op_password=$(echo "$item_json" | jq -r '(.fields // [])[] | select((.label // "") | type == "string" and test("pass|token|secret"; "i")) | .value // empty' | head -1 2>/dev/null) fi if [ -z "$op_username" ] || [ -z "$op_password" ]; then echo "Username or password not found in 1Password item" >&2 echo "Available fields:" >&2 echo "$item_json" | jq -r '(.fields // [])[] | " - \(.label // .id // "unknown")"' 2>/dev/null >&2 return 1 fi echo "username=$op_username" echo "password=$op_password" } # Main credential helper logic case "${1:-}" in get) # Initialize variables protocol="" host="" username="" password="" # Parse input from Git parse_input # Only handle HTTPS requests if [ "$protocol" != "https" ]; then exit 0 fi # Check 1Password CLI availability if ! check_op_cli; then exit 1 fi # Search for credentials if [ -n "$host" ]; then if get_credentials "$host"; then exit 0 fi fi # If we get here, no credentials were found exit 1 ;; store) # We don't store credentials in 1Password via this helper # Users should add them manually to 1Password exit 0 ;; erase) # We don't erase credentials from 1Password via this helper exit 0 ;; *) echo "Usage: $0 {get|store|erase}" >&2 exit 1 ;; esac