Software /
code /
prosody-modules
Comparison
mod_rest/example/rest.sh @ 5654:b3484a112300
mod_rest/rest.sh: Update to use httpie-oauth2 plugin
This bash implementation of OAuth2/OIDC was growing to the point where
it needed a massive refactor, which made me look into alternatives where
I finally settled on implementing oauth2 in a plugin for HTTPie.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sat, 26 Aug 2023 14:37:04 +0200 |
parent | 5432:1c52efb6fd42 |
child | 5655:acd2f397ce6b |
comparison
equal
deleted
inserted
replaced
5653:401356232e1b | 5654:b3484a112300 |
---|---|
3 # Copyright (c) Kim Alvefur | 3 # Copyright (c) Kim Alvefur |
4 # This file is MIT/X11 licensed. | 4 # This file is MIT/X11 licensed. |
5 | 5 |
6 # Dependencies: | 6 # Dependencies: |
7 # - https://httpie.io/ | 7 # - https://httpie.io/ |
8 # - https://github.com/stedolan/jq | 8 # - https://hg.sr.ht/~zash/httpie-oauth2 |
9 # - some sort of XDG 'open' command | |
10 | 9 |
11 # Settings | 10 # Settings |
12 HOST="" | 11 HOST="" |
13 DOMAIN="" | 12 DOMAIN="" |
14 | 13 |
15 AUTH_METHOD="session-read-only" | |
16 AUTH_ID="rest" | |
17 | |
18 if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/restrc" ]; then | 14 if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/restrc" ]; then |
19 # Config file can contain the above settings | 15 # Config file can contain the above settings |
20 source "${XDG_CONFIG_HOME:-$HOME/.config}/restrc" | 16 source "${XDG_CONFIG_HOME:-$HOME/.config}/restrc" |
17 | |
18 if [ -z "${SCOPE:-}" ]; then | |
19 SCOPE="openid xmpp" | |
20 fi | |
21 fi | 21 fi |
22 | 22 |
23 if [[ $# == 0 ]]; then | 23 if [[ $# == 0 ]]; then |
24 echo "${0##*/} [-h HOST] [-u USER|--login] [/path] kind=(message|presence|iq) ...." | 24 echo "${0##*/} [-h HOST] [/path] kind=(message|presence|iq) ...." |
25 # Last arguments are handed to HTTPie, so refer to its docs for further details | 25 # Last arguments are handed to HTTPie, so refer to its docs for further details |
26 exit 0 | 26 exit 0 |
27 fi | 27 fi |
28 | 28 |
29 if [[ "$1" == "-h" ]]; then | 29 if [[ "$1" == "-h" ]]; then |
43 else | 43 else |
44 HOST="$HOST.$DOMAIN" | 44 HOST="$HOST.$DOMAIN" |
45 fi | 45 fi |
46 fi | 46 fi |
47 | 47 |
48 if [[ "$1" == "-u" ]]; then | |
49 # -u username | |
50 AUTH_METHOD="auth" | |
51 AUTH_ID="$2" | |
52 shift 2 | |
53 elif [[ "$1" == "-rw" ]]; then | |
54 # To e.g. save Accept headers to the session | |
55 AUTH_METHOD="session" | |
56 shift 1 | |
57 fi | |
58 | |
59 if [[ "$1" == "--login" ]]; then | |
60 shift 1 | |
61 | |
62 # Check cache for OAuth client | |
63 if [ -f "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST" ]; then | |
64 source "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST" | |
65 fi | |
66 | |
67 OAUTH_META="$(http --check-status --json "https://$HOST/.well-known/oauth-authorization-server" Accept:application/json)" | |
68 AUTHORIZATION_ENDPOINT="$(echo "$OAUTH_META" | jq -e -r '.authorization_endpoint')" | |
69 TOKEN_ENDPOINT="$(echo "$OAUTH_META" | jq -e -r '.token_endpoint')" | |
70 | |
71 if [ -z "${OAUTH_CLIENT_INFO:-}" ]; then | |
72 # Register a new OAuth client | |
73 REGISTRATION_ENDPOINT="$(echo "$OAUTH_META" | jq -e -r '.registration_endpoint')" | |
74 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"]')" | |
75 mkdir -p "${XDG_CACHE_HOME:-$HOME/.cache}/rest/" | |
76 typeset -p OAUTH_CLIENT_INFO >> "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST" | |
77 fi | |
78 | |
79 CLIENT_ID="$(echo "$OAUTH_CLIENT_INFO" | jq -e -r '.client_id')" | |
80 CLIENT_SECRET="$(echo "$OAUTH_CLIENT_INFO" | jq -e -r '.client_secret')" | |
81 | |
82 if [ -n "${REFRESH_TOKEN:-}" ]; then | |
83 TOKEN_RESPONSE="$(http --check-status --form "$TOKEN_ENDPOINT" 'grant_type=refresh_token' "client_id=$CLIENT_ID" "client_secret=$CLIENT_SECRET" "refresh_token=$REFRESH_TOKEN")" | |
84 ACCESS_TOKEN="$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')" | |
85 if [ "$ACCESS_TOKEN" == "null" ]; then | |
86 ACCESS_TOKEN="" | |
87 fi | |
88 fi | |
89 | |
90 if [ -z "${ACCESS_TOKEN:-}" ]; then | |
91 CODE_CHALLENGE="$(head -c 33 /dev/urandom | base64 | tr /+ _-)" | |
92 open "$AUTHORIZATION_ENDPOINT?response_type=code&client_id=$CLIENT_ID&code_challenge=$CODE_CHALLENGE&scope=${SCOPE:-openid+prosody:user}" | |
93 read -p "Paste authorization code: " -s -r AUTHORIZATION_CODE | |
94 | |
95 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")" | |
96 ACCESS_TOKEN="$(echo "$TOKEN_RESPONSE" | jq -e -r '.access_token')" | |
97 REFRESH_TOKEN="$(echo "$TOKEN_RESPONSE" | jq -r '.refresh_token')" | |
98 | |
99 if [ "$REFRESH_TOKEN" != "null" ]; then | |
100 # FIXME Better type check would be nice, but nobody should ever have the | |
101 # string "null" as a legitimate refresh token... | |
102 typeset -p REFRESH_TOKEN >> "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST" | |
103 fi | |
104 | |
105 if [ -n "${COLORTERM:-}" ]; then | |
106 echo -ne '\e[1K\e[G' | |
107 else | |
108 echo | |
109 fi | |
110 fi | |
111 | |
112 USERINFO_ENDPOINT="$(echo "$OAUTH_META" | jq -e -r '.userinfo_endpoint')" | |
113 http --check-status -b --session rest "$USERINFO_ENDPOINT" "Authorization:Bearer $ACCESS_TOKEN" Accept:application/json >&2 | |
114 AUTH_METHOD="session-read-only" | |
115 AUTH_ID="rest" | |
116 | |
117 elif [[ "$1" == "--logout" ]]; then | |
118 # Revoke token | |
119 source "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST" | |
120 | |
121 OAUTH_META="$(http --check-status --json "https://$HOST/.well-known/oauth-authorization-server" Accept:application/json)" | |
122 REVOCATION_ENDPOINT="$(echo "$OAUTH_META" | jq -e -r '.revocation_endpoint')" | |
123 | |
124 CLIENT_ID="$(echo "$OAUTH_CLIENT_INFO" | jq -e -r '.client_id')" | |
125 CLIENT_SECRET="$(echo "$OAUTH_CLIENT_INFO" | jq -e -r '.client_secret')" | |
126 | |
127 http -h --check-status --auth "$CLIENT_ID:$CLIENT_SECRET" --form "$REVOCATION_ENDPOINT" token="$REFRESH_TOKEN" | |
128 | |
129 # Overwrite the token | |
130 typeset -p OAUTH_CLIENT_INFO > "${XDG_CACHE_HOME:-$HOME/.cache}/rest/$HOST" | |
131 exit 0 | |
132 fi | |
133 | |
134 if [[ $# == 0 ]]; then | |
135 # Just login? | |
136 exit 0 | |
137 fi | |
138 | 48 |
139 # For e.g /disco/example.com and such GET queries | 49 # For e.g /disco/example.com and such GET queries |
140 GET_PATH="" | 50 GET_PATH="" |
141 if [[ "$1" == /* ]]; then | 51 if [[ "$1" == /* ]]; then |
142 GET_PATH="$1" | 52 GET_PATH="$1" |
143 shift 1 | 53 shift 1 |
144 fi | 54 fi |
145 | 55 |
146 http --check-status -p b "--$AUTH_METHOD" "$AUTH_ID" "https://$HOST/rest$GET_PATH" "$@" | 56 https --check-status -p b --session rest -A oauth2 -a "$HOST" --oauth2-scope "$SCOPE" "$HOST/rest$GET_PATH" "$@" |