Software / code / prosody-modules
Diff
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 |
line wrap: on
line diff
--- 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 });