@appweaver/core 1.5.0 → 1.6.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.
- package/app/load-providers.js +9 -4
- package/cache/cache-service.d.ts +1 -1
- package/cache/cache-service.js +4 -5
- package/cache/cache.js +102 -13
- package/context/dependency-injection.d.ts +2 -2
- package/context/dependency-injection.js +3 -2
- package/health/health-service.js +3 -1
- package/memory/redis.d.ts +1 -1
- package/memory/redis.js +44 -5
- package/package.json +1 -1
- package/prisma/client/internal/class.js +3 -3
- package/prisma/client/internal/prismaNamespace.d.ts +1 -2
- package/prisma/client/internal/prismaNamespace.js +1 -2
- package/prisma/client/internal/prismaNamespaceBrowser.d.ts +1 -2
- package/prisma/client/internal/prismaNamespaceBrowser.js +1 -2
- package/prisma/client/models/OneTimeToken.d.ts +25 -53
- package/queue/bull-queue.js +66 -6
- package/security/auth-service.d.ts +1 -0
- package/security/auth-service.js +43 -13
- package/security/resources/one-time-token/model.js +10 -6
- package/security/store/database-security-store.js +20 -17
- package/security/store/redis-security-store.js +6 -1
- package/server/create-server.js +13 -7
- package/types/generated.d.ts +8 -11
|
@@ -23,7 +23,6 @@ export type OneTimeTokenMinAggregateOutputType = {
|
|
|
23
23
|
tokenHash: string | null;
|
|
24
24
|
purpose: string | null;
|
|
25
25
|
expiresAt: Date | null;
|
|
26
|
-
updatedAt: Date | null;
|
|
27
26
|
createdAt: Date | null;
|
|
28
27
|
};
|
|
29
28
|
export type OneTimeTokenMaxAggregateOutputType = {
|
|
@@ -31,16 +30,14 @@ export type OneTimeTokenMaxAggregateOutputType = {
|
|
|
31
30
|
tokenHash: string | null;
|
|
32
31
|
purpose: string | null;
|
|
33
32
|
expiresAt: Date | null;
|
|
34
|
-
updatedAt: Date | null;
|
|
35
33
|
createdAt: Date | null;
|
|
36
34
|
};
|
|
37
35
|
export type OneTimeTokenCountAggregateOutputType = {
|
|
38
36
|
id: number;
|
|
39
37
|
tokenHash: number;
|
|
40
38
|
purpose: number;
|
|
41
|
-
expiresAt: number;
|
|
42
39
|
data: number;
|
|
43
|
-
|
|
40
|
+
expiresAt: number;
|
|
44
41
|
createdAt: number;
|
|
45
42
|
_all: number;
|
|
46
43
|
};
|
|
@@ -55,7 +52,6 @@ export type OneTimeTokenMinAggregateInputType = {
|
|
|
55
52
|
tokenHash?: true;
|
|
56
53
|
purpose?: true;
|
|
57
54
|
expiresAt?: true;
|
|
58
|
-
updatedAt?: true;
|
|
59
55
|
createdAt?: true;
|
|
60
56
|
};
|
|
61
57
|
export type OneTimeTokenMaxAggregateInputType = {
|
|
@@ -63,16 +59,14 @@ export type OneTimeTokenMaxAggregateInputType = {
|
|
|
63
59
|
tokenHash?: true;
|
|
64
60
|
purpose?: true;
|
|
65
61
|
expiresAt?: true;
|
|
66
|
-
updatedAt?: true;
|
|
67
62
|
createdAt?: true;
|
|
68
63
|
};
|
|
69
64
|
export type OneTimeTokenCountAggregateInputType = {
|
|
70
65
|
id?: true;
|
|
71
66
|
tokenHash?: true;
|
|
72
67
|
purpose?: true;
|
|
73
|
-
expiresAt?: true;
|
|
74
68
|
data?: true;
|
|
75
|
-
|
|
69
|
+
expiresAt?: true;
|
|
76
70
|
createdAt?: true;
|
|
77
71
|
_all?: true;
|
|
78
72
|
};
|
|
@@ -156,9 +150,8 @@ export type OneTimeTokenGroupByOutputType = {
|
|
|
156
150
|
id: number;
|
|
157
151
|
tokenHash: string;
|
|
158
152
|
purpose: string;
|
|
159
|
-
expiresAt: Date;
|
|
160
153
|
data: runtime.JsonValue;
|
|
161
|
-
|
|
154
|
+
expiresAt: Date;
|
|
162
155
|
createdAt: Date;
|
|
163
156
|
_count: OneTimeTokenCountAggregateOutputType | null;
|
|
164
157
|
_avg: OneTimeTokenAvgAggregateOutputType | null;
|
|
@@ -176,39 +169,35 @@ export type OneTimeTokenWhereInput = {
|
|
|
176
169
|
id?: Prisma.IntFilter<"OneTimeToken"> | number;
|
|
177
170
|
tokenHash?: Prisma.StringFilter<"OneTimeToken"> | string;
|
|
178
171
|
purpose?: Prisma.StringFilter<"OneTimeToken"> | string;
|
|
179
|
-
expiresAt?: Prisma.DateTimeFilter<"OneTimeToken"> | Date | string;
|
|
180
172
|
data?: Prisma.JsonFilter<"OneTimeToken">;
|
|
181
|
-
|
|
173
|
+
expiresAt?: Prisma.DateTimeFilter<"OneTimeToken"> | Date | string;
|
|
182
174
|
createdAt?: Prisma.DateTimeFilter<"OneTimeToken"> | Date | string;
|
|
183
175
|
};
|
|
184
176
|
export type OneTimeTokenOrderByWithRelationInput = {
|
|
185
177
|
id?: Prisma.SortOrder;
|
|
186
178
|
tokenHash?: Prisma.SortOrder;
|
|
187
179
|
purpose?: Prisma.SortOrder;
|
|
188
|
-
expiresAt?: Prisma.SortOrder;
|
|
189
180
|
data?: Prisma.SortOrder;
|
|
190
|
-
|
|
181
|
+
expiresAt?: Prisma.SortOrder;
|
|
191
182
|
createdAt?: Prisma.SortOrder;
|
|
192
183
|
};
|
|
193
184
|
export type OneTimeTokenWhereUniqueInput = Prisma.AtLeast<{
|
|
194
185
|
id?: number;
|
|
186
|
+
tokenHash?: string;
|
|
195
187
|
AND?: Prisma.OneTimeTokenWhereInput | Prisma.OneTimeTokenWhereInput[];
|
|
196
188
|
OR?: Prisma.OneTimeTokenWhereInput[];
|
|
197
189
|
NOT?: Prisma.OneTimeTokenWhereInput | Prisma.OneTimeTokenWhereInput[];
|
|
198
|
-
tokenHash?: Prisma.StringFilter<"OneTimeToken"> | string;
|
|
199
190
|
purpose?: Prisma.StringFilter<"OneTimeToken"> | string;
|
|
200
|
-
expiresAt?: Prisma.DateTimeFilter<"OneTimeToken"> | Date | string;
|
|
201
191
|
data?: Prisma.JsonFilter<"OneTimeToken">;
|
|
202
|
-
|
|
192
|
+
expiresAt?: Prisma.DateTimeFilter<"OneTimeToken"> | Date | string;
|
|
203
193
|
createdAt?: Prisma.DateTimeFilter<"OneTimeToken"> | Date | string;
|
|
204
|
-
}, "id">;
|
|
194
|
+
}, "id" | "tokenHash">;
|
|
205
195
|
export type OneTimeTokenOrderByWithAggregationInput = {
|
|
206
196
|
id?: Prisma.SortOrder;
|
|
207
197
|
tokenHash?: Prisma.SortOrder;
|
|
208
198
|
purpose?: Prisma.SortOrder;
|
|
209
|
-
expiresAt?: Prisma.SortOrder;
|
|
210
199
|
data?: Prisma.SortOrder;
|
|
211
|
-
|
|
200
|
+
expiresAt?: Prisma.SortOrder;
|
|
212
201
|
createdAt?: Prisma.SortOrder;
|
|
213
202
|
_count?: Prisma.OneTimeTokenCountOrderByAggregateInput;
|
|
214
203
|
_avg?: Prisma.OneTimeTokenAvgOrderByAggregateInput;
|
|
@@ -223,78 +212,69 @@ export type OneTimeTokenScalarWhereWithAggregatesInput = {
|
|
|
223
212
|
id?: Prisma.IntWithAggregatesFilter<"OneTimeToken"> | number;
|
|
224
213
|
tokenHash?: Prisma.StringWithAggregatesFilter<"OneTimeToken"> | string;
|
|
225
214
|
purpose?: Prisma.StringWithAggregatesFilter<"OneTimeToken"> | string;
|
|
226
|
-
expiresAt?: Prisma.DateTimeWithAggregatesFilter<"OneTimeToken"> | Date | string;
|
|
227
215
|
data?: Prisma.JsonWithAggregatesFilter<"OneTimeToken">;
|
|
228
|
-
|
|
216
|
+
expiresAt?: Prisma.DateTimeWithAggregatesFilter<"OneTimeToken"> | Date | string;
|
|
229
217
|
createdAt?: Prisma.DateTimeWithAggregatesFilter<"OneTimeToken"> | Date | string;
|
|
230
218
|
};
|
|
231
219
|
export type OneTimeTokenCreateInput = {
|
|
232
220
|
tokenHash: string;
|
|
233
221
|
purpose: string;
|
|
234
|
-
expiresAt: Date | string;
|
|
235
222
|
data: Prisma.JsonNullValueInput | runtime.InputJsonValue;
|
|
236
|
-
|
|
223
|
+
expiresAt: Date | string;
|
|
237
224
|
createdAt?: Date | string;
|
|
238
225
|
};
|
|
239
226
|
export type OneTimeTokenUncheckedCreateInput = {
|
|
240
227
|
id?: number;
|
|
241
228
|
tokenHash: string;
|
|
242
229
|
purpose: string;
|
|
243
|
-
expiresAt: Date | string;
|
|
244
230
|
data: Prisma.JsonNullValueInput | runtime.InputJsonValue;
|
|
245
|
-
|
|
231
|
+
expiresAt: Date | string;
|
|
246
232
|
createdAt?: Date | string;
|
|
247
233
|
};
|
|
248
234
|
export type OneTimeTokenUpdateInput = {
|
|
249
235
|
tokenHash?: Prisma.StringFieldUpdateOperationsInput | string;
|
|
250
236
|
purpose?: Prisma.StringFieldUpdateOperationsInput | string;
|
|
251
|
-
expiresAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
252
237
|
data?: Prisma.JsonNullValueInput | runtime.InputJsonValue;
|
|
253
|
-
|
|
238
|
+
expiresAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
254
239
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
255
240
|
};
|
|
256
241
|
export type OneTimeTokenUncheckedUpdateInput = {
|
|
257
242
|
id?: Prisma.IntFieldUpdateOperationsInput | number;
|
|
258
243
|
tokenHash?: Prisma.StringFieldUpdateOperationsInput | string;
|
|
259
244
|
purpose?: Prisma.StringFieldUpdateOperationsInput | string;
|
|
260
|
-
expiresAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
261
245
|
data?: Prisma.JsonNullValueInput | runtime.InputJsonValue;
|
|
262
|
-
|
|
246
|
+
expiresAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
263
247
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
264
248
|
};
|
|
265
249
|
export type OneTimeTokenCreateManyInput = {
|
|
266
250
|
id?: number;
|
|
267
251
|
tokenHash: string;
|
|
268
252
|
purpose: string;
|
|
269
|
-
expiresAt: Date | string;
|
|
270
253
|
data: Prisma.JsonNullValueInput | runtime.InputJsonValue;
|
|
271
|
-
|
|
254
|
+
expiresAt: Date | string;
|
|
272
255
|
createdAt?: Date | string;
|
|
273
256
|
};
|
|
274
257
|
export type OneTimeTokenUpdateManyMutationInput = {
|
|
275
258
|
tokenHash?: Prisma.StringFieldUpdateOperationsInput | string;
|
|
276
259
|
purpose?: Prisma.StringFieldUpdateOperationsInput | string;
|
|
277
|
-
expiresAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
278
260
|
data?: Prisma.JsonNullValueInput | runtime.InputJsonValue;
|
|
279
|
-
|
|
261
|
+
expiresAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
280
262
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
281
263
|
};
|
|
282
264
|
export type OneTimeTokenUncheckedUpdateManyInput = {
|
|
283
265
|
id?: Prisma.IntFieldUpdateOperationsInput | number;
|
|
284
266
|
tokenHash?: Prisma.StringFieldUpdateOperationsInput | string;
|
|
285
267
|
purpose?: Prisma.StringFieldUpdateOperationsInput | string;
|
|
286
|
-
expiresAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
287
268
|
data?: Prisma.JsonNullValueInput | runtime.InputJsonValue;
|
|
288
|
-
|
|
269
|
+
expiresAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
289
270
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
|
|
290
271
|
};
|
|
291
272
|
export type OneTimeTokenCountOrderByAggregateInput = {
|
|
292
273
|
id?: Prisma.SortOrder;
|
|
293
274
|
tokenHash?: Prisma.SortOrder;
|
|
294
275
|
purpose?: Prisma.SortOrder;
|
|
295
|
-
expiresAt?: Prisma.SortOrder;
|
|
296
276
|
data?: Prisma.SortOrder;
|
|
297
|
-
|
|
277
|
+
expiresAt?: Prisma.SortOrder;
|
|
298
278
|
createdAt?: Prisma.SortOrder;
|
|
299
279
|
};
|
|
300
280
|
export type OneTimeTokenAvgOrderByAggregateInput = {
|
|
@@ -305,7 +285,6 @@ export type OneTimeTokenMaxOrderByAggregateInput = {
|
|
|
305
285
|
tokenHash?: Prisma.SortOrder;
|
|
306
286
|
purpose?: Prisma.SortOrder;
|
|
307
287
|
expiresAt?: Prisma.SortOrder;
|
|
308
|
-
updatedAt?: Prisma.SortOrder;
|
|
309
288
|
createdAt?: Prisma.SortOrder;
|
|
310
289
|
};
|
|
311
290
|
export type OneTimeTokenMinOrderByAggregateInput = {
|
|
@@ -313,7 +292,6 @@ export type OneTimeTokenMinOrderByAggregateInput = {
|
|
|
313
292
|
tokenHash?: Prisma.SortOrder;
|
|
314
293
|
purpose?: Prisma.SortOrder;
|
|
315
294
|
expiresAt?: Prisma.SortOrder;
|
|
316
|
-
updatedAt?: Prisma.SortOrder;
|
|
317
295
|
createdAt?: Prisma.SortOrder;
|
|
318
296
|
};
|
|
319
297
|
export type OneTimeTokenSumOrderByAggregateInput = {
|
|
@@ -323,39 +301,35 @@ export type OneTimeTokenSelect<ExtArgs extends runtime.Types.Extensions.Internal
|
|
|
323
301
|
id?: boolean;
|
|
324
302
|
tokenHash?: boolean;
|
|
325
303
|
purpose?: boolean;
|
|
326
|
-
expiresAt?: boolean;
|
|
327
304
|
data?: boolean;
|
|
328
|
-
|
|
305
|
+
expiresAt?: boolean;
|
|
329
306
|
createdAt?: boolean;
|
|
330
307
|
}, ExtArgs["result"]["oneTimeToken"]>;
|
|
331
308
|
export type OneTimeTokenSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
|
332
309
|
id?: boolean;
|
|
333
310
|
tokenHash?: boolean;
|
|
334
311
|
purpose?: boolean;
|
|
335
|
-
expiresAt?: boolean;
|
|
336
312
|
data?: boolean;
|
|
337
|
-
|
|
313
|
+
expiresAt?: boolean;
|
|
338
314
|
createdAt?: boolean;
|
|
339
315
|
}, ExtArgs["result"]["oneTimeToken"]>;
|
|
340
316
|
export type OneTimeTokenSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
|
341
317
|
id?: boolean;
|
|
342
318
|
tokenHash?: boolean;
|
|
343
319
|
purpose?: boolean;
|
|
344
|
-
expiresAt?: boolean;
|
|
345
320
|
data?: boolean;
|
|
346
|
-
|
|
321
|
+
expiresAt?: boolean;
|
|
347
322
|
createdAt?: boolean;
|
|
348
323
|
}, ExtArgs["result"]["oneTimeToken"]>;
|
|
349
324
|
export type OneTimeTokenSelectScalar = {
|
|
350
325
|
id?: boolean;
|
|
351
326
|
tokenHash?: boolean;
|
|
352
327
|
purpose?: boolean;
|
|
353
|
-
expiresAt?: boolean;
|
|
354
328
|
data?: boolean;
|
|
355
|
-
|
|
329
|
+
expiresAt?: boolean;
|
|
356
330
|
createdAt?: boolean;
|
|
357
331
|
};
|
|
358
|
-
export type OneTimeTokenOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "tokenHash" | "purpose" | "
|
|
332
|
+
export type OneTimeTokenOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "tokenHash" | "purpose" | "data" | "expiresAt" | "createdAt", ExtArgs["result"]["oneTimeToken"]>;
|
|
359
333
|
export type $OneTimeTokenPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
|
360
334
|
name: "OneTimeToken";
|
|
361
335
|
objects: {};
|
|
@@ -363,12 +337,11 @@ export type $OneTimeTokenPayload<ExtArgs extends runtime.Types.Extensions.Intern
|
|
|
363
337
|
id: number;
|
|
364
338
|
tokenHash: string;
|
|
365
339
|
purpose: string;
|
|
366
|
-
expiresAt: Date;
|
|
367
340
|
data: runtime.JsonValue;
|
|
341
|
+
expiresAt: Date;
|
|
368
342
|
/**
|
|
369
343
|
* Audit columns
|
|
370
344
|
*/
|
|
371
|
-
updatedAt: Date;
|
|
372
345
|
createdAt: Date;
|
|
373
346
|
}, ExtArgs["result"]["oneTimeToken"]>;
|
|
374
347
|
composites: {};
|
|
@@ -727,9 +700,8 @@ export interface OneTimeTokenFieldRefs {
|
|
|
727
700
|
readonly id: Prisma.FieldRef<"OneTimeToken", 'Int'>;
|
|
728
701
|
readonly tokenHash: Prisma.FieldRef<"OneTimeToken", 'String'>;
|
|
729
702
|
readonly purpose: Prisma.FieldRef<"OneTimeToken", 'String'>;
|
|
730
|
-
readonly expiresAt: Prisma.FieldRef<"OneTimeToken", 'DateTime'>;
|
|
731
703
|
readonly data: Prisma.FieldRef<"OneTimeToken", 'Json'>;
|
|
732
|
-
readonly
|
|
704
|
+
readonly expiresAt: Prisma.FieldRef<"OneTimeToken", 'DateTime'>;
|
|
733
705
|
readonly createdAt: Prisma.FieldRef<"OneTimeToken", 'DateTime'>;
|
|
734
706
|
}
|
|
735
707
|
/**
|
package/queue/bull-queue.js
CHANGED
|
@@ -7,8 +7,11 @@ const context_1 = require("../context");
|
|
|
7
7
|
class BullQueue extends common_1.Queue {
|
|
8
8
|
/** @internal */
|
|
9
9
|
_queues = new Map();
|
|
10
|
+
/** @internal */
|
|
11
|
+
_healthCheckQueue;
|
|
10
12
|
async onDestroy() {
|
|
11
13
|
await this.closeAll();
|
|
14
|
+
await this._healthCheckQueue?.close();
|
|
12
15
|
}
|
|
13
16
|
get(name) {
|
|
14
17
|
let processor = this._queues.get(name);
|
|
@@ -33,9 +36,10 @@ class BullQueue extends common_1.Queue {
|
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
async checkHealth() {
|
|
39
|
+
// Reused, so an outage is logged once and not on every check
|
|
40
|
+
this._healthCheckQueue ??= new BullQueueProcessor('health-check');
|
|
36
41
|
try {
|
|
37
|
-
|
|
38
|
-
await queue.close();
|
|
42
|
+
await this._healthCheckQueue.ensureConnected();
|
|
39
43
|
return { success: true };
|
|
40
44
|
}
|
|
41
45
|
catch (e) {
|
|
@@ -47,9 +51,7 @@ exports.BullQueue = BullQueue;
|
|
|
47
51
|
class BullQueueProcessor extends common_1.QueueProcessor {
|
|
48
52
|
name;
|
|
49
53
|
/** @internal */
|
|
50
|
-
_connection
|
|
51
|
-
maxRetriesPerRequest: null
|
|
52
|
-
});
|
|
54
|
+
_connection;
|
|
53
55
|
/** @internal */
|
|
54
56
|
_queue;
|
|
55
57
|
/** @internal */
|
|
@@ -59,6 +61,10 @@ class BullQueueProcessor extends common_1.QueueProcessor {
|
|
|
59
61
|
constructor(name) {
|
|
60
62
|
super();
|
|
61
63
|
this.name = name;
|
|
64
|
+
this._connection = (0, context_1.inject)(common_1.Redis).createClient({
|
|
65
|
+
connectionName: `queue:${name}`,
|
|
66
|
+
maxRetriesPerRequest: null
|
|
67
|
+
});
|
|
62
68
|
this._queue = new bullmq_1.Queue(name, {
|
|
63
69
|
connection: this._connection, // Needed because of different ioredis types
|
|
64
70
|
skipVersionCheck: true,
|
|
@@ -78,11 +84,18 @@ class BullQueueProcessor extends common_1.QueueProcessor {
|
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
});
|
|
87
|
+
// Without a listener BullMQ prints every reconnection error, the outage
|
|
88
|
+
// itself is logged once by the Redis connection monitor
|
|
89
|
+
this._queue.on('error', (error) => {
|
|
90
|
+
common_1.logger.debug(error, `Queue '${name}' error`);
|
|
91
|
+
});
|
|
81
92
|
}
|
|
82
93
|
async sendJob(data, name, options = {}) {
|
|
94
|
+
await this.ensureConnected();
|
|
83
95
|
return await this._queue.add(name ?? 'defaultJob', data, options);
|
|
84
96
|
}
|
|
85
97
|
async sendBulkJobs(jobs) {
|
|
98
|
+
await this.ensureConnected();
|
|
86
99
|
return await this._queue.addBulk(jobs.map(({ data, name, options }) => ({
|
|
87
100
|
data,
|
|
88
101
|
name: name ?? 'defaultJob',
|
|
@@ -182,9 +195,43 @@ class BullQueueProcessor extends common_1.QueueProcessor {
|
|
|
182
195
|
return true;
|
|
183
196
|
}
|
|
184
197
|
async close() {
|
|
198
|
+
const connected = this._connection.status === 'ready';
|
|
185
199
|
await this._queue.close();
|
|
200
|
+
this.ignoreLateErrors(this._queue);
|
|
186
201
|
for (const worker of this._workers) {
|
|
187
|
-
|
|
202
|
+
// Running jobs cannot finish while Redis is down
|
|
203
|
+
await worker.close(!connected);
|
|
204
|
+
this.ignoreLateErrors(worker);
|
|
205
|
+
}
|
|
206
|
+
// BullMQ does not close a connection it was given
|
|
207
|
+
if (connected) {
|
|
208
|
+
await this._connection.quit();
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
this._connection.disconnect();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Fails fast while Redis is down, the queue would otherwise wait for the
|
|
216
|
+
* reconnection. Waits only for a connection that is still being established.
|
|
217
|
+
*
|
|
218
|
+
* @internal
|
|
219
|
+
*/
|
|
220
|
+
async ensureConnected() {
|
|
221
|
+
const connection = this._connection;
|
|
222
|
+
if (['wait', 'connecting', 'connect'].includes(connection.status)) {
|
|
223
|
+
await new Promise((resolve) => {
|
|
224
|
+
const done = () => {
|
|
225
|
+
connection.off('ready', done);
|
|
226
|
+
connection.off('error', done);
|
|
227
|
+
resolve();
|
|
228
|
+
};
|
|
229
|
+
connection.once('ready', done);
|
|
230
|
+
connection.once('error', done);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (connection.status !== 'ready') {
|
|
234
|
+
throw new Error(`Queue '${this.name}' is unavailable, Redis connection is not ready`);
|
|
188
235
|
}
|
|
189
236
|
}
|
|
190
237
|
/** @internal */
|
|
@@ -204,4 +251,17 @@ class BullQueueProcessor extends common_1.QueueProcessor {
|
|
|
204
251
|
}
|
|
205
252
|
await Promise.allSettled(handlerActions);
|
|
206
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* BullMQ removes the listeners of its connections on close, but one still
|
|
256
|
+
* connecting fails afterward and emits an unhandled error that crashes the
|
|
257
|
+
* process.
|
|
258
|
+
*
|
|
259
|
+
* @internal
|
|
260
|
+
*/
|
|
261
|
+
ignoreLateErrors(target) {
|
|
262
|
+
const internal = target;
|
|
263
|
+
for (const key of ['connection', 'blockingConnection']) {
|
|
264
|
+
internal[key]?.on('error', () => undefined);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
207
267
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AuthScope, AuthSource, AuthUser, ResourceId, RouteConfig } from '@appweaver/common';
|
|
2
2
|
import { AuthTokens, JwtPayload, UserAdditionalData } from '../types';
|
|
3
3
|
export declare class AuthService {
|
|
4
|
+
constructor();
|
|
4
5
|
/**
|
|
5
6
|
* Finds an authenticated user by their unique identifier.
|
|
6
7
|
*
|
package/security/auth-service.js
CHANGED
|
@@ -19,6 +19,15 @@ class AuthService {
|
|
|
19
19
|
/** Optional, since an application with no OAuth2 provider never registers it.
|
|
20
20
|
* @internal */
|
|
21
21
|
_oauth2Service = (0, context_1.inject)(oauth2_service_1.OAuth2Service, false);
|
|
22
|
+
/** @internal */
|
|
23
|
+
_events = (0, context_1.inject)(common_1.Events);
|
|
24
|
+
constructor() {
|
|
25
|
+
// Evicts the users changed outside of this service as well, e.g. through
|
|
26
|
+
// the resource routes, regardless of the cache invalidation strategy
|
|
27
|
+
const modelName = this._authUserService.modelName;
|
|
28
|
+
this._events.onResourceEvent(modelName, 'update', ({ previous, current }) => this.evictCachedUsers(previous, current));
|
|
29
|
+
this._events.onResourceEvent(modelName, 'delete', ({ current }) => this.evictCachedUsers(current));
|
|
30
|
+
}
|
|
22
31
|
/**
|
|
23
32
|
* Finds an authenticated user by their unique identifier.
|
|
24
33
|
*
|
|
@@ -31,10 +40,7 @@ class AuthService {
|
|
|
31
40
|
if (!common_1.config.CACHE_ENABLED || common_1.config.SECURITY_CACHE_TTL < 0) {
|
|
32
41
|
return this._authUserService.find(id);
|
|
33
42
|
}
|
|
34
|
-
const cacheKey = this.
|
|
35
|
-
baseKey: `${AUTH_KEY}:${id}`,
|
|
36
|
-
modelName: this._authUserService.modelName
|
|
37
|
-
});
|
|
43
|
+
const cacheKey = this.authCacheKey(id);
|
|
38
44
|
const value = await this._cacheService.getCachedValue(cacheKey);
|
|
39
45
|
if (value) {
|
|
40
46
|
return value;
|
|
@@ -59,10 +65,7 @@ class AuthService {
|
|
|
59
65
|
const result = await this._authUserService.query({ email: username });
|
|
60
66
|
return result.items[0] ?? null;
|
|
61
67
|
}
|
|
62
|
-
const cacheKey = this.
|
|
63
|
-
baseKey: `${AUTH_KEY}:${username}`,
|
|
64
|
-
modelName: this._authUserService.modelName
|
|
65
|
-
});
|
|
68
|
+
const cacheKey = this.authCacheKey(username);
|
|
66
69
|
const value = await this._cacheService.getCachedValue(cacheKey);
|
|
67
70
|
if (value) {
|
|
68
71
|
return value;
|
|
@@ -87,12 +90,17 @@ class AuthService {
|
|
|
87
90
|
* @return {Promise<AuthUser>} A promise that resolves to the updated authenticated user object.
|
|
88
91
|
*/
|
|
89
92
|
async updateAuthUser(id, data) {
|
|
93
|
+
let authUser;
|
|
90
94
|
try {
|
|
91
|
-
|
|
95
|
+
authUser = await this._authUserService.update(id, data);
|
|
92
96
|
}
|
|
93
97
|
catch (e) {
|
|
94
98
|
throw new errors_1.HttpError('Auth user update error', 500, e);
|
|
95
99
|
}
|
|
100
|
+
// Awaited unlike the update event eviction, so the next request, e.g. with
|
|
101
|
+
// a token revoked by a logout, cannot read the previous state
|
|
102
|
+
await this.evictCachedUsers(authUser);
|
|
103
|
+
return authUser;
|
|
96
104
|
}
|
|
97
105
|
/**
|
|
98
106
|
* Registers a new authenticated user based on the provided authentication source, email, and optional password and
|
|
@@ -217,9 +225,12 @@ class AuthService {
|
|
|
217
225
|
authorize(authUser, url, routeConfig = {}, jwtPayload) {
|
|
218
226
|
if (!authUser ||
|
|
219
227
|
!authUser.enabled ||
|
|
228
|
+
// `iat` has a whole second precision, so a token issued within the same
|
|
229
|
+
// second as the logout is still accepted
|
|
220
230
|
(authUser.logoutAt &&
|
|
221
231
|
jwtPayload &&
|
|
222
|
-
new Date(authUser.logoutAt).getTime()
|
|
232
|
+
Math.floor(new Date(authUser.logoutAt).getTime() / 1000) >
|
|
233
|
+
jwtPayload.iat)) {
|
|
223
234
|
throw new errors_1.HttpError('Unauthorized access', 401);
|
|
224
235
|
}
|
|
225
236
|
if (jwtPayload && !(0, helper_1.checkScopeAccess)(url, jwtPayload.scope)) {
|
|
@@ -317,11 +328,11 @@ class AuthService {
|
|
|
317
328
|
scope,
|
|
318
329
|
source,
|
|
319
330
|
username: authUser.email,
|
|
320
|
-
sub: authUser.id
|
|
321
|
-
iat: new Date().getTime()
|
|
331
|
+
sub: authUser.id
|
|
322
332
|
};
|
|
323
333
|
const expiresIn = common_1.config.SECURITY_JWT_EXPIRES_IN;
|
|
324
334
|
const refreshExpiresIn = common_1.config.SECURITY_JWT_REFRESH_EXPIRES_IN;
|
|
335
|
+
// The signer sets the `iat` claim, in seconds like the expiration
|
|
325
336
|
const accessToken = server.jwt.sign(jwtPayload, { expiresIn });
|
|
326
337
|
const refreshToken = server.jwt.sign({ ...jwtPayload, scope: common_1.AuthScope.Refresh }, { expiresIn: refreshExpiresIn });
|
|
327
338
|
common_1.logger.debug({ id: authUser.id }, 'Access tokens generated');
|
|
@@ -335,9 +346,28 @@ class AuthService {
|
|
|
335
346
|
* successful.
|
|
336
347
|
*/
|
|
337
348
|
async logout(id) {
|
|
338
|
-
await this._cacheService.removeCachedValue(`${AUTH_KEY}:${id}`);
|
|
339
349
|
common_1.logger.debug({ id }, 'User logout');
|
|
340
350
|
return !!(await this.updateAuthUser(id, { logoutAt: new Date() }));
|
|
341
351
|
}
|
|
352
|
+
/** @internal */
|
|
353
|
+
async evictCachedUsers(...authUsers) {
|
|
354
|
+
const keys = new Set();
|
|
355
|
+
for (const authUser of authUsers) {
|
|
356
|
+
if (authUser) {
|
|
357
|
+
keys.add(this.authCacheKey(authUser.id));
|
|
358
|
+
keys.add(this.authCacheKey(authUser.email));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
for (const key of keys) {
|
|
362
|
+
await this._cacheService.removeCachedValue(key);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/** @internal */
|
|
366
|
+
authCacheKey(idOrUsername) {
|
|
367
|
+
return this._cacheService.buildCacheKey({
|
|
368
|
+
baseKey: `${AUTH_KEY}:${idOrUsername}`,
|
|
369
|
+
modelName: this._authUserService.modelName
|
|
370
|
+
});
|
|
371
|
+
}
|
|
342
372
|
}
|
|
343
373
|
exports.AuthService = AuthService;
|
|
@@ -8,25 +8,29 @@ const shouldCreateModel = common_1.config.SECURITY_STORE_PROVIDER ===
|
|
|
8
8
|
exports.default = shouldCreateModel
|
|
9
9
|
? (0, factory_1.createModel)({
|
|
10
10
|
name: 'OneTimeToken',
|
|
11
|
+
// Tokens are only created and deleted, never updated
|
|
11
12
|
audit: {
|
|
13
|
+
updatedAt: false,
|
|
12
14
|
createdById: false
|
|
13
15
|
},
|
|
14
16
|
scalars: {
|
|
15
17
|
tokenHash: {
|
|
16
18
|
type: 'string',
|
|
17
|
-
maxLength:
|
|
19
|
+
maxLength: 128,
|
|
20
|
+
unique: true
|
|
18
21
|
},
|
|
19
22
|
purpose: {
|
|
20
23
|
type: 'string',
|
|
21
|
-
maxLength:
|
|
22
|
-
},
|
|
23
|
-
expiresAt: {
|
|
24
|
-
type: 'dateTime'
|
|
24
|
+
maxLength: 64
|
|
25
25
|
},
|
|
26
26
|
data: {
|
|
27
27
|
type: 'json'
|
|
28
|
+
},
|
|
29
|
+
expiresAt: {
|
|
30
|
+
type: 'dateTime'
|
|
28
31
|
}
|
|
29
32
|
},
|
|
30
|
-
|
|
33
|
+
// Deleting the expired tokens filters by the expiration
|
|
34
|
+
index: [['expiresAt']]
|
|
31
35
|
})
|
|
32
36
|
: undefined;
|
|
@@ -9,44 +9,47 @@ class DatabaseSecurityStore extends common_1.SecurityStore {
|
|
|
9
9
|
_db = (0, context_1.inject)(common_1.Database);
|
|
10
10
|
async generateOneTimeToken(purpose, data, ttl) {
|
|
11
11
|
const token = (0, common_1.generateToken)('bytes', 64);
|
|
12
|
-
|
|
12
|
+
const oneTimeTokens = this._db.client().oneTimeToken;
|
|
13
|
+
// Expired tokens are removed here, so they need no scheduled cleanup
|
|
14
|
+
await oneTimeTokens.deleteMany({
|
|
15
|
+
where: { expiresAt: { lt: new Date() } }
|
|
16
|
+
});
|
|
17
|
+
await oneTimeTokens.create({
|
|
13
18
|
data: {
|
|
14
19
|
tokenHash: (0, common_1.makeHash)(token),
|
|
15
20
|
purpose,
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
data: data,
|
|
22
|
+
expiresAt: new Date(Date.now() + ttl)
|
|
18
23
|
}
|
|
19
24
|
});
|
|
20
25
|
return token;
|
|
21
26
|
}
|
|
22
27
|
async useOneTimeToken(token, purpose, validateContent) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
tokenHash: (0, common_1.makeHash)(token)
|
|
27
|
-
}
|
|
28
|
+
const oneTimeTokens = this._db.client().oneTimeToken;
|
|
29
|
+
const oneTimeToken = await oneTimeTokens.findUnique({
|
|
30
|
+
where: { tokenHash: (0, common_1.makeHash)(token) }
|
|
28
31
|
});
|
|
29
32
|
if (oneTimeToken === null ||
|
|
33
|
+
oneTimeToken.purpose !== purpose ||
|
|
30
34
|
oneTimeToken.expiresAt.getTime() < Date.now()) {
|
|
31
|
-
if (oneTimeToken) {
|
|
32
|
-
await this.removeOneTimeToken(oneTimeToken.id);
|
|
33
|
-
}
|
|
34
35
|
throw new errors_1.HttpError('Invalid or expired token provided', 401);
|
|
35
36
|
}
|
|
36
37
|
const data = oneTimeToken.data;
|
|
38
|
+
// A token failing the validation is kept, so it can be used again
|
|
37
39
|
if (validateContent) {
|
|
38
40
|
const result = validateContent(data);
|
|
39
41
|
if (!result.valid) {
|
|
40
42
|
throw new errors_1.HttpError(result.message, 401);
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
|
-
|
|
45
|
+
// Only the one of concurrent uses that removes the token gets its data
|
|
46
|
+
const { count } = await oneTimeTokens.deleteMany({
|
|
47
|
+
where: { id: oneTimeToken.id }
|
|
48
|
+
});
|
|
49
|
+
if (count === 0) {
|
|
50
|
+
throw new errors_1.HttpError('Invalid or expired token provided', 401);
|
|
51
|
+
}
|
|
44
52
|
return data;
|
|
45
53
|
}
|
|
46
|
-
/** @internal */
|
|
47
|
-
async removeOneTimeToken(id) {
|
|
48
|
-
// The generated client types the id after the configured primary key
|
|
49
|
-
await this._db.client().oneTimeToken.delete({ where: { id: id } });
|
|
50
|
-
}
|
|
51
54
|
}
|
|
52
55
|
exports.DatabaseSecurityStore = DatabaseSecurityStore;
|
|
@@ -19,13 +19,18 @@ class RedisSecurityStore extends common_1.SecurityStore {
|
|
|
19
19
|
if (value === null) {
|
|
20
20
|
throw new errors_1.HttpError('Invalid or expired token provided', 401);
|
|
21
21
|
}
|
|
22
|
+
// A token failing the validation is kept, so it can be used again
|
|
22
23
|
if (validateContent) {
|
|
23
24
|
const result = validateContent(value);
|
|
24
25
|
if (!result.valid) {
|
|
25
26
|
throw new errors_1.HttpError(result.message, 401);
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
// Only the one of concurrent uses that removes the token gets its data
|
|
30
|
+
const removed = await this._redis.removeValue(tokenKey);
|
|
31
|
+
if (!removed) {
|
|
32
|
+
throw new errors_1.HttpError('Invalid or expired token provided', 401);
|
|
33
|
+
}
|
|
29
34
|
return value;
|
|
30
35
|
}
|
|
31
36
|
}
|