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:
parent
90b712ea29
commit
2111f03542
|
|
@ -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
|
||||
|
|
|
|||
13
Dockerfile
13
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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue