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.

AuthHandler.js 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /* global APP, config, JitsiMeetJS, Promise */
  2. import Logger from 'jitsi-meet-logger';
  3. import { openConnection } from '../../../connection';
  4. import { setJWT } from '../../../react/features/base/jwt';
  5. import {
  6. JitsiConnectionErrors
  7. } from '../../../react/features/base/lib-jitsi-meet';
  8. import UIUtil from '../util/UIUtil';
  9. import LoginDialog from './LoginDialog';
  10. const logger = Logger.getLogger(__filename);
  11. let externalAuthWindow;
  12. let authRequiredDialog;
  13. const isTokenAuthEnabled
  14. = typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length;
  15. const getTokenAuthUrl
  16. = JitsiMeetJS.util.AuthUtil.getTokenAuthUrl.bind(null, config.tokenAuthUrl);
  17. /**
  18. * Authenticate using external service or just focus
  19. * external auth window if there is one already.
  20. *
  21. * @param {JitsiConference} room
  22. * @param {string} [lockPassword] password to use if the conference is locked
  23. */
  24. function doExternalAuth(room, lockPassword) {
  25. if (externalAuthWindow) {
  26. externalAuthWindow.focus();
  27. return;
  28. }
  29. if (room.isJoined()) {
  30. let getUrl;
  31. if (isTokenAuthEnabled) {
  32. getUrl = Promise.resolve(getTokenAuthUrl(room.getName(), true));
  33. initJWTTokenListener(room);
  34. } else {
  35. getUrl = room.getExternalAuthUrl(true);
  36. }
  37. getUrl.then(url => {
  38. externalAuthWindow = LoginDialog.showExternalAuthDialog(
  39. url,
  40. () => {
  41. externalAuthWindow = null;
  42. if (!isTokenAuthEnabled) {
  43. room.join(lockPassword);
  44. }
  45. }
  46. );
  47. });
  48. } else if (isTokenAuthEnabled) {
  49. redirectToTokenAuthService(room.getName());
  50. } else {
  51. room.getExternalAuthUrl().then(UIUtil.redirect);
  52. }
  53. }
  54. /**
  55. * Redirect the user to the token authentication service for the login to be
  56. * performed. Once complete it is expected that the service will bring the user
  57. * back with "?jwt={the JWT token}" query parameter added.
  58. * @param {string} [roomName] the name of the conference room.
  59. */
  60. function redirectToTokenAuthService(roomName) {
  61. // FIXME: This method will not preserve the other URL params that were
  62. // originally passed.
  63. UIUtil.redirect(getTokenAuthUrl(roomName, false));
  64. }
  65. /**
  66. * Initializes 'message' listener that will wait for a JWT token to be received
  67. * from the token authentication service opened in a popup window.
  68. * @param room the name of the conference room.
  69. */
  70. function initJWTTokenListener(room) {
  71. /**
  72. *
  73. */
  74. function listener({ data, source }) {
  75. if (externalAuthWindow !== source) {
  76. logger.warn('Ignored message not coming '
  77. + 'from external authnetication window');
  78. return;
  79. }
  80. let jwt;
  81. if (data && (jwt = data.jwtToken)) {
  82. logger.info('Received JSON Web Token (JWT):', jwt);
  83. APP.store.dispatch(setJWT(jwt));
  84. const roomName = room.getName();
  85. openConnection({
  86. retry: false,
  87. roomName
  88. }).then(connection => {
  89. // Start new connection
  90. const newRoom = connection.initJitsiConference(
  91. roomName, APP.conference._getConferenceOptions());
  92. // Authenticate from the new connection to get
  93. // the session-ID from the focus, which will then be used
  94. // to upgrade current connection's user role
  95. newRoom.room.moderator.authenticate()
  96. .then(() => {
  97. connection.disconnect();
  98. // At this point we'll have session-ID stored in
  99. // the settings. It will be used in the call below
  100. // to upgrade user's role
  101. room.room.moderator.authenticate()
  102. .then(() => {
  103. logger.info('User role upgrade done !');
  104. // eslint-disable-line no-use-before-define
  105. unregister();
  106. })
  107. .catch((err, errCode) => {
  108. logger.error('Authentication failed: ',
  109. err, errCode);
  110. unregister();
  111. });
  112. })
  113. .catch((error, code) => {
  114. unregister();
  115. connection.disconnect();
  116. logger.error(
  117. 'Authentication failed on the new connection',
  118. error, code);
  119. });
  120. }, err => {
  121. unregister();
  122. logger.error('Failed to open new connection', err);
  123. });
  124. }
  125. }
  126. /**
  127. *
  128. */
  129. function unregister() {
  130. window.removeEventListener('message', listener);
  131. }
  132. if (window.addEventListener) {
  133. window.addEventListener('message', listener, false);
  134. }
  135. }
  136. /**
  137. * Authenticate on the server.
  138. * @param {JitsiConference} room
  139. * @param {string} [lockPassword] password to use if the conference is locked
  140. */
  141. function doXmppAuth(room, lockPassword) {
  142. const loginDialog = LoginDialog.showAuthDialog(
  143. /* successCallback */ (id, password) => {
  144. room.authenticateAndUpgradeRole({
  145. id,
  146. password,
  147. roomPassword: lockPassword,
  148. /** Called when the XMPP login succeeds. */
  149. onLoginSuccessful() {
  150. loginDialog.displayConnectionStatus(
  151. 'connection.FETCH_SESSION_ID');
  152. }
  153. })
  154. .then(
  155. /* onFulfilled */ () => {
  156. loginDialog.displayConnectionStatus(
  157. 'connection.GOT_SESSION_ID');
  158. loginDialog.close();
  159. },
  160. /* onRejected */ error => {
  161. logger.error('authenticateAndUpgradeRole failed', error);
  162. const { authenticationError, connectionError } = error;
  163. if (authenticationError) {
  164. loginDialog.displayError(
  165. 'connection.GET_SESSION_ID_ERROR',
  166. { msg: authenticationError });
  167. } else if (connectionError) {
  168. loginDialog.displayError(connectionError);
  169. }
  170. });
  171. },
  172. /* cancelCallback */ () => loginDialog.close());
  173. }
  174. /**
  175. * Authenticate for the conference.
  176. * Uses external service for auth if conference supports that.
  177. * @param {JitsiConference} room
  178. * @param {string} [lockPassword] password to use if the conference is locked
  179. */
  180. function authenticate(room, lockPassword) {
  181. if (isTokenAuthEnabled || room.isExternalAuthEnabled()) {
  182. doExternalAuth(room, lockPassword);
  183. } else {
  184. doXmppAuth(room, lockPassword);
  185. }
  186. }
  187. /**
  188. * De-authenticate local user.
  189. *
  190. * @param {JitsiConference} room
  191. * @param {string} [lockPassword] password to use if the conference is locked
  192. * @returns {Promise}
  193. */
  194. function logout(room) {
  195. return new Promise(resolve => {
  196. room.room.moderator.logout(resolve);
  197. }).then(url => {
  198. // de-authenticate conference on the fly
  199. if (room.isJoined()) {
  200. room.join();
  201. }
  202. return url;
  203. });
  204. }
  205. /**
  206. * Notify user that authentication is required to create the conference.
  207. * @param {JitsiConference} room
  208. * @param {string} [lockPassword] password to use if the conference is locked
  209. */
  210. function requireAuth(room, lockPassword) {
  211. if (authRequiredDialog) {
  212. return;
  213. }
  214. authRequiredDialog = LoginDialog.showAuthRequiredDialog(
  215. room.getName(), authenticate.bind(null, room, lockPassword)
  216. );
  217. }
  218. /**
  219. * Close auth-related dialogs if there are any.
  220. */
  221. function closeAuth() {
  222. if (externalAuthWindow) {
  223. externalAuthWindow.close();
  224. externalAuthWindow = null;
  225. }
  226. if (authRequiredDialog) {
  227. authRequiredDialog.close();
  228. authRequiredDialog = null;
  229. }
  230. }
  231. /**
  232. *
  233. */
  234. function showXmppPasswordPrompt(roomName, connect) {
  235. return new Promise((resolve, reject) => {
  236. const authDialog = LoginDialog.showAuthDialog(
  237. (id, password) => {
  238. connect(id, password, roomName).then(connection => {
  239. authDialog.close();
  240. resolve(connection);
  241. }, err => {
  242. if (err === JitsiConnectionErrors.PASSWORD_REQUIRED) {
  243. authDialog.displayError(err);
  244. } else {
  245. authDialog.close();
  246. reject(err);
  247. }
  248. });
  249. }
  250. );
  251. });
  252. }
  253. /**
  254. * Show Authentication Dialog and try to connect with new credentials.
  255. * If failed to connect because of PASSWORD_REQUIRED error
  256. * then ask for password again.
  257. * @param {string} [roomName] name of the conference room
  258. * @param {function(id, password, roomName)} [connect] function that returns
  259. * a Promise which resolves with JitsiConnection or fails with one of
  260. * JitsiConnectionErrors.
  261. * @returns {Promise<JitsiConnection>}
  262. */
  263. function requestAuth(roomName, connect) {
  264. if (isTokenAuthEnabled) {
  265. // This Promise never resolves as user gets redirected to another URL
  266. return new Promise(() => redirectToTokenAuthService(roomName));
  267. }
  268. return showXmppPasswordPrompt(roomName, connect);
  269. }
  270. export default {
  271. authenticate,
  272. requireAuth,
  273. requestAuth,
  274. closeAuth,
  275. logout
  276. };