Comparison

mod_http_oauth2/mod_http_oauth2.lua @ 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
parent 6334:9b03238d4e0e
child 6340:c96bd4156664
comparison
equal deleted inserted replaced
6335:9102d75131e4 6336:6e80b2cb5fe6
186 return array(scope_string:gmatch("%S+")); 186 return array(scope_string:gmatch("%S+"));
187 end 187 end
188 188
189 local openid_claims = set.new(); 189 local openid_claims = set.new();
190 190
191 module:add_item("openid-claim", "openid"); 191 module:add_item("openid-claim", { claim = "openid"; title = "OpenID";
192 description = "Tells the application your JID and when you authenticated."; });
192 193
193 -- https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess 194 -- https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
194 -- The "offline_access" scope grants access to refresh tokens 195 -- The "offline_access" scope grants access to refresh tokens
195 module:add_item("openid-claim", "offline_access"); 196 module:add_item("openid-claim", { claim = "offline_access"; title = "Offline Access";
197 description = "Application may renew access without interaction."; });
196 198
197 module:handle_items("openid-claim", function(event) 199 module:handle_items("openid-claim", function(event)
198 authorization_server_metadata = nil; 200 authorization_server_metadata = nil;
199 openid_claims:add(event.item); 201 openid_claims:add(event.item.claim or event.item);
200 end, function() 202 end, function()
201 authorization_server_metadata = nil; 203 authorization_server_metadata = nil;
202 openid_claims = set.new(module:get_host_items("openid-claim")); 204 openid_claims = set.new(array.new(module:get_host_items("openid-claim")):map(function(item)
205 return item.claim or item;
206 end));
203 end, true); 207 end, true);
204 208
205 -- array -> array, array, array 209 -- array -> array, array, array
206 local function split_scopes(scope_list) 210 local function split_scopes(scope_list)
207 local claims, roles, unknown = array(), array(), array(); 211 local claims, roles, unknown = array(), array(), array();
1029 auth_state.consent = "granted"; 1033 auth_state.consent = "granted";
1030 end 1034 end
1031 1035
1032 else 1036 else
1033 -- Render consent page 1037 -- Render consent page
1034 return render_page(templates.consent, { state = auth_state; client = client; scopes = scopes+roles }, true); 1038 module:log("debug", "scopes=%q", scopes);
1039 local scope_choices = array.new(module:get_host_items("openid-claim")):map(function(item)
1040 if type(item) == "string" then
1041 return { claim = item };
1042 elseif type(item) == "table" and type(item.claim) == "string" then
1043 return item;
1044 end
1045 end):filter(function (item)
1046 if array_contains(scopes, item.claim) then
1047 module:log("debug", "scopes contains %q", item);
1048 return true;
1049 else
1050 module:log("debug", "scopes contains NO %q", item);
1051 return false;
1052 end
1053 end);
1054 for _, role in ipairs(roles) do
1055 if role == "xmpp" then
1056 scope_choices:push({ scope = role; title = "XMPP";
1057 description = "Unlimited access to your account, including sending and receiving messages."; });
1058 else
1059 scope_choices:push({ scope = role; title = role, description = "Prosody Role" });
1060 end
1061 end
1062 return render_page(templates.consent, { state = auth_state; client = client; scopes = scope_choices }, true);
1035 end 1063 end
1036 elseif not auth_state.consent then 1064 elseif not auth_state.consent then
1037 -- Notify client of rejection 1065 -- Notify client of rejection
1038 return error_response(request, redirect_uri, oauth_error("access_denied")); 1066 return error_response(request, redirect_uri, oauth_error("access_denied"));
1039 end 1067 end
1054 -- https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.1.2.1 1082 -- https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.1.2.1
1055 if array_contains(granted_scopes, "openid") then 1083 if array_contains(granted_scopes, "openid") then
1056 local client_secret = make_client_secret(params.client_id); 1084 local client_secret = make_client_secret(params.client_id);
1057 local id_token_signer = jwt.new_signer("HS256", client_secret); 1085 local id_token_signer = jwt.new_signer("HS256", client_secret);
1058 id_token = id_token_signer({ 1086 id_token = id_token_signer({
1059 iss = get_issuer(); 1087 iss = get_issuer(); -- REQUIRED
1060 sub = url.build({ scheme = "xmpp"; path = user_jid }); 1088 sub = url.build({ scheme = "xmpp"; path = user_jid }); -- REQUIRED
1061 aud = params.client_id; 1089 aud = params.client_id; -- REQUIRED
1062 auth_time = auth_state.user.iat; 1090 -- exp REQUIRED, set by util.jwt
1091 -- iat REQUIRED, set by util.jwt
1092 auth_time = auth_state.user.iat; -- REQUIRED when Essential Claim, otherwise OPTIONAL
1063 nonce = params.nonce; 1093 nonce = params.nonce;
1064 amr = auth_state.user.amr; -- RFC 8176: Authentication Method Reference Values 1094 amr = auth_state.user.amr; -- RFC 8176: Authentication Method Reference Values
1065 }); 1095 });
1066 end 1096 end
1067 local ret = response_handler(client, params, user_jid, id_token); 1097 local ret = response_handler(client, params, user_jid, id_token);