feature-custom-store-locations #4
@ -8,27 +8,30 @@ const { logError } = require("../utils/logger");
|
||||
async function auth(req, res, next) {
|
||||
const header = req.headers.authorization || "";
|
||||
const token = header.startsWith("Bearer ") ? header.slice(7).trim() : null;
|
||||
const cookies = parseCookieHeader(req.headers.cookie);
|
||||
const sid = cookies[cookieName()];
|
||||
|
||||
if (token) {
|
||||
const jwtSecret = process.env.JWT_SECRET;
|
||||
if (!jwtSecret) {
|
||||
if (!jwtSecret && !sid) {
|
||||
logError(req, "middleware.auth.jwtSecretMissing", new Error("JWT_SECRET is not configured"));
|
||||
return sendError(res, 500, "Authentication is unavailable");
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, jwtSecret);
|
||||
req.user = decoded; // id + role
|
||||
return next();
|
||||
} catch (err) {
|
||||
return sendError(res, 401, "Invalid or expired token");
|
||||
if (jwtSecret) {
|
||||
try {
|
||||
const decoded = jwt.verify(token, jwtSecret);
|
||||
req.user = decoded; // id + role
|
||||
return next();
|
||||
} catch (err) {
|
||||
if (!sid) {
|
||||
return sendError(res, 401, "Invalid or expired token");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const cookies = parseCookieHeader(req.headers.cookie);
|
||||
const sid = cookies[cookieName()];
|
||||
|
||||
if (!sid) {
|
||||
return sendError(res, 401, "Missing authentication");
|
||||
}
|
||||
|
||||
@ -10,16 +10,14 @@ async function optionalAuth(req, res, next) {
|
||||
|
||||
if (token) {
|
||||
const jwtSecret = process.env.JWT_SECRET;
|
||||
if (!jwtSecret) {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, jwtSecret);
|
||||
req.user = decoded;
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next();
|
||||
if (jwtSecret) {
|
||||
try {
|
||||
const decoded = jwt.verify(token, jwtSecret);
|
||||
req.user = decoded;
|
||||
return next();
|
||||
} catch (err) {
|
||||
// Continue to the session cookie fallback below.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
118
backend/tests/auth.middleware.test.js
Normal file
118
backend/tests/auth.middleware.test.js
Normal file
@ -0,0 +1,118 @@
|
||||
jest.mock("jsonwebtoken", () => ({
|
||||
verify: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../models/session.model", () => ({
|
||||
getActiveSessionWithUser: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../utils/logger", () => ({
|
||||
logError: jest.fn(),
|
||||
}));
|
||||
|
||||
const jwt = require("jsonwebtoken");
|
||||
const Session = require("../models/session.model");
|
||||
const auth = require("../middleware/auth");
|
||||
|
||||
function createResponse() {
|
||||
const res = {};
|
||||
res.status = jest.fn().mockReturnValue(res);
|
||||
res.json = jest.fn().mockReturnValue(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
describe("auth middleware", () => {
|
||||
const originalJwtSecret = process.env.JWT_SECRET;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.JWT_SECRET = "test-secret";
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
if (originalJwtSecret === undefined) {
|
||||
delete process.env.JWT_SECRET;
|
||||
} else {
|
||||
process.env.JWT_SECRET = originalJwtSecret;
|
||||
}
|
||||
});
|
||||
|
||||
test("uses a valid bearer token without reading the session cookie", async () => {
|
||||
jwt.verify.mockReturnValue({ id: 5, role: "admin" });
|
||||
|
||||
const req = {
|
||||
headers: {
|
||||
authorization: "Bearer valid-token",
|
||||
cookie: "sid=session-id",
|
||||
},
|
||||
};
|
||||
const res = createResponse();
|
||||
const next = jest.fn();
|
||||
|
||||
await auth(req, res, next);
|
||||
|
||||
expect(jwt.verify).toHaveBeenCalledWith("valid-token", "test-secret");
|
||||
expect(Session.getActiveSessionWithUser).not.toHaveBeenCalled();
|
||||
expect(req.user).toEqual({ id: 5, role: "admin" });
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("falls back to a valid session cookie when the bearer token is stale", async () => {
|
||||
jwt.verify.mockImplementation(() => {
|
||||
throw new Error("stale token");
|
||||
});
|
||||
Session.getActiveSessionWithUser.mockResolvedValue({
|
||||
id: "session-id",
|
||||
user_id: 7,
|
||||
role: "user",
|
||||
username: "shopper",
|
||||
});
|
||||
|
||||
const req = {
|
||||
headers: {
|
||||
authorization: "Bearer stale-token",
|
||||
cookie: "sid=session-id",
|
||||
},
|
||||
};
|
||||
const res = createResponse();
|
||||
const next = jest.fn();
|
||||
|
||||
await auth(req, res, next);
|
||||
|
||||
expect(Session.getActiveSessionWithUser).toHaveBeenCalledWith("session-id");
|
||||
expect(req.user).toEqual({
|
||||
id: 7,
|
||||
role: "user",
|
||||
username: "shopper",
|
||||
});
|
||||
expect(req.session_id).toBe("session-id");
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(res.status).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("rejects a stale bearer token when no session cookie is present", async () => {
|
||||
jwt.verify.mockImplementation(() => {
|
||||
throw new Error("stale token");
|
||||
});
|
||||
|
||||
const req = {
|
||||
headers: {
|
||||
authorization: "Bearer stale-token",
|
||||
},
|
||||
};
|
||||
const res = createResponse();
|
||||
const next = jest.fn();
|
||||
|
||||
await auth(req, res, next);
|
||||
|
||||
expect(Session.getActiveSessionWithUser).not.toHaveBeenCalled();
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
expect(res.status).toHaveBeenCalledWith(401);
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
error: {
|
||||
code: "unauthorized",
|
||||
message: "Invalid or expired token",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user