@friggframework/core 2.0.0-next.3 → 2.0.0-next.31

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.
@@ -0,0 +1,451 @@
1
+ const { Router } = require('express');
2
+ const mongoose = require('mongoose');
3
+ const https = require('https');
4
+ const http = require('http');
5
+ const { moduleFactory, integrationFactory } = require('./../backend-utils');
6
+ const { createAppHandler } = require('./../app-handler-helpers');
7
+
8
+ const router = Router();
9
+
10
+ const validateApiKey = (req, res, next) => {
11
+ const apiKey = req.headers['x-api-key'];
12
+
13
+ if (req.path === '/health') {
14
+ return next();
15
+ }
16
+
17
+ if (!apiKey || apiKey !== process.env.HEALTH_API_KEY) {
18
+ return res.status(401).json({
19
+ status: 'error',
20
+ message: 'Unauthorized'
21
+ });
22
+ }
23
+
24
+ next();
25
+ };
26
+
27
+ router.use(validateApiKey);
28
+
29
+ const checkExternalAPI = (url, timeout = 5000) => {
30
+ return new Promise((resolve) => {
31
+ const protocol = url.startsWith('https:') ? https : http;
32
+ const startTime = Date.now();
33
+
34
+ try {
35
+ const request = protocol.get(url, { timeout }, (res) => {
36
+ const responseTime = Date.now() - startTime;
37
+ resolve({
38
+ status: 'healthy',
39
+ statusCode: res.statusCode,
40
+ responseTime,
41
+ reachable: res.statusCode < 500
42
+ });
43
+ });
44
+
45
+ request.on('error', (error) => {
46
+ resolve({
47
+ status: 'unhealthy',
48
+ error: error.message,
49
+ responseTime: Date.now() - startTime,
50
+ reachable: false
51
+ });
52
+ });
53
+
54
+ request.on('timeout', () => {
55
+ request.destroy();
56
+ resolve({
57
+ status: 'timeout',
58
+ error: 'Request timeout',
59
+ responseTime: timeout,
60
+ reachable: false
61
+ });
62
+ });
63
+ } catch (error) {
64
+ resolve({
65
+ status: 'error',
66
+ error: error.message,
67
+ responseTime: Date.now() - startTime,
68
+ reachable: false
69
+ });
70
+ }
71
+ });
72
+ };
73
+
74
+ const getDatabaseState = () => {
75
+ const stateMap = {
76
+ 0: 'disconnected',
77
+ 1: 'connected',
78
+ 2: 'connecting',
79
+ 3: 'disconnecting'
80
+ };
81
+ const readyState = mongoose.connection.readyState;
82
+
83
+ return {
84
+ readyState,
85
+ stateName: stateMap[readyState],
86
+ isConnected: readyState === 1
87
+ };
88
+ };
89
+
90
+ const checkDatabaseHealth = async () => {
91
+ const { stateName, isConnected } = getDatabaseState();
92
+ const result = {
93
+ status: isConnected ? 'healthy' : 'unhealthy',
94
+ state: stateName
95
+ };
96
+
97
+ if (isConnected) {
98
+ const pingStart = Date.now();
99
+ await mongoose.connection.db.admin().ping({ maxTimeMS: 2000 });
100
+ result.responseTime = Date.now() - pingStart;
101
+ }
102
+
103
+ return result;
104
+ };
105
+
106
+ const getEncryptionConfiguration = () => {
107
+ const { STAGE, BYPASS_ENCRYPTION_STAGE, KMS_KEY_ARN, AES_KEY_ID } = process.env;
108
+
109
+ const defaultBypassStages = ['dev', 'test', 'local'];
110
+ const useEnv = BYPASS_ENCRYPTION_STAGE !== undefined;
111
+ const bypassStages = useEnv
112
+ ? BYPASS_ENCRYPTION_STAGE.split(',').map((s) => s.trim())
113
+ : defaultBypassStages;
114
+
115
+ const isBypassed = bypassStages.includes(STAGE);
116
+ const hasAES = AES_KEY_ID && AES_KEY_ID.trim() !== '';
117
+ const hasKMS = KMS_KEY_ARN && KMS_KEY_ARN.trim() !== '' && !hasAES;
118
+ const mode = hasAES ? 'aes' : hasKMS ? 'kms' : 'none';
119
+
120
+ return {
121
+ stage: STAGE || null,
122
+ isBypassed,
123
+ hasAES,
124
+ hasKMS,
125
+ mode,
126
+ };
127
+ };
128
+
129
+ const createTestEncryptionModel = () => {
130
+ const { Encrypt } = require('./../../encrypt');
131
+
132
+ const testSchema = new mongoose.Schema({
133
+ testSecret: { type: String, lhEncrypt: true },
134
+ normalField: { type: String },
135
+ nestedSecret: {
136
+ value: { type: String, lhEncrypt: true }
137
+ }
138
+ }, { timestamps: false });
139
+
140
+ testSchema.plugin(Encrypt);
141
+
142
+ return mongoose.models.TestEncryption ||
143
+ mongoose.model('TestEncryption', testSchema);
144
+ };
145
+
146
+ const createTestDocument = async (TestModel) => {
147
+ const testData = {
148
+ testSecret: 'This is a secret value that should be encrypted',
149
+ normalField: 'This is a normal field that should not be encrypted',
150
+ nestedSecret: {
151
+ value: 'This is a nested secret that should be encrypted'
152
+ }
153
+ };
154
+
155
+ const testDoc = new TestModel(testData);
156
+ await testDoc.save();
157
+
158
+ return { testDoc, testData };
159
+ };
160
+
161
+ const verifyDecryption = (retrievedDoc, originalData) => {
162
+ return retrievedDoc &&
163
+ retrievedDoc.testSecret === originalData.testSecret &&
164
+ retrievedDoc.normalField === originalData.normalField &&
165
+ retrievedDoc.nestedSecret?.value === originalData.nestedSecret.value;
166
+ };
167
+
168
+ const verifyEncryptionInDatabase = async (testDoc, originalData, TestModel) => {
169
+ const collectionName = TestModel.collection.name;
170
+ const rawDoc = await mongoose.connection.db
171
+ .collection(collectionName)
172
+ .findOne({ _id: testDoc._id });
173
+
174
+ const secretIsEncrypted = rawDoc &&
175
+ typeof rawDoc.testSecret === 'string' &&
176
+ rawDoc.testSecret.includes(':') &&
177
+ rawDoc.testSecret !== originalData.testSecret;
178
+
179
+ const nestedIsEncrypted = rawDoc?.nestedSecret?.value &&
180
+ typeof rawDoc.nestedSecret.value === 'string' &&
181
+ rawDoc.nestedSecret.value.includes(':') &&
182
+ rawDoc.nestedSecret.value !== originalData.nestedSecret.value;
183
+
184
+ const normalNotEncrypted = rawDoc &&
185
+ rawDoc.normalField === originalData.normalField;
186
+
187
+ return {
188
+ secretIsEncrypted,
189
+ nestedIsEncrypted,
190
+ normalNotEncrypted
191
+ };
192
+ };
193
+
194
+ const evaluateEncryptionTestResults = (decryptionWorks, encryptionResults) => {
195
+ const { secretIsEncrypted, nestedIsEncrypted, normalNotEncrypted } = encryptionResults;
196
+
197
+ if (decryptionWorks && secretIsEncrypted && nestedIsEncrypted && normalNotEncrypted) {
198
+ return {
199
+ status: 'enabled',
200
+ testResult: 'Encryption and decryption verified successfully'
201
+ };
202
+ }
203
+
204
+ if (decryptionWorks && (!secretIsEncrypted || !nestedIsEncrypted)) {
205
+ return {
206
+ status: 'unhealthy',
207
+ testResult: 'Fields are not being encrypted in database'
208
+ };
209
+ }
210
+
211
+ if (decryptionWorks && !normalNotEncrypted) {
212
+ return {
213
+ status: 'unhealthy',
214
+ testResult: 'Normal fields are being incorrectly encrypted'
215
+ };
216
+ }
217
+
218
+ return {
219
+ status: 'unhealthy',
220
+ testResult: 'Decryption failed or data mismatch'
221
+ };
222
+ };
223
+
224
+ const testEncryption = async () => {
225
+ const TestModel = createTestEncryptionModel();
226
+ const { testDoc, testData } = await createTestDocument(TestModel);
227
+
228
+ try {
229
+ const retrievedDoc = await TestModel.findById(testDoc._id);
230
+ const decryptionWorks = verifyDecryption(retrievedDoc, testData);
231
+ const encryptionResults = await verifyEncryptionInDatabase(testDoc, testData, TestModel);
232
+
233
+ const evaluation = evaluateEncryptionTestResults(decryptionWorks, encryptionResults);
234
+
235
+ return {
236
+ ...evaluation,
237
+ encryptionWorks: decryptionWorks
238
+ };
239
+ } finally {
240
+ await TestModel.deleteOne({ _id: testDoc._id });
241
+ }
242
+ };
243
+
244
+ const checkEncryptionHealth = async () => {
245
+ const config = getEncryptionConfiguration();
246
+
247
+ if (config.isBypassed || config.mode === 'none') {
248
+ const testResult = config.isBypassed
249
+ ? 'Encryption bypassed for this stage'
250
+ : 'No encryption keys configured';
251
+
252
+ return {
253
+ status: 'disabled',
254
+ mode: config.mode,
255
+ bypassed: config.isBypassed,
256
+ stage: config.stage,
257
+ testResult,
258
+ encryptionWorks: false,
259
+ debug: {
260
+ hasKMS: config.hasKMS,
261
+ hasAES: config.hasAES
262
+ }
263
+ };
264
+ }
265
+
266
+ try {
267
+ const testResults = await testEncryption();
268
+
269
+ return {
270
+ ...testResults,
271
+ mode: config.mode,
272
+ bypassed: config.isBypassed,
273
+ stage: config.stage,
274
+ debug: {
275
+ hasKMS: config.hasKMS,
276
+ hasAES: config.hasAES
277
+ }
278
+ };
279
+ } catch (error) {
280
+ return {
281
+ status: 'unhealthy',
282
+ mode: config.mode,
283
+ bypassed: config.isBypassed,
284
+ stage: config.stage,
285
+ testResult: `Encryption test failed: ${error.message}`,
286
+ encryptionWorks: false,
287
+ debug: {
288
+ hasKMS: config.hasKMS,
289
+ hasAES: config.hasAES
290
+ }
291
+ };
292
+ }
293
+ };
294
+
295
+ const checkExternalAPIs = async () => {
296
+ const apis = [
297
+ { name: 'github', url: 'https://api.github.com/status' },
298
+ { name: 'npm', url: 'https://registry.npmjs.org' }
299
+ ];
300
+
301
+ const results = await Promise.all(
302
+ apis.map(api =>
303
+ checkExternalAPI(api.url).then(result => ({ name: api.name, ...result }))
304
+ )
305
+ );
306
+
307
+ const apiStatuses = {};
308
+ let allReachable = true;
309
+
310
+ results.forEach(({ name, ...checkResult }) => {
311
+ apiStatuses[name] = checkResult;
312
+ if (!checkResult.reachable) {
313
+ allReachable = false;
314
+ }
315
+ });
316
+
317
+ return { apiStatuses, allReachable };
318
+ };
319
+
320
+ const checkIntegrations = () => {
321
+ const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
322
+ ? moduleFactory.moduleTypes
323
+ : [];
324
+
325
+ const integrationTypes = Array.isArray(integrationFactory.integrationTypes)
326
+ ? integrationFactory.integrationTypes
327
+ : [];
328
+
329
+ return {
330
+ status: 'healthy',
331
+ modules: {
332
+ count: moduleTypes.length,
333
+ available: moduleTypes,
334
+ },
335
+ integrations: {
336
+ count: integrationTypes.length,
337
+ available: integrationTypes,
338
+ },
339
+ };
340
+ };
341
+
342
+ const buildHealthCheckResponse = (startTime) => {
343
+ return {
344
+ service: 'frigg-core-api',
345
+ status: 'healthy',
346
+ timestamp: new Date().toISOString(),
347
+ checks: {},
348
+ calculateResponseTime: () => Date.now() - startTime
349
+ };
350
+ };
351
+
352
+ router.get('/health', async (_req, res) => {
353
+ const status = {
354
+ status: 'ok',
355
+ timestamp: new Date().toISOString(),
356
+ service: 'frigg-core-api'
357
+ };
358
+
359
+ res.status(200).json(status);
360
+ });
361
+
362
+ router.get('/health/detailed', async (_req, res) => {
363
+ const startTime = Date.now();
364
+ const response = buildHealthCheckResponse(startTime);
365
+
366
+ try {
367
+ response.checks.database = await checkDatabaseHealth();
368
+ const dbState = getDatabaseState();
369
+ if (!dbState.isConnected) {
370
+ response.status = 'unhealthy';
371
+ }
372
+ } catch (error) {
373
+ response.checks.database = {
374
+ status: 'unhealthy',
375
+ error: error.message
376
+ };
377
+ response.status = 'unhealthy';
378
+ }
379
+
380
+ try {
381
+ response.checks.encryption = await checkEncryptionHealth();
382
+ if (response.checks.encryption.status === 'unhealthy') {
383
+ response.status = 'unhealthy';
384
+ }
385
+ } catch (error) {
386
+ response.checks.encryption = {
387
+ status: 'unhealthy',
388
+ error: error.message
389
+ };
390
+ response.status = 'unhealthy';
391
+ }
392
+
393
+ const { apiStatuses, allReachable } = await checkExternalAPIs();
394
+ response.checks.externalApis = apiStatuses;
395
+ if (!allReachable) {
396
+ response.status = 'unhealthy';
397
+ }
398
+
399
+ try {
400
+ response.checks.integrations = checkIntegrations();
401
+ } catch (error) {
402
+ response.checks.integrations = {
403
+ status: 'unhealthy',
404
+ error: error.message
405
+ };
406
+ response.status = 'unhealthy';
407
+ }
408
+
409
+ response.responseTime = response.calculateResponseTime();
410
+ delete response.calculateResponseTime;
411
+
412
+ const statusCode = response.status === 'healthy' ? 200 : 503;
413
+ res.status(statusCode).json(response);
414
+ });
415
+
416
+ router.get('/health/live', (_req, res) => {
417
+ res.status(200).json({
418
+ status: 'alive',
419
+ timestamp: new Date().toISOString()
420
+ });
421
+ });
422
+
423
+ router.get('/health/ready', async (_req, res) => {
424
+ const dbState = getDatabaseState();
425
+ const isDbReady = dbState.isConnected;
426
+
427
+ let areModulesReady = false;
428
+ try {
429
+ const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
430
+ ? moduleFactory.moduleTypes
431
+ : [];
432
+ areModulesReady = moduleTypes.length > 0;
433
+ } catch (error) {
434
+ areModulesReady = false;
435
+ }
436
+
437
+ const isReady = isDbReady && areModulesReady;
438
+
439
+ res.status(isReady ? 200 : 503).json({
440
+ ready: isReady,
441
+ timestamp: new Date().toISOString(),
442
+ checks: {
443
+ database: isDbReady,
444
+ modules: areModulesReady
445
+ }
446
+ });
447
+ });
448
+
449
+ const handler = createAppHandler('HTTP Event: Health', router);
450
+
451
+ module.exports = { handler, router };
@@ -0,0 +1,203 @@
1
+ process.env.HEALTH_API_KEY = 'test-api-key';
2
+
3
+ jest.mock('mongoose', () => ({
4
+ set: jest.fn(),
5
+ connection: {
6
+ readyState: 1,
7
+ db: {
8
+ admin: () => ({
9
+ ping: jest.fn().mockResolvedValue(true)
10
+ })
11
+ }
12
+ }
13
+ }));
14
+
15
+ jest.mock('./../backend-utils', () => ({
16
+ moduleFactory: {
17
+ moduleTypes: ['test-module', 'another-module']
18
+ },
19
+ integrationFactory: {
20
+ integrationTypes: ['test-integration', 'another-integration']
21
+ }
22
+ }));
23
+
24
+ jest.mock('./../app-handler-helpers', () => ({
25
+ createAppHandler: jest.fn((name, router) => ({ name, router }))
26
+ }));
27
+
28
+ const { router } = require('./health');
29
+ const mongoose = require('mongoose');
30
+
31
+ const mockRequest = (path, headers = {}) => ({
32
+ path,
33
+ headers
34
+ });
35
+
36
+ const mockResponse = () => {
37
+ const res = {};
38
+ res.status = jest.fn().mockReturnValue(res);
39
+ res.json = jest.fn().mockReturnValue(res);
40
+ return res;
41
+ };
42
+
43
+ describe('Health Check Endpoints', () => {
44
+ beforeEach(() => {
45
+ mongoose.connection.readyState = 1;
46
+ });
47
+
48
+ describe('Middleware - validateApiKey', () => {
49
+ it('should allow access to /health without authentication', async () => {
50
+ expect(true).toBe(true);
51
+ });
52
+ });
53
+
54
+ describe('GET /health', () => {
55
+ it('should return basic health status', async () => {
56
+ const req = mockRequest('/health');
57
+ const res = mockResponse();
58
+
59
+ const routeHandler = router.stack.find(layer =>
60
+ layer.route && layer.route.path === '/health'
61
+ ).route.stack[0].handle;
62
+
63
+ await routeHandler(req, res);
64
+
65
+ expect(res.status).toHaveBeenCalledWith(200);
66
+ expect(res.json).toHaveBeenCalledWith({
67
+ status: 'ok',
68
+ timestamp: expect.any(String),
69
+ service: 'frigg-core-api'
70
+ });
71
+ });
72
+ });
73
+
74
+ describe('GET /health/detailed', () => {
75
+ it('should return detailed health status when healthy', async () => {
76
+ const req = mockRequest('/health/detailed', { 'x-api-key': 'test-api-key' });
77
+ const res = mockResponse();
78
+
79
+ const originalPromiseAll = Promise.all;
80
+ Promise.all = jest.fn().mockResolvedValue([
81
+ { name: 'github', status: 'healthy', reachable: true, statusCode: 200, responseTime: 100 },
82
+ { name: 'npm', status: 'healthy', reachable: true, statusCode: 200, responseTime: 150 }
83
+ ]);
84
+
85
+ const routeHandler = router.stack.find(layer =>
86
+ layer.route && layer.route.path === '/health/detailed'
87
+ ).route.stack[0].handle;
88
+
89
+ await routeHandler(req, res);
90
+
91
+ Promise.all = originalPromiseAll;
92
+
93
+ expect(res.status).toHaveBeenCalledWith(200);
94
+ expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
95
+ status: 'healthy',
96
+ service: 'frigg-core-api',
97
+ timestamp: expect.any(String),
98
+ checks: expect.objectContaining({
99
+ database: expect.objectContaining({
100
+ status: 'healthy',
101
+ state: 'connected'
102
+ }),
103
+ integrations: expect.objectContaining({
104
+ status: 'healthy'
105
+ })
106
+ }),
107
+ responseTime: expect.any(Number)
108
+ }));
109
+
110
+ const response = res.json.mock.calls[0][0];
111
+ expect(response).not.toHaveProperty('version');
112
+ expect(response).not.toHaveProperty('uptime');
113
+ expect(response.checks).not.toHaveProperty('memory');
114
+ expect(response.checks.database).not.toHaveProperty('type');
115
+ });
116
+
117
+ it('should return 503 when database is disconnected', async () => {
118
+ mongoose.connection.readyState = 0;
119
+
120
+ const req = mockRequest('/health/detailed', { 'x-api-key': 'test-api-key' });
121
+ const res = mockResponse();
122
+
123
+ const originalPromiseAll = Promise.all;
124
+ Promise.all = jest.fn().mockResolvedValue([
125
+ { name: 'github', status: 'healthy', reachable: true, statusCode: 200, responseTime: 100 },
126
+ { name: 'npm', status: 'healthy', reachable: true, statusCode: 200, responseTime: 150 }
127
+ ]);
128
+
129
+ const routeHandler = router.stack.find(layer =>
130
+ layer.route && layer.route.path === '/health/detailed'
131
+ ).route.stack[0].handle;
132
+
133
+ await routeHandler(req, res);
134
+
135
+ Promise.all = originalPromiseAll;
136
+
137
+ expect(res.status).toHaveBeenCalledWith(503);
138
+ expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
139
+ status: 'unhealthy'
140
+ }));
141
+ });
142
+ });
143
+
144
+ describe('GET /health/live', () => {
145
+ it('should return alive status', async () => {
146
+ const req = mockRequest('/health/live', { 'x-api-key': 'test-api-key' });
147
+ const res = mockResponse();
148
+
149
+ const routeHandler = router.stack.find(layer =>
150
+ layer.route && layer.route.path === '/health/live'
151
+ ).route.stack[0].handle;
152
+
153
+ routeHandler(req, res);
154
+
155
+ expect(res.status).toHaveBeenCalledWith(200);
156
+ expect(res.json).toHaveBeenCalledWith({
157
+ status: 'alive',
158
+ timestamp: expect.any(String)
159
+ });
160
+ });
161
+ });
162
+
163
+ describe('GET /health/ready', () => {
164
+ it('should return ready when all checks pass', async () => {
165
+ const req = mockRequest('/health/ready', { 'x-api-key': 'test-api-key' });
166
+ const res = mockResponse();
167
+
168
+ const routeHandler = router.stack.find(layer =>
169
+ layer.route && layer.route.path === '/health/ready'
170
+ ).route.stack[0].handle;
171
+
172
+ await routeHandler(req, res);
173
+
174
+ expect(res.status).toHaveBeenCalledWith(200);
175
+ expect(res.json).toHaveBeenCalledWith({
176
+ ready: true,
177
+ timestamp: expect.any(String),
178
+ checks: {
179
+ database: true,
180
+ modules: true
181
+ }
182
+ });
183
+ });
184
+
185
+ it('should return 503 when database is not connected', async () => {
186
+ mongoose.connection.readyState = 0;
187
+
188
+ const req = mockRequest('/health/ready', { 'x-api-key': 'test-api-key' });
189
+ const res = mockResponse();
190
+
191
+ const routeHandler = router.stack.find(layer =>
192
+ layer.route && layer.route.path === '/health/ready'
193
+ ).route.stack[0].handle;
194
+
195
+ await routeHandler(req, res);
196
+
197
+ expect(res.status).toHaveBeenCalledWith(503);
198
+ expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
199
+ ready: false
200
+ }));
201
+ });
202
+ });
203
+ });
@@ -0,0 +1,42 @@
1
+ const { createAppHandler } = require('./../app-handler-helpers');
2
+ const {
3
+ integrationFactory,
4
+ loadRouterFromObject,
5
+ } = require('./../backend-utils');
6
+ const { Router } = require('express');
7
+
8
+ const handlers = {};
9
+ for (const IntegrationClass of integrationFactory.integrationClasses) {
10
+ const router = Router();
11
+ const basePath = `/api/${IntegrationClass.Definition.name}-integration`;
12
+
13
+ console.log(`\n│ Configuring routes for ${IntegrationClass.Definition.name} Integration:`);
14
+
15
+ for (const routeDef of IntegrationClass.Definition.routes) {
16
+ if (typeof routeDef === 'function') {
17
+ router.use(basePath, routeDef(IntegrationClass));
18
+ console.log(`│ ANY ${basePath}/* (function handler)`);
19
+ } else if (typeof routeDef === 'object') {
20
+ router.use(
21
+ basePath,
22
+ loadRouterFromObject(IntegrationClass, routeDef)
23
+ );
24
+ const method = (routeDef.method || 'ANY').toUpperCase();
25
+ const fullPath = `${basePath}${routeDef.path}`;
26
+ console.log(`│ ${method} ${fullPath}`);
27
+ } else if (routeDef instanceof express.Router) {
28
+ router.use(basePath, routeDef);
29
+ console.log(`│ ANY ${basePath}/* (express router)`);
30
+ }
31
+ }
32
+ console.log('│');
33
+
34
+ handlers[`${IntegrationClass.Definition.name}`] = {
35
+ handler: createAppHandler(
36
+ `HTTP Event: ${IntegrationClass.Definition.name}`,
37
+ router
38
+ ),
39
+ };
40
+ }
41
+
42
+ module.exports = { handlers };
@@ -0,0 +1,15 @@
1
+ const catchAsyncError = require('express-async-handler');
2
+ const { User } = require('../../backend-utils');
3
+
4
+ module.exports = catchAsyncError(async (req, res, next) => {
5
+ const authorizationHeader = req.headers.authorization;
6
+
7
+ if (authorizationHeader) {
8
+ // Removes "Bearer " and trims
9
+ const token = authorizationHeader.split(' ')[1].trim();
10
+ // Load user for later middleware/routes to use
11
+ req.user = await User.newUser({ token });
12
+ }
13
+
14
+ return next();
15
+ });