dependency injection for jwtAuthenticator
This commit is contained in:
+18
-7
@@ -2,7 +2,6 @@ import express from "express";
|
||||
import {ExtendedWebSocketServer} from "./websocket";
|
||||
import {RestWebSocket} from "./rest/restWebSocket";
|
||||
import {RestUser} from "./rest/restUser";
|
||||
import {authenticateJwt} from "./rest/middleware/authenticateJwt";
|
||||
import {JwtTokenPropertiesExtractor} from "./rest/jwtTokenPropertiesExtractor";
|
||||
import cors from "cors";
|
||||
import {SpotifyTokenGenerator} from "./rest/spotifyTokenGenerator";
|
||||
@@ -13,8 +12,10 @@ import {authLimiter, spotifyLimiter} from "./rest/middleware/rateLimit";
|
||||
import {cookieJwtAuth} from "./rest/middleware/cookieAuth";
|
||||
import {UserService} from "./db/services/db/UserService";
|
||||
import {randomUUID} from "crypto";
|
||||
import {JwtAuthenticator} from "./utils/jwtAuthenticator";
|
||||
import {authenticateJwt} from "./rest/middleware/authenticateJwt";
|
||||
|
||||
export async function startServer() {
|
||||
export async function startServer(jwtSecret: string) {
|
||||
const app = express();
|
||||
const port = config.port;
|
||||
|
||||
@@ -44,6 +45,8 @@ export async function startServer() {
|
||||
const userService = await UserService.create();
|
||||
console.log("UserService created successfully.");
|
||||
|
||||
const _authenticateJwt = authenticateJwt(new JwtAuthenticator(jwtSecret));
|
||||
|
||||
const server = app.listen(port, () => {
|
||||
console.log(`Server is running on port ${port}`);
|
||||
});
|
||||
@@ -58,12 +61,12 @@ export async function startServer() {
|
||||
app.use("/api/auth", authLimiter, auth.createRouter());
|
||||
|
||||
app.use(cookieJwtAuth);
|
||||
app.use("/api/spotify", authenticateJwt, spotifyLimiter, spotify.createRouter());
|
||||
app.use("/api/websocket", authenticateJwt, restWebSocket.createRouter());
|
||||
app.use("/api/user", authenticateJwt, restUser.createRouter());
|
||||
app.use("/api/spotify", _authenticateJwt, spotifyLimiter, spotify.createRouter());
|
||||
app.use("/api/websocket", _authenticateJwt, restWebSocket.createRouter());
|
||||
app.use("/api/user", _authenticateJwt, restUser.createRouter());
|
||||
app.use(
|
||||
"/api/jwt",
|
||||
authenticateJwt,
|
||||
_authenticateJwt,
|
||||
jwtTokenPropertiesExtractor.createRouter(),
|
||||
);
|
||||
|
||||
@@ -108,8 +111,16 @@ export async function startServer() {
|
||||
return {app, server};
|
||||
}
|
||||
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
startServer().catch(error => {
|
||||
const JWT_SECRET = process.env.SECRET_KEY;
|
||||
|
||||
if (!JWT_SECRET || JWT_SECRET.length < 32) {
|
||||
console.error("CRITICAL ERROR: SECRET_KEY environment variable is not set or too short. Aborting.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
startServer(JWT_SECRET).catch(error => {
|
||||
console.error("Fatal error during server startup:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -1,24 +1,43 @@
|
||||
import {JwtAuthenticator} from "../../utils/jwtAuthenticator";
|
||||
import {Request, Response, NextFunction} from "express";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { unauthorized } from "../utils/responses";
|
||||
import { JwtAuthenticator } from "../../utils/jwtAuthenticator";
|
||||
|
||||
export function authenticateJwt(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) {
|
||||
//remove Bearer from the beginning of the token
|
||||
const token = req.headers["authorization"]?.slice("Bearer ".length);
|
||||
const BEARER_PREFIX = "Bearer ";
|
||||
|
||||
const jwtAuthenticator = new JwtAuthenticator(
|
||||
process.env.SECRET_KEY as string,
|
||||
);
|
||||
export function authenticateJwt(jwtAuthenticator: JwtAuthenticator) {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
const authHeader = req.headers["authorization"];
|
||||
|
||||
const decodedToken = jwtAuthenticator.verifyToken(token);
|
||||
console.log(decodedToken)
|
||||
if (!decodedToken) {
|
||||
return res.status(401).send("Unauthorized");
|
||||
}
|
||||
if (!authHeader) {
|
||||
return unauthorized(
|
||||
res,
|
||||
"Unauthorized: No Authorization header provided"
|
||||
);
|
||||
}
|
||||
|
||||
req.payload = decodedToken;
|
||||
next();
|
||||
if (!authHeader.startsWith(BEARER_PREFIX)) {
|
||||
return unauthorized(res, "Unauthorized: Token must be a Bearer token");
|
||||
}
|
||||
|
||||
const token = authHeader.slice(BEARER_PREFIX.length);
|
||||
|
||||
if (!token) {
|
||||
return unauthorized(res, "Unauthorized: Token is missing");
|
||||
}
|
||||
|
||||
try {
|
||||
const decodedToken = jwtAuthenticator.verifyToken(token);
|
||||
console.log(decodedToken);
|
||||
|
||||
if (!decodedToken) {
|
||||
return unauthorized(res, "Unauthorized: Invalid token");
|
||||
}
|
||||
|
||||
req.payload = decodedToken;
|
||||
next();
|
||||
} catch (error: any) {
|
||||
console.error("JWT Verification Error:", error.message);
|
||||
return unauthorized(res, "Unauthorized: Token verification failed");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ let server: http.Server;
|
||||
beforeAll(async () => {
|
||||
const { startServer } = await import("../src/index");
|
||||
|
||||
const instances = await startServer();
|
||||
const instances = await startServer("not-used");
|
||||
app = instances.app;
|
||||
server = instances.server;
|
||||
});
|
||||
|
||||
@@ -2,24 +2,25 @@ import { describe, it, expect, vi, beforeEach, afterEach, type Mocked } from "vi
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
|
||||
import { authenticateJwt } from "../../../src/rest/middleware/authenticateJwt";
|
||||
import { JwtAuthenticator } from "../../../src/utils/jwtAuthenticator";
|
||||
import { createMockJwtAuthenticator } from "../../helpers/testSetup";
|
||||
|
||||
vi.mock("../../../src/utils/jwtAuthenticator");
|
||||
|
||||
|
||||
describe("authenticateJwt middleware", () => {
|
||||
let mockJwtInstance: ReturnType<typeof createMockJwtAuthenticator>;
|
||||
let req: Mocked<Request>;
|
||||
let res: Mocked<Response>;
|
||||
let next: Mocked<NextFunction>;
|
||||
let _authenticateJwt: any;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.stubEnv('SECRET_KEY', 'test-secret-key');
|
||||
|
||||
mockJwtInstance = createMockJwtAuthenticator();
|
||||
|
||||
vi.mocked(JwtAuthenticator).mockImplementation(() => mockJwtInstance as any);
|
||||
// @ts-ignore
|
||||
_authenticateJwt = authenticateJwt(mockJwtInstance);
|
||||
|
||||
req = { headers: {} } as Mocked<Request>;
|
||||
res = {
|
||||
@@ -39,9 +40,8 @@ describe("authenticateJwt middleware", () => {
|
||||
req.headers.authorization = "Bearer valid-jwt-token";
|
||||
mockJwtInstance.verifyToken.mockReturnValue(mockPayload);
|
||||
|
||||
authenticateJwt(req, res, next);
|
||||
_authenticateJwt(req, res, next);
|
||||
|
||||
expect(vi.mocked(JwtAuthenticator)).toHaveBeenCalledWith("test-secret-key");
|
||||
expect(mockJwtInstance.verifyToken).toHaveBeenCalledWith("valid-jwt-token");
|
||||
expect(req.payload).toEqual(mockPayload);
|
||||
expect(next).toHaveBeenCalledOnce();
|
||||
@@ -51,19 +51,25 @@ describe("authenticateJwt middleware", () => {
|
||||
|
||||
describe("Failure Scenarios", () => {
|
||||
it.each([
|
||||
{ description: "no authorization header", authHeader: undefined, expectedToken: undefined },
|
||||
{ description: "empty authorization header", authHeader: "", expectedToken: "" },
|
||||
{ description: "header with only 'Bearer '", authHeader: "Bearer ", expectedToken: "" },
|
||||
{ description: "an invalid/expired token", authHeader: "Bearer invalid-token", expectedToken: "invalid-token" },
|
||||
])("should return 401 Unauthorized when there is $description", ({ authHeader, expectedToken }) => {
|
||||
{ description: "no authorization header", authHeader: undefined, errorMessage: "Unauthorized: No Authorization header provided" },
|
||||
{ description: "empty authorization header", authHeader: "", errorMessage: "Unauthorized: No Authorization header provided" },
|
||||
{ description: "header with only 'Bearer '", authHeader: "Bearer ", errorMessage: "Unauthorized: Token is missing" },
|
||||
{ description: "an invalid/expired token", authHeader: "Bearer invalid-token", errorMessage: "Unauthorized: Invalid token", callVerifyToken: true },
|
||||
])("should return 401 Unauthorized when there is $description", ({ authHeader, errorMessage, callVerifyToken }) => {
|
||||
req.headers.authorization = authHeader;
|
||||
mockJwtInstance.verifyToken.mockReturnValue(null); // Alle Fehlerfälle führen zu null
|
||||
mockJwtInstance.verifyToken.mockReturnValue(null);
|
||||
|
||||
authenticateJwt(req, res, next);
|
||||
_authenticateJwt(req, res, next);
|
||||
|
||||
expect(mockJwtInstance.verifyToken).toHaveBeenCalledWith(expectedToken);
|
||||
const expected = { ok: false, data: {details: undefined, message: errorMessage}}
|
||||
|
||||
if (!callVerifyToken) {
|
||||
expect(mockJwtInstance.verifyToken).not.toHaveBeenCalled();
|
||||
} else {
|
||||
expect(mockJwtInstance.verifyToken).toHaveBeenCalled();
|
||||
}
|
||||
expect(res.status).toHaveBeenCalledWith(401);
|
||||
expect(res.send).toHaveBeenCalledWith("Unauthorized");
|
||||
expect(res.send).toHaveBeenCalledWith(expected);
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user