#!/bin/bash
# Pre-write hook: High-Risk Change Gate (Just-in-Time Biometric Auth)
#
# Blocks Edit/Write to governance/security files unless the operator
# has authenticated via macOS biometric (Touch ID / password) for the
# current session.
#
# Enforces in ALL modes (YOLO and non-YOLO). The Claude Code permission
# dialog is not biometric — it's just allow/deny. This hook ensures
# that governance files always require operator identity verification.
#
# CRITICAL DESIGN: The hook itself performs the authentication via
# osascript (native macOS auth dialog). Claude NEVER handles auth.
# This prevents Claude from bypassing biometric by directly writing
# the session auth file.
#
# Flow:
#   1. Parse file path from stdin JSON
#   2. Match against high-risk patterns
#   3. If not high-risk → log allow, exit 0
#   4. If high-risk + session auth file exists and fresh → log allow, exit 0
#   5. If high-risk + no session auth → trigger macOS auth dialog
#      5a. Auth succeeds → write session file, log allow, exit 0
#      5b. Auth fails/canceled → log block, exit 2
#
# Receives JSON on stdin with tool_input.file_path.
# Exit 0 = allow, Exit 2 = block (stderr sent to Claude).
#
# Every invocation is logged to ~/.orxaq/audit/hook_invocations.jsonl

set -euo pipefail

AUDIT_DIR="$HOME/.orxaq/audit"
AUDIT_LOG="$AUDIT_DIR/hook_invocations.jsonl"
SESSION_DIR="$HOME/.orxaq/audit/yolo_sessions"
SESSION_AUTH_FILE="$SESSION_DIR/active"

# Ensure audit directory exists
mkdir -p "$AUDIT_DIR"

# Logging function — appends JSONL record to audit log
log_decision() {
    local file_path="$1"
    local decision="$2"
    local reason="$3"
    local high_risk="$4"
    local ts
    ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
    printf '{"timestamp":"%s","file_path":"%s","decision":"%s","reason":"%s","high_risk":%s}\n' \
        "$ts" "$file_path" "$decision" "$reason" "$high_risk" \
        >> "$AUDIT_LOG" 2>/dev/null || true
}

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [ -z "$FILE_PATH" ]; then
    # No file path (e.g. Bash tool) — allow without logging
    exit 0
fi

# High-risk path patterns that require gate authorization
HIGH_RISK=false

case "$FILE_PATH" in
    */governance/*)     HIGH_RISK=true ;;
    */security/*)       HIGH_RISK=true ;;
    */auth*.py)         HIGH_RISK=true ;;
    */credential*.py)   HIGH_RISK=true ;;
    */.env*)            HIGH_RISK=true ;;
    *.pem)              HIGH_RISK=true ;;
    *.key)              HIGH_RISK=true ;;
    */hooks/*)          HIGH_RISK=true ;;
    *CLAUDE.md)         HIGH_RISK=true ;;
    *AGENTS.md)         HIGH_RISK=true ;;
    *fingerprint*.py)   HIGH_RISK=true ;;
    *biometric*.py)     HIGH_RISK=true ;;
    *execution_gate*.py) HIGH_RISK=true ;;
    */.github/workflows/*) HIGH_RISK=true ;;
esac

if [ "$HIGH_RISK" = "false" ]; then
    log_decision "$FILE_PATH" "allow" "not_high_risk" "false"
    exit 0
fi

# High-risk file detected — check for session-level biometric auth
if [ -f "$SESSION_AUTH_FILE" ]; then
    # Session auth exists — validate freshness
    FILE_AGE=$(( $(date +%s) - $(stat -f %m "$SESSION_AUTH_FILE" 2>/dev/null || echo 0) ))
    if [ "$FILE_AGE" -lt 86400 ]; then
        log_decision "$FILE_PATH" "allow" "session_auth_valid" "true"
        exit 0
    fi
    # Stale session file — remove and require re-auth
    rm -f "$SESSION_AUTH_FILE"
fi

# No valid session auth — authenticate the operator directly.
#
# Priority order:
#   1. Swift helper (real Touch ID via LocalAuthentication framework)
#   2. osascript admin dialog (password fallback if Swift helper missing)
#
# ORXAQ_HOOK_NO_AUTH=1 skips auth entirely (for automated testing only).

SCRIPT_DIR_HOOK="$(cd "$(dirname "$0")/../.." && pwd)"
SWIFT_HELPER="$SCRIPT_DIR_HOOK/scripts/security/biometric_auth"

if [ "${ORXAQ_HOOK_NO_AUTH:-}" = "1" ]; then
    AUTH_RESULT="denied"
elif [ -x "$SWIFT_HELPER" ]; then
    # Use compiled Swift helper — triggers real Touch ID via Secure Enclave
    SWIFT_OUTPUT=$("$SWIFT_HELPER" --challenge "hook-gate-$(date +%s)" --reason "Orxaq: Authorize high-risk file edit" 2>/dev/null || true)
    SWIFT_SUCCESS=$(echo "$SWIFT_OUTPUT" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('success','false'))" 2>/dev/null || echo "false")
    if [ "$SWIFT_SUCCESS" = "True" ] || [ "$SWIFT_SUCCESS" = "true" ]; then
        AUTH_RESULT="ok"
    else
        AUTH_RESULT="denied"
    fi
else
    # Fallback: osascript admin dialog (password only, no Touch ID)
    AUTH_RESULT=$(osascript -e '
        try
            do shell script "echo authenticated" with administrator privileges
            return "ok"
        on error
            return "denied"
        end try
    ' 2>/dev/null || echo "denied")
fi

if [ "$AUTH_RESULT" = "ok" ]; then
    # Authentication succeeded — write session auth file
    mkdir -p "$SESSION_DIR"
    TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
    OPERATOR=$(id -un)
    # Record actual method used (touchid if Swift helper, osascript if fallback)
    if [ -x "$SWIFT_HELPER" ] && [ "${ORXAQ_HOOK_NO_AUTH:-}" != "1" ]; then
        AUTH_METHOD="touchid"
    else
        AUTH_METHOD="osascript"
    fi
    printf '{"event":"jit_biometric_auth","scope":"session","operator":"%s","timestamp":"%s","method":"%s","file_triggered":"%s"}\n' \
        "$OPERATOR" "$TS" "$AUTH_METHOD" "$FILE_PATH" \
        > "$SESSION_AUTH_FILE"

    log_decision "$FILE_PATH" "allow" "biometric_auth_success" "true"
    exit 0
fi

# Authentication failed or was canceled
log_decision "$FILE_PATH" "block" "biometric_auth_denied" "true"

cat >&2 <<'BLOCK_MSG'
BLOCKED: Biometric authentication was denied or canceled.
This edit targets a high-risk file (governance/security).
Operator identity verification is required to proceed.

The authentication dialog will appear again on the next attempt.
BLOCK_MSG

exit 2
