您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

functions.js 5.1KB

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