release: v0.5.0 - Authentication, API Keys & Advanced Features
Some checks failed
E2E Tests / Run E2E Tests (push) Has been cancelled
E2E Tests / Visual Regression Tests (push) Has been cancelled
E2E Tests / Smoke Tests (push) Has been cancelled

Complete v0.5.0 implementation:

Database (@db-engineer):
- 3 migrations: users, api_keys, report_schedules tables
- Foreign keys, indexes, constraints, enums

Backend (@backend-dev):
- JWT authentication service with bcrypt (cost=12)
- Auth endpoints: /register, /login, /refresh, /me
- API Keys service with hash storage and prefix validation
- API Keys endpoints: CRUD + rotate
- Security module with JWT HS256

Frontend (@frontend-dev):
- Login/Register pages with validation
- AuthContext with localStorage persistence
- Protected routes implementation
- API Keys management UI (create, revoke, rotate)
- Header with user dropdown

DevOps (@devops-engineer):
- .env.example and .env.production.example
- docker-compose.scheduler.yml
- scripts/setup-secrets.sh
- INFRASTRUCTURE_SETUP.md

QA (@qa-engineer):
- 85 E2E tests: auth.spec.ts, apikeys.spec.ts, scenarios.spec.ts, regression-v050.spec.ts
- auth-helpers.ts with 20+ utility functions
- Test plans and documentation

Architecture (@spec-architect):
- SECURITY.md with best practices
- SECURITY-CHECKLIST.md pre-deployment
- Updated architecture.md with auth flows
- Updated README.md with v0.5.0 features

Documentation:
- Updated todo.md with v0.5.0 status
- Added docs/README.md index
- Complete setup instructions

Dependencies added:
- bcrypt, python-jose, passlib, email-validator

Tested: JWT auth flow, API keys CRUD, protected routes, 85 E2E tests ready

Closes: v0.5.0 milestone
This commit is contained in:
Luca Sacchi Ricciardi
2026-04-07 19:22:47 +02:00
parent 9b9297b7dc
commit cc60ba17ea
49 changed files with 9847 additions and 176 deletions

View File

@@ -48,10 +48,17 @@ export async function createScenarioViaAPI(
description?: string;
tags?: string[];
region: string;
}
},
accessToken?: string
) {
const headers: Record<string, string> = {};
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
}
const response = await request.post(`${API_BASE_URL}/scenarios`, {
data: scenario,
headers: Object.keys(headers).length > 0 ? headers : undefined,
});
expect(response.ok()).toBeTruthy();
@@ -63,9 +70,17 @@ export async function createScenarioViaAPI(
*/
export async function deleteScenarioViaAPI(
request: APIRequestContext,
scenarioId: string
scenarioId: string,
accessToken?: string
) {
const response = await request.delete(`${API_BASE_URL}/scenarios/${scenarioId}`);
const headers: Record<string, string> = {};
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
}
const response = await request.delete(`${API_BASE_URL}/scenarios/${scenarioId}`, {
headers: Object.keys(headers).length > 0 ? headers : undefined,
});
// Accept 204 (No Content) or 200 (OK) or 404 (already deleted)
expect([200, 204, 404]).toContain(response.status());
@@ -76,9 +91,17 @@ export async function deleteScenarioViaAPI(
*/
export async function startScenarioViaAPI(
request: APIRequestContext,
scenarioId: string
scenarioId: string,
accessToken?: string
) {
const response = await request.post(`${API_BASE_URL}/scenarios/${scenarioId}/start`);
const headers: Record<string, string> = {};
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
}
const response = await request.post(`${API_BASE_URL}/scenarios/${scenarioId}/start`, {
headers: Object.keys(headers).length > 0 ? headers : undefined,
});
expect(response.ok()).toBeTruthy();
return await response.json();
}
@@ -88,9 +111,17 @@ export async function startScenarioViaAPI(
*/
export async function stopScenarioViaAPI(
request: APIRequestContext,
scenarioId: string
scenarioId: string,
accessToken?: string
) {
const response = await request.post(`${API_BASE_URL}/scenarios/${scenarioId}/stop`);
const headers: Record<string, string> = {};
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
}
const response = await request.post(`${API_BASE_URL}/scenarios/${scenarioId}/stop`, {
headers: Object.keys(headers).length > 0 ? headers : undefined,
});
expect(response.ok()).toBeTruthy();
return await response.json();
}
@@ -101,12 +132,19 @@ export async function stopScenarioViaAPI(
export async function sendTestLogs(
request: APIRequestContext,
scenarioId: string,
logs: unknown[]
logs: unknown[],
accessToken?: string
) {
const headers: Record<string, string> = {};
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
}
const response = await request.post(
`${API_BASE_URL}/scenarios/${scenarioId}/ingest`,
{
data: { logs },
headers: Object.keys(headers).length > 0 ? headers : undefined,
}
);
expect(response.ok()).toBeTruthy();