mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-06 13:45:35 +02:00
feat: email OTP + magic link auth for community tier
Two-path authentication: enter 6-digit code in terminal OR click magic link in email. Races both paths — whichever completes first wins. Saves JWT to ~/.gstack/auth-token.json with auto-refresh. Includes status and logout subcommands. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
# gstack-auth-refresh — silently refresh auth token if expired
|
||||
#
|
||||
# Usage:
|
||||
# gstack-auth-refresh — refresh and print access token
|
||||
# gstack-auth-refresh --check — exit 0 if authenticated, 1 if not
|
||||
#
|
||||
# Called by gstack-community-backup and other authenticated scripts.
|
||||
# If the refresh token is also expired, prints an error and exits 1.
|
||||
#
|
||||
# Env overrides (for testing):
|
||||
# GSTACK_STATE_DIR — override ~/.gstack state directory
|
||||
# GSTACK_DIR — override auto-detected gstack root
|
||||
set -euo pipefail
|
||||
|
||||
GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||
STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}"
|
||||
AUTH_FILE="$STATE_DIR/auth-token.json"
|
||||
|
||||
# Source Supabase config
|
||||
if [ -f "$GSTACK_DIR/supabase/config.sh" ]; then
|
||||
. "$GSTACK_DIR/supabase/config.sh"
|
||||
fi
|
||||
SUPABASE_URL="${GSTACK_SUPABASE_URL:-}"
|
||||
ANON_KEY="${GSTACK_SUPABASE_ANON_KEY:-}"
|
||||
AUTH_URL="${SUPABASE_URL}/auth/v1"
|
||||
|
||||
# ─── Helper: extract JSON field ──────────────────────────────
|
||||
json_field() {
|
||||
local json="$1"
|
||||
local field="$2"
|
||||
echo "$json" | grep -o "\"${field}\":[^,}]*" | head -1 | sed "s/\"${field}\"://;s/\"//g;s/ //g"
|
||||
}
|
||||
|
||||
# ─── Check auth file exists ─────────────────────────────────
|
||||
if [ ! -f "$AUTH_FILE" ]; then
|
||||
if [ "${1:-}" = "--check" ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Not authenticated. Run: gstack auth <email>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AUTH_JSON="$(cat "$AUTH_FILE")"
|
||||
ACCESS_TOKEN="$(json_field "$AUTH_JSON" "access_token")"
|
||||
REFRESH_TOKEN="$(json_field "$AUTH_JSON" "refresh_token")"
|
||||
EXPIRES_AT="$(json_field "$AUTH_JSON" "expires_at")"
|
||||
EMAIL="$(json_field "$AUTH_JSON" "email")"
|
||||
USER_ID="$(json_field "$AUTH_JSON" "user_id")"
|
||||
NOW="$(date +%s)"
|
||||
|
||||
# ─── Check-only mode ────────────────────────────────────────
|
||||
if [ "${1:-}" = "--check" ]; then
|
||||
[ -n "$ACCESS_TOKEN" ] && exit 0 || exit 1
|
||||
fi
|
||||
|
||||
# ─── Token still valid? Return it. ───────────────────────────
|
||||
# Add 60s buffer to avoid using a token that's about to expire
|
||||
BUFFER=60
|
||||
if [ -n "$EXPIRES_AT" ] && [ "$NOW" -lt "$(( EXPIRES_AT - BUFFER ))" ] 2>/dev/null; then
|
||||
echo "$ACCESS_TOKEN"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ─── Token expired — refresh it ─────────────────────────────
|
||||
if [ -z "$REFRESH_TOKEN" ] || [ "$REFRESH_TOKEN" = "null" ]; then
|
||||
echo "Session expired and no refresh token. Run: gstack auth <email>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$SUPABASE_URL" ] || [ -z "$ANON_KEY" ]; then
|
||||
echo "Error: Supabase not configured" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REFRESH_RESPONSE="$(curl -s --max-time 10 \
|
||||
-X POST "${AUTH_URL}/token?grant_type=refresh_token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "apikey: ${ANON_KEY}" \
|
||||
-d "{\"refresh_token\":\"${REFRESH_TOKEN}\"}" \
|
||||
2>/dev/null || echo "{}")"
|
||||
|
||||
NEW_ACCESS="$(json_field "$REFRESH_RESPONSE" "access_token")"
|
||||
NEW_REFRESH="$(json_field "$REFRESH_RESPONSE" "refresh_token")"
|
||||
NEW_EXPIRES_IN="$(json_field "$REFRESH_RESPONSE" "expires_in")"
|
||||
|
||||
if [ -z "$NEW_ACCESS" ] || [ "$NEW_ACCESS" = "null" ]; then
|
||||
echo "Session expired. Run: gstack auth <email>" >&2
|
||||
rm -f "$AUTH_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update token file
|
||||
NEW_EXPIRES_AT=$(( NOW + ${NEW_EXPIRES_IN:-3600} ))
|
||||
|
||||
cat > "$AUTH_FILE" <<TOKJSON
|
||||
{
|
||||
"access_token": "${NEW_ACCESS}",
|
||||
"refresh_token": "${NEW_REFRESH:-$REFRESH_TOKEN}",
|
||||
"expires_at": ${NEW_EXPIRES_AT},
|
||||
"email": "${EMAIL}",
|
||||
"user_id": "${USER_ID}"
|
||||
}
|
||||
TOKJSON
|
||||
chmod 600 "$AUTH_FILE"
|
||||
|
||||
echo "$NEW_ACCESS"
|
||||
Reference in New Issue
Block a user