fix(19): satisfy CI fast-checks + secret scan
CI / changes (pull_request) Successful in 9s
CI / api (pull_request) Successful in 3m2s
CI / fast-checks (pull_request) Successful in 4m20s
CI / security (pull_request) Successful in 1m14s
CI / harness (pull_request) Successful in 6m56s
CI / gate (pull_request) Successful in 2s

Lint (eslint --max-warnings 0):
- index.ts: disable no-unsafe-argument on the type-only Context mismatch when
  delegating to the OIDC handler inside the local-session skip wrapper
- localAuth.ts: handleLogout is sync (no await) — drop async (require-await)
- devBypass.ts: disable detect-possible-timing-attacks on the public well-known
  dev-placeholder string compare (not a secret comparison)
- remove dead code / unused bindings flagged by no-unused-vars: makeTestApp
  (localSession.test), makeUnauthContext + BrowserContext import (login.spec),
  unused memberId (admin.test), unused txSelectCount counter (me.test)
- localAuthMiddleware.test / me.test: fix unused + reflow-detached
  eslint-disable directives

Format: prettier --write across the 20 Phase-19 files that were never formatted.

Secret scan (gitleaks): allowlist two false positives — the synthetic >=32-char
TEST_SECRET in localSession.test.ts, and .planning/ design prose (a generic-api-key
regex hit on "credential atomically, 409-equivalent"). Neither is a real secret.

Verified locally: format:check, lint, typecheck, md:lint, gitleaks (no leaks),
PWA 266/266, API 452/452.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas Berger
2026-06-17 23:05:15 -04:00
co-authored by Claude Opus 4.8
parent 91ab9d1f78
commit b6490feff4
22 changed files with 318 additions and 294 deletions
+22 -26
View File
@@ -34,9 +34,9 @@ vi.mock('../../src/db/client.js', () => ({
select: vi.fn().mockImplementation(() => ({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockImplementation(() =>
Promise.resolve(mockCredRow ? [mockCredRow] : [])
),
limit: vi
.fn()
.mockImplementation(() => Promise.resolve(mockCredRow ? [mockCredRow] : [])),
}),
}),
})),
@@ -57,14 +57,12 @@ let issuedUserId: number | null = null;
let clearSessionCalled = false;
vi.mock('../../src/auth/localSession.js', () => ({
issueLocalSessionCookie: vi.fn().mockImplementation(
(_c: unknown, userId: number) => {
issueSessionCalled = true;
issuedUserId = userId;
// Simulate setting a cookie on the context
return Promise.resolve();
}
),
issueLocalSessionCookie: vi.fn().mockImplementation((_c: unknown, userId: number) => {
issueSessionCalled = true;
issuedUserId = userId;
// Simulate setting a cookie on the context
return Promise.resolve();
}),
clearLocalSessionCookie: vi.fn().mockImplementation((_c: unknown) => {
clearSessionCalled = true;
}),
@@ -76,15 +74,13 @@ vi.mock('../../src/auth/localSession.js', () => ({
// ---------------------------------------------------------------------------
vi.mock('../../src/auth/devBypass.js', () => ({
devAuthBypass:
() => async (_c: unknown, next: () => Promise<void>) => next(),
devAuthBypass: () => async (_c: unknown, next: () => Promise<void>) => next(),
// Phase 19 Option C: devSessionCookieMiddleware is a no-op in tests
devSessionCookieMiddleware: () => async (_c: unknown, next: () => Promise<void>) => next(),
}));
vi.mock('../../src/auth/localAuthMiddleware.js', () => ({
localAuthMiddleware:
() => async (_c: unknown, next: () => Promise<void>) => next(),
localAuthMiddleware: () => async (_c: unknown, next: () => Promise<void>) => next(),
}));
vi.mock('@hono/oidc-auth', () => ({
@@ -195,7 +191,9 @@ describe('POST /api/auth/local/login', () => {
mockCredRow = undefined; // No credential row found
const app = await getApp();
const res = await app.fetch(makeLoginRequest({ username: 'unknown-user', password: 'anypassword' }));
const res = await app.fetch(
makeLoginRequest({ username: 'unknown-user', password: 'anypassword' }),
);
expect(res.status).toBe(401);
const body = (await res.json()) as { error: string };
@@ -212,14 +210,14 @@ describe('POST /api/auth/local/login', () => {
// 5 failures to trigger the rate window
for (let i = 0; i < 5; i++) {
const res = await app.fetch(
makeLoginRequest({ username: 'alice', password: 'wrong' }, '10.0.0.1')
makeLoginRequest({ username: 'alice', password: 'wrong' }, '10.0.0.1'),
);
expect(res.status).toBe(401);
}
// 6th attempt from same IP → 429
const res6 = await app.fetch(
makeLoginRequest({ username: 'alice', password: 'wrong' }, '10.0.0.1')
makeLoginRequest({ username: 'alice', password: 'wrong' }, '10.0.0.1'),
);
expect(res6.status).toBe(429);
const body = (await res6.json()) as { error: string };
@@ -233,14 +231,12 @@ describe('POST /api/auth/local/login', () => {
// 10 failures from same IP → lockout
for (let i = 0; i < 10; i++) {
await app.fetch(
makeLoginRequest({ username: 'alice', password: 'wrong' }, '10.0.0.2')
);
await app.fetch(makeLoginRequest({ username: 'alice', password: 'wrong' }, '10.0.0.2'));
}
// 11th attempt → 423 (locked)
const res11 = await app.fetch(
makeLoginRequest({ username: 'alice', password: 'wrong' }, '10.0.0.2')
makeLoginRequest({ username: 'alice', password: 'wrong' }, '10.0.0.2'),
);
expect(res11.status).toBe(423);
const body = (await res11.json()) as { error: string };
@@ -252,7 +248,7 @@ describe('POST /api/auth/local/login', () => {
loginAttempts.delete('alice');
const resAfterReset = await app.fetch(
makeLoginRequest({ username: 'alice', password: 'wrong' }, '10.0.0.2')
makeLoginRequest({ username: 'alice', password: 'wrong' }, '10.0.0.2'),
);
expect(resAfterReset.status).toBe(401);
});
@@ -295,7 +291,7 @@ describe('POST /api/auth/local/login', () => {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-forwarded-for': '1.2.3.4' },
body: JSON.stringify({ username: 'mysecretusername' }),
})
}),
);
expect(res.status).toBe(400);
@@ -319,7 +315,7 @@ describe('POST /api/auth/local/logout', () => {
new Request('http://localhost/api/auth/local/logout', {
method: 'POST',
headers: { 'x-forwarded-for': '1.2.3.4' },
})
}),
);
expect(res.status).toBe(200);
@@ -336,7 +332,7 @@ describe('GET /api/auth/local/logout', () => {
new Request('http://localhost/api/auth/local/logout', {
method: 'GET',
headers: { 'x-forwarded-for': '1.2.3.4' },
})
}),
);
expect(res.status).toBe(200);