view mod_rest/example/rest.sh @ 5468:14b5446e22e1

mod_http_oauth2: Fix returning errors from response handlers This would either redirect the user back to the client along with the error code, or show the error HTML template. Previously this would just show some JSON to the user.
author Kim Alvefur <zash@zash.se>
date Thu, 18 May 2023 12:57:23 +0200
parents 1c52efb6fd42
children b3484a112300
line wrap: on
line source

#!/bin/bash -eu

# Copyright (c) Kim Alvefur
# This file is MIT/X11 licensed.

# Dependencies:
# - https://httpie.io/
# - https://github.com/stedolan/jq
# - some sort of XDG 'open' command

# Settings
HOST=""
DOMAIN=""

AUTH_METHOD="session-read-only"
AUTH_ID="rest"

if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/restrc" ]; then
	# Config file can contain the above settings
	source "${XDG_CONFIG_HOME:-$HOME/.config}/restrc"
fi

if [[ $# == 0 ]]; then
	echo "${0##*/} [-h HOST] [-u USER|--login] [/path] kind=(message|presence|iq) ...."
	# Last arguments are handed to HTTPie, so refer to its docs for further details
	exit 0
fi

if [[ "$1" == "-h" ]]; then
	HOST="$2"
	shift 2
elif [ -z "${HOST:-}" ]; then
	HOST="$(hostname)"
fi

if [[ "$HOST" != *.* ]]; then
	# Assumes subdomain of your DOMAIN
	if [ -z "${DOMAIN:-}" ]; then
		DOMAIN="$(hostname -d)"
	fi
	if [[ "$HOST" == *:* ]]; then
		HOST="${HOST%:*}.$DOMAIN:${HOST#*:}"
	else
		HOST="$HOST.$DOMAIN"
	fi
fi

if [[ "$1" == "-u" ]]; then
	# -u username
	AUTH_METHOD="auth"
	AUTH_ID="$2"
	shift 2
elif [[ "$1" == "-rw" ]]; then
	# To e.g. save Accept headers to the session
	AUTH_METHOD="session"
	shift 1
fi

if [[ "$1" == "--login" ]]; then
	shift 1

	# Check cache for OAuth client
	if [ -f "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST" ]; then
		source "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST"
	fi

	OAUTH_META="$(http --check-status --json "https://$HOST/.well-known/oauth-authorization-server" Accept:application/json)"
	AUTHORIZATION_ENDPOINT="$(echo "$OAUTH_META" | jq -e -r '.authorization_endpoint')"
	TOKEN_ENDPOINT="$(echo "$OAUTH_META" | jq -e -r '.token_endpoint')"

	if [ -z "${OAUTH_CLIENT_INFO:-}" ]; then
		# Register a new OAuth client
		REGISTRATION_ENDPOINT="$(echo "$OAUTH_META" | jq -e -r '.registration_endpoint')"
		OAUTH_CLIENT_INFO="$(http --check-status "$REGISTRATION_ENDPOINT" Content-Type:application/json Accept:application/json client_name=rest.sh client_uri="https://modules.prosody.im/mod_rest" application_type=native software_id=0bdb0eb9-18e8-43af-a7f6-bd26613374c0 redirect_uris:='["urn:ietf:wg:oauth:2.0:oob"]')"
		mkdir -p "${XDG_CACHE_HOME:-$HOME/.cache}/rest/"
		typeset -p OAUTH_CLIENT_INFO >> "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST"
	fi

	CLIENT_ID="$(echo "$OAUTH_CLIENT_INFO" | jq -e -r '.client_id')"
	CLIENT_SECRET="$(echo "$OAUTH_CLIENT_INFO" | jq -e -r '.client_secret')"

	if [ -n "${REFRESH_TOKEN:-}" ]; then
		TOKEN_RESPONSE="$(http --check-status --form "$TOKEN_ENDPOINT" 'grant_type=refresh_token' "client_id=$CLIENT_ID" "client_secret=$CLIENT_SECRET" "refresh_token=$REFRESH_TOKEN")"
		ACCESS_TOKEN="$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')"
		if [ "$ACCESS_TOKEN" == "null" ]; then
			ACCESS_TOKEN=""
		fi
	fi

	if [ -z "${ACCESS_TOKEN:-}" ]; then
		CODE_CHALLENGE="$(head -c 33 /dev/urandom | base64 | tr /+ _-)"
		open "$AUTHORIZATION_ENDPOINT?response_type=code&client_id=$CLIENT_ID&code_challenge=$CODE_CHALLENGE&scope=${SCOPE:-openid+prosody:user}"
		read -p "Paste authorization code: " -s -r AUTHORIZATION_CODE

		TOKEN_RESPONSE="$(http --check-status --form "$TOKEN_ENDPOINT" 'grant_type=authorization_code' "client_id=$CLIENT_ID" "client_secret=$CLIENT_SECRET" "code=$AUTHORIZATION_CODE" code_verifier="$CODE_CHALLENGE")"
		ACCESS_TOKEN="$(echo "$TOKEN_RESPONSE" | jq -e -r '.access_token')"
		REFRESH_TOKEN="$(echo "$TOKEN_RESPONSE" | jq -r '.refresh_token')"

		if [ "$REFRESH_TOKEN" != "null" ]; then
			# FIXME Better type check would be nice, but nobody should ever have the
			# string "null" as a legitimate refresh token...
			typeset -p REFRESH_TOKEN >> "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST"
		fi

		if [ -n "${COLORTERM:-}" ]; then
			echo -ne '\e[1K\e[G'
		else
			echo
		fi
	fi

	USERINFO_ENDPOINT="$(echo "$OAUTH_META" | jq -e -r '.userinfo_endpoint')"
	http --check-status -b --session rest "$USERINFO_ENDPOINT" "Authorization:Bearer $ACCESS_TOKEN" Accept:application/json >&2
	AUTH_METHOD="session-read-only"
	AUTH_ID="rest"

elif [[ "$1" == "--logout" ]]; then
	# Revoke token
	source "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST"

	OAUTH_META="$(http --check-status --json "https://$HOST/.well-known/oauth-authorization-server" Accept:application/json)"
	REVOCATION_ENDPOINT="$(echo "$OAUTH_META" | jq -e -r '.revocation_endpoint')"

	CLIENT_ID="$(echo "$OAUTH_CLIENT_INFO" | jq -e -r '.client_id')"
	CLIENT_SECRET="$(echo "$OAUTH_CLIENT_INFO" | jq -e -r '.client_secret')"

	http -h --check-status --auth "$CLIENT_ID:$CLIENT_SECRET" --form "$REVOCATION_ENDPOINT" token="$REFRESH_TOKEN"

	# Overwrite the token
	typeset -p OAUTH_CLIENT_INFO > "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST"
	exit 0
fi

if [[ $# == 0 ]]; then
	# Just login?
	exit 0
fi

# For e.g /disco/example.com and such GET queries
GET_PATH=""
if [[ "$1" == /* ]]; then
	GET_PATH="$1"
	shift 1
fi

http --check-status -p b "--$AUTH_METHOD" "$AUTH_ID" "https://$HOST/rest$GET_PATH" "$@"