@internetderdinge/api 1.224.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.
- package/.github/copilot-instructions.md +77 -0
- package/CHANGELOG.md +11 -0
- package/README.md +52 -0
- package/package.json +112 -0
- package/src/accounts/accounts.controller.ts +166 -0
- package/src/accounts/accounts.route.ts +107 -0
- package/src/accounts/accounts.schemas.ts +16 -0
- package/src/accounts/accounts.service.ts +85 -0
- package/src/accounts/accounts.validation.ts +118 -0
- package/src/accounts/auth0.service.ts +226 -0
- package/src/config/config.ts +49 -0
- package/src/config/logger.ts +33 -0
- package/src/config/morgan.ts +22 -0
- package/src/config/passport.cjs +30 -0
- package/src/config/roles.ts +13 -0
- package/src/config/tokens.cjs +10 -0
- package/src/devices/devices.controller.ts +276 -0
- package/src/devices/devices.model.ts +126 -0
- package/src/devices/devices.route.ts +198 -0
- package/src/devices/devices.schemas.ts +94 -0
- package/src/devices/devices.service.ts +320 -0
- package/src/devices/devices.validation.ts +221 -0
- package/src/devicesNotifications/devicesNotifications.controller.ts +72 -0
- package/src/devicesNotifications/devicesNotifications.model.ts +67 -0
- package/src/devicesNotifications/devicesNotifications.route.ts +150 -0
- package/src/devicesNotifications/devicesNotifications.schemas.ts +11 -0
- package/src/devicesNotifications/devicesNotifications.service.ts +222 -0
- package/src/devicesNotifications/devicesNotifications.validation.ts +56 -0
- package/src/email/email.service.ts +609 -0
- package/src/files/upload.service.ts +145 -0
- package/src/i18n/i18n.ts +51 -0
- package/src/i18n/saveMissingLocalJsonBackend.ts +92 -0
- package/src/index.ts +7 -0
- package/src/iotdevice/iotdevice.controller.ts +136 -0
- package/src/iotdevice/iotdevice.model.ts +32 -0
- package/src/iotdevice/iotdevice.route.ts +181 -0
- package/src/iotdevice/iotdevice.schemas.ts +79 -0
- package/src/iotdevice/iotdevice.service.ts +732 -0
- package/src/iotdevice/iotdevice.validation.ts +61 -0
- package/src/middlewares/auth.ts +110 -0
- package/src/middlewares/checkJwt.cjs +19 -0
- package/src/middlewares/error.js.legacy +44 -0
- package/src/middlewares/error.ts +41 -0
- package/src/middlewares/mongooseValidations/ensureSameOrganization.ts +15 -0
- package/src/middlewares/rateLimiter.ts +10 -0
- package/src/middlewares/validate.ts +25 -0
- package/src/middlewares/validateAction.ts +41 -0
- package/src/middlewares/validateAdmin.ts +21 -0
- package/src/middlewares/validateAi.ts +24 -0
- package/src/middlewares/validateCurrentAuthUser.ts +23 -0
- package/src/middlewares/validateCurrentUser.ts +35 -0
- package/src/middlewares/validateDevice.ts +191 -0
- package/src/middlewares/validateDeviceUserOrganization.ts +54 -0
- package/src/middlewares/validateOrganization.ts +109 -0
- package/src/middlewares/validateQuerySearchUserAndOrganization.ts +75 -0
- package/src/middlewares/validateTokens.ts +36 -0
- package/src/middlewares/validateUser.ts +75 -0
- package/src/middlewares/validateZod.ts +54 -0
- package/src/models/plugins/index.ts +7 -0
- package/src/models/plugins/paginate.plugin.ts +145 -0
- package/src/models/plugins/paginateNew.plugin.ts +206 -0
- package/src/models/plugins/simplePopulate.ts +12 -0
- package/src/models/plugins/toJSON.plugin.ts +51 -0
- package/src/organizations/organizations.controller.ts +101 -0
- package/src/organizations/organizations.model.ts +62 -0
- package/src/organizations/organizations.route.ts +119 -0
- package/src/organizations/organizations.schemas.ts +8 -0
- package/src/organizations/organizations.service.ts +85 -0
- package/src/organizations/organizations.validation.ts +76 -0
- package/src/pdf/pdf.controller.ts +18 -0
- package/src/pdf/pdf.route.ts +28 -0
- package/src/pdf/pdf.schemas.ts +7 -0
- package/src/pdf/pdf.service.ts +89 -0
- package/src/pdf/pdf.validation.ts +30 -0
- package/src/tokens/tokens.controller.ts +81 -0
- package/src/tokens/tokens.model.ts +24 -0
- package/src/tokens/tokens.route.ts +66 -0
- package/src/tokens/tokens.schemas.ts +15 -0
- package/src/tokens/tokens.service.ts +46 -0
- package/src/tokens/tokens.validation.ts +13 -0
- package/src/types/routeSpec.ts +1 -0
- package/src/users/users.controller.ts +234 -0
- package/src/users/users.model.ts +89 -0
- package/src/users/users.route.ts +171 -0
- package/src/users/users.schemas.ts +79 -0
- package/src/users/users.service.ts +393 -0
- package/src/users/users.validation.ts +166 -0
- package/src/utils/ApiError.ts +18 -0
- package/src/utils/buildRouterAndDocs.ts +85 -0
- package/src/utils/catchAsync.ts +9 -0
- package/src/utils/comparePapers.service.ts +48 -0
- package/src/utils/filterOptions.ts +37 -0
- package/src/utils/medicationName.ts +12 -0
- package/src/utils/pick.ts +16 -0
- package/src/utils/registerOpenApi.ts +32 -0
- package/src/utils/urlUtils.ts +14 -0
- package/src/utils/userName.ts +27 -0
- package/src/utils/zValidations.ts +89 -0
- package/src/validations/auth.validation.cjs +60 -0
- package/src/validations/custom.validation.ts +26 -0
- package/src/validations/index.cjs +2 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { objectId } from '../validations/custom.validation';
|
|
4
|
+
import { zGet, zObjectId, zPagination } from '../utils/zValidations';
|
|
5
|
+
|
|
6
|
+
extendZodWithOpenApi(z);
|
|
7
|
+
|
|
8
|
+
export const getDevice = {
|
|
9
|
+
params: z.object({
|
|
10
|
+
deviceId: zObjectId.openapi({ description: 'Device ObjectId' }),
|
|
11
|
+
}),
|
|
12
|
+
body: z.object({
|
|
13
|
+
deviceId: z.array(zObjectId).openapi({ description: 'Array of device IDs' }),
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
export const iotDevicesSchema = {
|
|
17
|
+
...zPagination,
|
|
18
|
+
query: zPagination.query.extend({
|
|
19
|
+
patient: zObjectId.optional(),
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const getDeviceSchema = {};
|
|
24
|
+
export const getEntrySchema = {
|
|
25
|
+
params: z.object({
|
|
26
|
+
deviceId: z.string(),
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
export const getEventsSchema = {
|
|
30
|
+
params: z.object({
|
|
31
|
+
deviceId: zObjectId.openapi({ description: 'Device ObjectId' }),
|
|
32
|
+
}),
|
|
33
|
+
query: z.object({
|
|
34
|
+
DateStart: z.string().openapi({ description: 'Start date (ISO‐string)', example: '2025-05-01T00:00:00Z' }),
|
|
35
|
+
DateEnd: z.string().openapi({ description: 'End date (ISO‐string)', example: '2025-05-31T23:59:59Z' }),
|
|
36
|
+
TypeFilter: z.string().openapi({ description: 'Optional type filter', example: '' }).optional().default(''),
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
export const updateEntrySchema = {};
|
|
40
|
+
|
|
41
|
+
export const pingDeviceSchema = {
|
|
42
|
+
params: z.object({
|
|
43
|
+
deviceId: zObjectId.openapi({ description: 'Device ObjectId' }),
|
|
44
|
+
}),
|
|
45
|
+
query: z.object({
|
|
46
|
+
dataResponse: z.string().openapi({ description: 'Data response', example: 'false' }),
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const shadowAlarmValidationSchema = {
|
|
51
|
+
params: z.object({
|
|
52
|
+
nrfId: z.string().openapi({ description: 'Device ID', example: 'nrf-22343' }),
|
|
53
|
+
shadowName: z.string().openapi({ description: 'Shadow name', example: 'alarm' }),
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const apiStatusRequestSchema = {
|
|
58
|
+
params: z.object({
|
|
59
|
+
kind: z.string().openapi({ description: 'Kind of API status', example: 'iot' }),
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import passport from "passport";
|
|
2
|
+
import httpStatus from "http-status";
|
|
3
|
+
import { expressjwt as jwt } from "express-jwt";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
import jwksRsa from "jwks-rsa";
|
|
6
|
+
import type { Request, Response, NextFunction } from "express";
|
|
7
|
+
import ApiError from "../utils/ApiError";
|
|
8
|
+
import Token from "../tokens/tokens.model";
|
|
9
|
+
import { roleRights } from "../config/roles";
|
|
10
|
+
|
|
11
|
+
interface AuthRequest extends Request {
|
|
12
|
+
auth?: {
|
|
13
|
+
id: string;
|
|
14
|
+
tokenId: string;
|
|
15
|
+
type: string;
|
|
16
|
+
[key: string]: any; // Additional user data
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type VerifyCallback = (
|
|
21
|
+
req: AuthRequest,
|
|
22
|
+
resolve: () => void,
|
|
23
|
+
reject: (error: ApiError) => void,
|
|
24
|
+
requiredRights: string[],
|
|
25
|
+
) => (err: any, user: any, info: any) => Promise<void>;
|
|
26
|
+
|
|
27
|
+
const verifyCallback: VerifyCallback =
|
|
28
|
+
(req, resolve, reject, requiredRights) => async (err, user, info) => {
|
|
29
|
+
if (err || info || !user) {
|
|
30
|
+
return reject(
|
|
31
|
+
new ApiError(httpStatus.UNAUTHORIZED, "Please authenticate"),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
req.user = user;
|
|
35
|
+
|
|
36
|
+
if (requiredRights.length) {
|
|
37
|
+
const userRights = roleRights.get(user.role) || [];
|
|
38
|
+
const hasRequiredRights = requiredRights.every((requiredRight) =>
|
|
39
|
+
userRights.includes(requiredRight),
|
|
40
|
+
);
|
|
41
|
+
if (!hasRequiredRights && req.params.userId !== user.id) {
|
|
42
|
+
return reject(new ApiError(httpStatus.FORBIDDEN, "Forbidden"));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
resolve();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const auth = function authFactory(...requiredRights: string[]) {
|
|
50
|
+
return async function authMiddleware(
|
|
51
|
+
req: AuthRequest,
|
|
52
|
+
res: Response,
|
|
53
|
+
next: NextFunction,
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
try {
|
|
56
|
+
// Check for custom token in X-API-Key header
|
|
57
|
+
const apiKey = req.headers["x-api-key"] as string | undefined;
|
|
58
|
+
if (apiKey && apiKey.length === 64) {
|
|
59
|
+
// 32 bytes hex encoded
|
|
60
|
+
const hashedToken = crypto
|
|
61
|
+
.createHash("sha256")
|
|
62
|
+
.update(apiKey)
|
|
63
|
+
.digest("hex");
|
|
64
|
+
const tokenDoc: any = await Token.findOne({
|
|
65
|
+
value: hashedToken,
|
|
66
|
+
}).select("+owner");
|
|
67
|
+
|
|
68
|
+
if (tokenDoc) {
|
|
69
|
+
const ownerId = tokenDoc.owner as string;
|
|
70
|
+
const roles = ["api"];
|
|
71
|
+
|
|
72
|
+
req.auth = {
|
|
73
|
+
id: ownerId,
|
|
74
|
+
tokenId: tokenDoc._id,
|
|
75
|
+
type: "api",
|
|
76
|
+
// For API-key auth, we can treat the token owner as the subject.
|
|
77
|
+
// Avoid fetching user profile from Auth0 Management API on every request.
|
|
78
|
+
sub: ownerId,
|
|
79
|
+
"https://memo.wirewire.de/roles": roles,
|
|
80
|
+
};
|
|
81
|
+
return next();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Fallback to Auth0 JWT validation
|
|
86
|
+
jwt({
|
|
87
|
+
secret: jwksRsa.expressJwtSecret({
|
|
88
|
+
cache: true,
|
|
89
|
+
rateLimit: true,
|
|
90
|
+
jwksRequestsPerMinute: 5,
|
|
91
|
+
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`,
|
|
92
|
+
}),
|
|
93
|
+
issuer: `https://${process.env.AUTH0_DOMAIN}/`,
|
|
94
|
+
algorithms: ["RS256"],
|
|
95
|
+
})(req, res, (err) => {
|
|
96
|
+
if (err) {
|
|
97
|
+
const status = err.status || 500;
|
|
98
|
+
const message =
|
|
99
|
+
err.message || "Sorry we were unable to process your request.";
|
|
100
|
+
return res.status(status).send({ message });
|
|
101
|
+
}
|
|
102
|
+
next();
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
105
|
+
next(error);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default auth;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const jwt = require('express-jwt');
|
|
2
|
+
const jwtAuthz = require('express-jwt-authz');
|
|
3
|
+
const jwksRsa = require('jwks-rsa');
|
|
4
|
+
|
|
5
|
+
const checkJwt = jwt({
|
|
6
|
+
secret: jwksRsa.expressJwtSecret({
|
|
7
|
+
cache: true,
|
|
8
|
+
rateLimit: true,
|
|
9
|
+
jwksRequestsPerMinute: 5,
|
|
10
|
+
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`,
|
|
11
|
+
}),
|
|
12
|
+
|
|
13
|
+
// Validate the audience and the issuer.
|
|
14
|
+
audience: process.env.AUTH0_AUDIENCE,
|
|
15
|
+
issuer: `https://${process.env.AUTH0_DOMAIN}/`,
|
|
16
|
+
algorithms: ['RS256'],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
module.exports = checkJwt;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
const httpStatus = require('http-status');
|
|
3
|
+
const config = require('../config/config.js');
|
|
4
|
+
const logger = require('../config/logger.js');
|
|
5
|
+
const ApiError = require('../utils/ApiError.js');
|
|
6
|
+
|
|
7
|
+
const errorConverter = (err, req, res, next) => {
|
|
8
|
+
let error = err;
|
|
9
|
+
if (!(error instanceof ApiError)) {
|
|
10
|
+
const statusCode =
|
|
11
|
+
error.statusCode || error instanceof mongoose.Error ? httpStatus.BAD_REQUEST : httpStatus.INTERNAL_SERVER_ERROR;
|
|
12
|
+
const message = error.message || httpStatus[statusCode];
|
|
13
|
+
error = new ApiError(statusCode, message, false, err.stack);
|
|
14
|
+
}
|
|
15
|
+
next(error);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line no-unused-vars
|
|
19
|
+
const errorHandler = (err, req, res, next) => {
|
|
20
|
+
let { statusCode, message } = err;
|
|
21
|
+
if (config.env === 'production' && !err.isOperational) {
|
|
22
|
+
statusCode = httpStatus.INTERNAL_SERVER_ERROR;
|
|
23
|
+
message = httpStatus[httpStatus.INTERNAL_SERVER_ERROR];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
res.locals.errorMessage = err.message;
|
|
27
|
+
|
|
28
|
+
const response = {
|
|
29
|
+
code: statusCode,
|
|
30
|
+
message,
|
|
31
|
+
...(config.env === 'development' && { stack: err.stack }),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (config.env === 'development') {
|
|
35
|
+
logger.error(err);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
res.status(statusCode).send(response);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
errorConverter,
|
|
43
|
+
errorHandler,
|
|
44
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
import httpStatus from 'http-status';
|
|
3
|
+
import config from '../config/config';
|
|
4
|
+
import logger from '../config/logger';
|
|
5
|
+
import ApiError from '../utils/ApiError';
|
|
6
|
+
|
|
7
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
8
|
+
|
|
9
|
+
export const errorConverter = (err: any, req: Request, res: Response, next: NextFunction): void => {
|
|
10
|
+
let error = err;
|
|
11
|
+
if (!(error instanceof ApiError)) {
|
|
12
|
+
const statusCode =
|
|
13
|
+
error.statusCode || (error instanceof mongoose.Error ? httpStatus.BAD_REQUEST : httpStatus.INTERNAL_SERVER_ERROR);
|
|
14
|
+
const message = error.message || httpStatus[statusCode];
|
|
15
|
+
error = new ApiError(statusCode, message, false, err.stack);
|
|
16
|
+
}
|
|
17
|
+
next(error);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line no-unused-vars
|
|
21
|
+
export const errorHandler = (err: ApiError, req: Request, res: Response, next: NextFunction): void => {
|
|
22
|
+
let { statusCode, message } = err;
|
|
23
|
+
if (config.env === 'production' && !err.isOperational) {
|
|
24
|
+
statusCode = httpStatus.INTERNAL_SERVER_ERROR;
|
|
25
|
+
message = httpStatus[httpStatus.INTERNAL_SERVER_ERROR];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
res.locals.errorMessage = err.message;
|
|
29
|
+
|
|
30
|
+
const response = {
|
|
31
|
+
code: statusCode,
|
|
32
|
+
message,
|
|
33
|
+
...(config.env === 'development' && { stack: err.stack }),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (config.env === 'development') {
|
|
37
|
+
logger.error(err);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
res.status(statusCode).send({ ...response, raw: err.raw || null });
|
|
41
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throws if patient doesn’t exist or isn’t in the given org.
|
|
3
|
+
*/
|
|
4
|
+
export async function ensureSameOrganization(
|
|
5
|
+
patientId: Types.ObjectId,
|
|
6
|
+
organizationId: Types.ObjectId,
|
|
7
|
+
UserModel: Model<any>,
|
|
8
|
+
) {
|
|
9
|
+
// console.log('ensureSameOrganization', patientId, organizationId);
|
|
10
|
+
const user = await UserModel.findById(patientId).select('organization');
|
|
11
|
+
if (!user) throw new Error('Patient not found');
|
|
12
|
+
if (!user.organization.equals(organizationId)) {
|
|
13
|
+
throw new Error('Patient must belong to the same organization');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
2
|
+
import httpStatus from 'http-status';
|
|
3
|
+
import pick from '../utils/pick';
|
|
4
|
+
import ApiError from '../utils/ApiError';
|
|
5
|
+
|
|
6
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
7
|
+
|
|
8
|
+
const validate =
|
|
9
|
+
(schema: Record<string, any>) =>
|
|
10
|
+
(req: Request, res: Response, next: NextFunction): void => {
|
|
11
|
+
const validSchema = pick(schema, ['params', 'query', 'body']);
|
|
12
|
+
const object = pick(req, Object.keys(validSchema));
|
|
13
|
+
const { value, error } = Joi.compile(validSchema)
|
|
14
|
+
.prefs({ errors: { label: 'key' } })
|
|
15
|
+
.validate(object);
|
|
16
|
+
|
|
17
|
+
if (error) {
|
|
18
|
+
const errorMessage = error.details.map((details) => details.message).join(', ');
|
|
19
|
+
return next(new ApiError(httpStatus.BAD_REQUEST, errorMessage));
|
|
20
|
+
}
|
|
21
|
+
Object.assign(req, value);
|
|
22
|
+
return next();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default validate;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import httpStatus from 'http-status';
|
|
2
|
+
import ApiError from '../utils/ApiError';
|
|
3
|
+
import { isAdmin } from './validateAdmin';
|
|
4
|
+
|
|
5
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
6
|
+
|
|
7
|
+
export const validateOrganizationUpdate = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
8
|
+
if (isAdmin(res.req.auth)) {
|
|
9
|
+
next();
|
|
10
|
+
} else {
|
|
11
|
+
if (!req.currentUser) {
|
|
12
|
+
next(new ApiError(httpStatus.INTERNAL_SERVER_ERROR, 'Current user not set in request'));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (req.currentUser.role && req.currentUser.role === 'onlyself') {
|
|
17
|
+
next(new ApiError(httpStatus.FORBIDDEN, 'User does not have sufficient permissions in the organization to update'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
next();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const validateOrganizationDelete = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
26
|
+
if (isAdmin(res.req.auth)) {
|
|
27
|
+
next();
|
|
28
|
+
} else {
|
|
29
|
+
if (!req.currentUser) {
|
|
30
|
+
next(new ApiError(httpStatus.INTERNAL_SERVER_ERROR, 'Current user not set in request'));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (req.currentUser.role && req.currentUser.role === 'onlyself') {
|
|
35
|
+
next(new ApiError(httpStatus.FORBIDDEN, 'User does not have sufficient permissions in the organization to update'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
next();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import httpStatus from 'http-status';
|
|
2
|
+
import ApiError from '../utils/ApiError';
|
|
3
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
4
|
+
|
|
5
|
+
const isAdmin = (user: Record<string, any> | undefined): boolean => {
|
|
6
|
+
//return false;
|
|
7
|
+
if (!user) return false;
|
|
8
|
+
|
|
9
|
+
// return false; // TODO: Remove this line when the user object is properly defined
|
|
10
|
+
return user['https://memo.wirewire.de/roles'] ? user['https://memo.wirewire.de/roles'].includes('admin') : false;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const validateAdmin = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
14
|
+
if (isAdmin(req.auth)) {
|
|
15
|
+
next();
|
|
16
|
+
} else {
|
|
17
|
+
next(new ApiError(httpStatus.FORBIDDEN, 'User is not part of the admin group (validateAdmin)'));
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export { isAdmin, validateAdmin };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import httpStatus from 'http-status';
|
|
2
|
+
import ApiError from '../utils/ApiError'; // keep .cjs import
|
|
3
|
+
import expressPkg from 'express';
|
|
4
|
+
const { Request, Response, NextFunction } = expressPkg;
|
|
5
|
+
|
|
6
|
+
interface User {
|
|
7
|
+
'https://memo.wirewire.de/roles'?: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// you can adjust the User source if your auth payload differs
|
|
11
|
+
export const isAiRole = (user?: User): boolean => {
|
|
12
|
+
if (!user) return false;
|
|
13
|
+
return user['https://memo.wirewire.de/roles']?.includes('ai') ?? false;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const validateAiRole = async (req: Request & { auth?: User }, res: Response, next: NextFunction): Promise<void> => {
|
|
17
|
+
// assuming the auth payload is attached to req.auth
|
|
18
|
+
|
|
19
|
+
console.log('Validating AI role for user:', req.auth);
|
|
20
|
+
if (isAiRole(req.auth)) {
|
|
21
|
+
return next();
|
|
22
|
+
}
|
|
23
|
+
return next(new ApiError(httpStatus.FORBIDDEN, 'User is not part of the ai group (validateAi)'));
|
|
24
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import httpStatus from 'http-status';
|
|
2
|
+
import ApiError from '../utils/ApiError';
|
|
3
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
4
|
+
|
|
5
|
+
const getCurrentAuthUser = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
6
|
+
if (res.req.auth.sub !== req.params.notificationId) {
|
|
7
|
+
next(new ApiError(httpStatus.BAD_REQUEST, 'Not allowed to access'));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
next();
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const validateParamsAccount = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
15
|
+
if (res.req.auth.sub !== req.params.accountId) {
|
|
16
|
+
next(new ApiError(httpStatus.BAD_REQUEST, 'Not allowed to access'));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
next();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default getCurrentAuthUser;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import httpStatus from "http-status";
|
|
2
|
+
import ApiError from "../utils/ApiError";
|
|
3
|
+
import userService from "../users/users.service";
|
|
4
|
+
|
|
5
|
+
import type { Request, Response, NextFunction } from "express";
|
|
6
|
+
|
|
7
|
+
const getCurrentUser = async (
|
|
8
|
+
req: Request,
|
|
9
|
+
res: Response,
|
|
10
|
+
next: NextFunction,
|
|
11
|
+
): Promise<void> => {
|
|
12
|
+
try {
|
|
13
|
+
// TODO: Check if the user is logged in
|
|
14
|
+
const currentUser = await userService.getUserByOwner(
|
|
15
|
+
res.req.auth.sub,
|
|
16
|
+
req.body.organization,
|
|
17
|
+
);
|
|
18
|
+
if (!currentUser) {
|
|
19
|
+
next(new ApiError(httpStatus.BAD_REQUEST, "User does not exist"));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
req.currentUser = currentUser;
|
|
23
|
+
next();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error("Error validating user service:", error);
|
|
26
|
+
next(
|
|
27
|
+
new ApiError(
|
|
28
|
+
httpStatus.INTERNAL_SERVER_ERROR,
|
|
29
|
+
"Failed to validate user service",
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default getCurrentUser;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import httpStatus from "http-status";
|
|
2
|
+
import ApiError from "../utils/ApiError";
|
|
3
|
+
import devicesService from "../devices/devices.service";
|
|
4
|
+
import { isAdmin } from "./validateAdmin";
|
|
5
|
+
import usersService from "../users/users.service";
|
|
6
|
+
|
|
7
|
+
import type { Request, Response, NextFunction } from "express";
|
|
8
|
+
import type { User } from "../users/users.types";
|
|
9
|
+
import type { Device } from "../devices/devices.types";
|
|
10
|
+
|
|
11
|
+
const validateDeviceIsInOrganization = async (
|
|
12
|
+
req: Request,
|
|
13
|
+
res: Response,
|
|
14
|
+
next: NextFunction,
|
|
15
|
+
): Promise<void> => {
|
|
16
|
+
const deviceId = (req.body?.deviceId ||
|
|
17
|
+
req.params?.deviceId ||
|
|
18
|
+
req.query?.deviceId) as string | undefined;
|
|
19
|
+
|
|
20
|
+
if (!deviceId) {
|
|
21
|
+
next();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const device = await devicesService.getById(deviceId);
|
|
26
|
+
|
|
27
|
+
if (!device) {
|
|
28
|
+
next(new ApiError(httpStatus.NOT_FOUND, "Device not found"));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (isAdmin(res.req.auth)) {
|
|
33
|
+
next();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const currentUser: User | null = await usersService.getUserByOwner(
|
|
38
|
+
res.req.auth.sub,
|
|
39
|
+
device.organization,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (!currentUser) {
|
|
43
|
+
next(
|
|
44
|
+
new ApiError(
|
|
45
|
+
httpStatus.FORBIDDEN,
|
|
46
|
+
"User is not part of the organization for this device",
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
req.body?.organization &&
|
|
54
|
+
req.body.organization.toString() !== device.organization?.toString()
|
|
55
|
+
) {
|
|
56
|
+
next(
|
|
57
|
+
new ApiError(
|
|
58
|
+
httpStatus.FORBIDDEN,
|
|
59
|
+
"Device is not part of the provided organization",
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
req.currentUser = currentUser;
|
|
66
|
+
next();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const validateDevice = async (
|
|
70
|
+
req: Request,
|
|
71
|
+
res: Response,
|
|
72
|
+
next: NextFunction,
|
|
73
|
+
): Promise<void> => {
|
|
74
|
+
if (isAdmin(res.req.auth)) {
|
|
75
|
+
next();
|
|
76
|
+
} else {
|
|
77
|
+
const currentDevice: Device | null = await devicesService.getById(
|
|
78
|
+
req.params.deviceId,
|
|
79
|
+
);
|
|
80
|
+
if (!currentDevice) {
|
|
81
|
+
next(new ApiError(httpStatus.NOT_FOUND, "Device not found"));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const currentUser: User | null = await usersService.getUserByOwner(
|
|
86
|
+
res.req.auth.sub,
|
|
87
|
+
currentDevice.organization,
|
|
88
|
+
);
|
|
89
|
+
if (!currentUser) {
|
|
90
|
+
next(
|
|
91
|
+
new ApiError(
|
|
92
|
+
httpStatus.FORBIDDEN,
|
|
93
|
+
"User is not part of the organization which has access to the device (validateDevice)",
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
req.currentUser = currentUser;
|
|
100
|
+
next();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const validateDeviceQuery = async (
|
|
105
|
+
req: Request,
|
|
106
|
+
res: Response,
|
|
107
|
+
next: NextFunction,
|
|
108
|
+
): Promise<void> => {
|
|
109
|
+
if (isAdmin(res.req.auth)) {
|
|
110
|
+
next();
|
|
111
|
+
} else {
|
|
112
|
+
// console.log('Validating device query for user:', res.req.auth.sub, req.query);
|
|
113
|
+
const currentDevice: Device | null = await devicesService.getById(
|
|
114
|
+
req.query.deviceId as string,
|
|
115
|
+
);
|
|
116
|
+
if (!currentDevice) {
|
|
117
|
+
next(new ApiError(httpStatus.NOT_FOUND, "Device not found"));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const currentUser: User | null = await usersService.getUserByOwner(
|
|
122
|
+
res.req.auth.sub,
|
|
123
|
+
currentDevice.organization,
|
|
124
|
+
);
|
|
125
|
+
if (!currentUser) {
|
|
126
|
+
next(
|
|
127
|
+
new ApiError(
|
|
128
|
+
httpStatus.FORBIDDEN,
|
|
129
|
+
"User is not part of the organization (validateDeviceOrOrganizationQuery)",
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
req.currentUser = currentUser;
|
|
136
|
+
next();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const validateDeviceOrOrganizationQuery = async (
|
|
141
|
+
req: Request,
|
|
142
|
+
res: Response,
|
|
143
|
+
next: NextFunction,
|
|
144
|
+
): Promise<void> => {
|
|
145
|
+
if (isAdmin(res.req.auth)) {
|
|
146
|
+
next();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const deviceId = req.query.deviceId as string;
|
|
151
|
+
const organizationId = req.query.organization as string;
|
|
152
|
+
|
|
153
|
+
if (deviceId) {
|
|
154
|
+
return validateDeviceQuery(req, res, next);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (organizationId) {
|
|
158
|
+
const currentUser: User | null = await usersService.getUserByOwner(
|
|
159
|
+
res.req.auth.sub,
|
|
160
|
+
organizationId,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if (!currentUser) {
|
|
164
|
+
next(
|
|
165
|
+
new ApiError(
|
|
166
|
+
httpStatus.FORBIDDEN,
|
|
167
|
+
"User is not part of the organization which has access to the device (validateDeviceQuery)",
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
req.currentUser = currentUser;
|
|
174
|
+
next();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
next(
|
|
179
|
+
new ApiError(
|
|
180
|
+
httpStatus.BAD_REQUEST,
|
|
181
|
+
"deviceId or organization is required",
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export {
|
|
187
|
+
validateDevice,
|
|
188
|
+
validateDeviceQuery,
|
|
189
|
+
validateDeviceOrOrganizationQuery,
|
|
190
|
+
validateDeviceIsInOrganization,
|
|
191
|
+
};
|