Software / code / prosody-modules
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); |