Quellcode durchsuchen

feat(jwt) log jwt validation errors

master
hmuresan vor 4 Jahren
Ursprung
Commit
3a8bd852b2

+ 23
- 0
react/features/base/conference/middleware.any.js Datei anzeigen

@@ -11,6 +11,7 @@ import { reloadNow } from '../../app/actions';
11 11
 import { openDisplayNamePrompt } from '../../display-name';
12 12
 import { showErrorNotification } from '../../notifications';
13 13
 import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, connectionDisconnected } from '../connection';
14
+import { validateJwt } from '../jwt';
14 15
 import { JitsiConferenceErrors } from '../lib-jitsi-meet';
15 16
 import { MEDIA_TYPE } from '../media';
16 17
 import {
@@ -247,6 +248,26 @@ function _connectionEstablished({ dispatch }, next, action) {
247 248
     return result;
248 249
 }
249 250
 
251
+/**
252
+ * Logs jwt validation errors from xmpp and from the client-side validator.
253
+ *
254
+ * @param {string} message -The error message from xmpp.
255
+ * @param {Object} state - The redux state.
256
+ * @returns {void}
257
+ */
258
+function _logJwtErrors(message, state) {
259
+    const { jwt } = state['features/base/jwt'];
260
+
261
+    if (!jwt) {
262
+        return;
263
+    }
264
+
265
+    const errorKeys = validateJwt(jwt);
266
+
267
+    message && logger.error(`JWT error: ${message}`);
268
+    errorKeys.length && logger.error('JWT parsing error:', errorKeys);
269
+}
270
+
250 271
 /**
251 272
  * Notifies the feature base/conference that the action
252 273
  * {@code CONNECTION_FAILED} is being dispatched within a specific redux
@@ -262,6 +283,8 @@ function _connectionEstablished({ dispatch }, next, action) {
262 283
  * @returns {Object} The value returned by {@code next(action)}.
263 284
  */
264 285
 function _connectionFailed({ dispatch, getState }, next, action) {
286
+    _logJwtErrors(action.error.message, getState());
287
+
265 288
     const result = next(action);
266 289
 
267 290
     if (typeof beforeUnloadHandler !== 'undefined') {

+ 15
- 0
react/features/base/jwt/constants.js Datei anzeigen

@@ -0,0 +1,15 @@
1
+/**
2
+ * The list of supported meeting features to enable/disable through jwt.
3
+ */
4
+export const MEET_FEATURES = [
5
+    'branding',
6
+    'calendar',
7
+    'callstats',
8
+    'livestreaming',
9
+    'lobby',
10
+    'moderation',
11
+    'outbound-call',
12
+    'recording',
13
+    'room',
14
+    'transcription'
15
+];

+ 109
- 0
react/features/base/jwt/functions.js Datei anzeigen

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

+ 1
- 0
react/features/base/jwt/index.js Datei anzeigen

@@ -1,3 +1,4 @@
1 1
 export * from './actions';
2 2
 export * from './actionTypes';
3 3
 export * from './functions';
4
+export * from './constants';

Laden…
Abbrechen
Speichern