Skip to content

Commit 5e0f3f1

Browse files
added middleware and fixed code quality
1 parent c00d82f commit 5e0f3f1

File tree

8 files changed

+85
-33
lines changed

8 files changed

+85
-33
lines changed

constants/requests.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ export const FEATURE_NOT_IMPLEMENTED = "Feature not implemented";
7474
export const IMPERSONATION_NOT_COMPLETED = "Please complete impersonation before creating a new request";
7575
export const IMPERSONATION_ALREADY_ATTEMPTED = "No active request is available for impersonation";
7676
export const IMPERSONATION_REQUEST_NOT_APPROVED = "Awaiting approval for impersonation request";
77+
export const INVALID_ACTION_PARAM = "Invalid 'action' parameter: must be either 'START' or 'STOP'";
78+
7779

7880

7981
export const IMPERSONATION_LOG_TYPE = {

controllers/impersonationRequests.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -163,30 +163,32 @@ export const impersonationController = async (
163163
const { action } = req.query;
164164
const requestId = req.params.id;
165165
const userId = req.userData?.id;
166-
167-
let body;
166+
let authCookie;
168167
let response;
169-
170168
try {
169+
171170
if (action === "START") {
171+
authCookie = await generateImpersonationTokenService(requestId, action);
172172
response = await startImpersonationService({ requestId, userId });
173-
body = await generateImpersonationTokenService(requestId, action);
174-
} else if (action === "STOP" && req?.isImpersonating) {
173+
}
174+
175+
if (action === "STOP") {
176+
if (!req.isImpersonating) {
177+
throw new Forbidden("Cannot stop impersonation: no active impersonation session");
178+
}
179+
authCookie = await generateImpersonationTokenService(requestId, action);
175180
response = await stopImpersonationService({ requestId, userId });
176-
body = await generateImpersonationTokenService(requestId, action);
177-
} else {
178-
throw new Forbidden("Invalid impersonation session");
179181
}
180182

181-
res.clearCookie(body.name);
182-
res.cookie(body.name, body.value, body.options);
183+
res.clearCookie(authCookie.name);
184+
res.cookie(authCookie.name, authCookie.value, authCookie.options);
183185

184186
return res.status(200).json({
185187
message: response?.returnMessage,
186188
data: response.updatedRequest
187189
});
188190
} catch (error) {
189-
logger.error("Error while handling impersonation request", error);
191+
logger.error(`Failed to process impersonation ${action} for requestId=${requestId}, userId=${userId}`, error);
190192
next(error);
191193
}
192194
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 } from "../constants/requests";
6+
7+
/**
8+
* Middleware to authorize impersonation based on the 'action' query param.
9+
* - START → Requires SUPERUSER role.
10+
* - END → Allows without additional checks.
11+
* - Invalid or missing action → Responds with 400 Bad Request.
12+
*/
13+
export const addAuthorizationForImpersonation = async (
14+
req: ImpersonationSessionRequest,
15+
res: ImpersonationRequestResponse,
16+
next: NextFunction
17+
) => {
18+
const { action } = req.query;
19+
20+
if (action === "START") {
21+
return authorizeRoles([SUPERUSER])(req, res, next);
22+
}
23+
24+
if (action === "STOP") {
25+
return next();
26+
}
27+
28+
return res.boom.badRequest(INVALID_ACTION_PARAM);
29+
};

middlewares/authenticate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const checkRestricted = async (req, res, next) => {
2525
req.query.action === "STOP";
2626

2727
if (req.method !== "GET" && !isStopImpersonationRoute) {
28-
return res.boom.forbidden("Only viewing is permitted during impersonation");
28+
return res.boom.forbidden("You are not allowed for this operation at the moment");
2929
}
3030
}
3131

models/impersonationRequests.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import {
44
IMPERSONATION_NOT_COMPLETED,
55
REQUEST_ALREADY_PENDING,
66
REQUEST_STATE,
7-
ERROR_WHILE_FETCHING_REQUEST
7+
ERROR_WHILE_FETCHING_REQUEST,
8+
ERROR_WHILE_UPDATING_REQUEST
89
} from "../constants/requests";
910
import { Timestamp } from "firebase-admin/firestore";
1011
import { Query, CollectionReference } from '@google-cloud/firestore';
11-
import { CreateImpersonationRequestModelDto, ImpersonationRequest, PaginatedImpersonationRequests,ImpersonationRequestQuery} from "../types/impersonationRequest";
12+
import { CreateImpersonationRequestModelDto, ImpersonationRequest, PaginatedImpersonationRequests,ImpersonationRequestQuery, UpdateImpersonationRequestModelDto} from "../types/impersonationRequest";
1213
import { Forbidden } from "http-errors";
1314
const logger = require("../utils/logger");
1415
const impersonationRequestModel = firestore.collection("impersonationRequests");

routes/impersonation.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const authorizeRoles = require("../middlewares/authorizeRoles");
55
const { SUPERUSER } = require("../constants/roles");
66
import authenticate from "../middlewares/authenticate";
77
import { createImpersonationRequestController, getImpersonationRequestByIdController, getImpersonationRequestsController, impersonationController } from "../controllers/impersonationRequests";
8+
import { addAuthorizationForImpersonation } from "../middlewares/addAuthorizationForImpersonation";
89

910
router.post(
1011
"/requests",
@@ -28,7 +29,12 @@ router.get(
2829
getImpersonationRequestByIdController
2930
);
3031

31-
32-
router.patch("/:id",authenticate,impersonationSessionValidator,impersonationController)
32+
router.patch(
33+
"/:id",
34+
authenticate,
35+
impersonationSessionValidator,
36+
addAuthorizationForImpersonation,
37+
impersonationController
38+
);
3339

3440
module.exports = router;

services/authService.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const generateAuthToken = (payload) => {
1515
const generateImpersonationAuthToken = (payload) => {
1616
return jwt.sign(payload, config.get("userToken.privateKey"), {
1717
algorithm: "RS256",
18-
expiresIn: config.get("impersonationTtl"),
18+
expiresIn: config.get("userToken.impersonationTtl"),
1919
});
2020
};
2121

services/impersonationRequests.ts

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
ERROR_WHILE_CREATING_REQUEST,
33
IMPERSONATION_LOG_TYPE,
4+
INVALID_ACTION_PARAM,
45
LOG_ACTION,
56
REQUEST_DOES_NOT_EXIST,
67
REQUEST_LOG_TYPE,
@@ -11,7 +12,7 @@ import { createImpersonationRequest, getImpersonationRequestById, updateImperson
1112
import { fetchUser } from "../models/users";
1213
import { addLog } from "./logService";
1314
import { User } from "../typeDefinitions/users";
14-
import { NotFound, Forbidden } from "http-errors";
15+
import { NotFound, Forbidden, BadRequest } from "http-errors";
1516
import { CreateImpersonationRequestServiceBody, ImpersonationRequest, ImpersonationSessionServiceBody, UpdateImpersonationRequestDataResponse } from "../types/impersonationRequest";
1617
import { Timestamp } from "firebase-admin/firestore";
1718
import config from "config";
@@ -158,7 +159,7 @@ export const stopImpersonationService = async (
158159
throw new NotFound(REQUEST_DOES_NOT_EXIST);
159160
}
160161
if (impersonationRequest.impersonatedUserId !== body.userId) {
161-
throw new Forbidden("You are not authorized for this action");
162+
throw new Forbidden("You are not allowed for this operation at the moment");
162163
}
163164

164165
const newBody = { endedAt: Timestamp.now() };
@@ -207,37 +208,48 @@ export const generateImpersonationTokenService = async (
207208
requestId: string,
208209
action: string
209210
): Promise<{ name: string, value: string, options: object }> => {
210-
try {
211+
try {
211212
const request = await getImpersonationRequestById(requestId);
212213
if (!request) {
213214
throw new NotFound(REQUEST_DOES_NOT_EXIST);
214215
}
215-
const impersonatedUserId = request.impersonatedUserId;
216-
const userId = request.userId;
217-
const cookieName = config.get("userToken.cookieName") as string;
218-
const rdsUiUrl = new URL(config.get("services.rdsUi.baseUrl"));
216+
217+
const { userId, impersonatedUserId } = request;
218+
const cookieName = config.get<string>("userToken.cookieName");
219+
const rdsUiUrl = new URL(config.get<string>("services.rdsUi.baseUrl"));
220+
const ttlInSeconds = Number(config.get("userToken.ttl"));
221+
219222
let token: string;
220-
if (action === "START") {
221-
token = await authService.generateImpersonationAuthToken({ userId, impersonatedUserId });
222-
} else if (action === "STOP") {
223-
token = await authService.generateAuthToken({ userId });
224-
} else {
225-
throw new Forbidden("Action can be only START/STOP");
223+
224+
switch (action) {
225+
case "START":
226+
token = await authService.generateImpersonationAuthToken({ userId, impersonatedUserId });
227+
break;
228+
229+
case "STOP":
230+
token = await authService.generateAuthToken({ userId });
231+
break;
232+
233+
default:
234+
throw new BadRequest(INVALID_ACTION_PARAM);
226235
}
227236

228237
return {
229238
name: cookieName,
230239
value: token,
231240
options: {
232241
domain: rdsUiUrl.hostname,
233-
expires: new Date(Date.now() + Number(config.get("userToken.ttl")) * 1000),
242+
expires: new Date(Date.now() + ttlInSeconds * 1000),
234243
httpOnly: true,
235244
secure: true,
236245
sameSite: "lax",
237-
}
246+
},
238247
};
239248
} catch (error) {
240-
logger.error("Error while generating impersonation token", error);
249+
logger.error(
250+
`Error generating impersonation token for requestId=${requestId}, action=${action}`,
251+
error
252+
);
241253
throw error;
242254
}
243255
};

0 commit comments

Comments
 (0)