kuzzle 2.19.12 → 2.20.0

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.
@@ -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,495 +19,393 @@
18
19
  * See the License for the specific language governing permissions and
19
20
  * limitations under the License.
20
21
  */
21
-
22
- "use strict";
23
-
24
- const _ = require("lodash");
25
- const jwt = require("jsonwebtoken");
26
- const ms = require("ms");
27
- const Bluebird = require("bluebird");
28
-
29
- const ApiKey = require("../../model/storage/apiKey");
30
- const { UnauthorizedError } = require("../../kerror/errors");
31
- const { Token } = require("../../model/security/token");
32
- const Repository = require("../shared/repository");
33
- const kerror = require("../../kerror");
34
- const debug = require("../../util/debug")("kuzzle:bootstrap:tokens");
35
- const { Mutex } = require("../../util/mutex");
36
-
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
38
+ var __importStar = (this && this.__importStar) || function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ var __importDefault = (this && this.__importDefault) || function (mod) {
46
+ return (mod && mod.__esModule) ? mod : { "default": mod };
47
+ };
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.TokenRepository = void 0;
50
+ const lodash_1 = __importDefault(require("lodash"));
51
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
52
+ const ms_1 = __importDefault(require("ms"));
53
+ const apiKey_1 = __importDefault(require("../../model/storage/apiKey"));
54
+ const errors_1 = require("../../kerror/errors");
55
+ const token_1 = require("../../model/security/token");
56
+ const kerror = __importStar(require("../../kerror"));
57
+ const debug_1 = __importDefault(require("../../util/debug"));
58
+ const mutex_1 = require("../../util/mutex");
59
+ const repository_1 = require("../shared/repository");
37
60
  const securityError = kerror.wrap("security", "token");
38
-
61
+ const debug = (0, debug_1.default)("kuzzle:bootstrap:tokens");
39
62
  const BOOTSTRAP_DONE_KEY = "token/bootstrap";
40
-
41
- /**
42
- * @class TokenRepository
43
- * @extends Repository
44
- * @param {Kuzzle} kuzzle
45
- * @param {object} [opts]
46
- */
47
- class TokenRepository extends Repository {
48
- constructor(opts = {}) {
49
- super();
50
- this.collection = "token";
51
- this.ObjectConstructor = Token;
52
- if (opts.ttl !== undefined) {
53
- this.ttl = opts.ttl;
63
+ class TokenRepository extends repository_1.Repository {
64
+ constructor(opts = {}) {
65
+ super();
66
+ this.collection = "token";
67
+ this.ObjectConstructor = token_1.Token;
68
+ if (opts.ttl !== undefined) {
69
+ this.ttl = opts.ttl;
70
+ }
71
+ this.tokenGracePeriod = Math.floor(global.kuzzle.config.security.jwt.gracePeriod);
72
+ this.anonymousToken = new token_1.Token({ userId: "-1" });
73
+ }
74
+ async init() {
75
+ await this.loadApiKeys();
76
+ /**
77
+ * Assign an existing token to a user. Stores the token in Kuzzle's cache.
78
+ * @param {String} hash - JWT
79
+ * @param {String} userId
80
+ * @param {Number} ttl - token expiration delay
81
+ * @returns {Token}
82
+ */
83
+ global.kuzzle.onAsk("core:security:token:assign", (hash, userId, ttl) => this.persistForUser(hash, userId, { singleUse: false, ttl }));
84
+ /**
85
+ * Creates and assigns a token to a user
86
+ * @param {User} user
87
+ * @param {Objects} opts (algorithm, expiresIn, bypassMaxTTL)
88
+ * @returns {Token}
89
+ */
90
+ global.kuzzle.onAsk("core:security:token:create", (user, opts) => this.generateToken(user, opts));
91
+ /**
92
+ * Deletes a token immediately
93
+ * @param {Token} token
94
+ */
95
+ global.kuzzle.onAsk("core:security:token:delete", (token) => this.expire(token));
96
+ /**
97
+ * Deletes all tokens assigned to the provided user ID.
98
+ * @param {String} userId
99
+ * @param {Objects} opts (keepApiKeys)
100
+ */
101
+ global.kuzzle.onAsk("core:security:token:deleteByKuid", (kuid, opts) => this.deleteByKuid(kuid, opts));
102
+ /**
103
+ * Gets a token
104
+ * @param {String} userId - user identifier
105
+ * @param {String} hash - JWT
106
+ * @returns {Token}
107
+ */
108
+ global.kuzzle.onAsk("core:security:token:get", (userId, hash) => this.loadForUser(userId, hash));
109
+ /**
110
+ * Refreshes an existing token for the given user.
111
+ * The old token will be kept for a (configurable) grace period, to allow
112
+ * pending requests to finish, but it will be marked as "refreshed" to
113
+ * prevent token duplication.
114
+ *
115
+ * @param {User} user
116
+ * @param {Token} token to refresh
117
+ * @param {String} expiresIn - new token expiration delay
118
+ * @returns {Token} new token
119
+ */
120
+ global.kuzzle.onAsk("core:security:token:refresh", (user, token, expiresIn) => this.refresh(user, token, expiresIn));
121
+ /**
122
+ * Verifies if the provided hash is valid, and returns the corresponding
123
+ * Token object
124
+ * @param {String} hash - JWT
125
+ * @returns {Token}
126
+ */
127
+ global.kuzzle.onAsk("core:security:token:verify", (hash) => this.verifyToken(hash));
54
128
  }
55
-
56
- this.tokenGracePeriod = Math.floor(
57
- global.kuzzle.config.security.jwt.gracePeriod
58
- );
59
- this.anonymousToken = new Token({ userId: "-1" });
60
- }
61
-
62
- async init() {
63
- await this._loadApiKeys();
64
-
65
- /**
66
- * Assign an existing token to a user. Stores the token in Kuzzle's cache.
67
- * @param {String} hash - JWT
68
- * @param {String} userId
69
- * @param {Number} ttl - token expiration delay
70
- * @returns {Token}
71
- */
72
- global.kuzzle.onAsk("core:security:token:assign", (hash, userId, ttl) =>
73
- this.persistForUser(hash, userId, ttl)
74
- );
75
-
76
- /**
77
- * Creates and assigns a token to a user
78
- * @param {User} user
79
- * @param {Objects} opts (algorithm, expiresIn, bypassMaxTTL)
80
- * @returns {Token}
81
- */
82
- global.kuzzle.onAsk("core:security:token:create", (user, opts) =>
83
- this.generateToken(user, opts)
84
- );
85
-
86
- /**
87
- * Deletes a token immediately
88
- * @param {Token} token
89
- */
90
- global.kuzzle.onAsk("core:security:token:delete", (token) =>
91
- this.expire(token)
92
- );
93
-
94
129
  /**
95
- * Deletes all tokens assigned to the provided user ID.
96
- * @param {String} userId
97
- * @param {Objects} opts (keepApiKeys)
130
+ * Expires the given token immediately
98
131
  */
99
- global.kuzzle.onAsk("core:security:token:deleteByKuid", (kuid, opts) =>
100
- this.deleteByKuid(kuid, opts)
101
- );
102
-
132
+ async expire(token) {
133
+ await super.expireFromCache(token);
134
+ await global.kuzzle.tokenManager.expire(token);
135
+ }
103
136
  /**
104
- * Gets a token
105
- * @param {String} userId - user identifier
106
- * @param {String} hash - JWT
107
- * @returns {Token}
137
+ * We allow a grace period before expiring the token to allow
138
+ * queued requests to execute, but we mark the token as "refreshed" to forbid
139
+ * any refreshes on that token, to prevent token bombing
140
+ *
141
+ * @param user
142
+ * @param requestToken
143
+ * @param expiresIn - new token expiration delay
108
144
  */
109
- global.kuzzle.onAsk("core:security:token:get", (userId, hash) =>
110
- this.loadForUser(userId, hash)
111
- );
112
-
145
+ async refresh(user, token, expiresIn) {
146
+ // do not refresh a token marked as already
147
+ if (token.refreshed) {
148
+ throw securityError.get("invalid");
149
+ }
150
+ // do not refresh API Keys or token that have an infinite TTL
151
+ if (token.type === "apiKey" || token.ttl < 0) {
152
+ throw securityError.get("refresh_forbidden", token.type === "apiKey" ? "API Key" : "Token with infinite TTL");
153
+ }
154
+ const refreshed = await this.generateToken(user, { expiresIn });
155
+ // Mark as "refreshed" only if generating the new token succeeds
156
+ token.refreshed = true;
157
+ await this.persistToCache(token, { ttl: this.tokenGracePeriod });
158
+ global.kuzzle.tokenManager.refresh(token, refreshed);
159
+ return refreshed;
160
+ }
113
161
  /**
114
- * Refreshes an existing token for the given user.
115
- * The old token will be kept for a (configurable) grace period, to allow
116
- * pending requests to finish, but it will be marked as "refreshed" to
117
- * prevent token duplication.
162
+ * @param user
163
+ * @param options - { algorithm, expiresIn, bypassMaxTTL (false), type (authToken) }
118
164
  *
119
- * @param {User} user
120
- * @param {Token} token to refresh
121
- * @param {String} expiresIn - new token expiration delay
122
- * @returns {Token} new token
165
+ * @returns {Promise.<Object>} { _id, jwt, userId, ttl, expiresAt }
123
166
  */
124
- global.kuzzle.onAsk(
125
- "core:security:token:refresh",
126
- (user, token, expiresIn) => this.refresh(user, token, expiresIn)
127
- );
128
-
167
+ async generateToken(user, { algorithm = global.kuzzle.config.security.jwt.algorithm, expiresIn = global.kuzzle.config.security.jwt.expiresIn, bypassMaxTTL = false, type = "authToken", singleUse = false, } = {}) {
168
+ if (!user || user._id === null) {
169
+ throw securityError.get("unknown_user");
170
+ }
171
+ const parsedExpiresIn = parseTimespan(expiresIn);
172
+ const maxTTL = type === "apiKey"
173
+ ? global.kuzzle.config.security.apiKey.maxTTL
174
+ : global.kuzzle.config.security.jwt.maxTTL;
175
+ if (!bypassMaxTTL &&
176
+ maxTTL > -1 &&
177
+ (parsedExpiresIn > maxTTL || parsedExpiresIn === -1)) {
178
+ throw securityError.get("ttl_exceeded");
179
+ }
180
+ const signOptions = { algorithm };
181
+ if (parsedExpiresIn === 0) {
182
+ throw kerror.get("api", "assert", "invalid_argument", "expiresIn", "a number of milliseconds, or a parsable timespan string");
183
+ }
184
+ // -1 mean infite duration, so we don't pass the expiresIn option to
185
+ // jwt.sign
186
+ else if (parsedExpiresIn !== -1) {
187
+ signOptions.expiresIn = Math.floor(parsedExpiresIn / 1000);
188
+ }
189
+ let encodedToken;
190
+ try {
191
+ encodedToken = jsonwebtoken_1.default.sign({ _id: user._id }, global.kuzzle.secret, signOptions);
192
+ }
193
+ catch (err) {
194
+ throw securityError.getFrom(err, "generation_failed", err.message);
195
+ }
196
+ if (type === "apiKey") {
197
+ encodedToken = token_1.Token.APIKEY_PREFIX + encodedToken;
198
+ }
199
+ else {
200
+ encodedToken = token_1.Token.AUTH_PREFIX + encodedToken;
201
+ }
202
+ return this.persistForUser(encodedToken, user._id, {
203
+ singleUse,
204
+ ttl: parsedExpiresIn,
205
+ });
206
+ }
129
207
  /**
130
- * Verifies if the provided hash is valid, and returns the corresponding
131
- * Token object
132
- * @param {String} hash - JWT
133
- * @returns {Token}
208
+ * Persists a token in the cache
209
+ *
210
+ * @param encodedToken - Encoded token
211
+ * @param userId - User ID
212
+ * @param ttl - TTL in ms (-1 for infinite duration)
134
213
  */
135
- global.kuzzle.onAsk("core:security:token:verify", (hash) =>
136
- this.verifyToken(hash)
137
- );
138
- }
139
-
140
- /**
141
- * Expires the given token immediately
142
- * @param {Token} requestToken
143
- * @returns {Promise}
144
- */
145
- async expire(token) {
146
- await super.expireFromCache(token);
147
- await global.kuzzle.tokenManager.expire(token);
148
- }
149
-
150
- /**
151
- * We allow a grace period before expiring the token to allow
152
- * queued requests to execute, but we mark the token as "refreshed" to forbid
153
- * any refreshes on that token, to prevent token bombing
154
- *
155
- * @param {User} user
156
- * @param {Token} requestToken
157
- * @param {String} expiresIn - new token expiration delay
158
- * @returns {Promise<Token>}
159
- */
160
- async refresh(user, token, expiresIn) {
161
- // do not refresh a token marked as already
162
- if (token.refreshed) {
163
- throw securityError.get("invalid");
164
- }
165
-
166
- // do not refresh API Keys or token that have an infinite TTL
167
- if (token.type === "apiKey" || token.ttl < 0) {
168
- throw securityError.get(
169
- "refresh_forbidden",
170
- token.type === "apiKey" ? "API Key" : "Token with infinite TTL"
171
- );
172
- }
173
-
174
- const refreshed = await this.generateToken(user, { expiresIn });
175
-
176
- // Mark as "refreshed" only if generating the new token succeeds
177
- token.refreshed = true;
178
- await this.persistToCache(token, { ttl: this.tokenGracePeriod });
179
-
180
- global.kuzzle.tokenManager.refresh(token, refreshed);
181
-
182
- return refreshed;
183
- }
184
-
185
- /**
186
- * @param {User} user
187
- * @param {Object} options - { algorithm, expiresIn, bypassMaxTTL (false), type (authToken) }
188
- *
189
- * @returns {Promise.<Object>} { _id, jwt, userId, ttl, expiresAt }
190
- */
191
- async generateToken(
192
- user,
193
- {
194
- algorithm = global.kuzzle.config.security.jwt.algorithm,
195
- expiresIn = global.kuzzle.config.security.jwt.expiresIn,
196
- bypassMaxTTL = false,
197
- type = "authToken",
198
- } = {}
199
- ) {
200
- if (!user || user._id === null) {
201
- throw securityError.get("unknown_user");
202
- }
203
-
204
- const parsedExpiresIn = parseTimespan(expiresIn);
205
-
206
- const maxTTL =
207
- type === "apiKey"
208
- ? global.kuzzle.config.security.apiKey.maxTTL
209
- : global.kuzzle.config.security.jwt.maxTTL;
210
-
211
- if (
212
- !bypassMaxTTL &&
213
- maxTTL > -1 &&
214
- (parsedExpiresIn > maxTTL || parsedExpiresIn === -1)
215
- ) {
216
- throw securityError.get("ttl_exceeded");
217
- }
218
-
219
- const signOptions = { algorithm };
220
-
221
- if (parsedExpiresIn === 0) {
222
- throw kerror.get(
223
- "api",
224
- "assert",
225
- "invalid_argument",
226
- "expiresIn",
227
- "a number of milliseconds, or a parsable timespan string"
228
- );
229
- }
230
- // -1 mean infite duration, so we don't pass the expiresIn option to
231
- // jwt.sign
232
- else if (parsedExpiresIn !== -1) {
233
- signOptions.expiresIn = Math.floor(parsedExpiresIn / 1000);
234
- }
235
-
236
- let encodedToken;
237
- try {
238
- encodedToken = jwt.sign(
239
- { _id: user._id },
240
- global.kuzzle.secret,
241
- signOptions
242
- );
243
- } catch (err) {
244
- throw securityError.getFrom(err, "generation_failed", err.message);
245
- }
246
-
247
- if (type === "apiKey") {
248
- encodedToken = Token.APIKEY_PREFIX + encodedToken;
249
- } else {
250
- encodedToken = Token.AUTH_PREFIX + encodedToken;
251
- }
252
-
253
- return this.persistForUser(encodedToken, user._id, parsedExpiresIn);
254
- }
255
-
256
- /**
257
- * Persists a token in the cache
258
- *
259
- * @param {String} encodedToken - Encoded token
260
- * @param {String} userId - User ID
261
- * @param {Number} ttl - TTL in ms (-1 for infinite duration)
262
- *
263
- * @returns {Promise}
264
- */
265
- async persistForUser(encodedToken, userId, ttl) {
266
- const redisTTL = ttl === -1 ? 0 : ttl;
267
- const expiresAt = ttl === -1 ? -1 : Date.now() + ttl;
268
- const token = new Token({
269
- _id: `${userId}#${encodedToken}`,
270
- expiresAt,
271
- jwt: encodedToken,
272
- ttl,
273
- userId,
274
- });
275
-
276
- try {
277
- return await this.persistToCache(token, { ttl: redisTTL });
278
- } catch (err) {
279
- throw kerror.getFrom(
280
- err,
281
- "services",
282
- "cache",
283
- "write_failed",
284
- err.message
285
- );
214
+ async persistForUser(encodedToken, userId, { ttl, singleUse, }) {
215
+ const redisTTL = ttl === -1 ? 0 : ttl;
216
+ const expiresAt = ttl === -1 ? -1 : Date.now() + ttl;
217
+ const token = new token_1.Token({
218
+ _id: `${userId}#${encodedToken}`,
219
+ expiresAt,
220
+ jwt: encodedToken,
221
+ singleUse,
222
+ ttl,
223
+ userId,
224
+ });
225
+ try {
226
+ return await this.persistToCache(token, { ttl: redisTTL });
227
+ }
228
+ catch (err) {
229
+ throw kerror.getFrom(err, "services", "cache", "write_failed", err.message);
230
+ }
286
231
  }
287
- }
288
-
289
- async verifyToken(token) {
290
- if (token === null) {
291
- return this.anonymousToken;
232
+ async verifyToken(token) {
233
+ if (token === null) {
234
+ return this.anonymousToken;
235
+ }
236
+ let decoded = null;
237
+ try {
238
+ decoded = jsonwebtoken_1.default.verify(this.removeTokenPrefix(token), global.kuzzle.secret);
239
+ // probably forged token => throw without providing any information
240
+ if (!decoded._id) {
241
+ throw new jsonwebtoken_1.default.JsonWebTokenError("Invalid token");
242
+ }
243
+ }
244
+ catch (err) {
245
+ if (err instanceof jsonwebtoken_1.default.TokenExpiredError) {
246
+ throw securityError.get("expired");
247
+ }
248
+ if (err instanceof jsonwebtoken_1.default.JsonWebTokenError) {
249
+ throw securityError.get("invalid");
250
+ }
251
+ throw securityError.getFrom(err, "verification_error", err.message);
252
+ }
253
+ let userToken;
254
+ try {
255
+ userToken = await this.loadForUser(decoded._id, token);
256
+ }
257
+ catch (err) {
258
+ if (err instanceof errors_1.UnauthorizedError) {
259
+ throw err;
260
+ }
261
+ throw securityError.getFrom(err, "verification_error", err.message);
262
+ }
263
+ if (userToken === null) {
264
+ throw securityError.get("invalid");
265
+ }
266
+ if (userToken.singleUse) {
267
+ await this.expire(userToken);
268
+ }
269
+ return userToken;
292
270
  }
293
-
294
- let decoded = null;
295
-
296
- try {
297
- decoded = jwt.verify(this.removeTokenPrefix(token), global.kuzzle.secret);
298
-
299
- // probably forged token => throw without providing any information
300
- if (!decoded._id) {
301
- throw new jwt.JsonWebTokenError("Invalid token");
302
- }
303
- } catch (err) {
304
- if (err instanceof jwt.TokenExpiredError) {
305
- throw securityError.get("expired");
306
- }
307
-
308
- if (err instanceof jwt.JsonWebTokenError) {
309
- throw securityError.get("invalid");
310
- }
311
-
312
- throw securityError.getFrom(err, "verification_error", err.message);
271
+ removeTokenPrefix(token) {
272
+ return token
273
+ .replace(token_1.Token.AUTH_PREFIX, "")
274
+ .replace(token_1.Token.APIKEY_PREFIX, "");
313
275
  }
314
-
315
- let userToken;
316
-
317
- try {
318
- userToken = await this.loadForUser(decoded._id, token);
319
- } catch (err) {
320
- if (err instanceof UnauthorizedError) {
321
- throw err;
322
- }
323
-
324
- throw securityError.getFrom(err, "verification_error", err.message);
276
+ loadForUser(userId, encodedToken) {
277
+ return this.load(`${userId}#${encodedToken}`);
325
278
  }
326
-
327
- if (userToken === null) {
328
- throw securityError.get("invalid");
279
+ async hydrate(userToken, data) {
280
+ if (!lodash_1.default.isObject(data)) {
281
+ return userToken;
282
+ }
283
+ lodash_1.default.assignIn(userToken, data);
284
+ if (!userToken.userId) {
285
+ return this.anonymousToken;
286
+ }
287
+ return userToken;
329
288
  }
330
-
331
- return userToken;
332
- }
333
-
334
- removeTokenPrefix(token) {
335
- return token
336
- .replace(Token.AUTH_PREFIX, "")
337
- .replace(Token.APIKEY_PREFIX, "");
338
- }
339
-
340
- loadForUser(userId, encodedToken) {
341
- return this.load(`${userId}#${encodedToken}`);
342
- }
343
-
344
- async hydrate(userToken, data) {
345
- if (!_.isObject(data)) {
346
- return userToken;
289
+ serializeToDatabase(token) {
290
+ return this.serializeToCache(token);
347
291
  }
348
-
349
- _.assignIn(userToken, data);
350
-
351
- if (!userToken.userId) {
352
- return this.anonymousToken;
292
+ /**
293
+ * Deletes tokens affiliated to the provided user identifier
294
+ */
295
+ async deleteByKuid(kuid, { keepApiKeys = true } = {}) {
296
+ const emptyKeyLength = super.getCacheKey("").length;
297
+ const userKey = super.getCacheKey(`${kuid}#*`);
298
+ const keys = await global.kuzzle.ask("core:cache:internal:searchKeys", userKey);
299
+ /*
300
+ Given the fact that user ids have no restriction, and that Redis pattern
301
+ matching is lacking the kind of features we need to safeguard against
302
+ matching unwanted keys, we need to prevent to accidentally remove tokens
303
+ from other users.
304
+ For instance, given these two users:
305
+ foo
306
+ foo#bar
307
+
308
+ If we remove foo, "foo#bar"'s JWT will match the
309
+ pattern "foo#*".
310
+
311
+ This test is possible because '#' is not a valid
312
+ JWT character
313
+ */
314
+ const ids = keys
315
+ .map((key) => {
316
+ return key.indexOf("#", userKey.length - 1) === -1
317
+ ? key.slice(emptyKeyLength)
318
+ : null;
319
+ })
320
+ .filter((key) => key !== null);
321
+ const expireToken = async (token) => {
322
+ const cacheToken = await this.load(token);
323
+ if (cacheToken !== null) {
324
+ if (keepApiKeys && cacheToken.type === "apiKey") {
325
+ return;
326
+ }
327
+ await this.expire(cacheToken);
328
+ }
329
+ };
330
+ const promises = [];
331
+ for (const id of ids) {
332
+ promises.push(expireToken(id));
333
+ }
334
+ await Promise.all(promises);
353
335
  }
354
-
355
- return userToken;
356
- }
357
-
358
- serializeToDatabase(token) {
359
- return this.serializeToCache(token);
360
- }
361
-
362
- /**
363
- * Deletes tokens affiliated to the provided user identifier
364
- *
365
- * @param {string} kuid
366
- * @returns {Promise}
367
- */
368
- async deleteByKuid(kuid, { keepApiKeys = true } = {}) {
369
- const emptyKeyLength = super.getCacheKey("").length;
370
- const userKey = super.getCacheKey(`${kuid}#*`);
371
-
372
- const keys = await global.kuzzle.ask(
373
- "core:cache:internal:searchKeys",
374
- userKey
375
- );
376
-
377
- /*
378
- Given the fact that user ids have no restriction, and that Redis pattern
379
- matching is lacking the kind of features we need to safeguard against
380
- matching unwanted keys, we need to prevent to accidentally remove tokens
381
- from other users.
382
- For instance, given these two users:
383
- foo
384
- foo#bar
385
-
386
- If we remove foo, "foo#bar"'s JWT will match the
387
- pattern "foo#*".
388
-
389
- This test is possible because '#' is not a valid
390
- JWT character
336
+ /**
337
+ * Loads authentication token from API key into Redis
391
338
  */
392
- const ids = keys
393
- .map((key) => {
394
- return key.indexOf("#", userKey.length - 1) === -1
395
- ? key.slice(emptyKeyLength)
396
- : null;
397
- })
398
- .filter((key) => key !== null);
399
-
400
- await Bluebird.map(ids, async (token) => {
401
- const cacheToken = await this.load(token);
402
-
403
- if (cacheToken !== null) {
404
- if (keepApiKeys && cacheToken.type === "apiKey") {
405
- return;
339
+ async loadApiKeys() {
340
+ const mutex = new mutex_1.Mutex("ApiKeysBootstrap", {
341
+ timeout: -1,
342
+ ttl: 30000,
343
+ });
344
+ await mutex.lock();
345
+ try {
346
+ const bootstrapped = await global.kuzzle.ask("core:cache:internal:get", BOOTSTRAP_DONE_KEY);
347
+ if (bootstrapped) {
348
+ debug("API keys already in cache. Skip.");
349
+ return;
350
+ }
351
+ debug("Loading API keys into Redis");
352
+ const promises = [];
353
+ await apiKey_1.default.batchExecute({ match_all: {} }, (documents) => {
354
+ for (const { _source } of documents) {
355
+ promises.push(this.persistForUser(_source.token, _source.userId, {
356
+ singleUse: false,
357
+ ttl: _source.ttl,
358
+ }));
359
+ }
360
+ });
361
+ await Promise.all(promises);
362
+ await global.kuzzle.ask("core:cache:internal:store", BOOTSTRAP_DONE_KEY, 1);
406
363
  }
407
-
408
- await this.expire(cacheToken);
409
- }
410
- });
411
- }
412
-
413
- /**
414
- * Loads authentication token from API key into Redis
415
- *
416
- * @returns {Promise}
417
- */
418
- async _loadApiKeys() {
419
- const mutex = new Mutex("ApiKeysBootstrap", {
420
- timeout: -1,
421
- ttl: 30000,
422
- });
423
-
424
- await mutex.lock();
425
-
426
- try {
427
- const bootstrapped = await global.kuzzle.ask(
428
- "core:cache:internal:get",
429
- BOOTSTRAP_DONE_KEY
430
- );
431
-
432
- if (bootstrapped) {
433
- debug("API keys already in cache. Skip.");
434
- return;
435
- }
436
-
437
- debug("Loading API keys into Redis");
438
-
439
- const promises = [];
440
-
441
- await ApiKey.batchExecute({ match_all: {} }, (documents) => {
442
- for (const { _source } of documents) {
443
- promises.push(
444
- this.persistForUser(_source.token, _source.userId, _source.ttl)
445
- );
364
+ finally {
365
+ await mutex.unlock();
446
366
  }
447
- });
448
-
449
- await Bluebird.all(promises);
450
-
451
- await global.kuzzle.ask(
452
- "core:cache:internal:store",
453
- BOOTSTRAP_DONE_KEY,
454
- 1
455
- );
456
- } finally {
457
- await mutex.unlock();
458
367
  }
459
- }
460
-
461
- /**
462
- * The repository main class refreshes automatically the TTL
463
- * of accessed entries, letting only unaccessed entries expire
464
- *
465
- * But tokens' TTL must remain the same than their expiration time,
466
- * refreshing a token entry has no meaning.
467
- *
468
- * So we need to override the TTL auto-refresh function to disable it
469
- */
470
- refreshCacheTTL() {
471
- // This comment is here to please Sonarqube. It requires a comment
472
- // explaining why a function is empty, but there is no sense
473
- // duplicating what has been just said in the JSDoc.
474
- // So, instead, here are the lyrics or Daft Punk's "Around the world":
475
- //
476
- // Around the world, around the world
477
- // Around the world, around the world
478
- // Around the world, around the world
479
- // Around the world, around the world
480
- // Around the world, around the world
481
- // [repeat 66 more times]
482
- // Around the world, around the world.
483
- }
368
+ /**
369
+ * The repository main class refreshes automatically the TTL
370
+ * of accessed entries, letting only unaccessed entries expire
371
+ *
372
+ * But tokens' TTL must remain the same than their expiration time,
373
+ * refreshing a token entry has no meaning.
374
+ *
375
+ * So we need to override the TTL auto-refresh function to disable it
376
+ */
377
+ refreshCacheTTL() {
378
+ // This comment is here to please Sonarqube. It requires a comment
379
+ // explaining why a function is empty, but there is no sense
380
+ // duplicating what has been just said in the JSDoc.
381
+ // So, instead, here are the lyrics or Daft Punk's "Around the world":
382
+ //
383
+ // Around the world, around the world
384
+ // Around the world, around the world
385
+ // Around the world, around the world
386
+ // Around the world, around the world
387
+ // Around the world, around the world
388
+ // [repeat 66 more times]
389
+ // Around the world, around the world.
390
+ }
484
391
  }
485
-
486
- module.exports = TokenRepository;
487
-
392
+ exports.TokenRepository = TokenRepository;
488
393
  /**
489
394
  * Returns a duration in milliseconds
490
395
  * - returns 0 if the duration is invalid
491
396
  * - -1 mean infinite
492
- *
493
- * @param {String|Number} time
494
- * @return {Number}
495
397
  */
496
398
  function parseTimespan(time) {
497
- if (typeof time === "string") {
498
- const milliseconds = ms(time);
499
-
500
- if (typeof milliseconds === "undefined") {
501
- return 0;
399
+ if (typeof time === "string") {
400
+ const milliseconds = (0, ms_1.default)(time);
401
+ if (typeof milliseconds === "undefined") {
402
+ return 0;
403
+ }
404
+ return milliseconds;
405
+ }
406
+ if (typeof time === "number") {
407
+ return time;
502
408
  }
503
-
504
- return milliseconds;
505
- }
506
-
507
- if (typeof time === "number") {
508
- return time;
509
- }
510
-
511
- return 0;
409
+ return 0;
512
410
  }
411
+ //# sourceMappingURL=tokenRepository.js.map