dependency injection for jwtAuthenticator

This commit is contained in:
StarAppeal
2025-09-18 22:26:38 +02:00
parent 075fafa122
commit 6aac43311a
4 changed files with 77 additions and 41 deletions
+18 -7
View File
@@ -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);
});
+38 -19
View File
@@ -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");
}
};
}
+1 -1
View File
@@ -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;
});
+20 -14
View File
@@ -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();
});
});