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.

middleware.js 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // @flow
  2. import jwtDecode from 'jwt-decode';
  3. import { SET_CONFIG } from '../config';
  4. import { SET_LOCATION_URL } from '../connection';
  5. import {
  6. getLocalParticipant,
  7. participantUpdated
  8. } from '../participants';
  9. import { MiddlewareRegistry } from '../redux';
  10. import { SET_JWT } from './actionTypes';
  11. import { setJWT } from './actions';
  12. import { parseJWTFromURLParams } from './functions';
  13. declare var APP: Object;
  14. /**
  15. * Middleware to parse token data upon setting a new room URL.
  16. *
  17. * @param {Store} store - The redux store.
  18. * @private
  19. * @returns {Function}
  20. */
  21. MiddlewareRegistry.register(store => next => action => {
  22. switch (action.type) {
  23. case SET_CONFIG:
  24. case SET_LOCATION_URL:
  25. // XXX The JSON Web Token (JWT) is not the only piece of state that we
  26. // have decided to store in the feature jwt, there is isGuest as well
  27. // which depends on the states of the features base/config and jwt. So
  28. // the JSON Web Token comes from the conference/room's URL and isGuest
  29. // needs a recalculation upon SET_CONFIG as well.
  30. return _setConfigOrLocationURL(store, next, action);
  31. case SET_JWT:
  32. return _setJWT(store, next, action);
  33. }
  34. return next(action);
  35. });
  36. /**
  37. * Overwrites the properties {@code avatarURL}, {@code email}, and {@code name}
  38. * of the local participant stored in the redux state base/participants.
  39. *
  40. * @param {Store} store - The redux store.
  41. * @param {Object} localParticipant - The {@code Participant} structure to
  42. * overwrite the local participant stored in the redux store base/participants
  43. * with.
  44. * @private
  45. * @returns {void}
  46. */
  47. function _overwriteLocalParticipant(
  48. { dispatch, getState },
  49. { avatarURL, email, name, features }) {
  50. let localParticipant;
  51. if ((avatarURL || email || name)
  52. && (localParticipant = getLocalParticipant(getState))) {
  53. const newProperties: Object = {
  54. id: localParticipant.id,
  55. local: true
  56. };
  57. if (avatarURL) {
  58. newProperties.avatarURL = avatarURL;
  59. }
  60. if (email) {
  61. newProperties.email = email;
  62. }
  63. if (name) {
  64. newProperties.name = name;
  65. }
  66. if (features) {
  67. newProperties.features = features;
  68. }
  69. dispatch(participantUpdated(newProperties));
  70. }
  71. }
  72. /**
  73. * Notifies the feature jwt that the action {@link SET_CONFIG} or
  74. * {@link SET_LOCATION_URL} is being dispatched within a specific redux
  75. * {@code store}.
  76. *
  77. * @param {Store} store - The redux store in which the specified {@code action}
  78. * is being dispatched.
  79. * @param {Dispatch} next - The redux dispatch function to dispatch the
  80. * specified {@code action} to the specified {@code store}.
  81. * @param {Action} action - The redux action {@code SET_CONFIG} or
  82. * {@code SET_LOCATION_URL} which is being dispatched in the specified
  83. * {@code store}.
  84. * @private
  85. * @returns {Object} The new state that is the result of the reduction of the
  86. * specified {@code action}.
  87. */
  88. function _setConfigOrLocationURL({ dispatch, getState }, next, action) {
  89. const result = next(action);
  90. const { locationURL } = getState()['features/base/connection'];
  91. dispatch(
  92. setJWT(locationURL ? parseJWTFromURLParams(locationURL) : undefined));
  93. return result;
  94. }
  95. /**
  96. * Notifies the feature jwt that the action {@link SET_JWT} is being dispatched
  97. * within a specific redux {@code store}.
  98. *
  99. * @param {Store} store - The redux store in which the specified {@code action}
  100. * is being dispatched.
  101. * @param {Dispatch} next - The redux dispatch function to dispatch the
  102. * specified {@code action} to the specified {@code store}.
  103. * @param {Action} action - The redux action {@code SET_JWT} which is being
  104. * dispatched in the specified {@code store}.
  105. * @private
  106. * @returns {Object} The new state that is the result of the reduction of the
  107. * specified {@code action}.
  108. */
  109. function _setJWT(store, next, action) {
  110. // eslint-disable-next-line no-unused-vars
  111. const { jwt, type, ...actionPayload } = action;
  112. if (!Object.keys(actionPayload).length) {
  113. if (jwt) {
  114. const {
  115. enableUserRolesBasedOnToken
  116. } = store.getState()['features/base/config'];
  117. action.isGuest = !enableUserRolesBasedOnToken;
  118. const jwtPayload = jwtDecode(jwt);
  119. if (jwtPayload) {
  120. const { context, iss } = jwtPayload;
  121. action.jwt = jwt;
  122. action.issuer = iss;
  123. if (context) {
  124. const user = _user2participant(context.user);
  125. action.callee = context.callee;
  126. action.group = context.group;
  127. action.server = context.server;
  128. action.user = user;
  129. user && _overwriteLocalParticipant(
  130. store, { ...user,
  131. features: context.features });
  132. }
  133. }
  134. } else if (typeof APP === 'undefined') {
  135. // The logic of restoring JWT overrides make sense only on mobile.
  136. // On Web it should eventually be restored from storage, but there's
  137. // no such use case yet.
  138. const { user } = store.getState()['features/base/jwt'];
  139. user && _undoOverwriteLocalParticipant(store, user);
  140. }
  141. }
  142. return next(action);
  143. }
  144. /**
  145. * Undoes/resets the values overwritten by {@link _overwriteLocalParticipant}
  146. * by either clearing them or setting to default values. Only the values that
  147. * have not changed since the overwrite happened will be restored.
  148. *
  149. * NOTE Once it is possible to edit and save participant properties, this
  150. * function should restore values from the storage instead.
  151. *
  152. * @param {Store} store - The redux store.
  153. * @param {Object} localParticipant - The {@code Participant} structure used
  154. * previously to {@link _overwriteLocalParticipant}.
  155. * @private
  156. * @returns {void}
  157. */
  158. function _undoOverwriteLocalParticipant(
  159. { dispatch, getState },
  160. { avatarURL, name, email }) {
  161. let localParticipant;
  162. if ((avatarURL || name || email)
  163. && (localParticipant = getLocalParticipant(getState))) {
  164. const newProperties: Object = {
  165. id: localParticipant.id,
  166. local: true
  167. };
  168. if (avatarURL === localParticipant.avatarURL) {
  169. newProperties.avatarURL = undefined;
  170. }
  171. if (email === localParticipant.email) {
  172. newProperties.email = undefined;
  173. }
  174. if (name === localParticipant.name) {
  175. newProperties.name = undefined;
  176. }
  177. newProperties.features = undefined;
  178. dispatch(participantUpdated(newProperties));
  179. }
  180. }
  181. /**
  182. * Converts the JWT {@code context.user} structure to the {@code Participant}
  183. * structure stored in the redux state base/participants.
  184. *
  185. * @param {Object} user - The JWT {@code context.user} structure to convert.
  186. * @private
  187. * @returns {{
  188. * avatarURL: ?string,
  189. * email: ?string,
  190. * id: ?string,
  191. * name: ?string
  192. * }}
  193. */
  194. function _user2participant({ avatar, avatarUrl, email, id, name }) {
  195. const participant = {};
  196. if (typeof avatarUrl === 'string') {
  197. participant.avatarURL = avatarUrl.trim();
  198. } else if (typeof avatar === 'string') {
  199. participant.avatarURL = avatar.trim();
  200. }
  201. if (typeof email === 'string') {
  202. participant.email = email.trim();
  203. }
  204. if (typeof id === 'string') {
  205. participant.id = id.trim();
  206. }
  207. if (typeof name === 'string') {
  208. participant.name = name.trim();
  209. }
  210. return Object.keys(participant).length ? participant : undefined;
  211. }