Christian Mueller f7d16c077e feat(middleware): add JWT auth module with bcryptjs login and protected routes
- src/api/middleware/auth.ts: signToken, verifyToken, authMiddleware (Bearer JWT, 24h expiry)
- src/api/routes/auth.ts: POST /api/auth/login (bcryptjs verify) and GET /api/auth/me (protected)
- src/index.ts: mount auth router at /api/auth
- tests/api/auth.test.ts: 7 tests covering login success, wrong password, no token, invalid token, valid access
- Using bcryptjs (pure JS) instead of bcrypt (requires Visual Studio on Windows)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 12:46:50 +02:00

89 lines
2.5 KiB
TypeScript

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import bcrypt from 'bcryptjs';
import app from '../../src/index';
import { initDb, closeDb } from '../../src/db/index';
beforeAll(() => {
const db = initDb(':memory:');
// Create a test user
const hash = bcrypt.hashSync('correctpassword', 10);
db.run(
`INSERT INTO users (username, password_hash) VALUES (?, ?)`,
['testuser', hash]
);
});
afterAll(() => {
closeDb();
});
describe('POST /api/auth/login', () => {
it('should return JWT on successful login', async () => {
const res = await request(app)
.post('/api/auth/login')
.send({ username: 'testuser', password: 'correctpassword' });
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('token');
expect(typeof res.body.token).toBe('string');
expect(res.body.token.split('.').length).toBe(3); // valid JWT format
});
it('should reject wrong password with 401', async () => {
const res = await request(app)
.post('/api/auth/login')
.send({ username: 'testuser', password: 'wrongpassword' });
expect(res.status).toBe(401);
expect(res.body).toHaveProperty('error');
});
it('should reject unknown user with 401', async () => {
const res = await request(app)
.post('/api/auth/login')
.send({ username: 'nouser', password: 'anything' });
expect(res.status).toBe(401);
});
it('should reject missing credentials with 400', async () => {
const res = await request(app)
.post('/api/auth/login')
.send({});
expect(res.status).toBe(400);
});
});
describe('Auth middleware', () => {
it('should reject requests without token with 401', async () => {
const res = await request(app).get('/api/auth/me');
expect(res.status).toBe(401);
});
it('should reject requests with invalid token with 401', async () => {
const res = await request(app)
.get('/api/auth/me')
.set('Authorization', 'Bearer invalid.token.here');
expect(res.status).toBe(401);
});
it('should allow access with valid token', async () => {
// First login to get a token
const loginRes = await request(app)
.post('/api/auth/login')
.send({ username: 'testuser', password: 'correctpassword' });
const token = loginRes.body.token;
const res = await request(app)
.get('/api/auth/me')
.set('Authorization', `Bearer ${token}`);
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('username', 'testuser');
});
});