4a3dfa5821
- Tabelle passkeys + lib/passkeys.ts (CRUD, Counter-Replay-Schutz)
- lib/webauthn.ts: RP-Config via RP_ID/RP_ORIGIN/RP_NAME, Challenge im
httpOnly-Cookie, Wrapper um @simplewebauthn/server
- API: app/api/passkey/{register,authenticate,route}; authenticate ist
öffentlich (proxy.ts ausgenommen) und setzt bei Erfolg die Session
- Login-Button "Mit Passkey anmelden", Verwaltung im Einstellungen-Tab
- Passwort bleibt als Fallback; Version 0.1.1
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
58 lines
3.0 KiB
Markdown
58 lines
3.0 KiB
Markdown
# CLAUDE.md
|
||
|
||
Hinweise für Claude Code (claude.ai/code) zur Arbeit in diesem Repository.
|
||
|
||
## Commands
|
||
|
||
```bash
|
||
npm run dev # Entwicklungsserver
|
||
npm run build # Produktions-Build (nach jeder Änderung zur Prüfung ausführen)
|
||
npm run lint # ESLint
|
||
```
|
||
|
||
Kein Test-Setup. Deploy via `./deploy.sh [tag]` — baut ein Multiplatform-Docker-Image
|
||
(amd64 + arm64) und pusht es nach `docker.citysensor.de`.
|
||
|
||
## Architektur
|
||
|
||
Next.js 16 App-Router-Anwendung. Seiten sind Server-Komponenten; interaktive Teile sind
|
||
Client-Komponenten in `app/MainClient.tsx` und `components/`. Layout und Design sind dem
|
||
Schwesterprojekt `~/Projekte/logbuch` nachempfunden, jedoch mit **grüner** Grundfarbe.
|
||
|
||
**Auth**: Single-User. Benutzername und Passwort sind fest in `.env` hinterlegt
|
||
(`APP_USERNAME` default `Birgit`, `APP_PASSWORD`). Login über `app/login/actions.ts` →
|
||
`lib/auth.ts` (Plaintext-Vergleich, kein bcrypt). Sessions sind JWT-Cookies via `jose`
|
||
(`lib/session.ts`, 8 h Gültigkeit, Cookie `arbeitszeit_session`). Middleware liegt in
|
||
`proxy.ts` (Next.js-16-Konvention) und exportiert `middleware`.
|
||
|
||
**Passkeys (WebAuthn)**: Zusätzlich zum Passwort (das als Fallback bleibt). `lib/webauthn.ts`
|
||
kapselt `@simplewebauthn/server` (RP-Config aus `RP_ID`/`RP_ORIGIN`/`RP_NAME`, Default
|
||
`localhost`); Credentials liegen in Tabelle `passkeys` (`lib/passkeys.ts`). Die Challenge wird
|
||
kurzlebig in einem httpOnly-Cookie zwischengespeichert. API unter `app/api/passkey/`:
|
||
`register` (GET Options / POST Verify, Session nötig), `authenticate` (GET/POST, **öffentlich** —
|
||
in `proxy.ts` von der Session-Pflicht ausgenommen, setzt bei Erfolg die Session), sowie
|
||
`GET`/`DELETE` auf `app/api/passkey/route.ts` zum Verwalten. Registrierung im Einstellungen-Tab
|
||
(`components/Passkeys.tsx`), Anmeldung per Button auf `app/login/page.tsx`. **WebAuthn braucht
|
||
HTTPS** (Prod via Traefik) bzw. `localhost` in der Entwicklung.
|
||
|
||
**Datenbank**: SQLite3 via `better-sqlite3`. `lib/db.ts` ist ein Singleton, legt Schema und
|
||
die `settings`-Tabelle beim ersten Zugriff an. Datei unter `DB_PATH`
|
||
(default `./data/arbeitszeit.db`, in Produktion `/app/data/arbeitszeit.db` auf einem Volume).
|
||
`better-sqlite3` ist nativ → `serverExternalPackages` in `next.config.ts` und im Dockerfile
|
||
`python3 make g++` in der `deps`-Stage.
|
||
|
||
**Datenzugriff**: `lib/repo.ts` (CRUD + Pause-Setting), `lib/calc.ts` (Zeit-/Wochentag-Helfer),
|
||
`lib/validate.ts` (Body-Validierung). API-Routen unter `app/api/` prüfen alle `getSession()`
|
||
und liefern 401 ohne Session.
|
||
|
||
## Datenmodell & Berechnungen
|
||
|
||
Tabelle `arbeitszeit`: `datum`, `ort` (Kunde FFM | Homeoffice | Andrena), `beginn`, `ende`
|
||
(je HH:MM), `reisezeit`, `vertriebsunterstuetzung` (default 2), `kommentar` (max. 500).
|
||
|
||
- `Arbeitszeit = Ende − Beginn` (dezimale Stunden)
|
||
- `Mehrarbeit = Arbeitszeit − 8 − Pause`
|
||
- Die Spalte „Arbeitszeit Kunde" ist fix 8 h (`ARBEITSZEIT_KUNDE_FIX`).
|
||
- Die **Pause** ist eine globale Einstellung in der `settings`-Tabelle (Tab „Einstellungen").
|
||
Mehrarbeit wird zur Laufzeit berechnet, nicht gespeichert.
|