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.
 	}