Secure TOTP Authenticator

This plugin generates TOTP tokens and enables them to be copied to the clipboard. After 30 seconds, the clipboard is automatically emptied again.

Image preview of Secure TOTP Authenticator plugin.

secure-totp.sh

Edit
Open on GitHub
#!/usr/bin/env bash

# <xbar.title>Secure TOTP Authenticator</xbar.title>
# <xbar.version>v1.0</xbar.version>
# <xbar.author>micxer</xbar.author>
# <xbar.author.github>micxer</xbar.author.github>
# <xbar.desc>This plugin generates TOTP tokens and enables them to be copied to the clipboard. After 30 seconds, the clipboard is automatically emptied again.</xbar.desc>
# <xbar.image>https://raw.githubusercontent.com/micxer/xbar-secure-totp-plugin/main/img/TOTP.png</xbar.image>
# <xbar.dependencies>bash,oathtool</xbar.dependencies>

# update the key value pairs as per your requirement
# Key - for your reference to identify a TOTP Account
# Value - base32 secret key corresponding to the TOTP Account
# <xbar.var>string(KEYFILE="$HOME/.otp"): Your file with OTP secrets</xbar.var>

# update PATH for Apple Silicon
if [[ $(uname -m) == 'arm64' ]]
then
    PATH="/opt/homebrew/bin:/opt/homebrew/sbin:$PATH"
else
    PATH="/usr/local/sbin:$PATH"
fi

# oath-toolkit needs to be installed. Use 'brew install oath-toolkit' and update
# the path to the oathtool binary below if necessary.
oathtool="$(which oathtool)"

export LANG="${LANG:-en_US.UTF-8}"

ICON="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"
totp_secrets=()

function check_keyfile {
  if [ ! -f "$KEYFILE" ]
  then
    echo " | templateImage=$ICON"
    echo "---"
    echo "⚠️ KEYFILE '$KEYFILE' not found!"
    exit
  fi
}

function check_oathtool {
  if [ ! -f "$oathtool" ] || [ ! -x "$oathtool" ]
  then
    echo " | templateImage=$ICON"
    echo "---"
    echo "⚠️ oathtool not installed or not executable!"
    exit
  fi
}

function read_secrets {
  while read -r secret
  do
    totp_secrets+=("$secret")
  done < "$KEYFILE"
}

function get_totp {
  for secret in "${totp_secrets[@]}" ; do
      KEY="${secret%%:*}"
      VALUE="${secret##*:}"
      if [ "$1" = "$KEY" ]
      then
        $oathtool --totp -b "$VALUE"
        break
      fi
  done
}

function do_copy {
  token="$( get_totp "$1" )"
  echo -n "$token" | pbcopy
  osascript -e "display notification \"TOTP code copied to clipboard!\nIt will be cleared in 30 seconds!\""
  sleep 30
  echo "" | pbcopy
  osascript -e "display notification \"Clipboard cleared!\""
}

function output_menu {
  echo " | templateImage=$ICON"
  echo "---"

  for secret in "${totp_secrets[@]}" ; do
      KEY="${secret%%:*}"
      echo "$KEY | shell='$0' param1=copy param2='$KEY' param3='$KEYFILE' terminal=false"
  done
}

check_oathtool

if [ "$1" = "copy" ]
then
  KEYFILE="$3"
fi

check_keyfile
read_secrets

if [ "$1" = "copy" ]
then
  do_copy "$2"
else
  output_menu
fi