mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-12 12:06:57 +02:00
a6f86ec1f0b3e7f030123d596842239493b80695
Second slice of issue #77. PATCH /api/admin/users/[id] accepted { newPassword } for any account and returned 200, but resetPassword() writes only passwordHash while verifyPassword() matches only rows whose authProvider is 'password'. So for an OAuth-only account (the "OAuth-only" checkbox stores 'none') or anyone who has signed in via OAuth (authProvider becomes the provider name), the endpoint wrote a hash that could never be used to sign in, returned success, and fired a "your password was reset" email. It lied. The endpoint already guards both other self-affecting operations server-side — "Cannot change your own role" and "Cannot delete your own account" — but a newPassword targeting the requesting admin succeeded. Two early returns in the newPassword branch, ordered length -> self -> eligibility so the existing length 400 keeps precedence. Both are placed before the resetPassword() call and before the email block: that ordering is what guarantees no unusable hash is written and no misleading email is sent, and it is a property of control flow rather than of an assertion, because neither is observable from a test. passwordHash never leaves the server (toUser omits it) and isEmailConfigured() is false under e2e, so an assertion either way would pass for a broken implementation too. Eligibility comes from canResetPassword() rather than an inline comparison, so the endpoint and the settings UI cannot drift; the 400 message comes from passwordResetBlockedReason() so the API reason and the UI explanation are the same string. DoD items verified: - [x] 1. eligible user other than self still 200s, new password works - [x] 2. authProvider 'none' returns 400 naming the reason - [x] 3. positive control: an eligible user CAN log in after a reset - [x] 4. targeting own id returns 400 naming the reason - [x] 5. sub-8-char password still 400s for length, beating eligibility - [x] 6. both guards precede the write and the email (diff-verified) - [x] 7. role, revokeSessions and DELETE byte-unchanged (15+/0-) - [x] 8. eligibility imported from wu-001, not re-implemented - [x] 9. declarative Given/When/Then, distinct victim emails throughout Verified independently by the orchestrator: pnpm check 0 errors / 14 pre-existing warnings; pnpm test:unit 306 passed / 26 files; pnpm build && pnpm test:e2e admin-users.spec.ts 14 passed (7 auth-setup + 3 pre-existing + 4 new). The reviewer additionally reverted the endpoint to its pre-change form and confirmed both guard scenarios fail with "Expected: 400, Received: 200", so they are genuine regression tests rather than assertions of behaviour that already held. Reviewed-by: adversarial-review (PASS) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RbqJcFofZSbhbGwUwZP3wU
Crumbs by Bretzel
A self-hostable, offline-first note-taking app that respects your privacy. Part of the Bretzel app universe.
Install
Docker (Recommended)
docker compose up -d
Open http://localhost:3000 and create your admin account on first visit.
With SSO (optional)
# docker-compose.yml
services:
crumbs:
image: ghcr.io/bretzel-app/crumbs:latest
ports:
- "3000:3000"
volumes:
- crumbs-data:/data
environment:
- ORIGIN=https://notes.example.com
- AUTH_OIDC_ISSUER=https://authentik.example.com/application/o/crumbs/
- AUTH_OIDC_CLIENT_ID=your-client-id
- AUTH_OIDC_CLIENT_SECRET=your-client-secret
- AUTH_OIDC_DISPLAY_NAME=Authentik
restart: unless-stopped
volumes:
crumbs-data:
Google and GitHub OAuth are also supported. See docs/AUTH.md for all providers and setup guides.
With Email Notifications (optional)
Add SMTP settings to enable email notifications (share alerts, welcome emails, security alerts):
environment:
- SMTP_HOST=smtp.example.com
- SMTP_PORT=587
- SMTP_USER=your-username
- SMTP_PASS=your-password
- SMTP_FROM=Crumbs <noreply@example.com>
Auto-enabled when SMTP_HOST is set. Users can opt out in Settings > Preferences. See docs/DEPLOYMENT.md for details.
Manual (Node.js)
git clone <repo-url> crumbs && cd crumbs
pnpm install
pnpm build
DATABASE_URL=./data/crumbs.db ORIGIN=http://localhost:3000 node build
Development
pnpm install
pnpm dev
Run make help to see all available commands, or use pnpm directly:
pnpm test # Unit + E2E tests
pnpm check # Type checking
pnpm build # Production build
Features
- Rich notes with Markdown, checklists, image attachments, and 12 color themes
- Organize with #tags, pinning, archive, and trash
- Full-text search across titles, content, and tags
- Note version history — browse and restore previous versions
- Share notes with other users on the same instance
- Email notifications for shares, security alerts, and account events (optional SMTP)
- PWA — installable, works offline via IndexedDB + LWW CRDT sync
- MCP server — let AI assistants (Claude Code, etc.) manage your notes
- Multi-user auth (Argon2) with optional OAuth/SSO (Google, GitHub, OIDC)
- Docker deployment with a single command
See docs/FEATURES.md for detailed feature documentation.
Tech Stack
| Layer | Technology |
|---|---|
| Framework | SvelteKit 2 (Svelte 5 runes) |
| Language | TypeScript (strict) |
| UI | Tailwind CSS 4 |
| Database | SQLite (better-sqlite3) + Drizzle ORM |
| Client DB | IndexedDB (idb) |
| Sync | LWW CRDTs |
| Auth | Argon2 + session cookies |
| Testing | Vitest + Playwright |
| Container | Docker (multi-stage) |
| CI/CD | GitHub Actions |
Documentation
| Document | Description |
|---|---|
| Features | Detailed feature list and behavior |
| Architecture | Local-first sync design, tech rationale, DB schema |
| Auth | Password auth, OAuth/SSO setup (Authentik, Keycloak, etc.) |
| Deployment | Docker, Node.js, reverse proxy, backups, env vars |
| API | REST API reference (auto-generated) |
| Contributing | How to contribute |
| Security | Vulnerability reporting policy |
| Changelog | Release history |
CI/CD
- CI — lint, type check, unit tests, E2E tests, Docker build on every push/PR
- Release — builds and pushes Docker image on
v*tags
Configure registry via GitHub Secrets: REGISTRY_URL, REGISTRY_USER, REGISTRY_TOKEN.
License
Languages
TypeScript
68.5%
Svelte
25.1%
HTML
2.7%
CSS
2.7%
Shell
0.5%
Other
0.5%
