Fix .gitignore and add missing authentication source files

This commit fixes overly broad .gitignore patterns that were excluding
important source code files from version control. Previously, wildcard
patterns like *auth*, *token*, *secret*, *connection*, and *credential*
were excluding ALL files containing these words, including critical
application code.

Changes:
- Updated .gitignore with specific patterns for sensitive config files
  (*.json, *.txt, *.yml, *.yaml extensions only)
- Removed broad wildcards that excluded source code files

Added missing source files:
- shared/auth/ (9 files): Complete authentication system
  - JWT handler, middleware, auth service, models, routes
- reports-app/backend/app/routers/auth.py: Authentication API router
- reports-app/backend/app/auth_middleware_wrapper.py: Middleware wrapper
- reports-app/frontend/src/stores/auth.js: Vue.js auth store
- reports-app/frontend/tests/: E2E tests and fixtures for auth
- reports-app/telegram-bot/app/auth/: Telegram auth linking module
- deployment/windows/scripts/Setup-ClaudeAuth.ps1: Windows deployment script
- security/secrets_scanner.py: Security scanning utility

These files are essential for the application to function and should
have been included in the initial commit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-25 15:02:28 +03:00
parent 6b13ffa183
commit f42eff71a6
19 changed files with 5035 additions and 21 deletions

View File

@@ -0,0 +1,223 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../../page-objects/LoginPage.js';
import { testCredentials } from '../../fixtures/auth.js';
test.describe('Authentication - Login Flow', () => {
let loginPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
await loginPage.navigate();
});
test('should display login page correctly', async ({ page }) => {
// Check page title and main elements
await expect(page).toHaveTitle(/ROA Reports/);
// Check login form elements are visible
await expect(page.locator(loginPage.usernameInput)).toBeVisible();
await expect(page.locator(loginPage.passwordInput)).toBeVisible();
await expect(page.locator(loginPage.loginButton)).toBeVisible();
// Check page title
const title = await loginPage.getPageTitle();
expect(title).toContain('ROA Reports');
});
test('should show validation errors for empty fields', async ({ page }) => {
// Check initial state - button should be disabled
expect(await loginPage.isLoginButtonDisabled()).toBe(true);
// Clear any existing content and verify empty state
await page.fill(loginPage.usernameInput, '');
await page.fill(loginPage.passwordInput, '');
await page.click(loginPage.loginCard); // Click outside to trigger validation
// Wait for Vue reactivity
await page.waitForTimeout(100);
// Button should remain disabled with empty fields
expect(await loginPage.isLoginButtonDisabled()).toBe(true);
// Verify form validation classes are applied
const hasInvalidFields = await loginPage.hasInvalidField();
// Note: validation might not show invalid state until user interaction
});
test('should show validation error for empty username', async ({ page }) => {
// Fill only password
await loginPage.fillCredentials('', 'password123');
// Trigger validation by clicking outside
await page.click(loginPage.loginCard);
// Check that login button is disabled
expect(await loginPage.isLoginButtonDisabled()).toBe(true);
});
test('should show validation error for empty password', async ({ page }) => {
// Fill only username
await loginPage.fillCredentials('username', '');
// Trigger validation by clicking outside
await page.click(loginPage.loginCard);
// Check that login button is disabled
expect(await loginPage.isLoginButtonDisabled()).toBe(true);
});
test('should enable login button with valid input', async ({ page: _page }) => {
// Fill both fields
await loginPage.fillCredentials('testuser', 'testpass');
// Check that login button is enabled
expect(await loginPage.isLoginButtonDisabled()).toBe(false);
});
test('should handle invalid credentials', async ({ page }) => {
// Mock the API response for invalid login
await page.route('**/api/auth/login', async route => {
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({
detail: 'Invalid credentials'
}),
});
});
// Attempt login with invalid credentials
await loginPage.login(testCredentials.invalid.username, testCredentials.invalid.password);
// Wait for response
await loginPage.waitForLoginResult();
// Check that we're still on login page
expect(await loginPage.isOnLoginPage()).toBe(true);
// Check that error message appears (via toast or error div)
// Note: Error might be shown via PrimeVue Toast, so we need to check for toast messages
const toastError = page.locator('.p-toast-message-error');
if (await toastError.isVisible()) {
const errorText = await toastError.textContent();
expect(errorText).toContain('Eroare');
}
});
test('should handle successful login', async ({ page }) => {
// Mock the API response for successful login
await page.route('**/api/auth/login', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
access_token: 'mock_access_token',
refresh_token: 'mock_refresh_token',
user: {
id: 1,
username: 'testuser',
full_name: 'Test User'
}
}),
});
});
// Mock companies endpoint for dashboard
await page.route('**/api/companies', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ code: 'COMP1', name: 'Company 1' },
{ code: 'COMP2', name: 'Company 2' }
]),
});
});
// Attempt login with valid credentials
await loginPage.login(testCredentials.valid.username, testCredentials.valid.password);
// Wait for redirect to dashboard
await page.waitForURL('/dashboard', { timeout: 10000 });
// Check that we're on dashboard page
expect(page.url()).toContain('/dashboard');
});
test('should show loading state during login', async ({ page }) => {
// Mock slow API response
await page.route('**/api/auth/login', async route => {
// Delay the response to see loading state
await new Promise(resolve => setTimeout(resolve, 1000));
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
access_token: 'mock_access_token',
refresh_token: 'mock_refresh_token',
user: { id: 1, username: 'testuser' }
}),
});
});
// Fill credentials and submit
await loginPage.fillCredentials(testCredentials.valid.username, testCredentials.valid.password);
await loginPage.clickLogin();
// Check loading state appears
await expect(page.locator(loginPage.loadingSpinner)).toBeVisible();
// Wait for loading to finish
await page.waitForLoadState('networkidle');
});
test('should handle network errors gracefully', async ({ page }) => {
// Mock network error
await page.route('**/api/auth/login', async route => {
await route.abort('failed');
});
// Attempt login
await loginPage.login(testCredentials.valid.username, testCredentials.valid.password);
// Wait a bit for error handling
await page.waitForTimeout(2000);
// Should still be on login page
expect(await loginPage.isOnLoginPage()).toBe(true);
// Check for error message in toast summary or detail
const toastSummary = page.locator('.p-toast-summary');
const toastDetail = page.locator('.p-toast-detail');
// Check if either summary or detail contains error text
const summaryVisible = await toastSummary.isVisible();
const detailVisible = await toastDetail.isVisible();
if (summaryVisible || detailVisible) {
let errorFound = false;
if (summaryVisible) {
const summaryText = await toastSummary.textContent();
if (summaryText && summaryText.toLowerCase().includes('eroare')) {
errorFound = true;
}
}
if (detailVisible) {
const detailText = await toastDetail.textContent();
if (detailText && detailText.toLowerCase().includes('eroare')) {
errorFound = true;
}
}
expect(errorFound).toBe(true);
}
});
test('should focus username field on page load', async ({ page }) => {
// Check that username field is focused
const focusedElement = await page.locator(':focus');
await expect(focusedElement).toHaveAttribute('id', 'username');
});
});