@canmingir/link-express 1.7.8 → 1.7.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canmingir/link-express",
3
- "version": "1.7.8",
3
+ "version": "1.7.10",
4
4
  "description": "",
5
5
  "main": "index.ts",
6
6
  "types": "index.ts",
@@ -57,6 +57,7 @@
57
57
  "@aws-sdk/client-dynamodb": "^3.614.0",
58
58
  "@aws-sdk/lib-dynamodb": "^3.614.0",
59
59
  "@elastic/ecs-pino-format": "^1.5.0",
60
+ "aws-jwt-verify": "^5.1.1",
60
61
  "axios": "^1.7.2",
61
62
  "cors": "^2.8.5",
62
63
  "dotenv": "^16.4.5",
@@ -158,8 +158,6 @@ export class EventMetrics {
158
158
  if (!response.ok) {
159
159
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
160
160
  }
161
-
162
- console.log("Metrics pushed to Pushgateway successfully");
163
161
  } catch (err) {
164
162
  console.error("Failed to push metrics to Pushgateway:", err);
165
163
  }
@@ -0,0 +1,11 @@
1
+ import { CognitoJwtVerifier } from "aws-jwt-verify";
2
+
3
+ export const cognitoAccessTokenVerifier = CognitoJwtVerifier.create({
4
+ userPoolId: process.env.COGNITO_USER_POOL_ID!,
5
+ tokenUse: "access",
6
+ clientId: process.env.COGNITO_CLIENT_ID!,
7
+ });
8
+
9
+ export async function verifyCognitoAccessToken(token: string) {
10
+ return cognitoAccessTokenVerifier.verify(token);
11
+ }
@@ -118,7 +118,6 @@ class DBMetrics {
118
118
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
119
119
  }
120
120
 
121
- console.log("[DBMetrics] Metrics pushed to Pushgateway successfully");
122
121
  } catch (err) {
123
122
  console.error("[DBMetrics] Failed to push metrics to Pushgateway:", err);
124
123
  }
@@ -6,6 +6,7 @@ import Permission from "../models/Permission.model";
6
6
  import axios from "axios";
7
7
  import config from "../config";
8
8
  import jwt from "jsonwebtoken";
9
+ import { verifyCognitoAccessToken } from "../lib/cognitoVerifier";
9
10
 
10
11
  const router = express.Router();
11
12
 
@@ -25,6 +26,8 @@ router.post(
25
26
  refreshToken,
26
27
  redirectUri,
27
28
  identityProvider,
29
+ username,
30
+ password,
28
31
  } = Joi.attempt(
29
32
  req.body,
30
33
  Joi.object({
@@ -34,9 +37,11 @@ router.post(
34
37
  refreshToken: Joi.string().optional(),
35
38
  redirectUri: Joi.string().optional(),
36
39
  identityProvider: Joi.string().required(),
40
+ username: Joi.string().optional().allow("admin"),
41
+ password: Joi.string().optional().allow("admin"),
37
42
  })
38
43
  .required()
39
- .options({ stripUnknown: true })
44
+ .options({ stripUnknown: true }),
40
45
  ) as {
41
46
  appId: string;
42
47
  projectId?: string;
@@ -44,8 +49,241 @@ router.post(
44
49
  refreshToken?: string;
45
50
  redirectUri?: string;
46
51
  identityProvider: string;
52
+ username?: string;
53
+ password?: string;
47
54
  };
48
55
 
56
+ if (username && password && identityProvider.toUpperCase() === "DEMO") {
57
+ if (username !== "admin" || password !== "admin") {
58
+ throw new AuthenticationError("Invalid demo credentials");
59
+ }
60
+
61
+ const userId = "1001";
62
+
63
+ let accessToken: string;
64
+
65
+ if (projectId) {
66
+ const permissions = await Permission.findAll({
67
+ where: { userId, projectId, appId },
68
+ });
69
+
70
+ if (!permissions.length) {
71
+ accessToken = jwt.sign(
72
+ {
73
+ sub: userId,
74
+ iss: "nuc",
75
+ aid: appId,
76
+ aud: projectId,
77
+ oid: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
78
+ rls: ["OWNER"],
79
+ identityProvider: "DEMO",
80
+ iat: Math.floor(Date.now() / 1000),
81
+ },
82
+ process.env.JWT_SECRET as string,
83
+ { expiresIn: "12h" },
84
+ );
85
+ } else {
86
+ accessToken = jwt.sign(
87
+ {
88
+ sub: userId,
89
+ iss: "nuc",
90
+ aud: projectId,
91
+ oid: permissions[0].organizationId,
92
+ aid: appId,
93
+ rls: permissions.map((p) => p.role),
94
+ identityProvider: "DEMO",
95
+ iat: Math.floor(Date.now() / 1000),
96
+ },
97
+ process.env.JWT_SECRET as string,
98
+ { expiresIn: "12h" },
99
+ );
100
+ }
101
+ } else {
102
+ accessToken = jwt.sign(
103
+ {
104
+ sub: userId,
105
+ iss: "nuc",
106
+ aid: appId,
107
+ identityProvider: "DEMO",
108
+ iat: Math.floor(Date.now() / 1000),
109
+ },
110
+ process.env.JWT_SECRET as string,
111
+ { expiresIn: "12h" },
112
+ );
113
+ }
114
+
115
+ const refreshToken = jwt.sign(
116
+ {
117
+ sub: userId,
118
+ type: "refresh",
119
+ identityProvider: "DEMO",
120
+ iat: Math.floor(Date.now() / 1000),
121
+ },
122
+ process.env.JWT_SECRET as string,
123
+ { expiresIn: "30d" },
124
+ );
125
+
126
+ return res.status(200).json({
127
+ accessToken,
128
+ refreshToken,
129
+ });
130
+ }
131
+
132
+ if (identityProvider.toUpperCase() === "COGNITO") {
133
+ if (!refreshToken) {
134
+ throw new AuthenticationError("Missing token");
135
+ }
136
+
137
+ const maybeInternal = jwt.decode(refreshToken) as any;
138
+
139
+ const isInternalRefresh =
140
+ maybeInternal?.type === "refresh" &&
141
+ (maybeInternal?.identityProvider || "").toUpperCase() === "COGNITO";
142
+
143
+ if (isInternalRefresh) {
144
+ const verified = jwt.verify(
145
+ refreshToken,
146
+ process.env.JWT_SECRET as string,
147
+ ) as { sub: string; type: string; identityProvider: string };
148
+
149
+ const userId = verified.sub;
150
+
151
+ let accessToken: string;
152
+
153
+ if (projectId) {
154
+ const permissions = await Permission.findAll({
155
+ where: { userId, projectId, appId },
156
+ });
157
+
158
+ if (!permissions.length) {
159
+ accessToken = jwt.sign(
160
+ {
161
+ sub: userId,
162
+ iss: "nuc",
163
+ aid: appId,
164
+ aud: projectId,
165
+ oid: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
166
+ rls: ["OWNER"],
167
+ identityProvider: "COGNITO",
168
+ iat: Math.floor(Date.now() / 1000),
169
+ },
170
+ process.env.JWT_SECRET as string,
171
+ { expiresIn: "12h" },
172
+ );
173
+ } else {
174
+ accessToken = jwt.sign(
175
+ {
176
+ sub: userId,
177
+ iss: "nuc",
178
+ aud: projectId,
179
+ oid: permissions[0].organizationId,
180
+ aid: appId,
181
+ rls: permissions.map((p) => p.role),
182
+ identityProvider: "COGNITO",
183
+ iat: Math.floor(Date.now() / 1000),
184
+ },
185
+ process.env.JWT_SECRET as string,
186
+ { expiresIn: "12h" },
187
+ );
188
+ }
189
+ } else {
190
+ accessToken = jwt.sign(
191
+ {
192
+ sub: userId,
193
+ iss: "nuc",
194
+ aid: appId,
195
+ identityProvider: "COGNITO",
196
+ iat: Math.floor(Date.now() / 1000),
197
+ },
198
+ process.env.JWT_SECRET as string,
199
+ { expiresIn: "12h" },
200
+ );
201
+ }
202
+
203
+ return res.status(200).json({
204
+ accessToken,
205
+ refreshToken,
206
+ });
207
+ }
208
+
209
+ if (refreshToken.split(".").length !== 3) {
210
+ throw new AuthenticationError(
211
+ "Expected Cognito access token (JWT) but received a non-JWT token",
212
+ );
213
+ }
214
+
215
+ const decodedCognito = await verifyCognitoAccessToken(refreshToken);
216
+ const userId = decodedCognito.sub;
217
+
218
+ let accessToken: string;
219
+
220
+ if (projectId) {
221
+ const permissions = await Permission.findAll({
222
+ where: { userId, projectId, appId },
223
+ });
224
+
225
+ if (!permissions.length) {
226
+ accessToken = jwt.sign(
227
+ {
228
+ sub: userId,
229
+ iss: "nuc",
230
+ aid: appId,
231
+ aud: projectId,
232
+ oid: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
233
+ rls: ["OWNER"],
234
+ identityProvider: "COGNITO",
235
+ iat: Math.floor(Date.now() / 1000),
236
+ },
237
+ process.env.JWT_SECRET as string,
238
+ { expiresIn: "12h" },
239
+ );
240
+ } else {
241
+ accessToken = jwt.sign(
242
+ {
243
+ sub: userId,
244
+ iss: "nuc",
245
+ aud: projectId,
246
+ oid: permissions[0].organizationId,
247
+ aid: appId,
248
+ rls: permissions.map((p) => p.role),
249
+ identityProvider: "COGNITO",
250
+ iat: Math.floor(Date.now() / 1000),
251
+ },
252
+ process.env.JWT_SECRET as string,
253
+ { expiresIn: "12h" },
254
+ );
255
+ }
256
+ } else {
257
+ accessToken = jwt.sign(
258
+ {
259
+ sub: userId,
260
+ iss: "nuc",
261
+ aid: appId,
262
+ identityProvider: "COGNITO",
263
+ iat: Math.floor(Date.now() / 1000),
264
+ },
265
+ process.env.JWT_SECRET as string,
266
+ { expiresIn: "12h" },
267
+ );
268
+ }
269
+
270
+ const internalRefreshToken = jwt.sign(
271
+ {
272
+ sub: userId,
273
+ type: "refresh",
274
+ identityProvider: "COGNITO",
275
+ iat: Math.floor(Date.now() / 1000),
276
+ },
277
+ process.env.JWT_SECRET as string,
278
+ { expiresIn: "30d" },
279
+ );
280
+
281
+ return res.status(200).json({
282
+ accessToken,
283
+ refreshToken: internalRefreshToken,
284
+ });
285
+ }
286
+
49
287
  if (!code && !refreshToken) {
50
288
  return res.status(400).send("Missing OAuth Code and Refresh Token");
51
289
  }
@@ -75,7 +313,9 @@ router.post(
75
313
  params.append("client_id", providerConfig.clientId);
76
314
  params.append(
77
315
  "client_secret",
78
- process.env[`${identityProvider.toUpperCase()}_CLIENT_SECRET`] as string
316
+ process.env[
317
+ `${identityProvider.toUpperCase()}_CLIENT_SECRET`
318
+ ] as string,
79
319
  );
80
320
  params.append("code", code);
81
321
  params.append("redirect_uri", redirectUri);
@@ -95,13 +335,13 @@ router.post(
95
335
 
96
336
  if (tokenResponse.data.error) {
97
337
  throw new AuthorizationError(
98
- tokenResponse.data.error_description || tokenResponse.data.error
338
+ tokenResponse.data.error_description || tokenResponse.data.error,
99
339
  );
100
340
  }
101
341
 
102
342
  if (!tokenResponse.data.access_token) {
103
343
  throw new AuthenticationError(
104
- "No access token received from OAuth provider"
344
+ "No access token received from OAuth provider",
105
345
  );
106
346
  }
107
347
 
@@ -120,7 +360,7 @@ router.post(
120
360
  Accept: "application/json",
121
361
  },
122
362
  timeout: 10000,
123
- }
363
+ },
124
364
  );
125
365
 
126
366
  console.log("User info response:", userResponse.data);
@@ -143,7 +383,7 @@ router.post(
143
383
  fallbackField: project.oauth?.jwt.identifier,
144
384
  });
145
385
  throw new Error(
146
- `Cannot find user identifier in ${identityProvider} OAuth response`
386
+ `Cannot find user identifier in ${identityProvider} OAuth response`,
147
387
  );
148
388
  }
149
389
 
@@ -164,7 +404,7 @@ router.post(
164
404
  iat: Math.floor(Date.now() / 1000),
165
405
  },
166
406
  process.env.JWT_SECRET as string,
167
- { expiresIn: "12h" }
407
+ { expiresIn: "12h" },
168
408
  );
169
409
  } else {
170
410
  accessToken = jwt.sign(
@@ -179,7 +419,7 @@ router.post(
179
419
  iat: Math.floor(Date.now() / 1000),
180
420
  },
181
421
  process.env.JWT_SECRET as string,
182
- { expiresIn: "12h" }
422
+ { expiresIn: "12h" },
183
423
  );
184
424
  }
185
425
  } else {
@@ -192,7 +432,7 @@ router.post(
192
432
  iat: Math.floor(Date.now() / 1000),
193
433
  },
194
434
  process.env.JWT_SECRET as string,
195
- { expiresIn: "12h" }
435
+ { expiresIn: "12h" },
196
436
  );
197
437
  }
198
438
 
@@ -200,7 +440,7 @@ router.post(
200
440
  accessToken,
201
441
  refreshToken: newRefreshToken,
202
442
  });
203
- }
443
+ },
204
444
  );
205
445
 
206
446
  router.get("/user", async (req: Request, res: Response): Promise<Response> => {
@@ -246,6 +486,21 @@ router.get("/user", async (req: Request, res: Response): Promise<Response> => {
246
486
  });
247
487
  }
248
488
 
489
+ if (identityProvider.toUpperCase() === "COGNITO") {
490
+ const avatarSeed = userId;
491
+ const avatarUrl = `https://api.dicebear.com/7.x/bottts/svg?seed=${avatarSeed}`;
492
+
493
+ return res.status(200).json({
494
+ user: {
495
+ id: userId,
496
+ identityProvider: "COGNITO",
497
+ name: "Cognito",
498
+ displayName: "Cognito Admin",
499
+ avatarUrl,
500
+ },
501
+ });
502
+ }
503
+
249
504
  const providerConfig = project.oauth?.providers[identityProvider] as {
250
505
  userUrl: string;
251
506
  userFields: {
@@ -267,7 +522,7 @@ router.get("/user", async (req: Request, res: Response): Promise<Response> => {
267
522
  Accept: "application/json",
268
523
  },
269
524
  timeout: 10000,
270
- }
525
+ },
271
526
  );
272
527
 
273
528
  const userFieldMapping = providerConfig.userFields;
@@ -287,97 +542,4 @@ router.get("/user", async (req: Request, res: Response): Promise<Response> => {
287
542
  });
288
543
  });
289
544
 
290
- router.post("/demo", async (req: Request, res: Response): Promise<Response> => {
291
- const { appId, projectId, username, password } = Joi.attempt(
292
- req.body,
293
- Joi.object({
294
- appId: Joi.string().required(),
295
- projectId: Joi.string().optional(),
296
- username: Joi.string().required(),
297
- password: Joi.string().required(),
298
- })
299
- .required()
300
- .options({ stripUnknown: true })
301
- ) as {
302
- appId: string;
303
- projectId?: string;
304
- username: string;
305
- password: string;
306
- };
307
-
308
- if (username !== "admin" || password !== "admin") {
309
- throw new AuthenticationError("Invalid demo credentials");
310
- }
311
-
312
- const userId = "1001";
313
-
314
- let accessToken: string;
315
-
316
- if (projectId) {
317
- const permissions = await Permission.findAll({
318
- where: { userId, projectId, appId },
319
- });
320
-
321
- if (!permissions.length) {
322
- accessToken = jwt.sign(
323
- {
324
- sub: userId,
325
- iss: "nuc",
326
- aid: appId,
327
- aud: projectId,
328
- oid: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
329
- rls: "OWNER",
330
- identityProvider: "DEMO",
331
- iat: Math.floor(Date.now() / 1000),
332
- },
333
- process.env.JWT_SECRET as string,
334
- { expiresIn: "12h" }
335
- );
336
- } else {
337
- accessToken = jwt.sign(
338
- {
339
- sub: userId,
340
- iss: "nuc",
341
- aud: projectId,
342
- oid: permissions[0].organizationId,
343
- aid: appId,
344
- rls: permissions.map((p) => p.role),
345
- identityProvider: "DEMO",
346
- iat: Math.floor(Date.now() / 1000),
347
- },
348
- process.env.JWT_SECRET as string,
349
- { expiresIn: "12h" }
350
- );
351
- }
352
- } else {
353
- accessToken = jwt.sign(
354
- {
355
- sub: userId,
356
- iss: "nuc",
357
- aid: appId,
358
- identityProvider: "DEMO",
359
- iat: Math.floor(Date.now() / 1000),
360
- },
361
- process.env.JWT_SECRET as string,
362
- { expiresIn: "12h" }
363
- );
364
- }
365
-
366
- const refreshToken = jwt.sign(
367
- {
368
- sub: userId,
369
- type: "refresh",
370
- identityProvider: "DEMO",
371
- iat: Math.floor(Date.now() / 1000),
372
- },
373
- process.env.JWT_SECRET as string,
374
- { expiresIn: "30d" }
375
- );
376
-
377
- return res.status(200).json({
378
- accessToken,
379
- refreshToken,
380
- });
381
- });
382
-
383
545
  export default router;
@@ -3,7 +3,7 @@
3
3
  "seed": [
4
4
  {
5
5
  "id": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
6
- "name": "Rebellion Coffee Shop"
6
+ "name": "Telco Corp."
7
7
  },
8
8
  {
9
9
  "id": "1c063446-7e78-432a-a273-34f481d0f0c3",
@@ -160,6 +160,46 @@
160
160
  "projectId": "21d2530b-4657-4ac0-b8cd-1a9f82786e32",
161
161
  "userId": "90180086",
162
162
  "role": "OWNER"
163
+ },
164
+ {
165
+ "id": "0baa8c53-532f-4b16-b049-34694ba0c088",
166
+ "appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
167
+ "organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
168
+ "projectId": "cb16e069-6214-47f1-9922-1f7fe7629525",
169
+ "userId": "64a8f4b8-5081-70c4-71f4-de20c302da12",
170
+ "role": "OWNER"
171
+ },
172
+ {
173
+ "id": "dcec22f6-881e-4c52-ac4b-5177b92375a2",
174
+ "appId": "10b7bc8c-a49c-4002-b0ec-63599e4b5210",
175
+ "organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
176
+ "projectId": "add6dfa4-45ba-4da2-bc5c-5a529610b52f",
177
+ "userId": "64a8f4b8-5081-70c4-71f4-de20c302da12",
178
+ "role": "OWNER"
179
+ },
180
+ {
181
+ "id": "58cc2585-fe2f-4a83-a257-17cda12a2dee",
182
+ "appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
183
+ "organizationId": "1c063446-7e78-432a-a273-34f481d0f0c3",
184
+ "projectId": "0c756054-2d28-4f87-9b12-8023a79136a5",
185
+ "userId": "64a8f4b8-5081-70c4-71f4-de20c302da12",
186
+ "role": "OWNER"
187
+ },
188
+ {
189
+ "id": "0fbcba2b-c988-4ac9-b896-1704fcb59909",
190
+ "appId": "10b7bc8c-a49c-4002-b0ec-63599e4b5210",
191
+ "organizationId": "1c063446-7e78-432a-a273-34f481d0f0c3",
192
+ "projectId": "e6d4744d-a11b-4c75-acad-e24a02903729",
193
+ "userId": "64a8f4b8-5081-70c4-71f4-de20c302da12",
194
+ "role": "OWNER"
195
+ },
196
+ {
197
+ "id": "121112c3-282b-4127-9314-20d0053d998f",
198
+ "appId": "977f5f57-8936-4388-8eb0-00a512cf01cc",
199
+ "organizationId": "5459ab03-204a-4627-bdde-667b7802cb35",
200
+ "projectId": "21d2530b-4657-4ac0-b8cd-1a9f82786e32",
201
+ "userId": "64a8f4b8-5081-70c4-71f4-de20c302da12",
202
+ "role": "OWNER"
163
203
  }
164
204
  ]
165
205
  }
@@ -10,7 +10,7 @@
10
10
  },
11
11
  {
12
12
  "id": "add6dfa4-45ba-4da2-bc5c-5a529610b52f",
13
- "name": "Telco Corp.",
13
+ "name": "Telco Customer Service Team",
14
14
  "coach": "Elijah",
15
15
  "icon": ":maki:communications-tower:",
16
16
  "organizationId": "dfb990bb-81dd-4584-82ce-050eb8f6a12f"