Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The project is pre-1.0, so breaking changes can appear in any release.

### Security

- Webhook delivery signs the exact event envelope with HMAC-SHA256, disables redirects, enforces a per-request timeout, and blocks private, loopback, link-local, unspecified, multicast, and IPv6 unique-local destinations by default. Validated DNS answers are pinned to prevent rebinding; private-network delivery requires an explicit per-endpoint opt-out.
- Authentication middleware no longer exempts any path ending in `.ico`. Only exact-match files such as `/favicon.ico` are exempt.
- Vite dev-server asset paths (`/@`, `/__vite_hmr`, `/node_modules/`, `/src/`, `/df/`) now skip authentication only while the dev proxy is active.
- Public and guest route matching is whole-path exact instead of prefix-based, and the server refuses to start unless `JWT_SECRET` is at least 32 bytes.
Expand All @@ -21,6 +22,7 @@ The project is pre-1.0, so breaking changes can appear in any release.

### Added

- The `webhooks` plugin adds administrator endpoint CRUD, write-only signing secrets, exact event-name filters, per-endpoint delivery history, five-attempt dead letters, and explicit manual retries. Successful endpoints are not resent when another endpoint fails.
- Compile-time typed content collections through `yeollin_content_collection!` and the `collections` plugin declaration. Collection field types drive concrete handlers, validation, exported schemas, and generated editor pages while the framework owns IDs, collection-scoped slugs, author, and timestamps.
- A shared draft/published content repository with paginated administrator CRUD, transactional `content.created` / `updated` / `published` / `unpublished` / `deleted` audit events, and an exact public-by-slug endpoint that exposes only published entries. The reference `content` plugin ships a typed `pages` collection with media-reference validation.
- The `media` plugin provides an administrator media library, typed multipart image uploads, paginated metadata, deletion, and a public serving route. JPEG, PNG, GIF, and WebP are verified from their signatures; upload size has a 10 MiB hard ceiling and a typed 1–10 MiB setting.
Expand All @@ -41,6 +43,7 @@ The project is pre-1.0, so breaking changes can appear in any release.

### Changed

- Deferred outbox failures now retry with exponential backoff capped at five minutes instead of a fixed polling delay.
- Passwords must be at least 12 **characters** — counted as characters, not bytes, so a short multi-byte password cannot pass. This applies to the bootstrap administrator too, so a deployment whose `YEOLLIN_ADMIN_PASSWORD` is shorter now fails at startup with the reason rather than seeding a weak account.
- Changing or resetting a password ends every session for that account. A refresh token minted before the change stops working, which is what makes a password change useful for containing a compromise.

Expand Down
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ tokio-util = { version = "0.7", features = ["io"] }
jsonwebtoken = { version = "11", features = ["rust_crypto"] }
argon2 = "0.6"
rand = "0.10"
hmac = "0.13"
sha2 = "0.11"

# Internal crates
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Yeollin CMS is a Tauri-inspired, plugin-based CMS *framework* rather than a fini
|------|----------|
| `crates/` | The Rust workspace crates: `core` (shared types), `auth` (JWT, Argon2, middleware), `plugin` (`PluginMetadata`, `FrontendAssets`), `plugin-macros` (`yeollin_plugin!`, `yeollin_app!`), `app` (`YeollinAppBuilder` runtime), `cli` (`init`, `prebuild`, `dev`, `build`). |
| `packages/` | The Node workspace. `packages/app` is the vinext frontend template that gets extracted into `.yeollin/app/` at prebuild time. It is a template, not the running app. |
| `plugins/` | Plugin crates. `auth` owns accounts and sessions; `audit-log` reads explicitly marked outbox events; `media` owns runtime image uploads; `content` demonstrates compile-time typed draft/publish collections; `example-plugin` is a minimal library plugin; `example-memo-plugin` demonstrates database CRUD, typed settings, and audited events. |
| `plugins/` | Plugin crates. `auth` owns accounts and sessions; `audit-log` reads explicitly marked outbox events; `media` owns runtime image uploads; `content` demonstrates compile-time typed draft/publish collections; `webhooks` delivers signed events with retry and dead-letter history; `example-plugin` is a minimal library plugin; `example-memo-plugin` demonstrates database CRUD, typed settings, and audited events. |
| `apps/` | Standalone application crates. `apps/example-app` wires the example plugins together with `yeollin_app!` and is the entry point used for local development. |

`.yeollin/` is generated during prebuild and is gitignored. Never edit it by hand.
Expand Down
4 changes: 4 additions & 0 deletions apps/example-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ sea-orm = { workspace = true }
audit-log = { path = "../../plugins/audit-log" }
media = { path = "../../plugins/media" }
content = { path = "../../plugins/content" }
webhooks = { path = "../../plugins/webhooks" }

[dev-dependencies]
reqwest = { workspace = true, features = ["json", "multipart"] }
tempfile = { workspace = true }
axum = { workspace = true }
hmac = { workspace = true }
sha2 = { workspace = true }
2 changes: 1 addition & 1 deletion apps/example-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async fn main() -> anyhow::Result<()> {
// Create app builder using yeollin_app! macro
// This macro handles both register_plugin() and vespera merge in one call
let app = yeollin::yeollin_app! {
plugins: [audit_log, auth, content, example_memo_plugin, example_plugin, media],
plugins: [audit_log, auth, content, example_memo_plugin, example_plugin, media, webhooks],
openapi: "openapi.json",
title: "Example CMS API",
version: "1.0.0",
Expand Down
4 changes: 4 additions & 0 deletions apps/example-app/tests/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ fn every_plugin_route_lives_under_its_declared_namespace() {
"/api/media",
"/api/media/file",
"/api/media/{id}",
"/api/webhooks",
"/api/webhooks/deliveries",
"/api/webhooks/deliveries/{id}/retry",
"/api/webhooks/{id}",
]
);

Expand Down
Loading
Loading