This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

tools: refactor pass-otp to be tool-agnostic and clean up pass wrapper

- Make pass-otp accept OTP secrets via stdin or args instead of calling passage directly
- Update pass wrapper to pipe password content to pass-otp
- Remove redundant PATH export for age plugins (already in runtimeInputs)

+148 -92
+80 -88
tools/pass-otp/flake-module.nix
··· 1 - { self, ... }: 1 + { ... }: 2 2 { 3 3 perSystem = 4 - { pkgs, system, ... }: 4 + { pkgs, ... }: 5 5 { 6 6 packages.pass-otp = pkgs.writeShellApplication { 7 7 name = "pass-otp"; 8 - runtimeInputs = [ pkgs.passage pkgs.oath-toolkit ]; 8 + runtimeInputs = [ 9 + pkgs.oath-toolkit 10 + ]; 9 11 text = '' 10 - # Handle OTP commands for passage-based password store 11 - 12 + # Tool-agnostic OTP generator that accepts secret via stdin or args 13 + 12 14 show_usage() { 13 - echo "Usage: pass-otp [show] [--clip,-c] pass-name" 14 - echo " pass-otp insert [--secret,-s] [--issuer,-i issuer] [--account,-a account] [--echo,-e] pass-name" 15 + echo "Usage: pass-otp [--clip,-c] <secret>" 16 + echo " echo <secret> | pass-otp [--clip,-c]" 17 + echo "" 18 + echo "Accepts OTP secret as argument or via stdin" 19 + echo "Secret can be:" 20 + echo " - Raw base32 secret" 21 + echo " - otpauth:// URL" 22 + echo " - Multi-line text with secret on line 2+" 15 23 } 16 - 17 - case "''${1:-}" in 18 - "") 19 - show_usage 20 - exit 1 21 - ;; 22 - "show") 23 - shift 24 - clip=false 25 - pass_name="''${1:-}" 26 - ;; 27 - "--clip"|"-c") 28 - shift 29 - clip=true 30 - pass_name="''${1:-}" 31 - 32 - if [[ -z "$pass_name" ]]; then 24 + 25 + clip=false 26 + secret_input="" 27 + 28 + # Parse arguments 29 + while [[ $# -gt 0 ]]; do 30 + case "$1" in 31 + "--clip"|"-c") 32 + clip=true 33 + shift 34 + ;; 35 + "--help"|"-h") 33 36 show_usage 34 - exit 1 35 - fi 36 - 37 - # Get the OTP secret from passage 38 - if secret=$(passage show "$pass_name" 2>/dev/null); then 39 - # Extract TOTP secret (assume it's on a line starting with "otpauth://" or just the secret) 40 - if echo "$secret" | grep -q "otpauth://"; then 41 - # Extract secret from otpauth URL 42 - secret_key=$(echo "$secret" | grep "otpauth://" | sed -n 's/.*secret=\([^&]*\).*/\1/p') 43 - else 44 - # Assume the secret is on the second line or find a base32-looking string 45 - secret_key=$(echo "$secret" | tail -n +2 | head -n 1 | tr -d ' ') 46 - fi 47 - 48 - if [[ -n "$secret_key" ]]; then 49 - otp_code=$(oathtool --totp --base32 "$secret_key") 50 - if [[ "$clip" == "true" ]]; then 51 - if [[ "$(uname)" == "Darwin" ]]; then 52 - echo -n "$otp_code" | pbcopy 53 - echo "Copied OTP code to clipboard." 54 - else 55 - echo -n "$otp_code" | xclip -selection clipboard 56 - echo "Copied OTP code to clipboard." 57 - fi 58 - else 59 - echo "$otp_code" 60 - fi 61 - else 62 - echo "Error: Could not extract OTP secret from $pass_name" 63 - exit 1 64 - fi 65 - else 66 - echo "Error: Could not read password entry $pass_name" 67 - exit 1 68 - fi 69 - ;; 70 - "insert") 71 - shift 72 - echo "OTP insert not yet implemented for passage. Please add OTP secrets manually." 73 - exit 1 74 - ;; 75 - *) 76 - # Default to show 77 - pass_name="$1" 78 - if secret=$(passage show "$pass_name" 2>/dev/null); then 79 - if echo "$secret" | grep -q "otpauth://"; then 80 - secret_key=$(echo "$secret" | grep "otpauth://" | sed -n 's/.*secret=\([^&]*\).*/\1/p') 81 - else 82 - secret_key=$(echo "$secret" | tail -n +2 | head -n 1 | tr -d ' ') 83 - fi 84 - 85 - if [[ -n "$secret_key" ]]; then 86 - oathtool --totp --base32 "$secret_key" 87 - else 88 - echo "Error: Could not extract OTP secret from $pass_name" 89 - exit 1 90 - fi 91 - else 92 - echo "Error: Could not read password entry $pass_name" 93 - exit 1 94 - fi 95 - ;; 96 - esac 37 + exit 0 38 + ;; 39 + *) 40 + secret_input="$1" 41 + shift 42 + ;; 43 + esac 44 + done 45 + 46 + # If no argument provided, read from stdin 47 + if [[ -z "$secret_input" ]]; then 48 + secret_input=$(cat) 49 + fi 50 + 51 + if [[ -z "$secret_input" ]]; then 52 + echo "Error: No OTP secret provided" 53 + show_usage 54 + exit 1 55 + fi 56 + 57 + # Extract TOTP secret from input 58 + if echo "$secret_input" | grep -q "^otpauth://"; then 59 + # Extract secret from otpauth URL 60 + secret_key=$(echo "$secret_input" | sed -n 's/.*secret=\([^&]*\).*/\1/p') 61 + elif echo "$secret_input" | wc -l | grep -q "^1$"; then 62 + # Single line - assume it's the raw secret 63 + secret_key=$(echo "$secret_input" | tr -d ' ') 64 + else 65 + # Multi-line - assume secret is on line 2 or later 66 + secret_key=$(echo "$secret_input" | tail -n +2 | head -n 1 | tr -d ' ') 67 + fi 68 + 69 + if [[ -z "$secret_key" ]]; then 70 + echo "Error: Could not extract OTP secret from input" 71 + exit 1 72 + fi 73 + 74 + # Generate OTP code 75 + otp_code=$(oathtool --totp --base32 "$secret_key") 76 + 77 + # Output or copy to clipboard 78 + if [[ "$clip" == "true" ]]; then 79 + if [[ "$(uname)" == "Darwin" ]]; then 80 + echo -n "$otp_code" | pbcopy 81 + echo "Copied OTP code to clipboard." 82 + else 83 + echo -n "$otp_code" | xclip -selection clipboard 84 + echo "Copied OTP code to clipboard." 85 + fi 86 + else 87 + echo "$otp_code" 88 + fi 97 89 ''; 98 90 }; 99 91 }; 100 - } 92 + }
+68 -4
tools/pass/flake-module.nix
··· 5 5 { 6 6 packages.pass = pkgs.writeShellApplication { 7 7 name = "pass"; 8 - runtimeInputs = [ 9 - pkgs.passage 10 - pkgs.age-plugin-se 8 + runtimeInputs = [ 9 + pkgs.passage 10 + pkgs.age-plugin-se 11 + pkgs.age-plugin-fido2-hmac 12 + pkgs.age-plugin-yubikey 11 13 self.packages.${system}.pass-otp 12 14 ]; 13 15 text = '' ··· 21 23 # Handle OTP commands by delegating to pass-otp 22 24 if [[ "''${1:-}" == "otp" ]]; then 23 25 shift 24 - pass-otp "$@" 26 + # Extract pass name and options 27 + clip_arg="" 28 + pass_name="" 29 + for arg in "$@"; do 30 + case "$arg" in 31 + "--clip"|"-c") 32 + clip_arg="--clip" 33 + ;; 34 + *) 35 + pass_name="$arg" 36 + ;; 37 + esac 38 + done 39 + 40 + if [[ -z "$pass_name" ]]; then 41 + echo "Usage: pass otp [--clip,-c] pass-name" 42 + exit 1 43 + fi 44 + 45 + # Get the password entry and pipe to pass-otp 46 + passage show "$pass_name" | pass-otp $clip_arg 47 + # Handle bulk operations and reencrypt with bulk key + SE fallback 48 + elif [[ "''${1:-}" == "bulk" ]] || [[ "''${1:-}" == "reencrypt" ]]; then 49 + if [[ "''${1:-}" == "bulk" ]]; then 50 + shift # Remove 'bulk' from args 51 + fi 52 + 53 + # For reencrypt, try bulk key first, fallback to SE for failures 54 + if [[ "''${1:-}" == "reencrypt" ]]; then 55 + echo "Re-encrypting with bulk key first, SE fallback for failures..." 56 + 57 + # First pass: try with bulk operations key 58 + temp_bulk_identity=$(mktemp) 59 + temp_se_identity=$(mktemp) 60 + cleanup() { rm -f "$temp_bulk_identity" "$temp_se_identity"; } 61 + trap cleanup EXIT 62 + 63 + # Extract bulk key (this requires one hardware auth) 64 + if passage show bulk-operations/age-key > "$temp_bulk_identity" 2>/dev/null; then 65 + echo "First pass: using bulk operations key..." 66 + PASSAGE_IDENTITIES_FILE="$temp_bulk_identity" passage reencrypt 2>/dev/null || { 67 + echo "Some files failed with bulk key, retrying failed files with SE..." 68 + # Re-enable SE identity for fallback 69 + sed 's/^# AGE-PLUGIN-SE/AGE-PLUGIN-SE/' ~/.passage/identities > "$temp_se_identity" 70 + PASSAGE_IDENTITIES_FILE="$temp_se_identity" passage reencrypt 71 + } 72 + else 73 + echo "Bulk key not available, using SE for all files..." 74 + passage reencrypt 75 + fi 76 + else 77 + # Regular bulk operations 78 + temp_identity=$(mktemp) 79 + trap 'rm -f "$temp_identity"' EXIT 80 + 81 + if passage show bulk-operations/age-key > "$temp_identity" 2>/dev/null; then 82 + echo "Using bulk operations key..." 83 + PASSAGE_IDENTITIES_FILE="$temp_identity" passage "$@" 84 + else 85 + echo "Bulk key not available, using normal identities..." 86 + passage "$@" 87 + fi 88 + fi 25 89 else 26 90 # Pass all other commands directly to passage 27 91 passage "$@"