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

functions.ts 7.9KB

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