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

* fix: make docker build resilient when lockfile is absent

* test: update e2e credentials for secure admin seed policy
This commit is contained in:
nyk 2026-03-04 08:33:09 +07:00 committed by GitHub
parent 90b712ea29
commit 2111f03542
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 28 additions and 10 deletions

View File

@ -1,5 +1,5 @@
AUTH_USER=testadmin AUTH_USER=testadmin
AUTH_PASS=testpass123 AUTH_PASS=testpass1234!
API_KEY=test-api-key-e2e-12345 API_KEY=test-api-key-e2e-12345
AUTH_SECRET=test-legacy-secret AUTH_SECRET=test-legacy-secret
MC_ALLOW_ANY_HOST=1 MC_ALLOW_ANY_HOST=1

View File

@ -3,14 +3,19 @@ RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app WORKDIR /app
FROM base AS deps FROM base AS deps
COPY package.json pnpm-lock.yaml ./ COPY package.json ./
COPY . .
# better-sqlite3 requires native compilation tools # 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 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 FROM base AS build
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app ./
COPY . .
RUN pnpm build RUN pnpm build
FROM node:20-slim AS runtime FROM node:20-slim AS runtime

View File

@ -99,6 +99,13 @@ rm -rf node_modules
pnpm install 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 ### "Invalid ELF header" or "Mach-O" errors
The native binary was compiled on a different platform. Rebuild: The native binary was compiled on a different platform. Rebuild:

View File

@ -6,6 +6,8 @@ import { test, expect } from '@playwright/test'
*/ */
test.describe('CSRF Origin Validation (Issue #20)', () => { test.describe('CSRF Origin Validation (Issue #20)', () => {
const TEST_PASS = 'testpass1234!'
test('POST with mismatched Origin is rejected', async ({ request }) => { test('POST with mismatched Origin is rejected', async ({ request }) => {
const res = await request.post('/api/auth/login', { const res = await request.post('/api/auth/login', {
data: { username: 'test', password: 'test' }, 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 }) => { test('POST with matching Origin is allowed', async ({ request }) => {
const res = await request.post('/api/auth/login', { const res = await request.post('/api/auth/login', {
data: { username: 'testadmin', password: 'testpass123' }, data: { username: 'testadmin', password: TEST_PASS },
headers: { headers: {
'origin': 'http://127.0.0.1:3005', 'origin': 'http://127.0.0.1:3005',
'host': '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 }) => { test('POST without Origin header is allowed (non-browser client)', async ({ request }) => {
const res = await request.post('/api/auth/login', { 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 // No Origin = non-browser client, should be allowed through CSRF check
expect(res.status()).not.toBe(403) expect(res.status()).not.toBe(403)

View File

@ -6,6 +6,8 @@ import { test, expect } from '@playwright/test'
*/ */
test.describe('Login Flow', () => { test.describe('Login Flow', () => {
const TEST_PASS = 'testpass1234!'
test('login page loads', async ({ page }) => { test('login page loads', async ({ page }) => {
await page.goto('/login') await page.goto('/login')
await expect(page).toHaveURL(/\/login/) await expect(page).toHaveURL(/\/login/)
@ -18,7 +20,7 @@ test.describe('Login Flow', () => {
test('login API returns session cookie on success', async ({ request }) => { test('login API returns session cookie on success', async ({ request }) => {
const res = await request.post('/api/auth/login', { 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' } headers: { 'x-forwarded-for': '10.88.88.1' }
}) })
expect(res.status()).toBe(200) expect(res.status()).toBe(200)
@ -39,7 +41,7 @@ test.describe('Login Flow', () => {
test('session cookie grants API access', async ({ request }) => { test('session cookie grants API access', async ({ request }) => {
// Login to get a session // Login to get a session
const loginRes = await request.post('/api/auth/login', { 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' } headers: { 'x-forwarded-for': '10.88.88.2' }
}) })
expect(loginRes.status()).toBe(200) expect(loginRes.status()).toBe(200)

View File

@ -6,6 +6,8 @@ import { test, expect } from '@playwright/test'
*/ */
test.describe('Login Rate Limiting (Issue #8)', () => { test.describe('Login Rate Limiting (Issue #8)', () => {
const TEST_PASS = 'testpass1234!'
test('blocks login after 5 rapid failed attempts', async ({ request }) => { test('blocks login after 5 rapid failed attempts', async ({ request }) => {
const results: number[] = [] 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 }) => { test('successful login is not blocked for fresh IP', async ({ request }) => {
const res = await request.post('/api/auth/login', { 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' } headers: { 'x-real-ip': '10.88.88.88' }
}) })
// Should succeed (200) or at least not be rate limited // Should succeed (200) or at least not be rate limited