Skip to content

Commit 36a094c

Browse files
authored
Merge pull request #2428 from Real-Dev-Squad/develop
Dev to Main Sync
2 parents d6ace5a + cd129fb commit 36a094c

32 files changed

+3836
-61
lines changed

config/custom-environment-variables.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ module.exports = {
8888
__name: "USER_TOKEN_REFRESH_TTL",
8989
__format: "number",
9090
},
91+
impersonationTtl: {
92+
__name: "USER_TOKEN_IMPERSONATION_TTL",
93+
__format: "number",
94+
},
9195
publicKey: "PUBLIC_KEY",
9296
privateKey: "PRIVATE_KEY",
9397
},
@@ -96,6 +100,10 @@ module.exports = {
96100
botPublicKey: "BOT_PUBLIC_KEY",
97101
},
98102

103+
discordService: {
104+
publicKey: "DISCORD_SERVICE_PUBLIC_KEY",
105+
},
106+
99107
cronJobHandler: {
100108
publicKey: "CRON_JOB_PUBLIC_KEY",
101109
},

config/default.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ module.exports = {
8686
cookieV2Name: `rds-session-v2-${NODE_ENV}`,
8787
ttl: 30 * 24 * 60 * 60, // in seconds
8888
refreshTtl: 180 * 24 * 60 * 60, // in seconds
89+
impersonationTtl: 15 * 60, // in seconds
8990
publicKey: "<publicKey>",
9091
privateKey: "<privateKey>",
9192
},
@@ -94,6 +95,10 @@ module.exports = {
9495
botPublicKey: "<botpublicKey>",
9596
},
9697

98+
discordService: {
99+
publicKey: "DISCORD_SERVICE_PUBLIC_KEY",
100+
},
101+
97102
// Cloudinary keys
98103
cloudinary: {
99104
cloud_name: "Cloud_name",

constants/bot.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
const CLOUDFLARE_WORKER = "Cloudflare Worker";
22
const BAD_TOKEN = "BAD.JWT.TOKEN";
33
const CRON_JOB_HANDLER = "Cron Job Handler";
4+
const DISCORD_SERVICE = "Discord Service";
45

56
const Services = {
67
CLOUDFLARE_WORKER: CLOUDFLARE_WORKER,
78
CRON_JOB_HANDLER: CRON_JOB_HANDLER,
89
};
910

10-
module.exports = { CLOUDFLARE_WORKER, BAD_TOKEN, CRON_JOB_HANDLER, Services };
11+
const DiscordServiceHeader = {
12+
name: "x-service-name"
13+
}
14+
15+
module.exports = { CLOUDFLARE_WORKER, BAD_TOKEN, CRON_JOB_HANDLER, Services, DISCORD_SERVICE, DiscordServiceHeader };

constants/requests.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,14 @@ export const INVALID_REQUEST_TYPE = "Invalid request type";
6868
export const INVALID_REQUEST_DEADLINE = "New deadline of the request must be greater than old deadline";
6969
export const REQUEST_UPDATED_SUCCESSFULLY = "Request updated successfully";
7070
export const UNAUTHORIZED_TO_UPDATE_REQUEST = "Unauthorized to update request";
71+
72+
export const FEATURE_NOT_IMPLEMENTED = "Feature not implemented";
73+
74+
export const INVALID_ACTION_PARAM = "Invalid 'action' parameter: must be either 'START' or 'STOP'";
75+
76+
export const OPERATION_NOT_ALLOWED = "You are not allowed for this operation at the moment";
77+
78+
export const IMPERSONATION_LOG_TYPE = {
79+
SESSION_STARTED:"SESSION_STARTED",
80+
SESSION_STOPPED:"SESSION_STOPPED"
81+
}
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import {
2+
ERROR_WHILE_CREATING_REQUEST,
3+
ERROR_WHILE_FETCHING_REQUEST,
4+
REQUEST_FETCHED_SUCCESSFULLY,
5+
REQUEST_DOES_NOT_EXIST,
6+
ERROR_WHILE_UPDATING_REQUEST,
7+
REQUEST_CREATED_SUCCESSFULLY,
8+
OPERATION_NOT_ALLOWED
9+
} from "../constants/requests";
10+
import { createImpersonationRequestService, updateImpersonationRequestService, generateImpersonationTokenService, startImpersonationService, stopImpersonationService } from "../services/impersonationRequests";
11+
import { getImpersonationRequestById, getImpersonationRequests } from "../models/impersonationRequests";
12+
import {
13+
CreateImpersonationRequest,
14+
CreateImpersonationRequestBody,
15+
UpdateImpersonationRequest,
16+
UpdateImpersonationRequestStatusBody,
17+
ImpersonationRequestResponse,
18+
GetImpersonationControllerRequest,
19+
GetImpersonationRequestByIdRequest,
20+
ImpersonationSessionRequest
21+
} from "../types/impersonationRequest";
22+
import { getPaginatedLink } from "../utils/helper";
23+
import { NextFunction } from "express";
24+
const logger = require("../utils/logger");
25+
26+
/**
27+
* Controller to handle creation of an impersonation request.
28+
*
29+
* @param {CreateImpersonationRequest} req - Express request object with user and body data.
30+
* @param {ImpersonationRequestResponse} res - Express response object.
31+
* @param {NextFunction} next - Express next middleware function.
32+
* @returns {Promise<ImpersonationRequestResponse | void>} Returns the created request or passes error to next middleware.
33+
*/
34+
export const createImpersonationRequestController = async (
35+
req: CreateImpersonationRequest,
36+
res: ImpersonationRequestResponse,
37+
next: NextFunction
38+
): Promise<ImpersonationRequestResponse | void> => {
39+
try {
40+
const { createdFor, reason } = req.body as CreateImpersonationRequestBody;
41+
const userId = req.userData?.id;
42+
43+
const impersonationRequest = await createImpersonationRequestService({
44+
createdBy: userId,
45+
createdFor,
46+
reason
47+
});
48+
49+
return res.status(201).json({
50+
message: REQUEST_CREATED_SUCCESSFULLY,
51+
data: {
52+
...impersonationRequest
53+
}
54+
});
55+
} catch (error) {
56+
logger.error(ERROR_WHILE_CREATING_REQUEST, error);
57+
next(error);
58+
}
59+
};
60+
61+
/**
62+
* Controller to fetch an impersonation request by its ID.
63+
*
64+
* @param {GetImpersonationRequestByIdRequest} req - Express request object containing `id` parameter.
65+
* @param {ImpersonationRequestResponse} res - Express response object.
66+
* @returns {Promise<ImpersonationRequestResponse>} Returns the request if found, or 404 if it doesn't exist.
67+
*/
68+
export const getImpersonationRequestByIdController = async (
69+
req: GetImpersonationRequestByIdRequest,
70+
res: ImpersonationRequestResponse
71+
): Promise<ImpersonationRequestResponse> => {
72+
const id = req.params.id;
73+
try {
74+
const request = await getImpersonationRequestById(id);
75+
76+
if (!request) {
77+
return res.status(404).json({
78+
message: REQUEST_DOES_NOT_EXIST,
79+
});
80+
}
81+
82+
return res.status(200).json({
83+
message: REQUEST_FETCHED_SUCCESSFULLY,
84+
data: request,
85+
});
86+
87+
} catch (error) {
88+
logger.error(ERROR_WHILE_FETCHING_REQUEST, error);
89+
return res.boom.badImplementation(ERROR_WHILE_FETCHING_REQUEST);
90+
}
91+
};
92+
93+
/**
94+
* Controller to fetch impersonation requests with optional filtering and pagination.
95+
*
96+
* @param {GetImpersonationControllerRequest} req - Express request object containing query parameters.
97+
* @param {ImpersonationRequestResponse} res - Express response object.
98+
* @returns {Promise<ImpersonationRequestResponse>} Returns paginated impersonation request data or 204 if none found.
99+
*/
100+
export const getImpersonationRequestsController = async (
101+
req: GetImpersonationControllerRequest,
102+
res: ImpersonationRequestResponse
103+
): Promise<ImpersonationRequestResponse> => {
104+
try {
105+
const { query } = req;
106+
107+
const requests = await getImpersonationRequests(query);
108+
if (!requests || requests.allRequests.length === 0) {
109+
return res.status(204).send();
110+
}
111+
112+
const { allRequests, next, prev } = requests;
113+
const count = allRequests.length;
114+
115+
let nextUrl = null;
116+
let prevUrl = null;
117+
if (next) {
118+
nextUrl = getPaginatedLink({
119+
endpoint: "/impersonation/requests",
120+
query,
121+
cursorKey: "next",
122+
docId: next,
123+
});
124+
}
125+
if (prev) {
126+
prevUrl = getPaginatedLink({
127+
endpoint: "/impersonation/requests",
128+
query,
129+
cursorKey: "prev",
130+
docId: prev,
131+
});
132+
}
133+
134+
return res.status(200).json({
135+
message: REQUEST_FETCHED_SUCCESSFULLY,
136+
data: allRequests,
137+
next: nextUrl,
138+
prev: prevUrl,
139+
count,
140+
});
141+
} catch (err) {
142+
logger.error(ERROR_WHILE_FETCHING_REQUEST, err);
143+
return res.boom.badImplementation(ERROR_WHILE_FETCHING_REQUEST);
144+
}
145+
};
146+
147+
148+
/**
149+
* Controller to Update the status of an impersonation request.
150+
*
151+
* @param {UpdateImpersonationRequest} req - Express request with params, body, and user data.
152+
* @param {ImpersonationRequestResponse} res - Express response object.
153+
* @param {NextFunction} next - Express middleware `next` function.
154+
* @returns {Promise<ImpersonationRequestResponse>} Returns updated request data or passes error to `next`.
155+
*/
156+
export const updateImpersonationRequestStatusController = async (
157+
req: UpdateImpersonationRequest,
158+
res: ImpersonationRequestResponse,
159+
next: NextFunction
160+
): Promise<ImpersonationRequestResponse> => {
161+
try {
162+
const requestId = req.params.id;
163+
const lastModifiedBy = req.userData.id;
164+
const requestBody: UpdateImpersonationRequestStatusBody = req.body;
165+
166+
const { returnMessage, updatedRequest: response } = await updateImpersonationRequestService({
167+
id: requestId,
168+
updatePayload: requestBody,
169+
lastModifiedBy,
170+
});
171+
172+
return res.status(200).json({
173+
message: returnMessage,
174+
data: {
175+
...response,
176+
},
177+
});
178+
} catch (error) {
179+
logger.error(ERROR_WHILE_UPDATING_REQUEST, error);
180+
next(error);
181+
}
182+
};
183+
184+
185+
186+
/**
187+
* Controller to handle impersonation session actions (START or STOP).
188+
*
189+
* @param {ImpersonationSessionRequest} req - Express request object containing user data, query params, and impersonation flag.
190+
* @param {ImpersonationRequestResponse} res - Express response object used to send the response.
191+
* @param {NextFunction} next - Express next middleware function for error handling.
192+
* @returns {Promise<ImpersonationRequestResponse>} Sends a JSON response with updated request data and sets authentication cookies based on action.
193+
*
194+
* @throws {Forbidden} If the action is invalid or STOP is requested without an active impersonation session.
195+
*/
196+
export const impersonationController = async (
197+
req: ImpersonationSessionRequest,
198+
res: ImpersonationRequestResponse,
199+
next: NextFunction
200+
): Promise<ImpersonationRequestResponse | void> => {
201+
const { action } = req.query;
202+
const requestId = req.params.id;
203+
const userId = req.userData?.id;
204+
let authCookie;
205+
let response;
206+
try {
207+
208+
if (action === "START") {
209+
authCookie = await generateImpersonationTokenService(requestId, action);
210+
response = await startImpersonationService({ requestId, userId });
211+
}
212+
213+
if (action === "STOP") {
214+
authCookie = await generateImpersonationTokenService(requestId, action);
215+
response = await stopImpersonationService({ requestId, userId });
216+
}
217+
218+
res.clearCookie(authCookie.name);
219+
res.cookie(authCookie.name, authCookie.value, authCookie.options);
220+
221+
return res.status(200).json({
222+
message: response.returnMessage,
223+
data: response.updatedRequest
224+
});
225+
} catch (error) {
226+
logger.error(`Failed to process impersonation ${action} for requestId=${requestId}, userId=${userId}`, error);
227+
return next(error);
228+
}
229+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { NextFunction } from "express";
2+
import authorizeRoles from "./authorizeRoles";
3+
const { SUPERUSER } = require("../constants/roles");
4+
import { ImpersonationRequestResponse, ImpersonationSessionRequest } from "../types/impersonationRequest";
5+
import { INVALID_ACTION_PARAM, OPERATION_NOT_ALLOWED } from "../constants/requests";
6+
7+
/**
8+
* Middleware to authorize impersonation actions based on the `action` query parameter.
9+
*
10+
* - If `action=START`: Only users with the SUPERUSER role are authorized.
11+
* - If `action=STOP`: Only allowed if the user is currently impersonating someone (`req.isImpersonating === true`).
12+
* - If `action` is missing or has an invalid value: Responds with 400 Bad Request.
13+
*
14+
* @param {ImpersonationSessionRequest} req - Express request object, extended to include impersonation context.
15+
* @param {ImpersonationRequestResponse} res - Express response object with Boom error handling.
16+
* @param {NextFunction} next - Express callback to pass control to the next middleware.
17+
*
18+
* @returns {void}
19+
*/
20+
export const addAuthorizationForImpersonation = async (
21+
req: ImpersonationSessionRequest,
22+
res: ImpersonationRequestResponse,
23+
next: NextFunction
24+
) => {
25+
const { action } = req.query;
26+
27+
if (action === "START") {
28+
return authorizeRoles([SUPERUSER])(req, res, next);
29+
}
30+
31+
if (action === "STOP") {
32+
if (!req.isImpersonating) {
33+
return res.boom.forbidden(OPERATION_NOT_ALLOWED);
34+
}
35+
return next();
36+
}
37+
38+
return res.boom.badRequest(INVALID_ACTION_PARAM);
39+
};

0 commit comments

Comments
 (0)