nyk commited on
Commit
d8f73c7
·
unverified ·
1 Parent(s): 6cc3161

fix: prevent Docker build failure when pnpm lockfile is missing (#130)

Browse files

* fix: make docker build resilient when lockfile is absent

* test: update e2e credentials for secure admin seed policy

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