Changeset

6336:6e80b2cb5fe6

mod_http_oauth2: Show scope descriptions This should help the user understand and provide _informed_ consent, although the texts can certainly be improved. Explaining these scopes is non-trivial.
author Kim Alvefur <zash@zash.se>
date Tue, 15 Jul 2025 16:28:40 +0200
parents 6335:9102d75131e4
children 6337:486115e3b64d
files mod_http_oauth2/html/consent.html mod_http_oauth2/mod_http_oauth2.lua
diffstat 2 files changed, 45 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/mod_http_oauth2/html/consent.html	Tue Jul 15 12:21:23 2025 +0200
+++ b/mod_http_oauth2/html/consent.html	Tue Jul 15 16:28:40 2025 +0200
@@ -35,8 +35,12 @@
 		<dd><a href="{client.policy_uri}">View policy</a></dd>}
 
 		<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>}
+		<dd>
+			<dl>{scopes#
+				<dt><input class="scope" type="checkbox" id="scope_{idx}" name="scope" value="{item.scope}" checked="" />
+			<label class="scope" for="scope_{idx}">{item.title?{item.claim}}</label></dt>
+				{item.description&<dd>{item.description}</dd>}}
+			</ul>
 		</dd>
 	</dl>
 
--- a/mod_http_oauth2/mod_http_oauth2.lua	Tue Jul 15 12:21:23 2025 +0200
+++ b/mod_http_oauth2/mod_http_oauth2.lua	Tue Jul 15 16:28:40 2025 +0200
@@ -188,18 +188,22 @@
 
 local openid_claims = set.new();
 
-module:add_item("openid-claim", "openid");
+module:add_item("openid-claim", { claim = "openid"; title = "OpenID";
+	description = "Tells the application your JID and when you authenticated."; });
 
 -- https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
 -- The "offline_access" scope grants access to refresh tokens
-module:add_item("openid-claim", "offline_access");
+module:add_item("openid-claim", { claim = "offline_access"; title = "Offline Access";
+	description = "Application may renew access without interaction."; });
 
 module:handle_items("openid-claim", function(event)
 	authorization_server_metadata = nil;
-	openid_claims:add(event.item);
+	openid_claims:add(event.item.claim or event.item);
 end, function()
 	authorization_server_metadata = nil;
-	openid_claims = set.new(module:get_host_items("openid-claim"));
+	openid_claims = set.new(array.new(module:get_host_items("openid-claim")):map(function(item)
+		return item.claim or item;
+	end));
 end, true);
 
 -- array -> array, array, array
@@ -1031,7 +1035,31 @@
 
 		else
 			-- Render consent page
-			return render_page(templates.consent, { state = auth_state; client = client; scopes = scopes+roles }, true);
+			module:log("debug", "scopes=%q", scopes);
+			local scope_choices = array.new(module:get_host_items("openid-claim")):map(function(item)
+				if type(item) == "string" then
+					return { claim = item };
+				elseif type(item) == "table" and type(item.claim) == "string" then
+					return item;
+				end
+			end):filter(function (item)
+				if array_contains(scopes, item.claim) then
+					module:log("debug", "scopes contains %q", item);
+					return true;
+				else
+					module:log("debug", "scopes contains NO %q", item);
+					return false;
+				end
+			end);
+			for _, role in ipairs(roles) do
+				if role == "xmpp" then
+					scope_choices:push({ scope = role; title = "XMPP";
+						description = "Unlimited access to your account, including sending and receiving messages."; });
+				else
+					scope_choices:push({ scope = role; title = role, description = "Prosody Role" });
+				end
+			end
+			return render_page(templates.consent, { state = auth_state; client = client; scopes = scope_choices }, true);
 		end
 	elseif not auth_state.consent then
 		-- Notify client of rejection
@@ -1056,10 +1084,12 @@
 		local client_secret = make_client_secret(params.client_id);
 		local id_token_signer = jwt.new_signer("HS256", client_secret);
 		id_token = id_token_signer({
-			iss = get_issuer();
-			sub = url.build({ scheme = "xmpp"; path = user_jid });
-			aud = params.client_id;
-			auth_time = auth_state.user.iat;
+			iss = get_issuer(); -- REQUIRED
+			sub = url.build({ scheme = "xmpp"; path = user_jid }); -- REQUIRED
+			aud = params.client_id; -- REQUIRED
+			-- exp REQUIRED, set by util.jwt
+			-- iat REQUIRED, set by util.jwt
+			auth_time = auth_state.user.iat; -- REQUIRED when Essential Claim, otherwise OPTIONAL
 			nonce = params.nonce;
 			amr = auth_state.user.amr; -- RFC 8176: Authentication Method Reference Values
 		});