chore: prepare release v0.3.0
This commit is contained in:
+21
-10
@@ -20,7 +20,7 @@ server-rendered admin for editing them.
|
||||
- **Markdown-first** — the visual editor round-trips through the same renderer
|
||||
that powers the public API, so stored content is always Markdown.
|
||||
|
||||
## Components (planned layout)
|
||||
## Components
|
||||
|
||||
```
|
||||
lib/volumen/
|
||||
@@ -28,27 +28,38 @@ lib/volumen/
|
||||
config.rb # loads /etc/volumen/config.toml, applies CLI overrides
|
||||
frontmatter.rb # parse/serialise the `+++ … +++` TOML block
|
||||
post.rb # Post model: metadata + raw body + rendered HTML
|
||||
store.rb # reads the content directory into Post objects
|
||||
markdown.rb # kramdown wrapper (render + sanitise)
|
||||
server.rb # Sinatra app: JSON API + admin routes
|
||||
store.rb # reads the content directory into Post objects (mtime-cached)
|
||||
markdown.rb # kramdown wrapper (render + <figure>/<figcaption> for titled images)
|
||||
feed_helpers.rb # RSS / JSON Feed / sitemap generation
|
||||
users.rb # users.toml + admin/author roles
|
||||
password.rb # scrypt hashing/verification (OpenSSL::KDF)
|
||||
api_routes.rb # /api/volumen/* (Sinatra routes)
|
||||
admin_routes.rb # /admin/* (Sinatra routes)
|
||||
server.rb # Sinatra app: composes the two route modules, shared helpers
|
||||
cli.rb # command dispatcher
|
||||
web/
|
||||
views/ # ERB templates for the admin
|
||||
public/ # admin CSS + the visual-editor JS island
|
||||
public/ # static assets (volumen-logo.svg, volumen-icon.svg)
|
||||
exe/volumen # CLI: serve, hash-password, version, help
|
||||
```
|
||||
|
||||
## Request flow (planned)
|
||||
## Request flow
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[HTTP request] --> B{Route}
|
||||
B -->|/api/volumen/*| C[JSON API]
|
||||
B -->|/admin/*| D[Admin: session + CSRF]
|
||||
B -->|/api/volumen/*| C[api_routes.rb]
|
||||
B -->|/admin/*| D[admin_routes.rb + CSRF + CSP]
|
||||
C --> E[Store]
|
||||
D --> E
|
||||
E --> F[(content_dir/*.md)]
|
||||
D --> U[Users]
|
||||
C --> F[FeedHelpers]
|
||||
C --> G[Markdown renderer]
|
||||
D --> G
|
||||
E --> H[(content_dir/*.md)]
|
||||
E --> I[(content_dir/media/*)]
|
||||
U --> J[(users.toml)]
|
||||
F --> E
|
||||
G --> K[<figure> for titled images]
|
||||
```
|
||||
|
||||
## Public site integration
|
||||
|
||||
+32
-3
@@ -51,6 +51,26 @@ than opening a public issue.
|
||||
Requests without a valid token get `403`.
|
||||
- `SameSite=Strict` cookies provide a second, independent layer.
|
||||
|
||||
## Content Security Policy (admin)
|
||||
|
||||
All `/admin/*` responses send a strict **Content-Security-Policy** header:
|
||||
|
||||
```
|
||||
default-src 'self';
|
||||
script-src 'self' 'unsafe-inline';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
img-src 'self' data:;
|
||||
font-src 'self';
|
||||
connect-src 'self';
|
||||
form-action 'self';
|
||||
frame-ancestors 'none'
|
||||
```
|
||||
|
||||
The inline allowances are required by the server-rendered admin (ERB templates
|
||||
emit small inline `<script>` blocks and inline `style=""` attributes);
|
||||
`frame-ancestors 'none'` prevents clickjacking via iframe embedding. The public
|
||||
JSON API does not set a CSP — it returns no HTML, so none is needed.
|
||||
|
||||
## CORS and Host handling
|
||||
|
||||
- The public read API sends `Access-Control-Allow-Origin: *` (read-only, no
|
||||
@@ -68,8 +88,15 @@ than opening a public issue.
|
||||
- `GET /media/*` resolves names with `File.basename` and an explicit
|
||||
containment check, so `../` traversal cannot read files outside the media
|
||||
directory.
|
||||
- Upload content-type is **not** validated (the `accept="image/*"` attribute is
|
||||
only a UI hint); this is acceptable under the trusted-author model.
|
||||
- Uploads are **rejected (HTTP 413)** when the file exceeds **10 MB**
|
||||
(`MAX_UPLOAD_BYTES`).
|
||||
- Upload MIME type is **whitelisted** to `image/webp` and `image/avif`
|
||||
(`ALLOWED_UPLOAD_TYPES`); anything else returns **HTTP 415** with a
|
||||
conversion hint pointing at `cwebp` / `avifenc`. This matches the
|
||||
WebP/AVIF-only posture the engine serves content in and rejects binaries
|
||||
(PHP, executable, …) outright.
|
||||
- Per-user profile photos go through the same upload validation and storage
|
||||
pipeline as post media.
|
||||
|
||||
## Secrets and files
|
||||
|
||||
@@ -91,7 +118,9 @@ A small, audited set: `sinatra`, `kramdown` (+ `kramdown-parser-gfm`),
|
||||
|
||||
## Known limitations / non-goals
|
||||
|
||||
- **No rate limiting or lockout** on `/admin/login`. Put nginx `limit_req` in
|
||||
- **No rate limiting or lockout** on `/admin/login` at the application layer
|
||||
(the engine does have an in-memory rate limiter, but nginx is the durable
|
||||
line of defence — see `docs/deployment.md`). Put nginx `limit_req` in
|
||||
front of it, or use fail2ban, to slow brute-force attempts.
|
||||
- **No 2FA** and **no audit log**.
|
||||
- **Raw HTML in posts is trusted** (see the trust model). There is no built-in
|
||||
|
||||
Reference in New Issue
Block a user