fix(#411): add runtime null checks in auth controller — defense-in-depth for AuthenticatedRequest

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-16 15:44:31 -06:00
parent d7de20e586
commit 4d9b75994f
2 changed files with 53 additions and 6 deletions

View File

@@ -21,7 +21,7 @@ vi.mock("better-auth/plugins", () => ({
}));
import { Test, TestingModule } from "@nestjs/testing";
import { HttpException, HttpStatus } from "@nestjs/common";
import { HttpException, HttpStatus, UnauthorizedException } from "@nestjs/common";
import type { AuthUser, AuthSession } from "@mosaic/shared";
import type { Request as ExpressRequest, Response as ExpressResponse } from "express";
import { AuthController } from "./auth.controller";
@@ -287,9 +287,50 @@ describe("AuthController", () => {
expect(result).toEqual(expected);
});
// Note: Tests for missing user/session were removed because
// AuthenticatedRequest guarantees both are present (enforced by AuthGuard).
// NestJS returns 401 before getSession is reached if the guard rejects.
it("should throw UnauthorizedException when req.user is undefined", () => {
const mockRequest = {
session: {
id: "session-123",
token: "session-token",
expiresAt: new Date(Date.now() + 86400000),
},
};
expect(() => controller.getSession(mockRequest as never)).toThrow(
UnauthorizedException,
);
expect(() => controller.getSession(mockRequest as never)).toThrow(
"Missing authentication context",
);
});
it("should throw UnauthorizedException when req.session is undefined", () => {
const mockRequest = {
user: {
id: "user-123",
email: "test@example.com",
name: "Test User",
},
};
expect(() => controller.getSession(mockRequest as never)).toThrow(
UnauthorizedException,
);
expect(() => controller.getSession(mockRequest as never)).toThrow(
"Missing authentication context",
);
});
it("should throw UnauthorizedException when both req.user and req.session are undefined", () => {
const mockRequest = {};
expect(() => controller.getSession(mockRequest as never)).toThrow(
UnauthorizedException,
);
expect(() => controller.getSession(mockRequest as never)).toThrow(
"Missing authentication context",
);
});
});
describe("getProfile", () => {

View File

@@ -10,6 +10,7 @@ import {
Logger,
HttpException,
HttpStatus,
UnauthorizedException,
} from "@nestjs/common";
import { Throttle } from "@nestjs/throttler";
import type { Request as ExpressRequest, Response as ExpressResponse } from "express";
@@ -33,8 +34,13 @@ export class AuthController {
@Get("session")
@UseGuards(AuthGuard)
getSession(@Request() req: AuthenticatedRequest): AuthSession {
// AuthGuard guarantees user and session are present — NestJS returns 401
// before this method is reached if the guard rejects.
// Defense-in-depth: AuthGuard should guarantee these, but if someone adds
// a route with AuthenticatedRequest and forgets @UseGuards(AuthGuard),
// TypeScript types won't help at runtime.
if (!req.user || !req.session) {
throw new UnauthorizedException("Missing authentication context");
}
return {
user: req.user,
session: {