`validateAuthEnv()` was running at module-load time, which fired during
`astro build` while constructing the route manifest — before any actual
request needed the secrets. CI build failed even though build-time code
doesn't use JWT_SECRET / OAuth secrets.
- src/lib/auth/env.ts: wrap authEnv in a Proxy that runs validation
on first property read; cache the validated object after.
- src/lib/auth/jwt.ts: defer `new TextEncoder().encode(...)` of the
secret behind a memoised getSecret() helper.
- src/lib/auth/oauth.ts: same for the JWT secret; vkOAuthConfig and
yandexOAuthConfig switched to getter-based properties so credential
access is also lazy.
- src/lib/auth/auth.test.ts: 3 tests previously asserted "throws at
module load"; updated to assert "throws on first access" — semantic
guarantee (invalid env throws) is preserved.
Runtime fail-fast is intact: any auth route reading authEnv.X will
throw with the same descriptive Zod error if a var is missing. Build
just no longer crashes when secrets aren't in env (e.g. CI deploy
pipeline running tsc/lint/build without secrets).
Verified: npm run build succeeds with no auth env vars set; tsc 0,
lint 0, vitest 339/339.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 6 API/CORS test files mock the Astro APIContext by spreading
`{} as any` to satisfy fields not under test (cookies, redirect,
clientAddress, etc.). Replacing each cast individually would require
either full typed contexts (obscures tests) or 70+ inline disables.
Add one file-level `eslint-disable @typescript-eslint/no-explicit-any`
with a comment explaining the rationale to each affected file.
Production code remains lint-clean. Verified: tsc 0, lint 0,
vitest 339/339.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds an opt-in modal that prompts a PRO user with local data to migrate
their localStorage notes and sessionStorage initiative into the cloud.
- src/lib/client/import.ts: pure helpers (hasLocalData, shouldShowImport)
- src/components/dm/ImportModal.astro: modal UI + import orchestration
with conflict handling for existing "DM Notes" record
- src/i18n/dm-translations.ts: 14 new keys for the import flow
- Wired into both EN and RU dm/index.astro
- tests/import-flow.test.ts: 18 unit tests on the helpers
Local storage is preserved as backup; only a "dm-import-asked" flag is
set after the user makes a decision (import or skip).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fix auth anti-patterns (JWT, OAuth, logout, middleware, url.origin)
- Extend DB schema with tier, npcs, counters, translations, notes, initiative
- Expand Open5e TypeScript interfaces (Monster, Spell) to full V2
- Build OpenRouter API client for FREE tier
- Build Kimi API client for PRO tier
- Add comprehensive tests for all modules
- Fix typo "combatантов" -> "участников боя" in dm-translations
- Replace hardcoded English "Result", "Rolls:", "Invalid notation"
in DiceRoller with translation keys
- Translate English aria-labels ("Quick dice selectors", "Roll history",
"Combatants") to Russian
- Move InitiativeTracker hardcoded strings ("Итого", "Инициатива",
"Порядок хода", help text) into the translation file
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bump container max-width and right context column at xl/2xl so the
reference and notes panels get noticeably more room on larger monitors.
Mobile and lg layouts are unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pages /dm/ and /ru/dm/ were static (prerendered), so middleware
never ran and Astro.locals.user was always null. Adding
prerender = false allows middleware to authenticate the user
before page render.
- Change COOKIE_NAME from 'session' to 'auth_token' in oauth.ts
- Update logout.ts to use Headers.append() and SameSite=Lax
- Fixes mismatch where callback set 'session' but middleware read 'auth_token'
Browser cannot parse multiple cookies from a single Set-Cookie header
joined by comma (RFC 6265). Use Headers.append() to send each cookie
in its own Set-Cookie header. Fixes state/verifier cookie mismatch.