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.

functions.ts 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // @ts-expect-error
  2. import jwtDecode from 'jwt-decode';
  3. import { IReduxState } from '../../app/types';
  4. import { getLocalParticipant } from '../participants/functions';
  5. import { parseURLParams } from '../util/parseURLParams';
  6. import { JWT_VALIDATION_ERRORS, MEET_FEATURES } from './constants';
  7. import logger from './logger';
  8. /**
  9. * Retrieves the JSON Web Token (JWT), if any, defined by a specific
  10. * {@link URL}.
  11. *
  12. * @param {URL} url - The {@code URL} to parse and retrieve the JSON Web Token
  13. * (JWT), if any, from.
  14. * @returns {string} The JSON Web Token (JWT), if any, defined by the specified
  15. * {@code url}; otherwise, {@code undefined}.
  16. */
  17. export function parseJWTFromURLParams(url: URL | typeof window.location = window.location) {
  18. // @ts-ignore
  19. return parseURLParams(url, true, 'search').jwt;
  20. }
  21. /**
  22. * Returns the user name after decoding the jwt.
  23. *
  24. * @param {IReduxState} state - The app state.
  25. * @returns {string}
  26. */
  27. export function getJwtName(state: IReduxState) {
  28. const { user } = state['features/base/jwt'];
  29. return user?.name;
  30. }
  31. /**
  32. * Check if the given JWT feature is enabled.
  33. *
  34. * @param {IReduxState} state - The app state.
  35. * @param {string} feature - The feature we want to check.
  36. * @param {boolean} ifNoToken - Default value if there is no token.
  37. * @param {boolean} ifNotInFeatures - Default value if features prop exists but does not have the {@code feature}.
  38. * @returns {bolean}
  39. */
  40. export function isJwtFeatureEnabled(state: IReduxState, feature: string, ifNoToken = false, ifNotInFeatures = false) {
  41. const { jwt } = state['features/base/jwt'];
  42. if (!jwt) {
  43. return ifNoToken;
  44. }
  45. const { features } = getLocalParticipant(state) || {};
  46. // If `features` is undefined, act as if everything is enabled.
  47. if (typeof features === 'undefined') {
  48. return true;
  49. }
  50. if (typeof features[feature as keyof typeof features] === 'undefined') {
  51. return ifNotInFeatures;
  52. }
  53. return String(features[feature as keyof typeof features]) === 'true';
  54. }
  55. /**
  56. * Checks whether a given timestamp is a valid UNIX timestamp in seconds.
  57. * We convert to milliseconds during the check since `Date` works with milliseconds for UNIX timestamp values.
  58. *
  59. * @param {any} timestamp - A UNIX timestamp in seconds as stored in the jwt.
  60. * @returns {boolean} - Whether the timestamp is indeed a valid UNIX timestamp or not.
  61. */
  62. function isValidUnixTimestamp(timestamp: number | string) {
  63. return typeof timestamp === 'number' && timestamp * 1000 === new Date(timestamp * 1000).getTime();
  64. }
  65. /**
  66. * Returns a list with all validation errors for the given jwt.
  67. *
  68. * @param {string} jwt - The jwt.
  69. * @returns {Array} - An array containing all jwt validation errors.
  70. */
  71. export function validateJwt(jwt: string) {
  72. const errors: Object[] = [];
  73. const currentTimestamp = new Date().getTime();
  74. try {
  75. const header = jwtDecode(jwt, { header: true });
  76. const payload = jwtDecode(jwt);
  77. if (!header) {
  78. errors.push({ key: JWT_VALIDATION_ERRORS.HEADER_NOT_FOUND });
  79. return errors;
  80. }
  81. if (!payload) {
  82. errors.push({ key: JWT_VALIDATION_ERRORS.PAYLOAD_NOT_FOUND });
  83. return errors;
  84. }
  85. const {
  86. aud,
  87. context,
  88. exp,
  89. iss,
  90. nbf,
  91. sub
  92. } = payload;
  93. // JaaS only
  94. if (sub?.startsWith('vpaas-magic-cookie')) {
  95. const { kid } = header;
  96. // if Key ID is missing, we return the error immediately without further validations.
  97. if (!kid) {
  98. errors.push({ key: JWT_VALIDATION_ERRORS.KID_NOT_FOUND });
  99. return errors;
  100. }
  101. if (kid.substring(0, kid.indexOf('/')) !== sub) {
  102. errors.push({ key: JWT_VALIDATION_ERRORS.KID_MISMATCH });
  103. }
  104. if (aud !== 'jitsi') {
  105. errors.push({ key: JWT_VALIDATION_ERRORS.AUD_INVALID });
  106. }
  107. if (iss !== 'chat') {
  108. errors.push({ key: JWT_VALIDATION_ERRORS.ISS_INVALID });
  109. }
  110. if (!context?.features) {
  111. errors.push({ key: JWT_VALIDATION_ERRORS.FEATURES_NOT_FOUND });
  112. }
  113. }
  114. if (!isValidUnixTimestamp(nbf)) {
  115. errors.push({ key: JWT_VALIDATION_ERRORS.NBF_INVALID });
  116. } else if (currentTimestamp < nbf * 1000) {
  117. errors.push({ key: JWT_VALIDATION_ERRORS.NBF_FUTURE });
  118. }
  119. if (!isValidUnixTimestamp(exp)) {
  120. errors.push({ key: JWT_VALIDATION_ERRORS.EXP_INVALID });
  121. } else if (currentTimestamp > exp * 1000) {
  122. errors.push({ key: JWT_VALIDATION_ERRORS.TOKEN_EXPIRED });
  123. }
  124. if (!context) {
  125. errors.push({ key: JWT_VALIDATION_ERRORS.CONTEXT_NOT_FOUND });
  126. } else if (context.features) {
  127. const { features } = context;
  128. const meetFeatures = Object.values(MEET_FEATURES);
  129. Object.keys(features).forEach(feature => {
  130. if (meetFeatures.includes(feature)) {
  131. const featureValue = features[feature];
  132. // cannot use truthy or falsy because we need the exact value and type check.
  133. if (
  134. featureValue !== true
  135. && featureValue !== false
  136. && featureValue !== 'true'
  137. && featureValue !== 'false'
  138. ) {
  139. errors.push({
  140. key: JWT_VALIDATION_ERRORS.FEATURE_VALUE_INVALID,
  141. args: { feature }
  142. });
  143. }
  144. } else {
  145. errors.push({
  146. key: JWT_VALIDATION_ERRORS.FEATURE_INVALID,
  147. args: { feature }
  148. });
  149. }
  150. });
  151. }
  152. } catch (e: any) {
  153. logger.error(`Unspecified JWT error${e?.message ? `: ${e.message}` : ''}`);
  154. }
  155. return errors;
  156. }
  157. /**
  158. * Extracts and returns the expiration date of jwt.
  159. *
  160. * @param {string|undefined} jwt - The jwt to check.
  161. * @returns {Date} The expiration date of the jwt.
  162. */
  163. export function getJwtExpirationDate(jwt: string | undefined) {
  164. if (!jwt) {
  165. return;
  166. }
  167. const payload = jwtDecode(jwt);
  168. if (payload) {
  169. const { exp } = payload;
  170. return new Date(exp * 1000);
  171. }
  172. }