@internetderdinge/api 1.229.0 → 1.229.2

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.
Files changed (171) hide show
  1. package/dist/src/accounts/accounts.controller.js +89 -0
  2. package/dist/src/accounts/accounts.route.js +101 -0
  3. package/dist/src/accounts/accounts.schemas.js +12 -0
  4. package/dist/src/accounts/accounts.service.js +65 -0
  5. package/dist/src/accounts/accounts.validation.js +99 -0
  6. package/dist/src/accounts/auth0.service.js +188 -0
  7. package/dist/src/config/config.js +48 -0
  8. package/dist/src/config/logger.js +27 -0
  9. package/dist/src/config/morgan.js +16 -0
  10. package/dist/src/config/passport.cjs +28 -0
  11. package/dist/src/config/roles.js +11 -0
  12. package/dist/src/config/tokens.cjs +10 -0
  13. package/dist/src/devices/devices.controller.js +172 -0
  14. package/dist/src/devices/devices.model.js +94 -0
  15. package/dist/src/devices/devices.route.js +153 -0
  16. package/dist/src/devices/devices.schemas.js +84 -0
  17. package/dist/src/devices/devices.service.js +198 -0
  18. package/dist/src/devices/devices.types.js +1 -0
  19. package/dist/src/devices/devices.validation.js +257 -0
  20. package/dist/src/devicesNotifications/devicesNotifications.controller.js +69 -0
  21. package/dist/src/devicesNotifications/devicesNotifications.model.js +39 -0
  22. package/dist/src/devicesNotifications/devicesNotifications.route.js +124 -0
  23. package/dist/src/devicesNotifications/devicesNotifications.schemas.js +10 -0
  24. package/dist/src/devicesNotifications/devicesNotifications.service.js +181 -0
  25. package/dist/src/devicesNotifications/devicesNotifications.validation.js +46 -0
  26. package/dist/src/email/email.service.js +580 -0
  27. package/dist/src/files/upload.service.js +124 -0
  28. package/dist/src/i18n/i18n.js +38 -0
  29. package/dist/src/i18n/saveMissingLocalJsonBackend.js +53 -0
  30. package/dist/src/i18n/types.js +1 -0
  31. package/dist/src/index.js +48 -0
  32. package/dist/src/iotdevice/iotdevice.controller.js +96 -0
  33. package/dist/src/iotdevice/iotdevice.model.js +17 -0
  34. package/dist/src/iotdevice/iotdevice.route.js +143 -0
  35. package/dist/src/iotdevice/iotdevice.schemas.js +60 -0
  36. package/dist/src/iotdevice/iotdevice.service.js +579 -0
  37. package/dist/src/iotdevice/iotdevice.types.js +1 -0
  38. package/dist/src/iotdevice/iotdevice.validation.js +54 -0
  39. package/dist/src/middlewares/auth.js +75 -0
  40. package/dist/src/middlewares/checkJwt.cjs +17 -0
  41. package/dist/src/middlewares/error.js +36 -0
  42. package/dist/src/middlewares/mongooseValidations/ensureSameOrganization.js +13 -0
  43. package/dist/src/middlewares/rateLimiter.js +7 -0
  44. package/dist/src/middlewares/validate.js +18 -0
  45. package/dist/src/middlewares/validateAction.js +35 -0
  46. package/dist/src/middlewares/validateAdmin.js +18 -0
  47. package/dist/src/middlewares/validateAi.js +16 -0
  48. package/dist/src/middlewares/validateCurrentAuthUser.js +17 -0
  49. package/dist/src/middlewares/validateCurrentUser.js +20 -0
  50. package/dist/src/middlewares/validateDevice.js +98 -0
  51. package/dist/src/middlewares/validateDeviceUserOrganization.js +26 -0
  52. package/dist/src/middlewares/validateOrganization.js +63 -0
  53. package/dist/src/middlewares/validateQuerySearchUserAndOrganization.js +44 -0
  54. package/dist/src/middlewares/validateTokens.js +23 -0
  55. package/dist/src/middlewares/validateUser.js +38 -0
  56. package/dist/src/middlewares/validateZod.js +33 -0
  57. package/dist/src/models/plugins/index.js +4 -0
  58. package/dist/src/models/plugins/paginate.plugin.js +117 -0
  59. package/dist/src/models/plugins/paginateNew.plugin.js +185 -0
  60. package/dist/src/models/plugins/simplePopulate.js +16 -0
  61. package/dist/src/models/plugins/toJSON.plugin.js +35 -0
  62. package/dist/src/organizations/organizations.controller.js +64 -0
  63. package/dist/src/organizations/organizations.model.js +41 -0
  64. package/dist/src/organizations/organizations.route.js +98 -0
  65. package/dist/src/organizations/organizations.schemas.js +7 -0
  66. package/dist/src/organizations/organizations.service.js +59 -0
  67. package/dist/src/organizations/organizations.validation.js +62 -0
  68. package/dist/src/pdf/pdf.controller.js +24 -0
  69. package/dist/src/pdf/pdf.route.js +22 -0
  70. package/dist/src/pdf/pdf.schemas.js +6 -0
  71. package/dist/src/pdf/pdf.service.js +65 -0
  72. package/dist/src/pdf/pdf.validation.js +27 -0
  73. package/dist/src/tokens/tokens.controller.js +60 -0
  74. package/dist/src/tokens/tokens.model.js +17 -0
  75. package/dist/src/tokens/tokens.route.js +52 -0
  76. package/dist/src/tokens/tokens.schemas.js +14 -0
  77. package/dist/src/tokens/tokens.service.js +30 -0
  78. package/dist/src/tokens/tokens.validation.js +9 -0
  79. package/dist/src/types/routeSpec.js +1 -0
  80. package/dist/src/users/users.controller.js +147 -0
  81. package/dist/src/users/users.model.js +50 -0
  82. package/dist/src/users/users.route.js +137 -0
  83. package/dist/src/users/users.schemas.js +69 -0
  84. package/dist/src/users/users.service.js +295 -0
  85. package/dist/src/users/users.types.js +1 -0
  86. package/dist/src/users/users.validation.js +144 -0
  87. package/dist/src/utils/ApiError.js +16 -0
  88. package/dist/src/utils/buildRouterAndDocs.js +72 -0
  89. package/dist/src/utils/catchAsync.js +4 -0
  90. package/dist/src/utils/comparePapers.service.js +32 -0
  91. package/dist/src/utils/deviceUtils.js +63 -0
  92. package/dist/src/utils/filterOptions.js +24 -0
  93. package/dist/src/utils/medicationName.js +10 -0
  94. package/dist/src/utils/pick.js +16 -0
  95. package/dist/src/utils/registerOpenApi.js +67 -0
  96. package/dist/src/utils/urlUtils.js +15 -0
  97. package/dist/src/utils/userName.js +22 -0
  98. package/dist/src/utils/zValidations.js +143 -0
  99. package/dist/src/validations/auth.validation.cjs +53 -0
  100. package/dist/src/validations/custom.validation.js +19 -0
  101. package/dist/src/validations/index.cjs +3 -0
  102. package/dist/tsconfig.tsbuildinfo +1 -0
  103. package/package.json +97 -80
  104. package/scripts/release-and-sync-paperless.mjs +137 -0
  105. package/src/accounts/accounts.controller.ts +1 -0
  106. package/src/accounts/accounts.service.ts +1 -0
  107. package/src/accounts/accounts.validation.ts +8 -5
  108. package/src/accounts/auth0.service.ts +55 -28
  109. package/src/config/config.ts +6 -0
  110. package/src/config/logger.ts +15 -9
  111. package/src/devices/devices.controller.ts +7 -1
  112. package/src/devices/devices.model.ts +4 -1
  113. package/src/devices/devices.schemas.ts +11 -9
  114. package/src/devices/devices.service.ts +1 -0
  115. package/src/devices/devices.types.ts +1 -0
  116. package/src/devices/devices.validation.ts +93 -32
  117. package/src/devicesNotifications/devicesNotifications.controller.ts +57 -28
  118. package/src/devicesNotifications/devicesNotifications.model.ts +20 -12
  119. package/src/devicesNotifications/devicesNotifications.service.ts +35 -17
  120. package/src/files/upload.service.ts +52 -28
  121. package/src/i18n/i18n.ts +1 -1
  122. package/src/i18n/types.ts +1 -0
  123. package/src/index.ts +47 -0
  124. package/src/iotdevice/iotdevice.controller.ts +1 -0
  125. package/src/iotdevice/iotdevice.model.ts +6 -3
  126. package/src/iotdevice/iotdevice.route.ts +85 -76
  127. package/src/iotdevice/iotdevice.service.ts +4 -3
  128. package/src/iotdevice/iotdevice.types.ts +6 -0
  129. package/src/middlewares/auth.ts +2 -8
  130. package/src/middlewares/error.ts +26 -12
  131. package/src/middlewares/mongooseValidations/ensureSameOrganization.ts +4 -3
  132. package/src/middlewares/validateAi.ts +17 -9
  133. package/src/middlewares/validateDevice.ts +1 -0
  134. package/src/middlewares/validateDeviceUserOrganization.ts +1 -0
  135. package/src/middlewares/validateOrganization.ts +1 -1
  136. package/src/middlewares/validateQuerySearchUserAndOrganization.ts +1 -0
  137. package/src/middlewares/validateTokens.ts +2 -1
  138. package/src/middlewares/validateUser.ts +1 -0
  139. package/src/middlewares/validateZod.ts +5 -5
  140. package/src/models/plugins/index.ts +5 -4
  141. package/src/models/plugins/paginate.plugin.ts +26 -16
  142. package/src/models/plugins/paginateNew.plugin.ts +33 -21
  143. package/src/models/plugins/simplePopulate.ts +8 -3
  144. package/src/models/plugins/toJSON.plugin.ts +12 -5
  145. package/src/organizations/organizations.controller.ts +1 -2
  146. package/src/organizations/organizations.model.ts +4 -4
  147. package/src/organizations/organizations.route.ts +1 -1
  148. package/src/organizations/organizations.service.ts +15 -6
  149. package/src/organizations/organizations.validation.ts +1 -1
  150. package/src/pdf/pdf.controller.ts +18 -1
  151. package/src/pdf/pdf.service.ts +25 -16
  152. package/src/tokens/tokens.controller.ts +6 -8
  153. package/src/tokens/tokens.model.ts +3 -1
  154. package/src/tokens/tokens.service.ts +3 -2
  155. package/src/types/express.d.ts +17 -0
  156. package/src/types/mongoose.d.ts +22 -0
  157. package/src/users/users.controller.ts +8 -9
  158. package/src/users/users.model.ts +6 -5
  159. package/src/users/users.route.ts +0 -1
  160. package/src/users/users.service.ts +16 -0
  161. package/src/users/users.types.ts +1 -0
  162. package/src/users/users.validation.ts +6 -2
  163. package/src/utils/ApiError.ts +8 -1
  164. package/src/utils/buildRouterAndDocs.ts +57 -22
  165. package/src/utils/catchAsync.ts +27 -3
  166. package/src/utils/medicationName.ts +5 -4
  167. package/src/utils/pick.ts +5 -1
  168. package/src/utils/registerOpenApi.ts +75 -24
  169. package/src/utils/userName.ts +1 -0
  170. package/src/utils/zValidations.ts +98 -27
  171. package/tsconfig.json +13 -4
@@ -0,0 +1,89 @@
1
+ import httpStatus from "http-status";
2
+ import deviceNotifications from "../devicesNotifications/devicesNotifications.service";
3
+ import ApiError from "../utils/ApiError.js";
4
+ import catchAsync from "../utils/catchAsync.js";
5
+ import * as accountsService from "./accounts.service.js";
6
+ const getAccountById = catchAsync(async (req, res) => {
7
+ const account = await accountsService.getAccountById(req.auth.sub);
8
+ const entryDeviceNotifications = await deviceNotifications.getByUser(req.auth.sub);
9
+ if (!account) {
10
+ throw new ApiError(httpStatus.NOT_FOUND, "Account not found");
11
+ }
12
+ res.send({
13
+ ...account.data,
14
+ notification: entryDeviceNotifications?.settings,
15
+ });
16
+ });
17
+ const setDeviceToken = catchAsync(async (req, res) => {
18
+ const account = await accountsService.getAccountById(req.auth.sub);
19
+ const devices = account.data.app_metadata.devices || [];
20
+ const alreadyExisting = devices.find((d) => d.fck === req.body.token);
21
+ if (!alreadyExisting) {
22
+ devices.push({ fck: req.body.token });
23
+ }
24
+ const update = {
25
+ ...account.app_metadata,
26
+ devices: devices.slice(Math.max(devices.length - 3, 1)),
27
+ };
28
+ const entry = await accountsService.updateMetaDataById(req.auth.sub, update);
29
+ res.send(entry);
30
+ });
31
+ const avatar = catchAsync(async (req, res) => {
32
+ const avatarImage = await auth0.avatar(req.auth.sub);
33
+ res.send(avatarImage);
34
+ });
35
+ const updateEntry = catchAsync(async (req, res) => {
36
+ const account = await accountsService.getAccountById(req.auth.sub);
37
+ const { isSocial } = account?.data.identities[0];
38
+ const { given_name, family_name, email, notification, ...updateBody } = req.body;
39
+ const trimmedGivenName = typeof given_name === "string" ? given_name.trim() : undefined;
40
+ const trimmedFamilyName = typeof family_name === "string" ? family_name.trim() : undefined;
41
+ const hasGivenName = !!trimmedGivenName;
42
+ const hasFamilyName = !!trimmedFamilyName;
43
+ const update = isSocial
44
+ ? {
45
+ ...account.data.app_metadata,
46
+ ...updateBody,
47
+ ...(hasGivenName ? { first_name: trimmedGivenName } : {}),
48
+ ...(hasFamilyName ? { last_name: trimmedFamilyName } : {}),
49
+ }
50
+ : updateBody;
51
+ const entry = await accountsService.updateMetaDataById(req.auth.sub, update);
52
+ if (notification) {
53
+ await deviceNotifications.updateByUserId(req.auth.sub, {
54
+ settings: notification,
55
+ });
56
+ }
57
+ if (!isSocial && (hasGivenName || hasFamilyName || email)) {
58
+ try {
59
+ await accountsService.updateUserById(req.auth.sub, {
60
+ ...(hasGivenName ? { given_name: trimmedGivenName } : {}),
61
+ ...(hasFamilyName ? { family_name: trimmedFamilyName } : {}),
62
+ ...(email ? { email } : {}),
63
+ });
64
+ }
65
+ catch (error) {
66
+ console.error("error", error.message);
67
+ throw new ApiError(httpStatus.CONFLICT, error.message);
68
+ }
69
+ }
70
+ res.send(entry);
71
+ });
72
+ const deleteCurrent = catchAsync(async (req, res) => {
73
+ const entry = await accountsService.deleteById(req.auth.sub);
74
+ res.send(entry);
75
+ });
76
+ const current = catchAsync(async (req, res) => {
77
+ const user = await accountsService.getAccountById(req.auth.sub);
78
+ res.send(user.data);
79
+ });
80
+ const mfaEnroll = catchAsync(async (req, res) => {
81
+ const { mfaEnroll } = req.body;
82
+ const user = await accountsService.mfaEnroll(req.auth.sub, mfaEnroll);
83
+ res.send(user);
84
+ });
85
+ const mfaDisable = catchAsync(async (req, res) => {
86
+ const user = await accountsService.mfaDisable(req.auth.sub);
87
+ res.send(user);
88
+ });
89
+ export { avatar, getAccountById, current, setDeviceToken, mfaEnroll, updateEntry, deleteCurrent, mfaDisable, };
@@ -0,0 +1,101 @@
1
+ import { Router } from "express";
2
+ import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
3
+ import auth from "../middlewares/auth.js";
4
+ import * as accountsValidation from "./accounts.validation.js";
5
+ import * as accountsController from "./accounts.controller.js";
6
+ import { accountResponseSchema } from "./accounts.schemas.js";
7
+ import { validateParamsAccount } from "../middlewares/validateCurrentAuthUser.js";
8
+ export const accountsRouteSpecs = [
9
+ {
10
+ method: "get",
11
+ path: "/current",
12
+ validate: [auth("manageUsers")],
13
+ requestSchema: accountsValidation.currentAccountSchema,
14
+ responseSchema: accountResponseSchema,
15
+ privateDocs: true,
16
+ handler: accountsController.current,
17
+ summary: "Get the current account",
18
+ },
19
+ {
20
+ method: "post",
21
+ path: "/current/mfa/enroll",
22
+ validate: [auth("manageUsers")],
23
+ requestSchema: accountsValidation.currentAccountMfaEnrollSchema,
24
+ responseSchema: accountResponseSchema,
25
+ privateDocs: true,
26
+ handler: accountsController.mfaEnroll,
27
+ summary: "Enroll current account in MFA",
28
+ },
29
+ {
30
+ method: "post",
31
+ path: "/current/mfa/disable",
32
+ validate: [auth("manageUsers")],
33
+ requestSchema: accountsValidation.currentAccountMfaEnrollSchema,
34
+ responseSchema: accountResponseSchema,
35
+ privateDocs: true,
36
+ handler: accountsController.mfaDisable,
37
+ summary: "Disable current account MFA",
38
+ },
39
+ {
40
+ method: "post",
41
+ path: "/:accountId/setDeviceToken",
42
+ validate: [auth("manageUsers"), validateParamsAccount],
43
+ requestSchema: accountsValidation.setDeviceTokenSchema,
44
+ responseSchema: accountResponseSchema,
45
+ privateDocs: true,
46
+ handler: accountsController.setDeviceToken,
47
+ summary: "Set a device token for an account",
48
+ },
49
+ {
50
+ method: "get",
51
+ path: "/:accountId/avatar.jpg",
52
+ validate: [auth("manageUsers")],
53
+ requestSchema: accountsValidation.getAvatarSchema,
54
+ responseSchema: accountResponseSchema, // or a binary/blob schema if you have one
55
+ privateDocs: true,
56
+ handler: accountsController.avatar,
57
+ summary: "Fetch account avatar",
58
+ },
59
+ {
60
+ method: "delete",
61
+ path: "/deleteCurrent",
62
+ validate: [auth("manageUsers")],
63
+ requestSchema: accountsValidation.deleteCurrentSchema,
64
+ responseSchema: accountResponseSchema,
65
+ privateDocs: true,
66
+ handler: accountsController.deleteCurrent,
67
+ summary: "Delete the current account",
68
+ },
69
+ {
70
+ method: "get",
71
+ path: "/:accountId",
72
+ validate: [auth("getUsers"), validateParamsAccount],
73
+ requestSchema: accountsValidation.getAccountSchema,
74
+ responseSchema: accountResponseSchema,
75
+ handler: accountsController.getAccountById,
76
+ summary: "Get an account by ID",
77
+ },
78
+ {
79
+ method: "post",
80
+ path: "/:accountId",
81
+ validate: [auth("manageUsers"), validateParamsAccount],
82
+ requestSchema: accountsValidation.updateAccountSchema,
83
+ responseSchema: accountResponseSchema,
84
+ privateDocs: true,
85
+ handler: accountsController.updateEntry,
86
+ summary: "Create or replace an account by ID",
87
+ },
88
+ {
89
+ method: "patch",
90
+ path: "/:accountId",
91
+ validate: [auth("manageUsers"), validateParamsAccount],
92
+ requestSchema: accountsValidation.updateAccountSchema,
93
+ responseSchema: accountResponseSchema,
94
+ privateDocs: true,
95
+ handler: accountsController.updateEntry,
96
+ summary: "Update fields on an account by ID",
97
+ },
98
+ ];
99
+ const router = Router();
100
+ buildRouterAndDocs(router, accountsRouteSpecs, "/accounts", ["Accounts"]);
101
+ export default router;
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ export const accountResponseSchema = z.object({
3
+ id: z.string().uuid(),
4
+ email: z.string().email(),
5
+ firstName: z.string().optional(),
6
+ lastName: z.string().optional(),
7
+ organizationId: z.string().uuid(),
8
+ roles: z.array(z.string()), // e.g. ['admin','user']
9
+ isMfaEnabled: z.boolean(),
10
+ createdAt: z.string(), // ISO timestamp
11
+ updatedAt: z.string(), // ISO timestamp
12
+ });
@@ -0,0 +1,65 @@
1
+ // @ts-nocheck
2
+ import { auth0, mfaDisableAccount, mfaEnrollAccount } from "./auth0.service.js";
3
+ /**
4
+ * Get user by id
5
+ * @param {ObjectId} id
6
+ * @returns {Promise<Stock>}
7
+ */
8
+ export const getAccountById = async (id) => {
9
+ return auth0.users.get({ id });
10
+ };
11
+ /**
12
+ * Get user by email
13
+ * @param {string} email
14
+ * @returns {Promise<Stock>}
15
+ */
16
+ /*
17
+ export const getAccountByEmail = async (email: string): Promise<Stock> => {
18
+ return auth0.getUsersByEmail(email); // Fixed incorrect variable `postID` to `email`
19
+ }; */
20
+ /**
21
+ * Enroll user in MFA
22
+ * @param {ObjectId} userId
23
+ * @param {string} mfaToken
24
+ * @returns {Promise<Stock>}
25
+ */
26
+ export const mfaEnroll = async (userId, mfaToken) => {
27
+ const params = { id: userId };
28
+ const body = {
29
+ mfa_token: mfaToken,
30
+ };
31
+ return mfaEnrollAccount(userId, body);
32
+ };
33
+ export const mfaDisable = async (userId) => {
34
+ await mfaDisableAccount(userId);
35
+ return { success: true };
36
+ };
37
+ /**
38
+ * Update user metadata by id
39
+ */
40
+ export const updateMetaDataById = async (id, updateBody) => {
41
+ // now use the generic update and pass app_metadata
42
+ return auth0.users.update({ id }, { app_metadata: updateBody });
43
+ };
44
+ /**
45
+ * Update user by id
46
+ */
47
+ export const updateUserById = async (id, updateBody) => {
48
+ // switch to the v3 ManagementClient users.update
49
+ return auth0.users.update({ id }, updateBody);
50
+ };
51
+ /**
52
+ * Delete user by id
53
+ */
54
+ export const deleteById = async (userId) => {
55
+ return auth0.users.delete({ id: userId });
56
+ };
57
+ export default {
58
+ getAccountById,
59
+ // getAccountByEmail,
60
+ mfaEnroll,
61
+ mfaDisable,
62
+ updateMetaDataById,
63
+ updateUserById,
64
+ deleteById,
65
+ };
@@ -0,0 +1,99 @@
1
+ import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
2
+ import { z } from "zod";
3
+ import { zPagination, zObjectId, zPatchBody, } from "../utils/zValidations.js";
4
+ extendZodWithOpenApi(z);
5
+ export const createAccountSchema = {
6
+ body: z.object({
7
+ name: z
8
+ .string()
9
+ .openapi({ example: "Sample Entry", description: "Name of the entry" })
10
+ .optional(),
11
+ medication: zObjectId.openapi({ description: "Medication ObjectId" }),
12
+ organization: zObjectId.openapi({ description: "Organization ObjectId" }),
13
+ patient: zObjectId.openapi({ description: "Patient ObjectId" }),
14
+ meta: z
15
+ .record(z.string(), z.any())
16
+ .openapi({
17
+ example: { key: "value" },
18
+ description: "Additional metadata for the entry",
19
+ })
20
+ .optional(),
21
+ }),
22
+ };
23
+ export const getUsersSchema = zPagination;
24
+ export const getAccountSchema = {
25
+ params: z.object({
26
+ accountId: z
27
+ .string()
28
+ .openapi({
29
+ example: process.env.SCHEMA_EXAMPLE_ACCOUNT_ID || "auth0|60452f4c0dc85b0062326",
30
+ description: "Auth Account ID",
31
+ }),
32
+ }),
33
+ };
34
+ export const updateAccountSchema = {
35
+ params: z.object({
36
+ accountId: z
37
+ .string()
38
+ .openapi({
39
+ example: process.env.SCHEMA_EXAMPLE_ACCOUNT_ID || "auth0|60452f4c0dc85b0062326",
40
+ description: "Auth Account ID",
41
+ }),
42
+ }),
43
+ body: zPatchBody({
44
+ language: z
45
+ .string()
46
+ .nullable()
47
+ .optional()
48
+ .openapi({ example: "en", description: "Language code" }),
49
+ gender: z
50
+ .string()
51
+ .nullable()
52
+ .optional()
53
+ .openapi({ example: "female", description: "Gender" }),
54
+ email: z
55
+ .string()
56
+ .email()
57
+ .optional()
58
+ .openapi({ description: "User email address" }),
59
+ given_name: z
60
+ .string()
61
+ .optional()
62
+ .openapi({ example: "John", description: "Given name" }),
63
+ family_name: z
64
+ .string()
65
+ .optional()
66
+ .openapi({ example: "Doe", description: "Family name" }),
67
+ debug: z.boolean().optional(),
68
+ demo: z.boolean().optional(),
69
+ notification: z
70
+ .record(z.string(), z.any())
71
+ .optional()
72
+ .openapi({ description: "Notification settings object" }),
73
+ }),
74
+ };
75
+ export const deleteEntrySchema = {
76
+ params: z.object({
77
+ accountId: z
78
+ .string()
79
+ .openapi({
80
+ example: process.env.SCHEMA_EXAMPLE_ACCOUNT_ID || "auth0|60452f4c0dc85b0062326",
81
+ description: "Auth Account ID",
82
+ }),
83
+ }),
84
+ };
85
+ export const currentAccountSchema = {};
86
+ export const currentAccountMfaEnrollSchema = {
87
+ /* body: z.object({
88
+ mfaEnroll: z.string().openapi({ example: 'totp', description: 'MFA token to enroll' }),
89
+ }), */
90
+ };
91
+ export const setDeviceTokenSchema = {
92
+ body: z.object({
93
+ token: z
94
+ .string()
95
+ .openapi({ example: "device-token", description: "Device token to set" }),
96
+ }),
97
+ };
98
+ export const getAvatarSchema = {};
99
+ export const deleteCurrentSchema = {};
@@ -0,0 +1,188 @@
1
+ // @ts-nocheck
2
+ import { AuthenticationClient, ManagementClient } from "auth0";
3
+ import { promises as fs, readFileSync } from "fs";
4
+ let tokenManagementClient = {
5
+ clientId: process.env.AUTH0_CLIENT_ID,
6
+ clientSecret: process.env.AUTH0_CLIENT_SECRET,
7
+ };
8
+ //if (config.env !== 'production') {
9
+ try {
10
+ console.warn("Auth0 client: use local token from cache try");
11
+ const token = readFileSync("./token.txt", "utf8");
12
+ console.warn("Auth0 client: use local token from cache");
13
+ tokenManagementClient = { token };
14
+ }
15
+ catch (error) {
16
+ console.log("Auth0 Client: use new token");
17
+ }
18
+ //}
19
+ // IoT Api
20
+ /*
21
+ export const auth0Management = new ManagementClient({
22
+ ...tokenManagementClient,
23
+ domain: process.env.AUTH0_MANAGEMENT_DOMAIN!,
24
+ grant_type: 'client_credentials',
25
+ audience: process.env.AUTH0_AUDIENCE!,
26
+ } as ManagementClientOptions);
27
+ */
28
+ // add this AuthenticationClient just for token fetching
29
+ export const auth0AuthClient = new AuthenticationClient({
30
+ domain: process.env.AUTH0_MANAGEMENT_DOMAIN,
31
+ clientId: process.env.AUTH0_MANAGEMENT_CLIENT_ID,
32
+ clientSecret: process.env.AUTH0_MANAGEMENT_CLIENT_SECRET,
33
+ });
34
+ // In-memory cache for client-credentials tokens
35
+ let cachedToken = null;
36
+ let tokenExpiresAt = null;
37
+ let pendingTokenPromise = null;
38
+ let cachedManagementToken = null;
39
+ let managementTokenExpiresAt = null;
40
+ let pendingManagementTokenPromise = null;
41
+ const TOKEN_FILE_PATH = "./token.txt";
42
+ const MANAGEMENT_TOKEN_FILE_PATH = "./token.management.txt";
43
+ const TOKEN_BUFFER_SECONDS = 60; // refresh a minute before expiry
44
+ const TOKEN_FALLBACK_TTL_SECONDS = 3300; // ~55 minutes when no expiry metadata exists
45
+ const loadTokenFromFile = async (filePath) => {
46
+ try {
47
+ const raw = await fs.readFile(filePath, "utf8");
48
+ try {
49
+ const parsed = JSON.parse(raw);
50
+ if (parsed?.token && parsed?.expiresAt) {
51
+ return { token: parsed.token, expiresAt: parsed.expiresAt };
52
+ }
53
+ }
54
+ catch (parseError) {
55
+ // Backward compatibility: token.txt previously contained only the token string
56
+ const now = Math.floor(Date.now() / 1000);
57
+ return { token: raw.trim(), expiresAt: now + TOKEN_FALLBACK_TTL_SECONDS };
58
+ }
59
+ }
60
+ catch (error) {
61
+ return null;
62
+ }
63
+ return null;
64
+ };
65
+ const writeTokenFile = async (filePath, token, expiresAt) => {
66
+ const payload = JSON.stringify({ token, expiresAt });
67
+ await fs.writeFile(filePath, payload, "utf8");
68
+ };
69
+ const tokenIsValid = (token, expiresAt, now) => {
70
+ if (!token || !expiresAt)
71
+ return false;
72
+ return now < expiresAt - TOKEN_BUFFER_SECONDS;
73
+ };
74
+ export const getAuth0Token = async () => {
75
+ const audience = process.env.AUTH0_AUDIENCE ||
76
+ process.env.AUTH0_MANAGEMENT_AUDIENCE ||
77
+ "localhost:3000/";
78
+ const grantOpts = { audience };
79
+ const now = Math.floor(Date.now() / 1000);
80
+ if (tokenIsValid(cachedToken, tokenExpiresAt, now))
81
+ return cachedToken;
82
+ if (pendingTokenPromise)
83
+ return pendingTokenPromise;
84
+ pendingTokenPromise = (async () => {
85
+ // Non-production: try to reuse a file-cached token (for local dev) before minting a new one
86
+ // if (process.env.NODE_ENV !== 'production') {
87
+ const fileToken = await loadTokenFromFile(TOKEN_FILE_PATH);
88
+ if (fileToken && fileToken.expiresAt > now + TOKEN_BUFFER_SECONDS) {
89
+ cachedToken = fileToken.token;
90
+ tokenExpiresAt = fileToken.expiresAt;
91
+ pendingTokenPromise = null;
92
+ return cachedToken;
93
+ }
94
+ //}
95
+ const tokenResponse = await auth0AuthClient.oauth.clientCredentialsGrant(grantOpts);
96
+ const expiresIn = tokenResponse.data.expires_in || 3600;
97
+ cachedToken = tokenResponse.data.access_token;
98
+ tokenExpiresAt = now + expiresIn;
99
+ // if (process.env.NODE_ENV !== 'production') {
100
+ await writeTokenFile(TOKEN_FILE_PATH, cachedToken, tokenExpiresAt);
101
+ // }
102
+ pendingTokenPromise = null;
103
+ return cachedToken;
104
+ })();
105
+ return pendingTokenPromise;
106
+ };
107
+ export const getAuth0ManagementToken = async () => {
108
+ const audience = process.env.AUTH0_MANAGEMENT_AUDIENCE;
109
+ if (!audience) {
110
+ throw new Error("Missing AUTH0_MANAGEMENT_AUDIENCE; cannot mint Management API token");
111
+ }
112
+ const grantOpts = { audience };
113
+ const now = Math.floor(Date.now() / 1000);
114
+ if (tokenIsValid(cachedManagementToken, managementTokenExpiresAt, now))
115
+ return cachedManagementToken;
116
+ if (pendingManagementTokenPromise)
117
+ return pendingManagementTokenPromise;
118
+ pendingManagementTokenPromise = (async () => {
119
+ const fileToken = await loadTokenFromFile(MANAGEMENT_TOKEN_FILE_PATH);
120
+ if (fileToken && fileToken.expiresAt > now + TOKEN_BUFFER_SECONDS) {
121
+ cachedManagementToken = fileToken.token;
122
+ managementTokenExpiresAt = fileToken.expiresAt;
123
+ pendingManagementTokenPromise = null;
124
+ return cachedManagementToken;
125
+ }
126
+ const tokenResponse = await auth0AuthClient.oauth.clientCredentialsGrant(grantOpts);
127
+ const expiresIn = tokenResponse.data.expires_in || 3600;
128
+ cachedManagementToken = tokenResponse.data.access_token;
129
+ managementTokenExpiresAt = now + expiresIn;
130
+ await writeTokenFile(MANAGEMENT_TOKEN_FILE_PATH, cachedManagementToken, managementTokenExpiresAt);
131
+ pendingManagementTokenPromise = null;
132
+ return cachedManagementToken;
133
+ })();
134
+ return pendingManagementTokenPromise;
135
+ };
136
+ export const auth0 = new ManagementClient({
137
+ domain: process.env.AUTH0_MANAGEMENT_DOMAIN,
138
+ audience: process.env.AUTH0_MANAGEMENT_AUDIENCE,
139
+ token: getAuth0ManagementToken,
140
+ });
141
+ export const getUserIdByEmail = async (email) => {
142
+ // use the users resource to look up by email
143
+ return auth0.usersByEmail.getByEmail({ email });
144
+ };
145
+ export const sendVerificationEmail = async (userID) => {
146
+ return auth0.jobs.verifyEmail({ user_id: userID });
147
+ };
148
+ export const getUserById = async (userId) => {
149
+ return auth0.users.get({ id: userId });
150
+ };
151
+ export const avatar = async (userId) => {
152
+ return auth0.users.get({ id: userId });
153
+ };
154
+ export const mfaEnrollAccount = async (userId, mfaToken) => {
155
+ const ticketResponse = await auth0.guardian.createEnrollmentTicket({
156
+ user_id: userId,
157
+ send_mail: false,
158
+ });
159
+ return ticketResponse;
160
+ };
161
+ export const mfaDisableAccount = async (userId) => {
162
+ await auth0.users.deleteAuthenticationMethods({ id: userId });
163
+ return { success: true };
164
+ };
165
+ export const getUsersByIds = async (postIDs) => {
166
+ let q = "";
167
+ postIDs.forEach((e, i) => {
168
+ if (e)
169
+ q = `${q} ${i >= 2 ? " OR " : ""} user_id:"${e}"`;
170
+ });
171
+ const params = {
172
+ search_engine: "v3",
173
+ q,
174
+ per_page: 100,
175
+ page: 0,
176
+ };
177
+ return auth0.users.getAll(params);
178
+ };
179
+ export default {
180
+ auth0,
181
+ avatar,
182
+ getAuth0Token,
183
+ getUsersByIds,
184
+ getUserIdByEmail,
185
+ getUserById,
186
+ mfaEnrollAccount,
187
+ sendVerificationEmail,
188
+ };
@@ -0,0 +1,48 @@
1
+ import dotenv from "dotenv";
2
+ // Load env from the current working directory
3
+ dotenv.config();
4
+ console.log("Current working directory:", process.cwd());
5
+ console.log("Loaded environment variables:", {
6
+ NODE_ENV: process.env.NODE_ENV,
7
+ PORT: process.env.PORT,
8
+ MONGODB_URL: process.env.MONGODB_URL,
9
+ });
10
+ import Joi from "joi";
11
+ const envVarsSchema = Joi.object()
12
+ .keys({
13
+ NODE_ENV: Joi.string()
14
+ .valid("production", "development", "test")
15
+ .required(),
16
+ PORT: Joi.number().default(3000),
17
+ MONGODB_URL: Joi.string().required().description("Mongo DB url"),
18
+ })
19
+ .unknown();
20
+ const { value: envVars, error } = envVarsSchema
21
+ .prefs({ errors: { label: "key" } })
22
+ .validate(process.env);
23
+ if (error) {
24
+ throw new Error(`Config validation error: ${error.message}`);
25
+ }
26
+ export const config = {
27
+ env: envVars.NODE_ENV,
28
+ port: envVars.PORT,
29
+ mongoose: {
30
+ url: envVars.MONGODB_URL + (envVars.NODE_ENV === "test" ? "-test" : ""),
31
+ useCreateIndex: true,
32
+ useNewUrlParser: true,
33
+ useUnifiedTopology: true,
34
+ useFindAndModify: false,
35
+ },
36
+ /* email: {
37
+ smtp: {
38
+ host: envVars.SMTP_HOST,
39
+ port: envVars.SMTP_PORT,
40
+ auth: {
41
+ user: envVars.SMTP_USERNAME,
42
+ pass: envVars.SMTP_PASSWORD,
43
+ },
44
+ },
45
+ from: envVars.EMAIL_FROM,
46
+ }, */
47
+ };
48
+ export default config;
@@ -0,0 +1,27 @@
1
+ // @ts-nocheck
2
+ import winston from "winston";
3
+ import config from "./config";
4
+ const enumerateErrorFormat = winston.format((info) => {
5
+ if (info instanceof Error) {
6
+ Object.assign(info, { message: info.stack });
7
+ }
8
+ return info;
9
+ });
10
+ const logger = winston.createLogger({
11
+ level: config.env === "development" ? "debug" : "info",
12
+ format: winston.format.combine(enumerateErrorFormat(), config.env === "development"
13
+ ? winston.format.colorize()
14
+ : winston.format.uncolorize(), winston.format.splat(), winston.format.printf((info) => {
15
+ const { level, message, stack, ...rest } = info;
16
+ const restKeys = Object.keys(rest);
17
+ const restText = restKeys.length ? ` ${JSON.stringify(rest)}` : "";
18
+ const stackText = stack ? `\n${stack}` : "";
19
+ return `${level}: ${message}${stackText}${restText}`;
20
+ })),
21
+ transports: [
22
+ new winston.transports.Console({
23
+ stderrLevels: ["error"],
24
+ }),
25
+ ],
26
+ });
27
+ export default logger;
@@ -0,0 +1,16 @@
1
+ import morgan from 'morgan';
2
+ import config from './config';
3
+ import logger from './logger';
4
+ morgan.token('message', (req, res) => res.locals.errorMessage || '');
5
+ const getIpFormat = () => (config.env === 'production' ? ':remote-addr - ' : '');
6
+ const successResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms`;
7
+ const errorResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms - message: :message`;
8
+ const successHandler = morgan(successResponseFormat, {
9
+ skip: (req, res) => res.statusCode >= 400,
10
+ stream: { write: (message) => logger.info(message.trim()) },
11
+ });
12
+ const errorHandler = morgan(errorResponseFormat, {
13
+ skip: (req, res) => res.statusCode < 400,
14
+ stream: { write: (message) => logger.error(message.trim()) },
15
+ });
16
+ export default { successHandler, errorHandler };
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
3
+ const config = require('./config.cjs');
4
+ const { tokenTypes } = require('./tokens.cjs');
5
+ const { User } = require('../models.cjs');
6
+ const jwtOptions = {
7
+ secretOrKey: config.jwt.secret,
8
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
9
+ };
10
+ const jwtVerify = async (payload, done) => {
11
+ try {
12
+ if (payload.type !== tokenTypes.ACCESS) {
13
+ throw new Error('Invalid token type');
14
+ }
15
+ const user = await User.findById(payload.sub);
16
+ if (!user) {
17
+ return done(null, false);
18
+ }
19
+ done(null, user);
20
+ }
21
+ catch (error) {
22
+ done(error, false);
23
+ }
24
+ };
25
+ const jwtStrategy = new JwtStrategy(jwtOptions, jwtVerify);
26
+ module.exports = {
27
+ jwtStrategy,
28
+ };