use cookie to store the bearer token

This commit is contained in:
StarAppeal
2025-09-11 05:35:51 +02:00
parent 65c9857704
commit 9e63c9d33e
7 changed files with 238 additions and 65 deletions
+95 -65
View File
@@ -1,10 +1,10 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {describe, it, expect, vi, beforeEach, afterEach} from "vitest";
import request from "supertest";
import express from "express";
import { RestAuth } from "../../src/rest/auth";
import { UserService } from "../../src/db/services/db/UserService";
import { JwtAuthenticator } from "../../src/utils/jwtAuthenticator";
import { PasswordUtils } from "../../src/utils/passwordUtils";
import {RestAuth} from "../../src/rest/auth";
import {UserService} from "../../src/db/services/db/UserService";
import {JwtAuthenticator} from "../../src/utils/jwtAuthenticator";
import {PasswordUtils} from "../../src/utils/passwordUtils";
import {createMockJwtAuthenticator, createMockUserService, createPublicTestApp} from "../helpers/testSetup";
import crypto from "crypto";
@@ -75,11 +75,11 @@ describe("RestAuth", () => {
uuid: mockUUID,
timezone: "Europe/Berlin",
location: "Berlin, Germany",
config: { isVisible: false, isAdmin: false, canBeModified: false },
config: {isVisible: false, isAdmin: false, canBeModified: false},
};
mockUserService.existsUserByName.mockResolvedValue(false);
mockPasswordUtils.validatePassword.mockReturnValue({ valid: true });
mockPasswordUtils.validatePassword.mockReturnValue({valid: true});
mockPasswordUtils.hashPassword.mockResolvedValue(hashedPassword);
mockCrypto.randomUUID.mockReturnValue(mockUUID);
mockUserService.createUser.mockResolvedValue(createdUser);
@@ -92,7 +92,7 @@ describe("RestAuth", () => {
name: "testuser",
password: hashedPassword,
uuid: mockUUID,
config: { isVisible: false, isAdmin: false, canBeModified: false },
config: {isVisible: false, isAdmin: false, canBeModified: false},
timezone: "Europe/Berlin",
location: "Berlin, Germany",
});
@@ -118,12 +118,12 @@ describe("RestAuth", () => {
});
it.each([
{ field: "username" },
{ field: "password" },
{ field: "timezone" },
{ field: "location" },
])("should return bad request when $field is missing", async ({ field }) => {
const invalidData = { ...validRegistrationData };
{field: "username"},
{field: "password"},
{field: "timezone"},
{field: "location"},
])("should return bad request when $field is missing", async ({field}) => {
const invalidData = {...validRegistrationData};
delete (invalidData as any)[field];
const response = await request(app).post("/auth/register").send(invalidData).expect(400);
@@ -132,16 +132,16 @@ describe("RestAuth", () => {
});
it.each([
{ field: "username", value: "", message: "username" },
{ field: "username", value: "ab", message: "username" },
{ field: "password", value: "", message: "password" },
{ field: "password", value: "short", message: "password" },
{ field: "timezone", value: "", message: "timezone" },
{ field: "location", value: "", message: "location" },
])("should return bad request for invalid value in $field", async ({ field, value, message }) => {
{field: "username", value: "", message: "username"},
{field: "username", value: "ab", message: "username"},
{field: "password", value: "", message: "password"},
{field: "password", value: "short", message: "password"},
{field: "timezone", value: "", message: "timezone"},
{field: "location", value: "", message: "location"},
])("should return bad request for invalid value in $field", async ({field, value, message}) => {
const response = await request(app)
.post("/auth/register")
.send({ ...validRegistrationData, [field]: value })
.send({...validRegistrationData, [field]: value})
.expect(400);
expect(response.body.ok).toBe(false);
expect(response.body.data.details[0]).toContain(message);
@@ -149,10 +149,10 @@ describe("RestAuth", () => {
});
describe("POST /login", () => {
const validLoginData = { username: "testuser", password: "TestPassword123!" };
const validLoginData = {username: "testuser", password: "TestPassword123!"};
it("should login successfully with valid credentials", async () => {
const mockUser = { name: "testuser", password: "hashed", uuid: "uuid-123", id: "user-id-123" };
const mockUser = {name: "testuser", password: "hashed", uuid: "uuid-123", id: "user-id-123"};
const mockToken = "jwt-token-123";
mockUserService.getUserAuthByName.mockResolvedValue(mockUser);
@@ -168,58 +168,88 @@ describe("RestAuth", () => {
id: "user-id-123",
uuid: "uuid-123",
});
const cookieHeader = response.headers['set-cookie'];
expect(cookieHeader).toBeDefined();
const cookies = Array.isArray(cookieHeader) ? cookieHeader : [cookieHeader!];
const authTokenCookie = cookies.find((cookie: string) => cookie.startsWith("auth-token="));
expect(authTokenCookie).toBeDefined();
expect(authTokenCookie).toContain(`auth-token=${mockToken}`);
expect(authTokenCookie).toContain("HttpOnly");
expect(authTokenCookie).toContain("Path=/");
expect(authTokenCookie).toContain("SameSite=Lax");
});
it("should handle user with _id instead of id", async () => {
const mockUser = { name: "testuser", password: "hashed", uuid: "uuid-123", _id: "user-id-123" };
const mockToken = "jwt-token-123";
describe("POST /logout", () => {
it("should clear the auth-token cookie and return a success message", async () => {
const response = await request(app).post("/auth/logout").send().expect(200);
mockUserService.getUserAuthByName.mockResolvedValue(mockUser);
mockPasswordUtils.comparePassword.mockResolvedValue(true);
mockJwtAuthenticator.generateToken.mockReturnValue(mockToken);
expect(response.body.ok).toBe(true);
expect(response.body.data.message).toBe("Successfully logged out");
await request(app).post("/auth/login").send(validLoginData).expect(200);
const cookieHeader = response.headers['set-cookie'];
expect(cookieHeader).toBeDefined();
const cookies = Array.isArray(cookieHeader) ? cookieHeader : [cookieHeader!];
expect(mockJwtAuthenticator.generateToken).toHaveBeenCalledWith({
username: "testuser",
id: "user-id-123",
uuid: "uuid-123",
const authTokenCookie = cookies.find((cookie: string) => cookie.startsWith("auth-token="));
expect(authTokenCookie).toBeDefined();
expect(authTokenCookie).toContain("auth-token=;");
expect(authTokenCookie).toContain("Expires=Thu, 01 Jan 1970 00:00:00 GMT");
});
});
it("should return not found when user does not exist", async () => {
mockUserService.getUserAuthByName.mockResolvedValue(null);
const response = await request(app).post("/auth/login").send(validLoginData).expect(404);
expect(response.body.ok).toBe(false);
expect(response.body.data.message).toBe("User not found");
});
it("should handle user with _id instead of id", async () => {
const mockUser = {name: "testuser", password: "hashed", uuid: "uuid-123", _id: "user-id-123"};
const mockToken = "jwt-token-123";
it("should return unauthorized for invalid password", async () => {
const mockUser = { name: "testuser", password: "hashed" };
mockUserService.getUserAuthByName.mockResolvedValue(mockUser);
mockPasswordUtils.comparePassword.mockResolvedValue(false);
const response = await request(app).post("/auth/login").send(validLoginData).expect(401);
expect(response.body.ok).toBe(false);
expect(response.body.data.message).toBe("Invalid password");
});
mockUserService.getUserAuthByName.mockResolvedValue(mockUser);
mockPasswordUtils.comparePassword.mockResolvedValue(true);
mockJwtAuthenticator.generateToken.mockReturnValue(mockToken);
it.each([
{ field: "username", value: "" },
{ field: "password", value: "" },
{ field: "username", value: undefined },
{ field: "password", value: undefined },
])("should return bad request if $field is '$value'", async ({ field, value }) => {
const invalidData = { ...validLoginData };
if (value === undefined) {
delete (invalidData as any)[field];
} else {
(invalidData as any)[field] = value;
}
await request(app).post("/auth/login").send(validLoginData).expect(200);
const response = await request(app).post("/auth/login").send(invalidData).expect(400);
expect(mockJwtAuthenticator.generateToken).toHaveBeenCalledWith({
username: "testuser",
id: "user-id-123",
uuid: "uuid-123",
});
});
expect(response.body.ok).toBe(false);
expect(response.body.data.details[0]).toContain(field);
it("should return not found when user does not exist", async () => {
mockUserService.getUserAuthByName.mockResolvedValue(null);
const response = await request(app).post("/auth/login").send(validLoginData).expect(404);
expect(response.body.ok).toBe(false);
expect(response.body.data.message).toBe("User not found");
});
it("should return unauthorized for invalid password", async () => {
const mockUser = {name: "testuser", password: "hashed"};
mockUserService.getUserAuthByName.mockResolvedValue(mockUser);
mockPasswordUtils.comparePassword.mockResolvedValue(false);
const response = await request(app).post("/auth/login").send(validLoginData).expect(401);
expect(response.body.ok).toBe(false);
expect(response.body.data.message).toBe("Invalid password");
});
it.each([
{field: "username", value: ""},
{field: "password", value: ""},
{field: "username", value: undefined},
{field: "password", value: undefined},
])("should return bad request if $field is '$value'", async ({field, value}) => {
const invalidData = {...validLoginData};
if (value === undefined) {
delete (invalidData as any)[field];
} else {
(invalidData as any)[field] = value;
}
const response = await request(app).post("/auth/login").send(invalidData).expect(400);
expect(response.body.ok).toBe(false);
expect(response.body.data.details[0]).toContain(field);
});
});
});
});
+53
View File
@@ -0,0 +1,53 @@
import { describe, it, expect, vi } from "vitest";
import { Request, Response, NextFunction } from "express";
import {cookieJwtAuth} from "../../../src/rest/middleware/cookieAuth";
describe("cookieJwtAuth Middleware", () => {
it("should do nothing if Authorization header already exists", () => {
const req = {
headers: {
authorization: "Bearer existing-token",
},
cookies: {
"auth-token": "some-cookie-token",
},
} as unknown as Request;
const res = {} as Response;
const next = vi.fn() as NextFunction;
cookieJwtAuth(req, res, next);
expect(req.headers.authorization).toBe("Bearer existing-token");
expect(next).toHaveBeenCalledOnce();
});
it("should add Authorization header if it is missing but cookie exists", () => {
const req = {
headers: {},
cookies: {
"auth-token": "my-secret-cookie-token",
},
} as unknown as Request;
const res = {} as Response;
const next = vi.fn() as NextFunction;
cookieJwtAuth(req, res, next);
expect(req.headers.authorization).toBe("Bearer my-secret-cookie-token");
expect(next).toHaveBeenCalledOnce();
});
it("should do nothing if neither Authorization header nor cookie exist", () => {
const req = {
headers: {},
cookies: {},
} as unknown as Request;
const res = {} as Response;
const next = vi.fn() as NextFunction;
cookieJwtAuth(req, res, next);
expect(req.headers.authorization).toBeUndefined();
expect(next).toHaveBeenCalledOnce();
});
});