@canmingir/link-express 1.7.9 → 1.7.11

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.9",
3
+ "version": "1.7.11",
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
 
@@ -15,6 +16,26 @@ if (!project) {
15
16
  throw new Error("Project configuration is required");
16
17
  }
17
18
 
19
+ const providers = project?.oauth?.providers || {};
20
+
21
+ const identityProviders: Record<string, typeof providers[string]> = {};
22
+
23
+ for (const [key, value] of Object.entries(providers)) {
24
+ const identityProviderKey = key.toLowerCase();
25
+
26
+ if (identityProviders[identityProviderKey]) {
27
+ throw new Error(
28
+ `Duplicate OAuth provider configuration detected for key "${key}". Provider keys must be unique in a case-insensitive manner.`,
29
+ );
30
+ }
31
+
32
+ identityProviders[identityProviderKey] = value;
33
+ }
34
+
35
+ function getProviderConfig(identityProvider: string) {
36
+ return identityProviders[identityProvider.toLowerCase()];
37
+ }
38
+
18
39
  router.post(
19
40
  "/",
20
41
  async (req: Request, res: Response): Promise<Response | void> => {
@@ -25,6 +46,8 @@ router.post(
25
46
  refreshToken,
26
47
  redirectUri,
27
48
  identityProvider,
49
+ username,
50
+ password,
28
51
  } = Joi.attempt(
29
52
  req.body,
30
53
  Joi.object({
@@ -34,9 +57,11 @@ router.post(
34
57
  refreshToken: Joi.string().optional(),
35
58
  redirectUri: Joi.string().optional(),
36
59
  identityProvider: Joi.string().required(),
60
+ username: Joi.string().optional().allow("admin"),
61
+ password: Joi.string().optional().allow("admin"),
37
62
  })
38
63
  .required()
39
- .options({ stripUnknown: true })
64
+ .options({ stripUnknown: true }),
40
65
  ) as {
41
66
  appId: string;
42
67
  projectId?: string;
@@ -44,13 +69,246 @@ router.post(
44
69
  refreshToken?: string;
45
70
  redirectUri?: string;
46
71
  identityProvider: string;
72
+ username?: string;
73
+ password?: string;
47
74
  };
48
75
 
76
+ if (username && password && identityProvider.toUpperCase() === "DEMO") {
77
+ if (username !== "admin" || password !== "admin") {
78
+ throw new AuthenticationError("Invalid demo credentials");
79
+ }
80
+
81
+ const userId = "1001";
82
+
83
+ let accessToken: string;
84
+
85
+ if (projectId) {
86
+ const permissions = await Permission.findAll({
87
+ where: { userId, projectId, appId },
88
+ });
89
+
90
+ if (!permissions.length) {
91
+ accessToken = jwt.sign(
92
+ {
93
+ sub: userId,
94
+ iss: "nuc",
95
+ aid: appId,
96
+ aud: projectId,
97
+ oid: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
98
+ rls: ["OWNER"],
99
+ identityProvider: "DEMO",
100
+ iat: Math.floor(Date.now() / 1000),
101
+ },
102
+ process.env.JWT_SECRET as string,
103
+ { expiresIn: "12h" },
104
+ );
105
+ } else {
106
+ accessToken = jwt.sign(
107
+ {
108
+ sub: userId,
109
+ iss: "nuc",
110
+ aud: projectId,
111
+ oid: permissions[0].organizationId,
112
+ aid: appId,
113
+ rls: permissions.map((p) => p.role),
114
+ identityProvider: "DEMO",
115
+ iat: Math.floor(Date.now() / 1000),
116
+ },
117
+ process.env.JWT_SECRET as string,
118
+ { expiresIn: "12h" },
119
+ );
120
+ }
121
+ } else {
122
+ accessToken = jwt.sign(
123
+ {
124
+ sub: userId,
125
+ iss: "nuc",
126
+ aid: appId,
127
+ identityProvider: "DEMO",
128
+ iat: Math.floor(Date.now() / 1000),
129
+ },
130
+ process.env.JWT_SECRET as string,
131
+ { expiresIn: "12h" },
132
+ );
133
+ }
134
+
135
+ const refreshToken = jwt.sign(
136
+ {
137
+ sub: userId,
138
+ type: "refresh",
139
+ identityProvider: "DEMO",
140
+ iat: Math.floor(Date.now() / 1000),
141
+ },
142
+ process.env.JWT_SECRET as string,
143
+ { expiresIn: "30d" },
144
+ );
145
+
146
+ return res.status(200).json({
147
+ accessToken,
148
+ refreshToken,
149
+ });
150
+ }
151
+
152
+ if (identityProvider.toUpperCase() === "COGNITO") {
153
+ if (!refreshToken) {
154
+ throw new AuthenticationError("Missing token");
155
+ }
156
+
157
+ const maybeInternal = jwt.decode(refreshToken) as any;
158
+
159
+ const isInternalRefresh =
160
+ maybeInternal?.type === "refresh" &&
161
+ (maybeInternal?.identityProvider || "").toUpperCase() === "COGNITO";
162
+
163
+ if (isInternalRefresh) {
164
+ const verified = jwt.verify(
165
+ refreshToken,
166
+ process.env.JWT_SECRET as string,
167
+ ) as { sub: string; type: string; identityProvider: string };
168
+
169
+ const userId = verified.sub;
170
+
171
+ let accessToken: string;
172
+
173
+ if (projectId) {
174
+ const permissions = await Permission.findAll({
175
+ where: { userId, projectId, appId },
176
+ });
177
+
178
+ if (!permissions.length) {
179
+ accessToken = jwt.sign(
180
+ {
181
+ sub: userId,
182
+ iss: "nuc",
183
+ aid: appId,
184
+ aud: projectId,
185
+ oid: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
186
+ rls: ["OWNER"],
187
+ identityProvider: "COGNITO",
188
+ iat: Math.floor(Date.now() / 1000),
189
+ },
190
+ process.env.JWT_SECRET as string,
191
+ { expiresIn: "12h" },
192
+ );
193
+ } else {
194
+ accessToken = jwt.sign(
195
+ {
196
+ sub: userId,
197
+ iss: "nuc",
198
+ aud: projectId,
199
+ oid: permissions[0].organizationId,
200
+ aid: appId,
201
+ rls: permissions.map((p) => p.role),
202
+ identityProvider: "COGNITO",
203
+ iat: Math.floor(Date.now() / 1000),
204
+ },
205
+ process.env.JWT_SECRET as string,
206
+ { expiresIn: "12h" },
207
+ );
208
+ }
209
+ } else {
210
+ accessToken = jwt.sign(
211
+ {
212
+ sub: userId,
213
+ iss: "nuc",
214
+ aid: appId,
215
+ identityProvider: "COGNITO",
216
+ iat: Math.floor(Date.now() / 1000),
217
+ },
218
+ process.env.JWT_SECRET as string,
219
+ { expiresIn: "12h" },
220
+ );
221
+ }
222
+
223
+ return res.status(200).json({
224
+ accessToken,
225
+ refreshToken,
226
+ });
227
+ }
228
+
229
+ if (refreshToken.split(".").length !== 3) {
230
+ throw new AuthenticationError(
231
+ "Expected Cognito access token (JWT) but received a non-JWT token",
232
+ );
233
+ }
234
+
235
+ const decodedCognito = await verifyCognitoAccessToken(refreshToken);
236
+ const userId = decodedCognito.sub;
237
+
238
+ let accessToken: string;
239
+
240
+ if (projectId) {
241
+ const permissions = await Permission.findAll({
242
+ where: { userId, projectId, appId },
243
+ });
244
+
245
+ if (!permissions.length) {
246
+ accessToken = jwt.sign(
247
+ {
248
+ sub: userId,
249
+ iss: "nuc",
250
+ aid: appId,
251
+ aud: projectId,
252
+ oid: "dfb990bb-81dd-4584-82ce-050eb8f6a12f",
253
+ rls: ["OWNER"],
254
+ identityProvider: "COGNITO",
255
+ iat: Math.floor(Date.now() / 1000),
256
+ },
257
+ process.env.JWT_SECRET as string,
258
+ { expiresIn: "12h" },
259
+ );
260
+ } else {
261
+ accessToken = jwt.sign(
262
+ {
263
+ sub: userId,
264
+ iss: "nuc",
265
+ aud: projectId,
266
+ oid: permissions[0].organizationId,
267
+ aid: appId,
268
+ rls: permissions.map((p) => p.role),
269
+ identityProvider: "COGNITO",
270
+ iat: Math.floor(Date.now() / 1000),
271
+ },
272
+ process.env.JWT_SECRET as string,
273
+ { expiresIn: "12h" },
274
+ );
275
+ }
276
+ } else {
277
+ accessToken = jwt.sign(
278
+ {
279
+ sub: userId,
280
+ iss: "nuc",
281
+ aid: appId,
282
+ identityProvider: "COGNITO",
283
+ iat: Math.floor(Date.now() / 1000),
284
+ },
285
+ process.env.JWT_SECRET as string,
286
+ { expiresIn: "12h" },
287
+ );
288
+ }
289
+
290
+ const internalRefreshToken = jwt.sign(
291
+ {
292
+ sub: userId,
293
+ type: "refresh",
294
+ identityProvider: "COGNITO",
295
+ iat: Math.floor(Date.now() / 1000),
296
+ },
297
+ process.env.JWT_SECRET as string,
298
+ { expiresIn: "30d" },
299
+ );
300
+
301
+ return res.status(200).json({
302
+ accessToken,
303
+ refreshToken: internalRefreshToken,
304
+ });
305
+ }
306
+
49
307
  if (!code && !refreshToken) {
50
308
  return res.status(400).send("Missing OAuth Code and Refresh Token");
51
309
  }
52
310
 
53
- const providerConfig = project.oauth?.providers[identityProvider] as {
311
+ const providerConfig = getProviderConfig(identityProvider) as {
54
312
  clientId: string;
55
313
  tokenUrl: string;
56
314
  userUrl: string;
@@ -75,7 +333,9 @@ router.post(
75
333
  params.append("client_id", providerConfig.clientId);
76
334
  params.append(
77
335
  "client_secret",
78
- process.env[`${identityProvider.toUpperCase()}_CLIENT_SECRET`] as string
336
+ process.env[
337
+ `${identityProvider.toUpperCase()}_CLIENT_SECRET`
338
+ ] as string,
79
339
  );
80
340
  params.append("code", code);
81
341
  params.append("redirect_uri", redirectUri);
@@ -95,13 +355,13 @@ router.post(
95
355
 
96
356
  if (tokenResponse.data.error) {
97
357
  throw new AuthorizationError(
98
- tokenResponse.data.error_description || tokenResponse.data.error
358
+ tokenResponse.data.error_description || tokenResponse.data.error,
99
359
  );
100
360
  }
101
361
 
102
362
  if (!tokenResponse.data.access_token) {
103
363
  throw new AuthenticationError(
104
- "No access token received from OAuth provider"
364
+ "No access token received from OAuth provider",
105
365
  );
106
366
  }
107
367
 
@@ -120,7 +380,7 @@ router.post(
120
380
  Accept: "application/json",
121
381
  },
122
382
  timeout: 10000,
123
- }
383
+ },
124
384
  );
125
385
 
126
386
  console.log("User info response:", userResponse.data);
@@ -143,7 +403,7 @@ router.post(
143
403
  fallbackField: project.oauth?.jwt.identifier,
144
404
  });
145
405
  throw new Error(
146
- `Cannot find user identifier in ${identityProvider} OAuth response`
406
+ `Cannot find user identifier in ${identityProvider} OAuth response`,
147
407
  );
148
408
  }
149
409
 
@@ -164,7 +424,7 @@ router.post(
164
424
  iat: Math.floor(Date.now() / 1000),
165
425
  },
166
426
  process.env.JWT_SECRET as string,
167
- { expiresIn: "12h" }
427
+ { expiresIn: "12h" },
168
428
  );
169
429
  } else {
170
430
  accessToken = jwt.sign(
@@ -179,7 +439,7 @@ router.post(
179
439
  iat: Math.floor(Date.now() / 1000),
180
440
  },
181
441
  process.env.JWT_SECRET as string,
182
- { expiresIn: "12h" }
442
+ { expiresIn: "12h" },
183
443
  );
184
444
  }
185
445
  } else {
@@ -192,7 +452,7 @@ router.post(
192
452
  iat: Math.floor(Date.now() / 1000),
193
453
  },
194
454
  process.env.JWT_SECRET as string,
195
- { expiresIn: "12h" }
455
+ { expiresIn: "12h" },
196
456
  );
197
457
  }
198
458
 
@@ -200,7 +460,7 @@ router.post(
200
460
  accessToken,
201
461
  refreshToken: newRefreshToken,
202
462
  });
203
- }
463
+ },
204
464
  );
205
465
 
206
466
  router.get("/user", async (req: Request, res: Response): Promise<Response> => {
@@ -246,7 +506,22 @@ router.get("/user", async (req: Request, res: Response): Promise<Response> => {
246
506
  });
247
507
  }
248
508
 
249
- const providerConfig = project.oauth?.providers[identityProvider] as {
509
+ if (identityProvider.toUpperCase() === "COGNITO") {
510
+ const avatarSeed = userId;
511
+ const avatarUrl = `https://api.dicebear.com/7.x/bottts/svg?seed=${avatarSeed}`;
512
+
513
+ return res.status(200).json({
514
+ user: {
515
+ id: userId,
516
+ identityProvider: "COGNITO",
517
+ name: "Cognito",
518
+ displayName: "Cognito Admin",
519
+ avatarUrl,
520
+ },
521
+ });
522
+ }
523
+
524
+ const providerConfig = getProviderConfig(identityProvider) as {
250
525
  userUrl: string;
251
526
  userFields: {
252
527
  name: string;
@@ -267,7 +542,7 @@ router.get("/user", async (req: Request, res: Response): Promise<Response> => {
267
542
  Accept: "application/json",
268
543
  },
269
544
  timeout: 10000,
270
- }
545
+ },
271
546
  );
272
547
 
273
548
  const userFieldMapping = providerConfig.userFields;
@@ -287,97 +562,4 @@ router.get("/user", async (req: Request, res: Response): Promise<Response> => {
287
562
  });
288
563
  });
289
564
 
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
565
  export default router;
@@ -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
  }