fix(imports): convert require() to ESM import for randomBytes in auth.js
This commit is contained in:
@@ -0,0 +1,50 @@
|
|||||||
|
# Oikos
|
||||||
|
|
||||||
|
Self-hosted family planner PWA. Node.js/Express, Vanilla JS (no build step), SQLite, Docker.
|
||||||
|
|
||||||
|
## Hard Constraints
|
||||||
|
|
||||||
|
Violations are always bugs - no exceptions.
|
||||||
|
|
||||||
|
- Never add frontend frameworks (React, Vue, Svelte), bundlers (Webpack, Vite), or CSS libraries (Tailwind, Bootstrap).
|
||||||
|
- No external frontend dependencies. Only allowed exception: Lucide Icons (`public/lucide.min.js`, self-hosted). No CDN links at runtime.
|
||||||
|
- `import`/`export` everywhere. Never `require()`.
|
||||||
|
- Never `eval()`. Never `innerHTML` with user data. Use `textContent` or DOM API.
|
||||||
|
- All UI text via `t('key')`. Never hardcode strings in components. `de` is the reference locale.
|
||||||
|
- Migrations append-only. Add entries to the `migrations` array in `server/db.js`. Never modify or reorder existing entries.
|
||||||
|
- All colors, radii, shadows, font sizes from `public/styles/tokens.css`. Never hardcode design values.
|
||||||
|
- Every route handler in `try/catch`. No unhandled promise rejections.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Request flow: client → Express static (`public/`) or `/api/v1/*` → session auth → route handler → better-sqlite3 (sync) → JSON.
|
||||||
|
|
||||||
|
Key locations that are non-obvious:
|
||||||
|
- `public/i18n.js` - `t()`, `formatDate()`, `formatTime()`, `SUPPORTED_LOCALES`
|
||||||
|
- `public/api.js` - fetch wrapper (handles auth, CSRF, errors)
|
||||||
|
- `public/router.js` - History API router (no library)
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- API responses: `{ data: ... }` on success, `{ error: string, code: number }` on failure.
|
||||||
|
- Dates/times: always `formatDate()`/`formatTime()` from `i18n.js`. Never format manually.
|
||||||
|
- Pages (`public/pages/*.js`): export `render()`. No side effects on import.
|
||||||
|
- Web Components (`public/components/*.js`): `oikos-` prefix, one component per file.
|
||||||
|
- Tests: `test-[module].js` in project root, `--experimental-sqlite` flag. Add `test:[module]` script to `package.json`.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev # Start with --watch
|
||||||
|
npm test # All test suites (requires Node ≥22)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
| What | Where |
|
||||||
|
|------|-------|
|
||||||
|
| Data model, UI specs | `docs/SPEC.md` |
|
||||||
|
| Design tokens | `public/styles/tokens.css` |
|
||||||
|
| DB schema (source of truth) | `server/db.js` |
|
||||||
|
| i18n keys | `public/locales/de.json` |
|
||||||
|
| Code conventions, commit format | `CONTRIBUTING.md` |
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# CLAUDE.md Migration Summary
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
| | Lines |
|
||||||
|
|---|---|
|
||||||
|
| Before | 82 |
|
||||||
|
| After | 50 |
|
||||||
|
| Reduction | -39% (-32 lines) |
|
||||||
|
|
||||||
|
## What was removed and why
|
||||||
|
|
||||||
|
| Removed | Reason |
|
||||||
|
|---|---|
|
||||||
|
| `## Quick Reference` commands block (6 lines) | `npm start`, `npm run dev`, `npm test` are all in `package.json scripts`. Claude reads `package.json` on demand. `docker compose up -d` is a deployment detail, not a development constraint. |
|
||||||
|
| "These are non-negotiable. Every violation is a bug." intro | Moved to tighter one-liner before the list. |
|
||||||
|
| Full directory tree (21 lines) | Claude navigates the filesystem directly. Listing every file adds no behavioral value. Only non-obvious locations were kept. |
|
||||||
|
| "Pages are ES modules" standalone paragraph | Merged into Conventions. |
|
||||||
|
| Semicolons | Inferrable from reading any source file. |
|
||||||
|
| Header comment convention | Already documented in `CONTRIBUTING.md`. |
|
||||||
|
| DB table column pattern (`id`, `created_at`, `updated_at`) | Already in `CONTRIBUTING.md`. |
|
||||||
|
| Commit format and Changelog instructions (2 lines) | Already in `CONTRIBUTING.md`. Claude can read it when committing. |
|
||||||
|
| `## Current State` paragraph | Describes finished features - zero behavioral value. Becomes stale immediately. |
|
||||||
|
| "When to consult" column from Reference table | Padding. Claude decides when to read reference docs based on task context. |
|
||||||
|
|
||||||
|
## What moved to rules files
|
||||||
|
|
||||||
|
None. The remaining content is either universal (applies to every file) or a short pointer. No subsystem-specific rules justify a separate file at this project size.
|
||||||
|
|
||||||
|
## What was kept and why
|
||||||
|
|
||||||
|
| Kept | Why |
|
||||||
|
|---|---|
|
||||||
|
| All 8 Hard Constraints | Each prevents a class of wrong code that Claude would otherwise produce. The no-frameworks rule in particular would be violated without an explicit reminder. |
|
||||||
|
| API response shape `{data}` / `{error, code}` | Not inferrable without reading multiple route files. Applies to every new route. |
|
||||||
|
| `formatDate()`/`formatTime()` | Without this, Claude formats dates manually (e.g. `new Date().toLocaleDateString()`), producing inconsistent output. |
|
||||||
|
| `pages/*.js` → `render()`, no side effects | Structural contract not obvious from reading one page file. |
|
||||||
|
| `oikos-` prefix | Web Component naming convention. |
|
||||||
|
| Non-obvious file locations (`i18n.js`, `api.js`, `router.js`) | These live at `public/` root, not in a subdirectory. Easy to miss when navigating. |
|
||||||
|
| Request flow one-liner | Architectural orientation for new tasks. |
|
||||||
|
| Reference table (trimmed) | On-demand pointers replace inline content for spec details. |
|
||||||
|
|
||||||
|
## Token delta estimate
|
||||||
|
|
||||||
|
At ~4 chars/token average for this content:
|
||||||
|
- Before: ~1,800 tokens loaded every session
|
||||||
|
- After: ~1,100 tokens loaded every session
|
||||||
|
- Savings: ~700 tokens per session
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Oikos - Standalone Docker Compose for Portainer / remote deployment
|
||||||
|
# Pulls the pre-built image from GitHub Container Registry.
|
||||||
|
# No git clone required.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# 1. Copy this file and the .env section below to your server
|
||||||
|
# 2. Create a .env file next to this compose file (see below)
|
||||||
|
# 3. docker compose -f docker-compose.portainer.yml up -d
|
||||||
|
# 4. docker compose -f docker-compose.portainer.yml exec oikos node setup.js
|
||||||
|
# 5. Open http://<your-host>:3000
|
||||||
|
#
|
||||||
|
# Required .env variables:
|
||||||
|
# SESSION_SECRET=<random-string-min-32-chars>
|
||||||
|
# DB_ENCRYPTION_KEY=<random-string-min-32-chars>
|
||||||
|
#
|
||||||
|
# Generate secrets:
|
||||||
|
# openssl rand -base64 32
|
||||||
|
|
||||||
|
services:
|
||||||
|
oikos:
|
||||||
|
image: ghcr.io/ulsklyc/oikos:latest
|
||||||
|
container_name: oikos
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- oikos_data:/data
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3000
|
||||||
|
- DB_PATH=/data/oikos.db
|
||||||
|
- SESSION_SECRET=${SESSION_SECRET:?Set SESSION_SECRET in .env}
|
||||||
|
- DB_ENCRYPTION_KEY=${DB_ENCRYPTION_KEY:?Set DB_ENCRYPTION_KEY in .env}
|
||||||
|
# Set to true when behind a reverse proxy with HTTPS
|
||||||
|
- SESSION_SECURE=${SESSION_SECURE:-false}
|
||||||
|
# Weather (optional)
|
||||||
|
- OPENWEATHER_API_KEY=${OPENWEATHER_API_KEY:-}
|
||||||
|
- OPENWEATHER_CITY=${OPENWEATHER_CITY:-Berlin}
|
||||||
|
- OPENWEATHER_UNITS=${OPENWEATHER_UNITS:-metric}
|
||||||
|
- OPENWEATHER_LANG=${OPENWEATHER_LANG:-de}
|
||||||
|
# Google Calendar (optional)
|
||||||
|
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
|
||||||
|
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-}
|
||||||
|
- GOOGLE_REDIRECT_URI=${GOOGLE_REDIRECT_URI:-}
|
||||||
|
# Apple Calendar CalDAV (optional)
|
||||||
|
- APPLE_CALDAV_URL=${APPLE_CALDAV_URL:-https://caldav.icloud.com}
|
||||||
|
- APPLE_USERNAME=${APPLE_USERNAME:-}
|
||||||
|
- APPLE_APP_SPECIFIC_PASSWORD=${APPLE_APP_SPECIFIC_PASSWORD:-}
|
||||||
|
# Sync interval in minutes
|
||||||
|
- SYNC_INTERVAL_MINUTES=${SYNC_INTERVAL_MINUTES:-15}
|
||||||
|
# Rate limiting
|
||||||
|
- RATE_LIMIT_WINDOW_MS=${RATE_LIMIT_WINDOW_MS:-60000}
|
||||||
|
- RATE_LIMIT_MAX_ATTEMPTS=${RATE_LIMIT_MAX_ATTEMPTS:-5}
|
||||||
|
- RATE_LIMIT_BLOCK_DURATION_MS=${RATE_LIMIT_BLOCK_DURATION_MS:-900000}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
oikos_data:
|
||||||
|
driver: local
|
||||||
+9
-12
@@ -4,16 +4,14 @@
|
|||||||
* Abhängigkeiten: express, bcrypt, express-session, server/db.js
|
* Abhängigkeiten: express, bcrypt, express-session, server/db.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
import express from 'express';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
const express = require('express');
|
import session from 'express-session';
|
||||||
const bcrypt = require('bcrypt');
|
import rateLimit from 'express-rate-limit';
|
||||||
const session = require('express-session');
|
import { randomBytes } from 'node:crypto';
|
||||||
const rateLimit = require('express-rate-limit');
|
import * as db from './db.js';
|
||||||
const db = require('./db');
|
import { generateToken, csrfMiddleware } from './middleware/csrf.js';
|
||||||
|
import { createLogger } from './logger.js';
|
||||||
const { generateToken, csrfMiddleware } = require('./middleware/csrf');
|
|
||||||
const { createLogger } = require('./logger');
|
|
||||||
|
|
||||||
const log = createLogger('Auth');
|
const log = createLogger('Auth');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@@ -97,7 +95,6 @@ if (!process.env.SESSION_SECRET) {
|
|||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
throw new Error('[Auth] SESSION_SECRET muss in der .env gesetzt sein (Produktion).');
|
throw new Error('[Auth] SESSION_SECRET muss in der .env gesetzt sein (Produktion).');
|
||||||
}
|
}
|
||||||
const { randomBytes } = require('node:crypto');
|
|
||||||
process.env.SESSION_SECRET = randomBytes(32).toString('hex');
|
process.env.SESSION_SECRET = randomBytes(32).toString('hex');
|
||||||
log.warn('SESSION_SECRET nicht gesetzt - zufaelliges Einmal-Secret generiert (Sessions ueberleben keinen Neustart).');
|
log.warn('SESSION_SECRET nicht gesetzt - zufaelliges Einmal-Secret generiert (Sessions ueberleben keinen Neustart).');
|
||||||
}
|
}
|
||||||
@@ -410,4 +407,4 @@ router.delete('/users/:id', requireAuth, requireAdmin, csrfMiddleware, (req, res
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { router, sessionMiddleware, requireAuth, requireAdmin };
|
export { router, sessionMiddleware, requireAuth, requireAdmin };
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
* Abhängigkeiten: keine
|
* Abhängigkeiten: keine
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// SQL-String für Migration v1 (gespiegelt aus db.js MIGRATIONS[0].up)
|
// SQL-String für Migration v1 (gespiegelt aus db.js MIGRATIONS[0].up)
|
||||||
// Änderungen in db.js MIGRATIONS müssen hier synchron gehalten werden.
|
// Änderungen in db.js MIGRATIONS müssen hier synchron gehalten werden.
|
||||||
const MIGRATIONS_SQL = {
|
const MIGRATIONS_SQL = {
|
||||||
@@ -182,4 +180,4 @@ const MIGRATIONS_SQL = {
|
|||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { MIGRATIONS_SQL };
|
export { MIGRATIONS_SQL };
|
||||||
|
|||||||
+8
-7
@@ -9,15 +9,13 @@
|
|||||||
* Ohne DB_ENCRYPTION_KEY gesetzt läuft die App mit unverschlüsseltem SQLite (für Entwicklung).
|
* Ohne DB_ENCRYPTION_KEY gesetzt läuft die App mit unverschlüsseltem SQLite (für Entwicklung).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
import Database from 'better-sqlite3';
|
||||||
|
import path from 'path';
|
||||||
const Database = require('better-sqlite3');
|
import { createLogger } from './logger.js';
|
||||||
const path = require('path');
|
|
||||||
const { createLogger } = require('./logger');
|
|
||||||
|
|
||||||
const log = createLogger('DB');
|
const log = createLogger('DB');
|
||||||
|
|
||||||
const DB_PATH = process.env.DB_PATH || path.join(__dirname, '..', 'oikos.db');
|
const DB_PATH = process.env.DB_PATH || path.join(import.meta.dirname, '..', 'oikos.db');
|
||||||
const DB_KEY = process.env.DB_ENCRYPTION_KEY;
|
const DB_KEY = process.env.DB_ENCRYPTION_KEY;
|
||||||
|
|
||||||
let db;
|
let db;
|
||||||
@@ -32,6 +30,7 @@ let db;
|
|||||||
* @returns {import('better-sqlite3').Database}
|
* @returns {import('better-sqlite3').Database}
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
|
if (db) return db;
|
||||||
db = new Database(DB_PATH);
|
db = new Database(DB_PATH);
|
||||||
|
|
||||||
if (DB_KEY) {
|
if (DB_KEY) {
|
||||||
@@ -369,4 +368,6 @@ function transaction(fn) {
|
|||||||
return get().transaction(fn)();
|
return get().transaction(fn)();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { init, get, transaction, currentVersion };
|
init(); // auto-initialise when module is first imported
|
||||||
|
|
||||||
|
export { init, get, transaction, currentVersion };
|
||||||
|
|||||||
+1
-3
@@ -5,8 +5,6 @@
|
|||||||
* Steuerung: LOG_LEVEL env var (debug, info, warn, error). Default: info.
|
* Steuerung: LOG_LEVEL env var (debug, info, warn, error). Default: info.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
||||||
const currentLevel = LEVELS[process.env.LOG_LEVEL] ?? LEVELS.info;
|
const currentLevel = LEVELS[process.env.LOG_LEVEL] ?? LEVELS.info;
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
@@ -37,4 +35,4 @@ function createLogger(mod) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { createLogger };
|
export { createLogger };
|
||||||
|
|||||||
Reference in New Issue
Block a user