diff --git a/.github/type-coverage.json b/.github/type-coverage.json index 714f44da..f4387ed8 100644 --- a/.github/type-coverage.json +++ b/.github/type-coverage.json @@ -2,8 +2,8 @@ "succeeded": true, "atLeast": 90, "atLeastFailed": false, - "correctCount": 6488, + "correctCount": 6491, "percent": 98.54, "percentString": "98.54", - "totalCount": 6584 + "totalCount": 6587 } diff --git a/package-lock.json b/package-lock.json index c447e4e6..8350c7b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "residents", - "version": "0.3.7", + "version": "0.3.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "residents", - "version": "0.3.7", + "version": "0.3.8", "license": "MIT", "dependencies": { "@paralleldrive/cuid2": "^3.0.0", diff --git a/package.json b/package.json index 8bf31d51..c9e0d21e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "residents", - "version": "0.3.9", + "version": "0.3.10", "author": "Conor Luddy ", "license": "MIT", "description": "Residents is a Node.js Express back-end foundation designed for bootstrapping new projects quickly and efficiently. Its main goal is to set up a robust infrastructure for user management, because users are the core of any application. These users are your Residents. It leverages a robust stack including Postgres, Drizzle ORM, JWT, PassportJS, Docker and Swagger to streamline development and deployment processes.", diff --git a/src/index.ts b/src/index.ts index c6efe200..4cd25bc3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,7 +42,7 @@ if (process.env.NODE_ENV === 'development') { app.disable('x-powered-by') app.use(helmet()) app.use(rateLimiter) -app.use(express.json()) +app.use(express.json({ limit: '10kb' })) // Health check app.get('/health', (_req, res) => { diff --git a/src/middleware/auth/jsonWebTokens.ts b/src/middleware/auth/jsonWebTokens.ts index f2169a79..43e9f114 100644 --- a/src/middleware/auth/jsonWebTokens.ts +++ b/src/middleware/auth/jsonWebTokens.ts @@ -20,7 +20,7 @@ export const authenticateToken = (req: ResidentRequest, _res: Response { + jwt.verify(token, secret, { algorithms: ['HS256'] }, (err, user) => { if (err) { throw new UnauthorizedError(MESSAGES.TOKEN_INVALID) } diff --git a/src/utils/generateJwt.ts b/src/utils/generateJwt.ts index ffb920de..29adea66 100644 --- a/src/utils/generateJwt.ts +++ b/src/utils/generateJwt.ts @@ -12,6 +12,7 @@ export const generateJwtFromUser = (user: User | SafeUser | PublicUser, expiryOv throw new Error(MESSAGES.JWT_SECRET_NOT_FOUND) } return jwt.sign(userToPublicUser(user), JWT_TOKEN_SECRET, { + algorithm: 'HS256', expiresIn: (expiryOverride ?? EXPIRATION_JWT_TOKEN ?? DEFAULT_EXPIRATION) as StringValue, }) } diff --git a/tests/integration/security.test.ts b/tests/integration/security.test.ts new file mode 100644 index 00000000..d4f70647 --- /dev/null +++ b/tests/integration/security.test.ts @@ -0,0 +1,40 @@ +import request from 'supertest' +import jwt from 'jsonwebtoken' +import { app } from '../../src' +import { postgresDatabaseClient } from '../../src/db' +import MESSAGES from '../../src/constants/messages' + +describe('Integration: Security hardening', () => { + beforeAll(async () => { + await postgresDatabaseClient.connect() + await postgresDatabaseClient.query('BEGIN') + }) + + afterAll(async () => { + await postgresDatabaseClient.query('ROLLBACK') + await postgresDatabaseClient.end() + }) + + it('rejects a JWT signed with alg: none', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const unsignedToken = jwt.sign({ id: 'fake', username: 'fake' }, '', { algorithm: 'none' as any }) + const response = await request(app) + .get('/users/self') + .set('Authorization', `Bearer ${unsignedToken}`) + expect(response.status).toBe(401) + expect(response.body).toMatchObject({ message: MESSAGES.UNAUTHORIZED }) + }) + + it('rejects a JSON body larger than 10kb with 413', async () => { + const oversizedBody = { + firstName: 'Test', + lastName: 'User', + email: 'test@example.com', + username: 'testuser', + password: 'STRONGP4$$w0rd_', + padding: 'x'.repeat(11_000), + } + const response = await request(app).post('/users/register').send(oversizedBody) + expect(response.status).toBe(413) + }) +})