Software /
code /
prosody-modules
Diff
mod_http_oauth2/mod_http_oauth2.lua @ 6208:e20901443eae draft
Merge
author | Trần H. Trung <xmpp:trần.h.trung@trung.fun> |
---|---|
date | Mon, 17 Mar 2025 23:42:11 +0700 (6 weeks ago) |
parent | 5882:761142ee0ff2 |
child | 6211:750d64c47ec6 |
line wrap: on
line diff
--- a/mod_http_oauth2/mod_http_oauth2.lua Wed Feb 26 19:36:35 2025 +0700 +++ b/mod_http_oauth2/mod_http_oauth2.lua Mon Mar 17 23:42:11 2025 +0700 @@ -28,6 +28,27 @@ end end +local function strict_url_parse(urlstr) + local url_parts = url.parse(urlstr); + if not url_parts then return url_parts; end + if url_parts.userinfo then return false; end + if url_parts.port then + local port = tonumber(url_parts.port); + if not port then return false; end + if port <= 0 or port > 0xffff then return false; end + if port ~= math.floor(port) then return false; end + end + if url_parts.host then + if encodings.stringprep.nameprep(url_parts.host) ~= url_parts.host then + return false; + end + if not encodings.idna.to_ascii(url_parts.host) then + return false; + end + end + return url_parts; +end + local function strict_formdecode(query) if not query then return nil; @@ -697,8 +718,13 @@ if not request.headers.authorization then return; end local auth_type, auth_data = string.match(request.headers.authorization, "^(%S+)%s(.+)$"); + if not auth_type then return nil; end - if auth_type == "Basic" then + -- As described in Section 2.3 of [RFC5234], the string Bearer is case-insensitive. + -- https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-5.1.1 + auth_type = auth_type:lower(); + + if auth_type == "basic" then local creds = base64.decode(auth_data); if not creds then return; end local username, password = string.match(creds, "^([^:]+):(.*)$"); @@ -708,7 +734,7 @@ username = username; password = password; }; - elseif auth_type == "Bearer" then + elseif auth_type == "bearer" then return { type = "bearer"; bearer_token = auth_data; @@ -1356,7 +1382,7 @@ end local function redirect_uri_allowed(redirect_uri, client_uri, app_type) - local uri = url.parse(redirect_uri); + local uri = strict_url_parse(redirect_uri); if not uri then return false; end @@ -1391,8 +1417,8 @@ }); 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 + local client_uri = strict_url_parse(client_metadata.client_uri); + if not client_uri or client_uri.scheme ~= "https" or not client_uri.host or loopbacks:contains(client_uri.host) then return nil, oauth_error("invalid_client_metadata", "Missing, invalid or insecure client_uri"); end @@ -1558,6 +1584,7 @@ -- This is the normal 'authorization_code' flow. -- Step 1. Create OAuth client + ["GET /register"] = { headers = { content_type = "application/schema+json" }; body = json.encode(registration_schema) }; ["POST /register"] = handle_register_request; -- Device flow @@ -1569,24 +1596,6 @@ ["POST /authorize"] = handle_authorization_request; ["OPTIONS /authorize"] = { status_code = 403; body = "" }; - -- Step 3. User is redirected to the 'redirect_uri' along with an - -- authorization code. In the insecure 'implicit' flow, the access token - -- is delivered here. - - -- Step 4. Retrieve access token using the code. - ["POST /token"] = handle_token_grant; - - -- Step 4 is later repeated using the refresh token to get new access tokens. - - -- Step 5. Revoke token (access or refresh) - ["POST /revoke"] = handle_revocation_request; - - -- Get info about a token - ["POST /introspect"] = handle_introspection_request; - - -- OpenID - ["GET /userinfo"] = handle_userinfo_request; - -- Optional static content for templates ["GET /style.css"] = templates.css and { headers = { @@ -1601,11 +1610,26 @@ body = templates.js; } or nil; - -- Some convenient fallback handlers - ["GET /register"] = { headers = { content_type = "application/schema+json" }; body = json.encode(registration_schema) }; + -- Step 3. User is redirected to the 'redirect_uri' along with an + -- authorization code. In the insecure 'implicit' flow, the access token + -- is delivered here. + + -- Step 4. Retrieve access token using the code. + ["POST /token"] = handle_token_grant; ["GET /token"] = function() return 405; end; + + -- Step 4 is later repeated using the refresh token to get new access tokens. + + -- Get info about a token + ["POST /introspect"] = handle_introspection_request; + ["GET /introspect"] = function() return 405; end; + + -- Get info about the user, used for OpenID Connect + ["GET /userinfo"] = handle_userinfo_request; + + -- Step 5. Revoke token (access or refresh) + ["POST /revoke"] = handle_revocation_request; ["GET /revoke"] = function() return 405; end; - ["GET /introspect"] = function() return 405; end; }; }); @@ -1652,7 +1676,7 @@ ui_locales_supported = allowed_locales[1] and allowed_locales; -- OpenID - userinfo_endpoint = handle_register_request and module:http_url() .. "/userinfo" or nil; + userinfo_endpoint = handle_userinfo_request and module:http_url() .. "/userinfo" or nil; jwks_uri = nil; -- REQUIRED in OpenID Discovery but not in OAuth 2.0 Metadata id_token_signing_alg_values_supported = { "HS256" }; -- The algorithm RS256 MUST be included, but we use HS256 and client_secret as shared key. }