Software /
code /
prosody-modules
Changeset
5663:f6e8165a2ec2 draft
Merge upstream
author | Trần H. Trung <xmpp:trần.h.trung@trung.fun> |
---|---|
date | Tue, 29 Aug 2023 22:46:27 +0700 |
parents | 5640:5e7e609fae0c (current diff) 5659:9ce8ee530438 (diff) |
children | 5664:52db2da66680 |
files | |
diffstat | 11 files changed, 128 insertions(+), 175 deletions(-) [+] |
line wrap: on
line diff
--- a/mod_bidi/README.markdown Tue Aug 01 01:27:45 2023 +0700 +++ b/mod_bidi/README.markdown Tue Aug 29 22:46:27 2023 +0700 @@ -1,11 +1,15 @@ --- labels: -- 'Stage-Stable' -summary: 'XEP-0288: Bidirectional Server-to-Server Connections' -... +- Stage-Stable +summary: "XEP-0288: Bidirectional Server-to-Server Connections" +--- -Introduction -============ +::: {.alert .alert-warning} +This module is unreliable when used with Prosody 0.12, switch to +[mod_s2s_bidi][doc:modules:mod_s2s_bidi] +::: + +# Introduction This module implements [XEP-0288: Bidirectional Server-to-Server Connections](http://xmpp.org/extensions/xep-0288.html). It allows @@ -14,13 +18,9 @@ Install and enable it like any other module. It has no configuration. -Compatibility -============= +# Compatibility - ------- -------------------------- - trunk Bidi available natively with [mod_s2s_bidi][doc:modules:mod_s2s_bidi] - 0.11 Works - 0.10 Works - 0.9 Works - 0.8 Works (use the 0.8 repo) - ------- -------------------------- + ------ ------------------------------------------- + 0.12 Bidi available natively with [mod_s2s_bidi][doc:modules:mod_s2s_bidi] + 0.11 Works + ------ -------------------------------------------
--- a/mod_client_management/mod_client_management.lua Tue Aug 01 01:27:45 2023 +0700 +++ b/mod_client_management/mod_client_management.lua Tue Aug 29 22:46:27 2023 +0700 @@ -437,6 +437,10 @@ return true, "No clients associated with this account"; end + local function date_or_time(last_seen) + return last_seen and os.date(os.difftime(os.time(), last_seen) >= 86400 and "%Y-%m-%d" or "%H:%M:%S", last_seen); + end + local colspec = { { title = "ID"; key = "id"; width = "1p" }; { @@ -446,13 +450,18 @@ mapper = user_agent_tostring; }; { + title = "First seen"; + key = "first_seen"; + width = math.max(#os.date("%Y-%m-%d"), #os.date("%H:%M:%S")); + align = "right"; + mapper = date_or_time; + }; + { title = "Last seen"; key = "last_seen"; width = math.max(#os.date("%Y-%m-%d"), #os.date("%H:%M:%S")); align = "right"; - mapper = function(last_seen) - return last_seen and os.date(os.difftime(os.time(), last_seen) >= 86400 and "%Y-%m-%d" or "%H:%M:%S", last_seen); - end; + mapper = date_or_time; }; { title = "Authentication";
--- a/mod_http_oauth2/README.markdown Tue Aug 01 01:27:45 2023 +0700 +++ b/mod_http_oauth2/README.markdown Tue Aug 29 22:46:27 2023 +0700 @@ -55,7 +55,7 @@ - [RFC 9207: OAuth 2.0 Authorization Server Issuer Identification](https://www.rfc-editor.org/rfc/rfc9207.html) - [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) - [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) (_partial, e.g. missing JWKS_) -- [OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html) +- [OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html) ## Configuration @@ -146,9 +146,6 @@ `client_uri`. `native` - - `native` - : For native e.g. desktop clients etc. `redirect_uris` **MUST** match one of:
--- a/mod_http_oauth2/html/consent.html Tue Aug 01 01:27:45 2023 +0700 +++ b/mod_http_oauth2/html/consent.html Tue Aug 29 22:46:27 2023 +0700 @@ -1,22 +1,25 @@ <!DOCTYPE html> -<html> +<html lang="en"> <head> -<meta charset="utf-8"> +<meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{site_name} - Authorize {client.client_name}</title> -<link rel="stylesheet" href="style.css"> +<link rel="stylesheet" href="style.css" /> </head> <body> - <main> - {state.error&<div class="error"> +{state.error& + <dialog open="" class="error"> <p>{state.error}</p> - </div>} - + <form method="dialog"><button>dismiss</button></form> + </dialog>} + <header> <h1>{site_name}</h1> + </header> + <main> <fieldset> - <form method="post"> <legend>Authorize new application</legend> <p>A new application wants to connect to your account.</p> + <form method="post"> <dl> <dt>Name</dt> <dd>{client.client_name}</dd> @@ -33,18 +36,18 @@ <dt>Requested permissions</dt> <dd>{scopes# - <input class="scope" type="checkbox" id="scope_{idx}" name="scope" value="{item}" checked><label class="scope" for="scope_{idx}">{item}</label>} + <input class="scope" type="checkbox" id="scope_{idx}" name="scope" value="{item}" checked="" /><label class="scope" for="scope_{idx}">{item}</label>} </dd> </dl> <p>To allow <em>{client.client_name}</em> to access your account - <em>{state.user.username}@{state.user.host}</em> and associated data, - select 'Allow'. Otherwise, select 'Deny'. + <em>{state.user.username}@{state.user.host}</em> and associated data, + select 'Allow'. Otherwise, select 'Deny'. </p> - <input type="hidden" name="user_token" value="{state.user.token}"> - <button type="submit" name="consent" value="denied">Deny</button> - <button type="submit" name="consent" value="granted">Allow</button> + <input type="hidden" name="user_token" value="{state.user.token}"> + <button type="submit" name="consent" value="denied">Deny</button> + <button type="submit" name="consent" value="granted">Allow</button> </form> </fieldset> </main>
--- a/mod_http_oauth2/html/device.html Tue Aug 01 01:27:45 2023 +0700 +++ b/mod_http_oauth2/html/device.html Tue Aug 29 22:46:27 2023 +0700 @@ -1,30 +1,33 @@ <!DOCTYPE html> -<html> +<html lang="en"> <head> -<meta charset="utf-8"> +<meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{site_name} - Authorize{client&d} Device</title> -<link rel="stylesheet" href="style.css"> +<link rel="stylesheet" href="style.css" /> </head> <body> +{error& + <dialog open="" class="error"> + <p>{error.text}</p> + <form method="dialog"><button>dismiss</button></form> + </dialog>} + <header> + <h1>{site_name}</h1> + </header> <main> - <h1>{site_name}</h1> <fieldset> <legend>Device Authorization</legend> - {error&<div class="error"> - <p>{error.text}</p> - </div>} {client& <p>Authorization completed. You can go back to <em>{client.client_name}</em>.</p>} {client~ <p>Enter the code to continue.</p> <form method="get"> - <input type="text" name="user_code" placeholder="XXXX-XXXX" aria-label="user-code" required > - <input type="submit" value="Continue"> + <input type="text" name="user_code" placeholder="XXXX-XXXX" aria-label="Code" required="" /> + <button type="submit">Continue</button> </form>} </fieldset> </main> </body> </html> -
--- a/mod_http_oauth2/html/error.html Tue Aug 01 01:27:45 2023 +0700 +++ b/mod_http_oauth2/html/error.html Tue Aug 29 22:46:27 2023 +0700 @@ -1,14 +1,16 @@ <!DOCTYPE html> -<html> +<html lang="en"> <head> -<meta charset="utf-8"> +<meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{site_name} - Error</title> -<link rel="stylesheet" href="style.css"> +<link rel="stylesheet" href="style.css" /> </head> <body> + <header> + <h1>{site_name}</h1> + </header> <main> - <h1>{site_name}</h1> <h2>Authentication error</h2> <p>There was a problem with the authentication request. If you were trying to sign in to a third-party application, you may want to report this issue to the developers.</p>
--- a/mod_http_oauth2/html/login.html Tue Aug 01 01:27:45 2023 +0700 +++ b/mod_http_oauth2/html/login.html Tue Aug 29 22:46:27 2023 +0700 @@ -1,24 +1,30 @@ <!DOCTYPE html> -<html> +<html lang="en"> <head> -<meta charset="utf-8"> +<meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{site_name} - Sign in</title> -<link rel="stylesheet" href="style.css"> +<link rel="stylesheet" href="style.css" /> </head> <body> +{state.error& + <dialog open="" class="error"> + <p>{state.error}</p> + <form method="dialog"><button>dismiss</button></form> + </dialog>} + <header> + <h1>{site_name}</h1> + </header> <main> - <h1>{site_name}</h1> <fieldset> <legend>Sign in</legend> <p>Sign in to your account to continue.</p> - {state.error&<div class="error"> - <p>{state.error}</p> - </div>} <form method="post"> - <input type="text" name="username" placeholder="Username" aria-label="Username" required {extra.username_hint~autofocus}{extra.username_hint& value="{extra.username_hint?}"}><br/> - <input type="password" name="password" placeholder="Password" aria-label="Password" autocomplete="current-password" required {extra.username_hint&autofocus}><br/> - <input type="submit" value="Sign in"> + <input type="text" name="username" placeholder="Username" aria-label="Username" + autocomplete="username" required="" {extra.username_hint~autofocus=""} {extra.username_hint&value="{extra.username_hint?}"} /><br/> + <input type="password" name="password" placeholder="Password" aria-label="Password" + autocomplete="current-password" required="" {extra.username_hint&autofocus=""} /><br /> + <input type="submit" value="Sign in" /> </form> </fieldset> </main>
--- a/mod_http_oauth2/html/oob.html Tue Aug 01 01:27:45 2023 +0700 +++ b/mod_http_oauth2/html/oob.html Tue Aug 29 22:46:27 2023 +0700 @@ -1,18 +1,20 @@ <!DOCTYPE html> -<html> +<html lang="en"> <head> -<meta charset="utf-8"> +<meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{site_name} - Authorization Code</title> -<link rel="stylesheet" href="style.css"> +<link rel="stylesheet" href="style.css" /> </head> <body> + <header> + <h1>{site_name}</h1> + </header> <main> - <h1>{site_name}</h1> <h2>Your Authorization Code</h2> <p>Here’s your authorization code, copy and paste it into {client.client_name}</p> <div class="oob"> - <p><input readonly name="authorization_code" value="{authorization_code}"></p> + <p><input readonly="" name="authorization_code" value="{authorization_code}" aria-label="Authorization Code"></p> </div> </main> </body>
--- a/mod_http_oauth2/html/style.css Tue Aug 01 01:27:45 2023 +0700 +++ b/mod_http_oauth2/html/style.css Tue Aug 29 22:46:27 2023 +0700 @@ -1,6 +1,5 @@ body { - margin-top:14%; text-align:center; background-color:#f8f8f8; font-family:sans-serif @@ -21,7 +20,7 @@ .error { - margin: 0.75em; + margin: 0.75em auto; background-color: #f8d7da; color: #842029; border: solid 1px #f5c2c7; @@ -53,7 +52,7 @@ text-align: left; } -main { +header, main, footer { max-width: 600px; padding: 0 1.5em 1.5em 1.5em; } @@ -106,7 +105,10 @@ @media(min-width: 768px) { - main + body { + margin-top:14vh; + } + header, main, footer { margin-left: auto; margin-right: auto;
--- a/mod_http_oauth2/mod_http_oauth2.lua Tue Aug 01 01:27:45 2023 +0700 +++ b/mod_http_oauth2/mod_http_oauth2.lua Tue Aug 29 22:46:27 2023 +0700 @@ -394,15 +394,19 @@ return oauth_error("invalid_request", "PKCE required"); end + local prefix = "authorization_code:"; local code = id.medium(); if params.redirect_uri == device_uri then local is_device, device_state = verify_device_token(params.state); if is_device then -- reconstruct the device_code + prefix = "device_code:"; code = b64url(hashes.hmac_sha256(verification_key, device_state.user_code)); + else + return oauth_error("invalid_request"); end end - local ok = codes:set("authorization_code:" .. params.client_id .. "#" .. code, { + local ok = codes:set(prefix.. params.client_id .. "#" .. code, { expires = os.time() + 600; granted_jid = granted_jid; granted_scopes = granted_scopes; @@ -578,7 +582,7 @@ return oauth_error("invalid_client", "incorrect credentials"); end - local code = codes:get("device_code:" .. params.device_code); + local code = codes:get("device_code:" .. params.client_id .. "#" .. params.device_code); if type(code) ~= "table" or code_expired(code) then return oauth_error("expired_token"); elseif code.error then @@ -586,7 +590,7 @@ elseif not code.granted_jid then return oauth_error("authorization_pending"); end - codes:set("device_code:" .. params.device_code, nil); + codes:set("device_code:" .. params.client_id .. "#" .. params.device_code, nil); return json.encode(new_access_token(code.granted_jid, code.granted_role, code.granted_scopes, client, code.id_token)); end @@ -991,9 +995,10 @@ local verification_uri = module:http_url() .. "/device"; local verification_uri_complete = verification_uri .. "?" .. http.formencode({ user_code = user_code }); - local dc_ok = codes:set("device_code:" .. params.client_id .. "#" .. device_code, { expires = os.time() + 1200 }); + local expires = os.time() + 600; + local dc_ok = codes:set("device_code:" .. params.client_id .. "#" .. device_code, { expires = expires }); local uc_ok = codes:set("user_code:" .. user_code, - { user_code = user_code; expires = os.time() + 600; client_id = params.client_id; + { user_code = user_code; expires = expires; client_id = params.client_id; scope = requested_scopes:concat(" ") }); if not dc_ok or not uc_ok then return oauth_error("temporarily_unavailable"); @@ -1041,6 +1046,8 @@ } end +local strict_auth_revoke = module:get_option_boolean("oauth2_require_auth_revoke", false); + local function handle_revocation_request(event) local request, response = event.request, event.response; response.headers.cache_control = "no-store"; @@ -1055,6 +1062,11 @@ if not verify_client_secret(credentials.username, credentials.password) then return 401; end + -- TODO check that it's their token I guess? + elseif strict_auth_revoke then + -- Why require auth to revoke a leaked token? + response.headers.www_authenticate = string.format("Basic realm=%q", module.host.."/"..module.name); + return 401; end local form_data = strict_formdecode(event.request.body); @@ -1224,6 +1236,16 @@ return nil, oauth_error("invalid_request", "Failed schema validation."); end + local client_uri = url.parse(client_metadata.client_uri); + if not client_uri or client_uri.scheme ~= "https" or loopbacks:contains(client_uri.host) then + return nil, oauth_error("invalid_client_metadata", "Missing, invalid or insecure client_uri"); + end + + if not client_metadata.application_type and redirect_uri_allowed(client_metadata.redirect_uris[1], client_uri, "native") then + client_metadata.application_type = "native"; + -- else defaults to "web" + end + -- Fill in default values for propname, propspec in pairs(registration_schema.properties) do if client_metadata[propname] == nil and type(propspec) == "table" and propspec.default ~= nil then @@ -1238,11 +1260,6 @@ end end - local client_uri = url.parse(client_metadata.client_uri); - if not client_uri or client_uri.scheme ~= "https" or loopbacks:contains(client_uri.host) then - return nil, oauth_error("invalid_client_metadata", "Missing, invalid or insecure client_uri"); - end - for _, redirect_uri in ipairs(client_metadata.redirect_uris) do if not redirect_uri_allowed(redirect_uri, client_uri, client_metadata.application_type) then return nil, oauth_error("invalid_redirect_uri", "Invalid, insecure or inappropriate redirect URI.");
--- a/mod_rest/example/rest.sh Tue Aug 01 01:27:45 2023 +0700 +++ b/mod_rest/example/rest.sh Tue Aug 29 22:46:27 2023 +0700 @@ -5,23 +5,25 @@ # Dependencies: # - https://httpie.io/ -# - https://github.com/stedolan/jq -# - some sort of XDG 'open' command +# - https://hg.sr.ht/~zash/httpie-oauth2 + +# shellcheck disable=SC1091 # 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" + + if [ -z "${SCOPE:-}" ]; then + SCOPE="openid xmpp" + fi fi if [[ $# == 0 ]]; then - echo "${0##*/} [-h HOST] [-u USER|--login] [/path] kind=(message|presence|iq) ...." + echo "${0##*/} [-h HOST] [/path] kind=(message|presence|iq) ...." # Last arguments are handed to HTTPie, so refer to its docs for further details exit 0 fi @@ -45,96 +47,6 @@ 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="" @@ -143,4 +55,4 @@ shift 1 fi -http --check-status -p b "--$AUTH_METHOD" "$AUTH_ID" "https://$HOST/rest$GET_PATH" "$@" +https --check-status -p b --session rest -A oauth2 -a "$HOST" --oauth2-scope "$SCOPE" "$HOST/rest$GET_PATH" "$@"