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 7.7KB

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