Software /
code /
prosody-modules
Diff
mod_auth_oauth_external/mod_auth_oauth_external.lua @ 5345:3390bb2f9f6c
mod_auth_oauth_external: Support PLAIN via resource owner password grant
Might not be supported by the backend but PLAIN is the lowest common
denominator, so not having it would lock out a lot of clients.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Thu, 16 Mar 2023 12:45:52 +0100 |
parent | 5344:0a6d2b79a8bf |
child | 5346:d9bc8712a745 |
line wrap: on
line diff
--- a/mod_auth_oauth_external/mod_auth_oauth_external.lua Thu Mar 16 12:45:22 2023 +0100 +++ b/mod_auth_oauth_external/mod_auth_oauth_external.lua Thu Mar 16 12:45:52 2023 +0100 @@ -6,12 +6,14 @@ -- TODO -- local issuer_identity = module:get_option_string("oauth_external_issuer"); local oidc_discovery_url = module:get_option_string("oauth_external_discovery_url") local validation_endpoint = module:get_option_string("oauth_external_validation_endpoint"); +local token_endpoint = module:get_option_string("oauth_external_token_endpoint"); local username_field = module:get_option_string("oauth_external_username_field", "preferred_username"); +local allow_plain = module:get_option_boolean("oauth_external_resource_owner_password", true); -- XXX Hold up, does whatever done here even need any of these things? Are we -- the OAuth client? Is the XMPP client the OAuth client? What are we??? --- TODO -- local client_id = module:get_option_string("oauth_external_client_id"); +local client_id = module:get_option_string("oauth_external_client_id"); -- TODO -- local client_secret = module:get_option_string("oauth_external_client_secret"); --[[ More or less required endpoints @@ -29,6 +31,46 @@ local profile = {}; profile.http_client = http.default; -- TODO configurable local extra = { oidc_discovery_url = oidc_discovery_url }; + if token_endpoint and allow_plain then + local map_username = function (username, _realm) return username; end; --jid.join; -- TODO configurable + function profile:plain_test(username, password, realm) + local tok, err = async.wait_for(self.profile.http_client:request(token_endpoint, { + headers = { ["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"; ["Accept"] = "application/json" }; + body = http.formencode({ + grant_type = "password"; + client_id = client_id; + username = map_username(username, realm); + password = password; + scope = "openid"; + }); + })) + if err or not (tok.code >= 200 and tok.code < 300) then + return false, nil; + end + local token_resp = json.decode(tok.body); + if not token_resp or string.lower(token_resp.token_type or "") ~= "bearer" then + return false, nil; + end + local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint, + { headers = { ["Authorization"] = "Bearer " .. token_resp.access_token; ["Accept"] = "application/json" } })); + if err then + return false, nil; + end + if not (ret.code >= 200 and ret.code < 300) then + return false, nil; + end + local response = json.decode(ret.body); + if type(response) ~= "table" or (response[username_field]) ~= username then + return false, nil, nil; + end + if response.jid then + self.username, self.realm, self.resource = jid.prepped_split(response.jid, true); + end + self.role = response.role; + self.token_info = response; + return true, true; + end + end function profile:oauthbearer(token) if token == "" then return false, nil, extra;