Mercurial > prosody-modules
comparison mod_auth_oauth_external/mod_auth_oauth_external.lua @ 5650:0eb2d5ea2428
merge
| author | Stephen Paul Weber <singpolyma@singpolyma.net> |
|---|---|
| date | Sat, 06 May 2023 19:40:23 -0500 |
| parents | d9bc8712a745 |
| children | b40299bbdf14 |
comparison
equal
deleted
inserted
replaced
| 5649:2c69577b28c2 | 5650:0eb2d5ea2428 |
|---|---|
| 1 local http = require "net.http"; | |
| 2 local async = require "util.async"; | |
| 3 local json = require "util.json"; | |
| 4 local sasl = require "util.sasl"; | |
| 5 | |
| 6 local issuer_identity = module:get_option_string("oauth_external_issuer"); | |
| 7 local oidc_discovery_url = module:get_option_string("oauth_external_discovery_url", | |
| 8 issuer_identity and issuer_identity .. "/.well-known/oauth-authorization-server" or nil); | |
| 9 local validation_endpoint = module:get_option_string("oauth_external_validation_endpoint"); | |
| 10 local token_endpoint = module:get_option_string("oauth_external_token_endpoint"); | |
| 11 | |
| 12 local username_field = module:get_option_string("oauth_external_username_field", "preferred_username"); | |
| 13 local allow_plain = module:get_option_boolean("oauth_external_resource_owner_password", true); | |
| 14 | |
| 15 -- XXX Hold up, does whatever done here even need any of these things? Are we | |
| 16 -- the OAuth client? Is the XMPP client the OAuth client? What are we??? | |
| 17 local client_id = module:get_option_string("oauth_external_client_id"); | |
| 18 -- TODO -- local client_secret = module:get_option_string("oauth_external_client_secret"); | |
| 19 | |
| 20 --[[ More or less required endpoints | |
| 21 digraph "oauth endpoints" { | |
| 22 issuer -> discovery -> { registration validation } | |
| 23 registration -> { client_id client_secret } | |
| 24 { client_id client_secret validation } -> required | |
| 25 } | |
| 26 --]] | |
| 27 | |
| 28 local host = module.host; | |
| 29 local provider = {}; | |
| 30 | |
| 31 function provider.get_sasl_handler() | |
| 32 local profile = {}; | |
| 33 profile.http_client = http.default; -- TODO configurable | |
| 34 local extra = { oidc_discovery_url = oidc_discovery_url }; | |
| 35 if token_endpoint and allow_plain then | |
| 36 local map_username = function (username, _realm) return username; end; --jid.join; -- TODO configurable | |
| 37 function profile:plain_test(username, password, realm) | |
| 38 local tok, err = async.wait_for(self.profile.http_client:request(token_endpoint, { | |
| 39 headers = { ["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"; ["Accept"] = "application/json" }; | |
| 40 body = http.formencode({ | |
| 41 grant_type = "password"; | |
| 42 client_id = client_id; | |
| 43 username = map_username(username, realm); | |
| 44 password = password; | |
| 45 scope = "openid"; | |
| 46 }); | |
| 47 })) | |
| 48 if err or not (tok.code >= 200 and tok.code < 300) then | |
| 49 return false, nil; | |
| 50 end | |
| 51 local token_resp = json.decode(tok.body); | |
| 52 if not token_resp or string.lower(token_resp.token_type or "") ~= "bearer" then | |
| 53 return false, nil; | |
| 54 end | |
| 55 local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint, | |
| 56 { headers = { ["Authorization"] = "Bearer " .. token_resp.access_token; ["Accept"] = "application/json" } })); | |
| 57 if err then | |
| 58 return false, nil; | |
| 59 end | |
| 60 if not (ret.code >= 200 and ret.code < 300) then | |
| 61 return false, nil; | |
| 62 end | |
| 63 local response = json.decode(ret.body); | |
| 64 if type(response) ~= "table" or (response[username_field]) ~= username then | |
| 65 return false, nil, nil; | |
| 66 end | |
| 67 if response.jid then | |
| 68 self.username, self.realm, self.resource = jid.prepped_split(response.jid, true); | |
| 69 end | |
| 70 self.role = response.role; | |
| 71 self.token_info = response; | |
| 72 return true, true; | |
| 73 end | |
| 74 end | |
| 75 function profile:oauthbearer(token) | |
| 76 if token == "" then | |
| 77 return false, nil, extra; | |
| 78 end | |
| 79 | |
| 80 local ret, err = async.wait_for(self.profile.http_client:request(validation_endpoint, | |
| 81 { headers = { ["Authorization"] = "Bearer " .. token; ["Accept"] = "application/json" } })); | |
| 82 if err then | |
| 83 return false, nil, extra; | |
| 84 end | |
| 85 local response = ret and json.decode(ret.body); | |
| 86 if not (ret.code >= 200 and ret.code < 300) then | |
| 87 return false, nil, response or extra; | |
| 88 end | |
| 89 if type(response) ~= "table" or type(response[username_field]) ~= "string" then | |
| 90 return false, nil, nil; | |
| 91 end | |
| 92 | |
| 93 return response[username_field], true, response; | |
| 94 end | |
| 95 return sasl.new(host, profile); | |
| 96 end | |
| 97 | |
| 98 module:provides("auth", provider); |
