151 lines
4.0 KiB
Bash
Executable File
151 lines
4.0 KiB
Bash
Executable File
#!/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
|