You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mod_auth_token.lua 4.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. -- Token authentication
  2. -- Copyright (C) 2015 Atlassian
  3. local basexx = require 'basexx'
  4. local have_async, async = pcall(require, "util.async");
  5. local formdecode = require "util.http".formdecode;
  6. local generate_uuid = require "util.uuid".generate;
  7. local http = require "net.http";
  8. local json = require 'cjson'
  9. json.encode_empty_table('array')
  10. local new_sasl = require "util.sasl".new;
  11. local path = require "util.paths";
  12. local sasl = require "util.sasl";
  13. local timer = require "util.timer";
  14. local token_util = module:require "token/util";
  15. -- define auth provider
  16. local provider = {};
  17. local host = module.host;
  18. local appId = module:get_option_string("app_id");
  19. local appSecret = module:get_option_string("app_secret");
  20. local asapKeyServer = module:get_option_string("asap_key_server");
  21. local allowEmptyToken = module:get_option_boolean("allow_empty_token");
  22. local disableRoomNameConstraints = module:get_option_boolean("disable_room_name_constraints");
  23. if allowEmptyToken == true then
  24. module:log("warn", "WARNING - empty tokens allowed");
  25. end
  26. if appId == nil then
  27. module:log("error", "'app_id' must not be empty");
  28. return;
  29. end
  30. if appSecret == nil and asapKeyServer == nil then
  31. module:log("error", "'app_secret' or 'asap_key_server' must be specified");
  32. return;
  33. end
  34. if asapKeyServer and not have_async then
  35. module:log("error", "requires a version of Prosody with util.async");
  36. return;
  37. end
  38. -- Extract 'token' param from BOSH URL when session is created
  39. module:hook("bosh-session", function(event)
  40. local session, request = event.session, event.request;
  41. local query = request.url.query;
  42. if query ~= nil then
  43. session.auth_token = query and formdecode(query).token or nil;
  44. end
  45. end)
  46. function provider.test_password(username, password)
  47. return nil, "Password based auth not supported";
  48. end
  49. function provider.get_password(username)
  50. return nil;
  51. end
  52. function provider.set_password(username, password)
  53. return nil, "Set password not supported";
  54. end
  55. function provider.user_exists(username)
  56. return nil;
  57. end
  58. function provider.create_user(username, password)
  59. return nil;
  60. end
  61. function provider.delete_user(username)
  62. return nil;
  63. end
  64. local http_timeout = 30;
  65. local http_headers = {
  66. ["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")"
  67. };
  68. -- TODO: This *needs* to be memoized before going to prod.
  69. function get_public_key(keyId)
  70. local wait, done = async.waiter();
  71. local content, code; --, request, response;
  72. local function cb(content_, code_, response_, request_)
  73. content, code = content_, code_;
  74. done();
  75. end
  76. local request = http.request(path.join(asapKeyServer, keyId), {
  77. headers = http_headers or {},
  78. method = "GET"
  79. }, cb);
  80. -- TODO: Is the done() call racey?
  81. timer.add_task(http_timeout, function() http.destroy_request(request); done(); end);
  82. wait();
  83. if code == 200 or code == 204 then
  84. return content;
  85. end
  86. return nil
  87. end
  88. function provider.get_sasl_handler(session)
  89. -- JWT token extracted from BOSH URL
  90. local token = session.auth_token;
  91. local function get_username_from_token(self, message)
  92. if token == nil then
  93. if allowEmptyToken then
  94. return true
  95. else
  96. return false, "not-allowed", "token required";
  97. end
  98. end
  99. local pubKey;
  100. if asapKeyServer and session.auth_token ~= nil then
  101. local dotFirst = session.auth_token:find("%.")
  102. if not dotFirst then return nil, "Invalid token" end
  103. local header = json.decode(basexx.from_url64(session.auth_token:sub(1,dotFirst-1)))
  104. local kid = header["kid"]
  105. if kid == nil then
  106. return false, "not-allowed", "'kid' claim is missing";
  107. end
  108. pubKey = get_public_key(kid);
  109. if pubKey == nil then
  110. return false, "not-allowed", "could not obtain public key";
  111. end
  112. end
  113. -- now verify the whole token
  114. local result, msg;
  115. if asapKeyServer then
  116. result, msg = token_util.verify_token(token, appId, pubKey, disableRoomNameConstraints);
  117. else
  118. result, msg = token_util.verify_token(token, appId, appSecret, disableRoomNameConstraints);
  119. end
  120. if result == true then
  121. -- Binds room name to the session which is later checked on MUC join
  122. session.jitsi_meet_room = room;
  123. return true
  124. else
  125. return false, "not-allowed", msg
  126. end
  127. end
  128. return new_sasl(host, { anonymous = get_username_from_token });
  129. end
  130. module:provides("auth", provider);
  131. local function anonymous(self, message)
  132. local username = generate_uuid();
  133. -- This calls the handler created in 'provider.get_sasl_handler(session)'
  134. local result, err, msg = self.profile.anonymous(self, username, self.realm);
  135. self.username = username;
  136. if result == true then
  137. return "success"
  138. else
  139. return "failure", err, msg
  140. end
  141. end
  142. sasl.registerMechanism("ANONYMOUS", {"anonymous"}, anonymous);