kuzzle 2.14.3 → 2.14.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/core/auth/tokenManager.d.ts +65 -0
- package/lib/core/auth/tokenManager.js +222 -263
- package/lib/core/realtime/hotelClerk.js +2 -2
- package/lib/core/security/tokenRepository.js +1 -1
- package/lib/kuzzle/kuzzle.js +1 -1
- package/lib/model/security/token.d.ts +29 -0
- package/lib/model/security/token.js +25 -24
- package/package-lock.json +15 -4
- package/package.json +2 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import '../../types/Global';
|
|
2
|
+
import { Token } from '../../model/security/token';
|
|
3
|
+
/**
|
|
4
|
+
* Maintains a list of valid tokens used by connected protocols.
|
|
5
|
+
*
|
|
6
|
+
* When a token expires, this module cleans up the corresponding connection's
|
|
7
|
+
* subscriptions if any, and notify the user
|
|
8
|
+
*/
|
|
9
|
+
export declare class TokenManager {
|
|
10
|
+
private tokens;
|
|
11
|
+
private anonymousUserId;
|
|
12
|
+
private tokensByConnection;
|
|
13
|
+
private timer;
|
|
14
|
+
constructor();
|
|
15
|
+
init(): Promise<void>;
|
|
16
|
+
runTimer(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Link a connection and a token.
|
|
19
|
+
* If one or another expires, associated subscriptions are cleaned up
|
|
20
|
+
* @param token
|
|
21
|
+
* @param connectionId
|
|
22
|
+
*/
|
|
23
|
+
link(token: Token, connectionId: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Unlink a connection from its associated token
|
|
26
|
+
*
|
|
27
|
+
* @param token
|
|
28
|
+
* @param connectionId
|
|
29
|
+
*/
|
|
30
|
+
unlink(token: Token, connectionId: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Called when a token expires before its time (e.g. following a
|
|
33
|
+
* auth:logout action)
|
|
34
|
+
* This method removes all maintained links and cleans up the
|
|
35
|
+
* hotel clerk
|
|
36
|
+
*
|
|
37
|
+
* @param token
|
|
38
|
+
*/
|
|
39
|
+
expire(token: Token): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Refresh an existing token with a new one
|
|
42
|
+
*
|
|
43
|
+
* @param oldToken
|
|
44
|
+
* @param newToken
|
|
45
|
+
*/
|
|
46
|
+
refresh(oldToken: Token, newToken: Token): void;
|
|
47
|
+
checkTokensValidity(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Gets the token matching user & connection if any
|
|
50
|
+
*/
|
|
51
|
+
getConnectedUserToken(userId: string, connectionId: string): Token | null;
|
|
52
|
+
/**
|
|
53
|
+
* Returns the kuid associated to a connection
|
|
54
|
+
*/
|
|
55
|
+
getKuidFromConnection(connectionId: string): string | null;
|
|
56
|
+
/**
|
|
57
|
+
* Adds token to internal collections
|
|
58
|
+
*
|
|
59
|
+
* @param token
|
|
60
|
+
* @param connectionId
|
|
61
|
+
*/
|
|
62
|
+
private add;
|
|
63
|
+
private removeConnectionLinkedToToken;
|
|
64
|
+
private deleteByIndex;
|
|
65
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/*
|
|
2
3
|
* Kuzzle, a backend software, self-hostable and ready to use
|
|
3
4
|
* to power modern apps
|
|
@@ -18,13 +19,29 @@
|
|
|
18
19
|
* See the License for the specific language governing permissions and
|
|
19
20
|
* limitations under the License.
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.TokenManager = void 0;
|
|
27
|
+
const sorted_array_1 = __importDefault(require("sorted-array"));
|
|
28
|
+
require("../../types/Global");
|
|
29
|
+
const token_1 = require("../../model/security/token");
|
|
30
|
+
/**
|
|
31
|
+
* Extends the Token model with a set of linked connection IDs.
|
|
32
|
+
*/
|
|
33
|
+
class ManagedToken extends token_1.Token {
|
|
34
|
+
constructor(token, connectionIds) {
|
|
35
|
+
super(token);
|
|
36
|
+
this.connectionIds = connectionIds;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Returns an unique string that identify a token and allows to sort them by expiration date.
|
|
40
|
+
*/
|
|
41
|
+
static indexFor(token) {
|
|
42
|
+
return `${token.expiresAt};${token._id}`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
28
45
|
/*
|
|
29
46
|
Maximum delay of a setTimeout call. If larger than this value,
|
|
30
47
|
it's replaced by 1 (see setTimeout documentation)
|
|
@@ -34,277 +51,219 @@ const Token = require('../../model/security/token');
|
|
|
34
51
|
Until this constant is exposed in NodeJS' API, we have to manually set it.
|
|
35
52
|
*/
|
|
36
53
|
const TIMEOUT_MAX = Math.pow(2, 31) - 1;
|
|
37
|
-
|
|
38
54
|
/**
|
|
39
|
-
* Maintains a list of valid tokens used by
|
|
40
|
-
* When a token expires, this module cleans up the corresponding connection's
|
|
41
|
-
* subscriptions, and notify the user
|
|
55
|
+
* Maintains a list of valid tokens used by connected protocols.
|
|
42
56
|
*
|
|
43
|
-
*
|
|
57
|
+
* When a token expires, this module cleans up the corresponding connection's
|
|
58
|
+
* subscriptions if any, and notify the user
|
|
44
59
|
*/
|
|
45
60
|
class TokenManager {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return a.idx < b.idx ? -1 : 1;
|
|
76
|
-
});
|
|
77
|
-
this.tokensByConnection = new Map();
|
|
78
|
-
|
|
79
|
-
this.timer = null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async init () {
|
|
83
|
-
const anonymous = await global.kuzzle.ask('core:security:user:anonymous:get');
|
|
84
|
-
this.anonymousUserId = anonymous._id;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
runTimer () {
|
|
88
|
-
if (this.tokens.array.length > 0) {
|
|
89
|
-
const delay = Math.min(this.tokens.array[0].expiresAt - Date.now(), TIMEOUT_MAX);
|
|
90
|
-
|
|
91
|
-
if (this.timer) {
|
|
92
|
-
clearTimeout(this.timer);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
this.timer = setTimeout(this.checkTokensValidity.bind(this), delay);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Link a connection and a token.
|
|
101
|
-
* If one or another expires, associated subscriptions are cleaned up
|
|
102
|
-
* @param {Token} token
|
|
103
|
-
* @param {String} connectionId
|
|
104
|
-
*/
|
|
105
|
-
link (token, connectionId) {
|
|
106
|
-
// Embedded SDK does not use tokens
|
|
107
|
-
if (! token || token._id === this.anonymousUserId) {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const idx = getTokenIndex(token);
|
|
112
|
-
const currentToken = this.tokensByConnection.get(connectionId);
|
|
113
|
-
|
|
114
|
-
if (currentToken) {
|
|
115
|
-
if (currentToken._id === token._id) {
|
|
116
|
-
return; // Connection and Token already linked
|
|
117
|
-
}
|
|
118
|
-
this._removeConnectionLinkedToToken(connectionId, currentToken);
|
|
61
|
+
constructor() {
|
|
62
|
+
this.anonymousUserId = null;
|
|
63
|
+
this.tokensByConnection = new Map();
|
|
64
|
+
this.timer = null;
|
|
65
|
+
/*
|
|
66
|
+
* Tokens are sorted by their expiration date
|
|
67
|
+
*
|
|
68
|
+
* The token id is added to the key to handle
|
|
69
|
+
* equality between different tokens sharing
|
|
70
|
+
* the exact same expiration date
|
|
71
|
+
*
|
|
72
|
+
* We should always put infinite duration token at the end of the array
|
|
73
|
+
* because the loop that checks if a token is expired is always verifiying the first element of the array.
|
|
74
|
+
* Since an infinite token cannot be expired, if there is an infinite duration token at the first element
|
|
75
|
+
* the loop will verify the same token over and over again because the token cannot be removed from the queue
|
|
76
|
+
* and the other tokens will never be verifier.
|
|
77
|
+
*/
|
|
78
|
+
this.tokens = new sorted_array_1.default([], (a, b) => {
|
|
79
|
+
if (a.idx === b.idx) {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
if (a.idx && a.idx[0] === '-') {
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
if (b.idx && b.idx[0] === '-') {
|
|
86
|
+
return -1;
|
|
87
|
+
}
|
|
88
|
+
return a.idx < b.idx ? -1 : 1;
|
|
89
|
+
});
|
|
119
90
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
this._add(token, [connectionId]);
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
const data = this.tokens.array[pos];
|
|
127
|
-
data.connectionIds.add(connectionId);
|
|
128
|
-
this.tokensByConnection.set(connectionId, data);
|
|
91
|
+
async init() {
|
|
92
|
+
const anonymous = await global.kuzzle.ask('core:security:user:anonymous:get');
|
|
93
|
+
this.anonymousUserId = anonymous._id;
|
|
129
94
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
unlink (token, connectionId) {
|
|
139
|
-
// Embedded SDK does not use tokens
|
|
140
|
-
if (! token || token._id === this.anonymousUserId) {
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const idx = getTokenIndex(token);
|
|
145
|
-
const pos = this.tokens.search({ idx });
|
|
146
|
-
|
|
147
|
-
if (pos === -1) {
|
|
148
|
-
return;
|
|
95
|
+
runTimer() {
|
|
96
|
+
if (this.tokens.array.length > 0) {
|
|
97
|
+
const delay = Math.min(this.tokens.array[0].expiresAt - Date.now(), TIMEOUT_MAX);
|
|
98
|
+
if (this.timer) {
|
|
99
|
+
clearTimeout(this.timer);
|
|
100
|
+
}
|
|
101
|
+
this.timer = setTimeout(this.checkTokensValidity.bind(this), delay);
|
|
102
|
+
}
|
|
149
103
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Link a connection and a token.
|
|
106
|
+
* If one or another expires, associated subscriptions are cleaned up
|
|
107
|
+
* @param token
|
|
108
|
+
* @param connectionId
|
|
109
|
+
*/
|
|
110
|
+
link(token, connectionId) {
|
|
111
|
+
// Embedded SDK does not use tokens
|
|
112
|
+
if (!token || token._id === this.anonymousUserId) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const idx = ManagedToken.indexFor(token);
|
|
116
|
+
const currentToken = this.tokensByConnection.get(connectionId);
|
|
117
|
+
if (currentToken) {
|
|
118
|
+
if (currentToken._id === token._id) {
|
|
119
|
+
return; // Connection and Token already linked
|
|
120
|
+
}
|
|
121
|
+
this.removeConnectionLinkedToToken(connectionId, currentToken);
|
|
122
|
+
}
|
|
123
|
+
const pos = this.tokens.search({ idx });
|
|
124
|
+
if (pos === -1) {
|
|
125
|
+
this.add(token, new Set([connectionId]));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
const managedToken = this.tokens.array[pos];
|
|
129
|
+
managedToken.connectionIds.add(connectionId);
|
|
130
|
+
this.tokensByConnection.set(connectionId, managedToken);
|
|
131
|
+
}
|
|
156
132
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Unlink a connection from its associated token
|
|
135
|
+
*
|
|
136
|
+
* @param token
|
|
137
|
+
* @param connectionId
|
|
138
|
+
*/
|
|
139
|
+
unlink(token, connectionId) {
|
|
140
|
+
// Embedded SDK does not use tokens
|
|
141
|
+
if (!token || token._id === this.anonymousUserId) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const idx = ManagedToken.indexFor(token);
|
|
145
|
+
const pos = this.tokens.search({ idx });
|
|
146
|
+
if (pos === -1) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.removeConnectionLinkedToToken(connectionId, this.tokens.array[pos]);
|
|
150
|
+
const currentToken = this.tokensByConnection.get(connectionId);
|
|
151
|
+
if (currentToken && currentToken._id === token._id) {
|
|
152
|
+
this.tokensByConnection.delete(connectionId);
|
|
153
|
+
}
|
|
170
154
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Called when a token expires before its time (e.g. following a
|
|
157
|
+
* auth:logout action)
|
|
158
|
+
* This method removes all maintained links and cleans up the
|
|
159
|
+
* hotel clerk
|
|
160
|
+
*
|
|
161
|
+
* @param token
|
|
162
|
+
*/
|
|
163
|
+
async expire(token) {
|
|
164
|
+
if (token._id === this.anonymousUserId) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const idx = ManagedToken.indexFor(token);
|
|
168
|
+
const searchResult = this.tokens.search({ idx });
|
|
169
|
+
if (searchResult > -1) {
|
|
170
|
+
const managedToken = this.tokens.array[searchResult];
|
|
171
|
+
for (const connectionId of managedToken.connectionIds) {
|
|
172
|
+
this.tokensByConnection.delete(connectionId);
|
|
173
|
+
await global.kuzzle.ask('core:realtime:user:remove', connectionId);
|
|
174
|
+
}
|
|
175
|
+
this.deleteByIndex(searchResult);
|
|
176
|
+
}
|
|
184
177
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
this._add(newToken, connectionIds);
|
|
209
|
-
|
|
210
|
-
// Delete old token
|
|
211
|
-
this._deleteByIndex(pos);
|
|
178
|
+
/**
|
|
179
|
+
* Refresh an existing token with a new one
|
|
180
|
+
*
|
|
181
|
+
* @param oldToken
|
|
182
|
+
* @param newToken
|
|
183
|
+
*/
|
|
184
|
+
refresh(oldToken, newToken) {
|
|
185
|
+
const oldIndex = ManagedToken.indexFor(oldToken);
|
|
186
|
+
const pos = this.tokens.search({ idx: oldIndex });
|
|
187
|
+
// If the old token has been created and then refreshed within the same
|
|
188
|
+
// second, then it has the exact same characteristics than the new one.
|
|
189
|
+
// This should never happen, though, especially if we add at least 1
|
|
190
|
+
// real-time subscribe in the middle of the login+refresh sequence (all
|
|
191
|
+
// within the same second) but, oh, well... it costs nothing to fix a
|
|
192
|
+
// potentially very, very, very hard to debug random problem before it
|
|
193
|
+
// occurs
|
|
194
|
+
if (pos > -1 && oldToken._id !== newToken._id) {
|
|
195
|
+
const connectionIds = this.tokens.array[pos].connectionIds;
|
|
196
|
+
this.add(newToken, connectionIds);
|
|
197
|
+
// Delete old token
|
|
198
|
+
this.deleteByIndex(pos);
|
|
199
|
+
}
|
|
212
200
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return;
|
|
201
|
+
async checkTokensValidity() {
|
|
202
|
+
const arr = this.tokens.array;
|
|
203
|
+
// API key can never expire (-1)
|
|
204
|
+
if (arr.length > 0 && (arr[0].expiresAt > 0 && arr[0].expiresAt < Date.now())) {
|
|
205
|
+
const connectionIds = arr[0].connectionIds;
|
|
206
|
+
arr.shift();
|
|
207
|
+
for (const connectionId of connectionIds) {
|
|
208
|
+
await global.kuzzle.ask('core:realtime:tokenExpired:notify', connectionId);
|
|
209
|
+
}
|
|
210
|
+
setImmediate(() => this.checkTokensValidity());
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (arr.length > 0) {
|
|
214
|
+
this.runTimer();
|
|
215
|
+
}
|
|
229
216
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Gets the token matching user & connection if any
|
|
219
|
+
*/
|
|
220
|
+
getConnectedUserToken(userId, connectionId) {
|
|
221
|
+
const token = this.tokensByConnection.get(connectionId);
|
|
222
|
+
return token && token.userId === userId ? token : null;
|
|
233
223
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
getConnectedUserToken(userId, connectionId) {
|
|
244
|
-
const data = this.tokensByConnection.get(connectionId);
|
|
245
|
-
|
|
246
|
-
return data && data.userId === userId
|
|
247
|
-
? new Token({...data, connectionId})
|
|
248
|
-
: null;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Returns the token associated to a connection
|
|
253
|
-
*/
|
|
254
|
-
getToken (connectionId) {
|
|
255
|
-
return this.tokensByConnection.get(connectionId);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Adds token to internal collections
|
|
260
|
-
*
|
|
261
|
-
* @param {Token} token
|
|
262
|
-
* @param {string} connectionId
|
|
263
|
-
* @private
|
|
264
|
-
*/
|
|
265
|
-
_add(token, connectionIds) {
|
|
266
|
-
const data = Object.assign({}, token, {
|
|
267
|
-
connectionIds: new Set(connectionIds),
|
|
268
|
-
idx: getTokenIndex(token)
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
for (const connectionId of connectionIds) {
|
|
272
|
-
this.tokensByConnection.set(connectionId, data);
|
|
224
|
+
/**
|
|
225
|
+
* Returns the kuid associated to a connection
|
|
226
|
+
*/
|
|
227
|
+
getKuidFromConnection(connectionId) {
|
|
228
|
+
const token = this.tokensByConnection.get(connectionId);
|
|
229
|
+
if (!token) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
return token.userId;
|
|
273
233
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Adds token to internal collections
|
|
236
|
+
*
|
|
237
|
+
* @param token
|
|
238
|
+
* @param connectionId
|
|
239
|
+
*/
|
|
240
|
+
add(token, connectionIds) {
|
|
241
|
+
const orderedToken = Object.assign({}, token, {
|
|
242
|
+
connectionIds: new Set(connectionIds),
|
|
243
|
+
idx: ManagedToken.indexFor(token)
|
|
244
|
+
});
|
|
245
|
+
for (const connectionId of connectionIds) {
|
|
246
|
+
this.tokensByConnection.set(connectionId, orderedToken);
|
|
247
|
+
}
|
|
248
|
+
this.tokens.insert(orderedToken);
|
|
249
|
+
if (this.tokens.array[0].idx === orderedToken.idx) {
|
|
250
|
+
this.runTimer();
|
|
251
|
+
}
|
|
278
252
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const pos = this.tokens.search({ idx: token.idx });
|
|
286
|
-
this._deleteByIndex(pos);
|
|
253
|
+
removeConnectionLinkedToToken(connectionId, managedToken) {
|
|
254
|
+
managedToken.connectionIds.delete(connectionId);
|
|
255
|
+
if (managedToken.connectionIds.size === 0) {
|
|
256
|
+
const pos = this.tokens.search({ idx: managedToken.idx });
|
|
257
|
+
this.deleteByIndex(pos);
|
|
258
|
+
}
|
|
287
259
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
return;
|
|
260
|
+
deleteByIndex(index) {
|
|
261
|
+
const orderedToken = this.tokens.array[index];
|
|
262
|
+
if (!orderedToken) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
this.tokens.array.splice(index, 1);
|
|
295
266
|
}
|
|
296
|
-
|
|
297
|
-
this.tokens.array.splice(index, 1);
|
|
298
|
-
}
|
|
299
267
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
* Calculate a simple sortable token index
|
|
303
|
-
* @param token
|
|
304
|
-
* @returns {string}
|
|
305
|
-
*/
|
|
306
|
-
function getTokenIndex(token) {
|
|
307
|
-
return `${token.expiresAt};${token._id}`;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
module.exports = TokenManager;
|
|
268
|
+
exports.TokenManager = TokenManager;
|
|
269
|
+
//# sourceMappingURL=tokenManager.js.map
|
|
@@ -551,7 +551,7 @@ class HotelClerk {
|
|
|
551
551
|
});
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
-
const
|
|
554
|
+
const kuid = global.kuzzle.tokenManager.getKuidFromConnection(connectionId);
|
|
555
555
|
|
|
556
556
|
const subscription = new Subscription(
|
|
557
557
|
room.index,
|
|
@@ -559,7 +559,7 @@ class HotelClerk {
|
|
|
559
559
|
undefined,
|
|
560
560
|
roomId,
|
|
561
561
|
connectionId,
|
|
562
|
-
{ _id:
|
|
562
|
+
{ _id: kuid });
|
|
563
563
|
|
|
564
564
|
global.kuzzle.emit('core:realtime:user:unsubscribe:after', {
|
|
565
565
|
/* @deprecated */
|
|
@@ -28,7 +28,7 @@ const Bluebird = require('bluebird');
|
|
|
28
28
|
|
|
29
29
|
const ApiKey = require('../../model/storage/apiKey');
|
|
30
30
|
const { UnauthorizedError } = require('../../kerror/errors');
|
|
31
|
-
const Token = require('../../model/security/token');
|
|
31
|
+
const { Token } = require('../../model/security/token');
|
|
32
32
|
const Repository = require('../shared/repository');
|
|
33
33
|
const kerror = require('../../kerror');
|
|
34
34
|
const debug = require('../../util/debug')('kuzzle:bootstrap:tokens');
|
package/lib/kuzzle/kuzzle.js
CHANGED
|
@@ -38,7 +38,7 @@ const PassportWrapper = require('../core/auth/passportWrapper');
|
|
|
38
38
|
const PluginsManager = require('../core/plugin/pluginsManager');
|
|
39
39
|
const Router = require('../core/network/router');
|
|
40
40
|
const Statistics = require('../core/statistics');
|
|
41
|
-
const TokenManager = require('../core/auth/tokenManager');
|
|
41
|
+
const { TokenManager } = require('../core/auth/tokenManager');
|
|
42
42
|
const Validation = require('../core/validation');
|
|
43
43
|
const Logger = require('./log');
|
|
44
44
|
const vault = require('./vault');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface TokenContent {
|
|
2
|
+
/**
|
|
3
|
+
* Token ID (also Redis key)
|
|
4
|
+
*
|
|
5
|
+
* @example `${userId}#${jwt}`
|
|
6
|
+
*/
|
|
7
|
+
_id?: string;
|
|
8
|
+
expiresAt?: number;
|
|
9
|
+
ttl?: number;
|
|
10
|
+
userId?: string;
|
|
11
|
+
connectionIds?: string[];
|
|
12
|
+
jwt?: string;
|
|
13
|
+
refreshed?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Represents a token that identify an user.
|
|
17
|
+
*/
|
|
18
|
+
export declare class Token implements TokenContent {
|
|
19
|
+
_id: string;
|
|
20
|
+
expiresAt: number;
|
|
21
|
+
ttl: number;
|
|
22
|
+
userId: string;
|
|
23
|
+
jwt: string;
|
|
24
|
+
refreshed: boolean;
|
|
25
|
+
constructor(data?: TokenContent);
|
|
26
|
+
get type(): 'apiKey' | 'authToken';
|
|
27
|
+
static get AUTH_PREFIX(): string;
|
|
28
|
+
static get APIKEY_PREFIX(): string;
|
|
29
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/*
|
|
2
3
|
* Kuzzle, a backend software, self-hostable and ready to use
|
|
3
4
|
* to power modern apps
|
|
@@ -18,32 +19,32 @@
|
|
|
18
19
|
* See the License for the specific language governing permissions and
|
|
19
20
|
* limitations under the License.
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.Token = void 0;
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Represents a token that identify an user.
|
|
26
26
|
*/
|
|
27
27
|
class Token {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
constructor(data = {}) {
|
|
29
|
+
this._id = data._id || null;
|
|
30
|
+
this.expiresAt = data.expiresAt || null;
|
|
31
|
+
this.ttl = data.ttl || null;
|
|
32
|
+
this.userId = data.userId || null;
|
|
33
|
+
this.jwt = data.jwt || null;
|
|
34
|
+
this.refreshed = Boolean(data.refreshed);
|
|
35
|
+
}
|
|
36
|
+
get type() {
|
|
37
|
+
if (this.jwt && this.jwt.startsWith(Token.APIKEY_PREFIX)) {
|
|
38
|
+
return 'apiKey';
|
|
39
|
+
}
|
|
40
|
+
return 'authToken';
|
|
41
|
+
}
|
|
42
|
+
static get AUTH_PREFIX() {
|
|
43
|
+
return 'kauth-';
|
|
44
|
+
}
|
|
45
|
+
static get APIKEY_PREFIX() {
|
|
46
|
+
return 'kapikey-';
|
|
41
47
|
}
|
|
42
|
-
return 'authToken';
|
|
43
|
-
}
|
|
44
48
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Token.APIKEY_PREFIX = 'kapikey-';
|
|
48
|
-
|
|
49
|
-
module.exports = Token;
|
|
49
|
+
exports.Token = Token;
|
|
50
|
+
//# sourceMappingURL=token.js.map
|
package/package-lock.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kuzzle",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.5",
|
|
4
4
|
"lockfileVersion": 1,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"dependencies": {
|
|
@@ -400,9 +400,9 @@
|
|
|
400
400
|
}
|
|
401
401
|
},
|
|
402
402
|
"@elastic/elasticsearch": {
|
|
403
|
-
"version": "7.
|
|
404
|
-
"resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-7.
|
|
405
|
-
"integrity": "sha512-
|
|
403
|
+
"version": "7.13.0",
|
|
404
|
+
"resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-7.13.0.tgz",
|
|
405
|
+
"integrity": "sha512-WgwLWo2p9P2tdqzBGX9fHeG8p5IOTXprXNTECQG2mJ7z9n93N5AFBJpEw4d35tWWeCWi9jI13A2wzQZH7XZ/xw==",
|
|
406
406
|
"requires": {
|
|
407
407
|
"debug": "^4.3.1",
|
|
408
408
|
"hpagent": "^0.1.1",
|
|
@@ -7593,6 +7593,17 @@
|
|
|
7593
7593
|
"winston-transport": "^4.4.0"
|
|
7594
7594
|
},
|
|
7595
7595
|
"dependencies": {
|
|
7596
|
+
"@elastic/elasticsearch": {
|
|
7597
|
+
"version": "7.15.0",
|
|
7598
|
+
"resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-7.15.0.tgz",
|
|
7599
|
+
"integrity": "sha512-FUKvjV2IKtIiWsvBy7D+wLbSEONsmNR15RRN7P/Sb30g4ObZRHH2qGOP5PPnzxdntEkzZ8HzY7nKKXFS+3Du1g==",
|
|
7600
|
+
"requires": {
|
|
7601
|
+
"debug": "^4.3.1",
|
|
7602
|
+
"hpagent": "^0.1.1",
|
|
7603
|
+
"ms": "^2.1.3",
|
|
7604
|
+
"secure-json-parse": "^2.4.0"
|
|
7605
|
+
}
|
|
7606
|
+
},
|
|
7596
7607
|
"retry": {
|
|
7597
7608
|
"version": "0.13.1",
|
|
7598
7609
|
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kuzzle",
|
|
3
3
|
"author": "The Kuzzle Team <support@kuzzle.io>",
|
|
4
|
-
"version": "2.14.
|
|
4
|
+
"version": "2.14.5",
|
|
5
5
|
"description": "Kuzzle is an open-source solution that handles all the data management through a secured API, with a large choice of protocols.",
|
|
6
6
|
"bin": {
|
|
7
7
|
"kuzzle": "bin/start-kuzzle-server"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"lib": "lib"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@elastic/elasticsearch": "
|
|
42
|
+
"@elastic/elasticsearch": "7.13.0",
|
|
43
43
|
"aedes": "^0.46.1",
|
|
44
44
|
"bluebird": "^3.7.2",
|
|
45
45
|
"cli-color": "^2.0.0",
|