mirror of
https://github.com/Nezumi-2711/uptime-monitoring.git
synced 2026-09-22 13:48:31 +00:00
116 lines
4.0 KiB
TypeScript
116 lines
4.0 KiB
TypeScript
import { applyD1Migrations, env, SELF, type D1Migration } from 'cloudflare:test';
|
|
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
import { hashPassword } from '../src/worker/lib/password';
|
|
|
|
const ADMIN_PASSWORD = 'correct-horse-battery-staple';
|
|
|
|
async function seedAdmin() {
|
|
await env.DB.batch([
|
|
env.DB.prepare('DELETE FROM login_attempts'),
|
|
env.DB.prepare('DELETE FROM sessions'),
|
|
env.DB.prepare('DELETE FROM admin_credentials'),
|
|
]);
|
|
|
|
const now = Date.now();
|
|
await env.DB.prepare('INSERT INTO admin_credentials (id, password_hash, created_at, updated_at) VALUES (1, ?, ?, ?)')
|
|
.bind(await hashPassword(ADMIN_PASSWORD), now, now)
|
|
.run();
|
|
}
|
|
|
|
function login(password = ADMIN_PASSWORD, ipAddress = '198.51.100.10') {
|
|
return SELF.fetch('https://example.com/api/auth/login', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'CF-Connecting-IP': ipAddress,
|
|
Origin: 'https://example.com',
|
|
},
|
|
body: JSON.stringify({ password }),
|
|
});
|
|
}
|
|
|
|
function cookieFrom(response: Response) {
|
|
return response.headers.get('Set-Cookie')?.split(';', 1)[0] ?? '';
|
|
}
|
|
|
|
describe('authentication', () => {
|
|
beforeAll(async () => {
|
|
const testEnv = env as Env & { TEST_MIGRATIONS: D1Migration[] };
|
|
await applyD1Migrations(testEnv.DB, testEnv.TEST_MIGRATIONS);
|
|
});
|
|
beforeEach(seedAdmin);
|
|
|
|
it('logs in with the admin password and creates an HttpOnly session', async () => {
|
|
const response = await login();
|
|
const body = await response.json<{ authenticated: boolean }>();
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.headers.get('Set-Cookie')).toContain('upwatch_session=');
|
|
expect(response.headers.get('Set-Cookie')).toContain('HttpOnly');
|
|
expect(response.headers.get('Set-Cookie')).toContain('SameSite=Lax');
|
|
expect(body).toEqual({ authenticated: true });
|
|
|
|
const session = await env.DB.prepare('SELECT id FROM sessions').first();
|
|
expect(session).not.toBeNull();
|
|
});
|
|
|
|
it('rejects an incorrect password without setting a cookie', async () => {
|
|
const response = await login('incorrect-password');
|
|
|
|
expect(response.status).toBe(401);
|
|
expect(response.headers.get('Set-Cookie')).toBeNull();
|
|
expect(await response.json()).toEqual({
|
|
message: 'Password is incorrect',
|
|
});
|
|
});
|
|
|
|
it('returns authentication state from the session endpoint', async () => {
|
|
const anonymousResponse = await SELF.fetch('https://example.com/api/auth/me');
|
|
expect(anonymousResponse.status).toBe(200);
|
|
expect(await anonymousResponse.json()).toEqual({ authenticated: false });
|
|
|
|
const loginResponse = await login();
|
|
const authenticatedResponse = await SELF.fetch('https://example.com/api/auth/me', {
|
|
headers: { Cookie: cookieFrom(loginResponse) },
|
|
});
|
|
|
|
expect(authenticatedResponse.status).toBe(200);
|
|
expect(await authenticatedResponse.json()).toEqual({ authenticated: true });
|
|
});
|
|
|
|
it('revokes the persisted session on logout', async () => {
|
|
const loginResponse = await login();
|
|
const cookie = cookieFrom(loginResponse);
|
|
const logoutResponse = await SELF.fetch('https://example.com/api/auth/logout', {
|
|
method: 'POST',
|
|
headers: {
|
|
Cookie: cookie,
|
|
Origin: 'https://example.com',
|
|
},
|
|
});
|
|
|
|
expect(logoutResponse.status).toBe(200);
|
|
expect(await logoutResponse.json()).toEqual({ ok: true });
|
|
const sessionCount = await env.DB.prepare('SELECT COUNT(*) AS count FROM sessions').first<{ count: number }>();
|
|
expect(sessionCount?.count).toBe(0);
|
|
|
|
const sessionResponse = await SELF.fetch('https://example.com/api/auth/me', {
|
|
headers: { Cookie: cookie },
|
|
});
|
|
expect(await sessionResponse.json()).toEqual({ authenticated: false });
|
|
});
|
|
|
|
it('rate limits repeated failed login attempts by IP', async () => {
|
|
const responses: Response[] = [];
|
|
for (let attempt = 0; attempt < 11; attempt += 1) {
|
|
responses.push(await login('incorrect-password', '203.0.113.42'));
|
|
}
|
|
|
|
expect(responses.slice(0, 10).every((response) => response.status === 401)).toBe(true);
|
|
expect(responses[10].status).toBe(429);
|
|
expect(await responses[10].json()).toEqual({
|
|
message: 'Too many login attempts. Try again later',
|
|
});
|
|
});
|
|
});
|