Files
KNELAgentIdentityProvisioning/skills/agent-provisioning/scripts/cloudron-totp-enroll.sh
T
mrcharles 627948a98e scripts: fix oidc code extraction + Cloudron 10 totp routes
- sed kept the URL prefix in the auth-code extraction; the garbage code
  was the cause of every invalid_grant on the token exchange
- enrollment now uses the Cloudron 10 profile-scoped routes mined from
  the dashboard bundle: POST /api/v1/profile/totp_secret and
  POST /api/v1/profile/totp_enable
- seed storage verifies the setfield write and falls back to a full
  vault item recreate (smcli setfield 400s on some ciphers)
- prompt=login added to the auth URL for session-switch flows

Verified: 32 COO-org identities enrolled end-to-end (seed in vault +
real TOTP login as each identity). Full detail on the #942 comment.
https://projects.knownelement.com/issues/942
2026-09-09 12:34:40 -05:00

53 lines
2.9 KiB
Bash
Executable File

#!/bin/bash
# Enroll TOTP for a Cloudron identity (Cloudron 10 API: profile-scoped routes).
# Prereq: cloudron-oidc-login.sh <user> has run (bearer at /tmp/.oidc-bearer-<user>).
# usage: cloudron-totp-enroll.sh <cloudron-username> [vault-item]
# Flow: POST /profile/totp_secret -> seed -> TOTP code -> POST /profile/totp_enable
# -> seed into vault -> end-to-end verify via fresh TOTP login.
set -euo pipefail
U="${1:?usage: cloudron-totp-enroll.sh <cloudron-username> [vault-item]}"
ITEM="${2:-$U Cloudron}"
BASE="https://my.knownelement.com"
BEARER_FILE="/tmp/.oidc-bearer-$U"
[ -f "$BEARER_FILE" ] || { echo "FAIL: run cloudron-oidc-login.sh $U first" >&2; exit 1; }
ACCESS=$(cat "$BEARER_FILE")
SECRET=$(curl -sk -X POST "$BASE/api/v1/profile/totp_secret" \
-H "Authorization: Bearer $ACCESS" -H 'Content-Type: application/json' -d '{}' \
| sed -n 's/.*"secret":[[:space:]]*"\([^"]*\)".*/\1/p')
[ -n "$SECRET" ] || { echo "FAIL: no totp secret returned (bearer stale? re-login)" >&2; exit 3; }
CODE=$(bash "$(dirname "$0")/totp.sh" "$SECRET")
ENABLED=$(curl -sk -X POST "$BASE/api/v1/profile/totp_enable" \
-H "Authorization: Bearer $ACCESS" -H 'Content-Type: application/json' \
-d "{\"totpToken\":\"$CODE\"}" -w '|%{http_code}')
printf '%s' "$ENABLED" | grep -q '|200$' || { echo "FAIL: totp_enable said: $(printf '%s' "$ENABLED" | head -c 150)" >&2; exit 4; }
SM() { if [ "$(id -un)" = "TSGCOO" ]; then /data2/TSGCOO/.local/bin/sm "$@" </dev/null; else sudo -u TSGCOO /data2/TSGCOO/.local/bin/sm "$@" </dev/null; fi; }
# store seed; smcli setfield 400s ("Data missing") on some ciphers, so verify
# the write and fall back to a full item recreate with the seed included
STORED=0
if SM setfield "$ITEM" totp_seed "$SECRET" >/dev/null 2>&1; then
STORED=1
else
VU=$(SM get "$ITEM" --field username); VP=$(SM get "$ITEM" --field password); VR=$(SM get "$ITEM" --field uri 2>/dev/null || true)
[ -n "$VR" ] || VR="$BASE"
SM rm "$ITEM" >/dev/null 2>&1
SM set "$ITEM" "username=$VU" "password=$VP" "uri=$VR" "totp_seed=$SECRET" >/dev/null 2>&1 && STORED=1
fi
[ "$STORED" = 1 ] || { echo "FAIL: could not store seed in vault ($ITEM)" >&2; exit 5; }
[ "$(SM get "$ITEM" --field totp_seed)" = "$SECRET" ] || { echo "FAIL: seed readback mismatch ($ITEM)" >&2; exit 5; }
# end-to-end verify: fresh login WITH the stored seed lands a working bearer
bash "$(dirname "$0")/cloudron-oidc-login.sh" "$U" "$ITEM" >/dev/null 2>&1
if [ -s "$BEARER_FILE" ]; then
PROF=$(curl -sk "$BASE/api/v1/profile" -H "Authorization: Bearer $(cat "$BEARER_FILE")")
WHO=$(printf '%s' "$PROF" | sed -n 's/.*"username":[[:space:]]*"\([^"]*\)".*/\1/p')
TOTP_ON=$(printf '%s' "$PROF" | sed -n 's/.*"totpEnabled":[[:space:]]*\(true\|false\).*/\1/p')
if [ "$WHO" = "$U" ] && [ "$TOTP_ON" = "true" ]; then
echo "OK: $U 2FA enrolled (totpEnabled=true), seed in vault ($ITEM), TOTP login verified"
exit 0
fi
fi
echo "PARTIAL: verify failed — check state for $U"