|
|
@@ -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
|
+}
|