#!/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[]?.href // "" | test($host; "i")) or (.title | test($host; "i")) or (.additional_information | test($host; "i")) ) | .id' | head -1); then return 1 fi if [ -z "$item_uuid" ]; then # Fallback: 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 | test($host; "i")) | .id' | head -1); then return 1 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 == "username") | .value // empty' | head -1) op_password=$(echo "$item_json" | jq -r '.fields[] | select(.id == "repo_token" or .label == "repo_token") | .value // empty' | head -1) if [ -z "$op_username" ] || [ -z "$op_password" ]; then echo "Username or password not found in 1Password item" >&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