diff --git a/.env.test b/.env.test index faa0771..be0144e 100644 --- a/.env.test +++ b/.env.test @@ -1,5 +1,5 @@ AUTH_USER=testadmin -AUTH_PASS=testpass123 +AUTH_PASS=testpass1234! API_KEY=test-api-key-e2e-12345 AUTH_SECRET=test-legacy-secret MC_ALLOW_ANY_HOST=1 diff --git a/Dockerfile b/Dockerfile index b5e7c55..fcd7893 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,14 +3,19 @@ RUN corepack enable && corepack prepare pnpm@latest --activate WORKDIR /app FROM base AS deps -COPY package.json pnpm-lock.yaml ./ +COPY package.json ./ +COPY . . # better-sqlite3 requires native compilation tools RUN apt-get update && apt-get install -y python3 make g++ --no-install-recommends && rm -rf /var/lib/apt/lists/* -RUN pnpm install --frozen-lockfile +RUN if [ -f pnpm-lock.yaml ]; then \ + pnpm install --frozen-lockfile; \ + else \ + echo "WARN: pnpm-lock.yaml not found in build context; running non-frozen install"; \ + pnpm install --no-frozen-lockfile; \ + fi FROM base AS build -COPY --from=deps /app/node_modules ./node_modules -COPY . . +COPY --from=deps /app ./ RUN pnpm build FROM node:20-slim AS runtime diff --git a/docs/deployment.md b/docs/deployment.md index bde3b2c..ed85625 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -99,6 +99,13 @@ rm -rf node_modules pnpm install ``` +### "pnpm-lock.yaml not found" during Docker build + +If your deployment context omits `pnpm-lock.yaml`, Docker build now falls back to +`pnpm install --no-frozen-lockfile`. + +For reproducible builds, include `pnpm-lock.yaml` in the build context. + ### "Invalid ELF header" or "Mach-O" errors The native binary was compiled on a different platform. Rebuild: diff --git a/tests/csrf-validation.spec.ts b/tests/csrf-validation.spec.ts index 8614070..7b527bc 100644 --- a/tests/csrf-validation.spec.ts +++ b/tests/csrf-validation.spec.ts @@ -6,6 +6,8 @@ import { test, expect } from '@playwright/test' */ test.describe('CSRF Origin Validation (Issue #20)', () => { + const TEST_PASS = 'testpass1234!' + test('POST with mismatched Origin is rejected', async ({ request }) => { const res = await request.post('/api/auth/login', { data: { username: 'test', password: 'test' }, @@ -21,7 +23,7 @@ test.describe('CSRF Origin Validation (Issue #20)', () => { test('POST with matching Origin is allowed', async ({ request }) => { const res = await request.post('/api/auth/login', { - data: { username: 'testadmin', password: 'testpass123' }, + data: { username: 'testadmin', password: TEST_PASS }, headers: { 'origin': 'http://127.0.0.1:3005', 'host': '127.0.0.1:3005' @@ -33,7 +35,7 @@ test.describe('CSRF Origin Validation (Issue #20)', () => { test('POST without Origin header is allowed (non-browser client)', async ({ request }) => { const res = await request.post('/api/auth/login', { - data: { username: 'testadmin', password: 'testpass123' }, + data: { username: 'testadmin', password: TEST_PASS }, }) // No Origin = non-browser client, should be allowed through CSRF check expect(res.status()).not.toBe(403) diff --git a/tests/login-flow.spec.ts b/tests/login-flow.spec.ts index 91c5a89..ef5face 100644 --- a/tests/login-flow.spec.ts +++ b/tests/login-flow.spec.ts @@ -6,6 +6,8 @@ import { test, expect } from '@playwright/test' */ test.describe('Login Flow', () => { + const TEST_PASS = 'testpass1234!' + test('login page loads', async ({ page }) => { await page.goto('/login') await expect(page).toHaveURL(/\/login/) @@ -18,7 +20,7 @@ test.describe('Login Flow', () => { test('login API returns session cookie on success', async ({ request }) => { const res = await request.post('/api/auth/login', { - data: { username: 'testadmin', password: 'testpass123' }, + data: { username: 'testadmin', password: TEST_PASS }, headers: { 'x-forwarded-for': '10.88.88.1' } }) expect(res.status()).toBe(200) @@ -39,7 +41,7 @@ test.describe('Login Flow', () => { test('session cookie grants API access', async ({ request }) => { // Login to get a session const loginRes = await request.post('/api/auth/login', { - data: { username: 'testadmin', password: 'testpass123' }, + data: { username: 'testadmin', password: TEST_PASS }, headers: { 'x-forwarded-for': '10.88.88.2' } }) expect(loginRes.status()).toBe(200) diff --git a/tests/rate-limiting.spec.ts b/tests/rate-limiting.spec.ts index 9ca5894..610b395 100644 --- a/tests/rate-limiting.spec.ts +++ b/tests/rate-limiting.spec.ts @@ -6,6 +6,8 @@ import { test, expect } from '@playwright/test' */ test.describe('Login Rate Limiting (Issue #8)', () => { + const TEST_PASS = 'testpass1234!' + test('blocks login after 5 rapid failed attempts', async ({ request }) => { const results: number[] = [] @@ -25,7 +27,7 @@ test.describe('Login Rate Limiting (Issue #8)', () => { test('successful login is not blocked for fresh IP', async ({ request }) => { const res = await request.post('/api/auth/login', { - data: { username: 'testadmin', password: 'testpass123' }, + data: { username: 'testadmin', password: TEST_PASS }, headers: { 'x-real-ip': '10.88.88.88' } }) // Should succeed (200) or at least not be rate limited