includio-cms 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +22 -21
- package/CHANGELOG.md +147 -0
- package/DOCS.md +1 -1
- package/README.md +138 -32
- package/ROADMAP.md +11 -4
- package/dist/admin/api/rest/handler.d.ts +13 -1
- package/dist/admin/api/rest/handler.js +13 -1
- package/dist/admin/api/rest/middleware/apiKey.js +9 -1
- package/dist/admin/api/rest/middleware/generateApiKey.d.ts +16 -0
- package/dist/admin/api/rest/middleware/generateApiKey.js +19 -0
- package/dist/admin/client/collection/collection-entries.svelte +1 -1
- package/dist/admin/client/collection/empty-state.svelte +1 -1
- package/dist/admin/client/collection/row-actions.svelte +3 -3
- package/dist/admin/client/collection/table-toolbar.svelte +3 -1
- package/dist/admin/client/entry/entry-header.svelte +3 -1
- package/dist/admin/client/users/create-user-dialog.svelte +4 -4
- package/dist/admin/client/users/delete-user-dialog.svelte +4 -2
- package/dist/admin/client/users/lang.d.ts +10 -2
- package/dist/admin/client/users/lang.js +10 -4
- package/dist/admin/client/users/users-page.svelte +3 -2
- package/dist/admin/components/media/file-upload.svelte +2 -0
- package/dist/ai-claude/index.d.ts +9 -1
- package/dist/ai-claude/index.js +9 -1
- package/dist/ai-openai/index.d.ts +9 -1
- package/dist/ai-openai/index.js +9 -1
- package/dist/cli/index.js +115 -13
- package/dist/cms/runtime/schema.d.ts +2 -0
- package/dist/cms/runtime/schema.js +4 -0
- package/dist/cms/runtime/types.d.ts +1 -1
- package/dist/core/cms.d.ts +13 -1
- package/dist/core/cms.js +13 -1
- package/dist/core/errors.d.ts +71 -0
- package/dist/core/errors.js +179 -0
- package/dist/core/server/consentLogs/operations/create.d.ts +13 -1
- package/dist/core/server/consentLogs/operations/create.js +13 -1
- package/dist/core/server/entries/operations/create.js +6 -1
- package/dist/core/server/entries/operations/get.js +14 -3
- package/dist/core/server/entries/operations/resolveEntry.d.ts +32 -1
- package/dist/core/server/entries/operations/resolveEntry.js +36 -4
- package/dist/core/server/entries/operations/update.js +5 -1
- package/dist/core/server/fields/utils/resolveMedia.d.ts +18 -1
- package/dist/core/server/fields/utils/resolveMedia.js +13 -1
- package/dist/core/server/forms/submissions/operations/create.d.ts +21 -1
- package/dist/core/server/forms/submissions/operations/create.js +18 -2
- package/dist/core/server/forms/submissions/utils/parseMultipart.d.ts +15 -1
- package/dist/core/server/forms/submissions/utils/parseMultipart.js +15 -1
- package/dist/core/server/media/operations/uploadFile.js +4 -3
- package/dist/core/server/media/styles/sharp/generateImageStyle.js +3 -2
- package/dist/core/server/media/utils/generateAdminThumbnail.js +3 -2
- package/dist/core/server/media/utils/generateBlurDataUrl.js +2 -1
- package/dist/db-postgres/index.d.ts +10 -0
- package/dist/db-postgres/index.js +10 -0
- package/dist/email-nodemailer/index.d.ts +13 -1
- package/dist/email-nodemailer/index.js +13 -1
- package/dist/entity/index.d.ts +16 -1
- package/dist/entity/index.js +16 -1
- package/dist/files-local/index.d.ts +12 -1
- package/dist/files-local/index.js +12 -1
- package/dist/paraglide/messages/_index.d.ts +3 -36
- package/dist/paraglide/messages/_index.js +3 -71
- package/dist/paraglide/messages/hello_world.d.ts +5 -0
- package/dist/paraglide/messages/hello_world.js +33 -0
- package/dist/paraglide/messages/login_hello.d.ts +16 -0
- package/dist/paraglide/messages/login_hello.js +34 -0
- package/dist/paraglide/messages/login_please_login.d.ts +16 -0
- package/dist/paraglide/messages/login_please_login.js +34 -0
- package/dist/server/auth.d.ts +11 -0
- package/dist/server/auth.js +11 -0
- package/dist/server/security/csp.d.ts +16 -0
- package/dist/server/security/csp.js +33 -0
- package/dist/server/security/csrf.d.ts +13 -0
- package/dist/server/security/csrf.js +49 -0
- package/dist/server/security/index.d.ts +3 -0
- package/dist/server/security/index.js +3 -0
- package/dist/server/security/rate-limit.d.ts +44 -0
- package/dist/server/security/rate-limit.js +97 -0
- package/dist/server/utils/withTimeout.d.ts +21 -0
- package/dist/server/utils/withTimeout.js +37 -0
- package/dist/sveltekit/config.d.ts +67 -4
- package/dist/sveltekit/config.js +73 -4
- package/dist/sveltekit/server/handle.d.ts +15 -1
- package/dist/sveltekit/server/handle.js +22 -1
- package/dist/sveltekit/server/index.d.ts +1 -0
- package/dist/sveltekit/server/index.js +1 -0
- package/dist/sveltekit/server/layout.d.ts +12 -1
- package/dist/sveltekit/server/layout.js +12 -1
- package/dist/sveltekit/server/preview.d.ts +21 -1
- package/dist/sveltekit/server/preview.js +21 -1
- package/dist/types/cms.d.ts +4 -0
- package/dist/types/cms.schema.d.ts +452 -0
- package/dist/types/cms.schema.js +629 -0
- package/dist/updates/0.21.0/index.d.ts +2 -0
- package/dist/updates/0.21.0/index.js +55 -0
- package/dist/updates/0.22.0/index.d.ts +2 -0
- package/dist/updates/0.22.0/index.js +75 -0
- package/dist/updates/index.js +3 -1
- package/package.json +12 -2
- package/dist/paraglide/messages/en.d.ts +0 -5
- package/dist/paraglide/messages/en.js +0 -14
- package/dist/paraglide/messages/pl.d.ts +0 -5
- package/dist/paraglide/messages/pl.js +0 -14
package/API.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# includio-cms — Public API v0.
|
|
1
|
+
# includio-cms — Public API v0.22.0
|
|
2
2
|
|
|
3
3
|
> Auto-generated by `scripts/generate-api-md.ts`. Do not edit by hand.
|
|
4
4
|
|
|
5
|
-
Entry points: **15** · Stable: **
|
|
5
|
+
Entry points: **15** · Stable: **346** · Experimental: **2**
|
|
6
6
|
|
|
7
7
|
Tags:
|
|
8
8
|
- `@public` — frozen for v1.0; semver-protected.
|
|
@@ -13,19 +13,19 @@ Tags:
|
|
|
13
13
|
|
|
14
14
|
### `includio-cms`
|
|
15
15
|
|
|
16
|
-
- `createEntityAPI(cms: CMS, opts?: EntityAPIOptions): <inferred>` — Creates a high-level Entity API (CRUD + publish/archive) bound to a CMS
|
|
16
|
+
- `createEntityAPI(cms: CMS, opts?: EntityAPIOptions): <inferred>` — Creates a high-level Entity API (CRUD + publish/archive) bound to a CMS
|
|
17
17
|
- `getAuth(): <inferred>` — Returns the underlying `better-auth` instance from the initialized CMS.
|
|
18
|
-
- `getCMS(): CMS` — Returns the singleton CMS instance. Must be called after `includioCMS()`
|
|
19
|
-
- `interface ResolvedMedia`
|
|
20
|
-
- `resolveMediaWithStyles(mediaIds: string[], styles?: ImageFieldStyle[]): Promise<Record<string, ResolvedMedia>>` — Resolve media files by IDs and generate image styles. Useful for plugins
|
|
18
|
+
- `getCMS(): CMS` — Returns the singleton CMS instance. Must be called after `includioCMS()`
|
|
19
|
+
- `interface ResolvedMedia` — Resolved media file plus its image styles + blur placeholder. Returned by
|
|
20
|
+
- `resolveMediaWithStyles(mediaIds: string[], styles?: ImageFieldStyle[]): Promise<Record<string, ResolvedMedia>>` — Resolve media files by IDs and generate image styles. Useful for plugins or
|
|
21
21
|
|
|
22
22
|
### `includio-cms/core`
|
|
23
23
|
|
|
24
|
-
- `createEntityAPI(cms: CMS, opts?: EntityAPIOptions): <inferred>` — Creates a high-level Entity API (CRUD + publish/archive) bound to a CMS
|
|
24
|
+
- `createEntityAPI(cms: CMS, opts?: EntityAPIOptions): <inferred>` — Creates a high-level Entity API (CRUD + publish/archive) bound to a CMS
|
|
25
25
|
- `getAuth(): <inferred>` — Returns the underlying `better-auth` instance from the initialized CMS.
|
|
26
|
-
- `getCMS(): CMS` — Returns the singleton CMS instance. Must be called after `includioCMS()`
|
|
27
|
-
- `interface ResolvedMedia`
|
|
28
|
-
- `resolveMediaWithStyles(mediaIds: string[], styles?: ImageFieldStyle[]): Promise<Record<string, ResolvedMedia>>` — Resolve media files by IDs and generate image styles. Useful for plugins
|
|
26
|
+
- `getCMS(): CMS` — Returns the singleton CMS instance. Must be called after `includioCMS()`
|
|
27
|
+
- `interface ResolvedMedia` — Resolved media file plus its image styles + blur placeholder. Returned by
|
|
28
|
+
- `resolveMediaWithStyles(mediaIds: string[], styles?: ImageFieldStyle[]): Promise<Record<string, ResolvedMedia>>` — Resolve media files by IDs and generate image styles. Useful for plugins or
|
|
29
29
|
|
|
30
30
|
### `includio-cms/types`
|
|
31
31
|
|
|
@@ -239,11 +239,11 @@ Tags:
|
|
|
239
239
|
### `includio-cms/sveltekit`
|
|
240
240
|
|
|
241
241
|
- `const CmsProvider: LegacyComponentType`
|
|
242
|
-
- `defineCollection(config: CollectionInput): CollectionConfig` — Defines a collection (multi-entry content type).
|
|
242
|
+
- `defineCollection(config: CollectionInput): CollectionConfig` — Defines a collection (multi-entry content type, e.g. blog posts, products).
|
|
243
243
|
- `defineConfig(config: CMSConfig): CMSConfig` — Defines the root CMS configuration. Pass to `includioCMS()` in `hooks.server.ts`.
|
|
244
|
-
- `defineForm(config: FormConfig): FormConfig` — Defines a public form (submitted via
|
|
245
|
-
- `defineObject(config: Omit<ObjectField, 'type'>): ObjectField` — Defines a reusable object field (nested record). Use inside `fields[]
|
|
246
|
-
- `defineSingle(config: SingleInput): SingleConfig` — Defines a singleton (single-entry content type, e.g. site settings).
|
|
244
|
+
- `defineForm(config: FormConfig): FormConfig` — Defines a public form (submitted via `POST /api/forms/[slug]/submit`).
|
|
245
|
+
- `defineObject(config: Omit<ObjectField, 'type'>): ObjectField` — Defines a reusable object field (nested record). Use inline inside `fields[]`
|
|
246
|
+
- `defineSingle(config: SingleInput): SingleConfig` — Defines a singleton (single-entry content type, e.g. site settings, homepage).
|
|
247
247
|
- `enableHybridEditing(): <inferred>` — Call in a layout/component script to enable data-hybrid-path rendering for all descendant HybridTarget/Image/Video.
|
|
248
248
|
- `extractBlocks(doc: StructuredContentDoc, type?: string): SCNode[]` — Extract block-level nodes, optionally filtered by type.
|
|
249
249
|
- `extractInlineBlocks(doc: StructuredContentDoc, blockType?: string): SCInlineBlockAttrs[]` — Extract inline block nodes, optionally filtered by blockType.
|
|
@@ -267,15 +267,16 @@ Tags:
|
|
|
267
267
|
|
|
268
268
|
### `includio-cms/sveltekit/server`
|
|
269
269
|
|
|
270
|
-
- `cmsLayoutLoad(event: RequestEvent): <inferred>` — Returns `cmsContext` from `event.locals` for use in a SvelteKit layout
|
|
271
|
-
- `countEntries(opts: CountEntriesOptions): Promise<number>` — Count entries matching the same filters as `resolveEntries`, without
|
|
270
|
+
- `cmsLayoutLoad(event: RequestEvent): <inferred>` — Returns `cmsContext` from `event.locals` for use in a SvelteKit layout
|
|
271
|
+
- `countEntries(opts: CountEntriesOptions): Promise<number>` — Count entries matching the same filters as `resolveEntries`, without
|
|
272
272
|
- `type CountEntriesOptions = Omit< ResolveEntriesOptions, 'limit' | 'offset' | 'populate' | 'orderBy' | 'dataOrderBy' >`
|
|
273
273
|
- `const createConsentLog: <inferred>`
|
|
274
274
|
- `const createFormSubmission: <inferred>`
|
|
275
|
-
- `createRestApiHandler(): <inferred>` — REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
276
|
-
- `
|
|
277
|
-
- `
|
|
278
|
-
- `
|
|
275
|
+
- `createRestApiHandler(): <inferred>` — REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
276
|
+
- `generateApiKey(): string` — Generate a cryptographically random API key (32 bytes, base64url-encoded).
|
|
277
|
+
- `getPreviewEntry(event: RequestEvent, options: { language: string }): Promise<Entry | null>` — Resolves the preview entry from a `?preview=<versionId>` query param.
|
|
278
|
+
- `includioCMS(cmsConfig: CMSConfig): Handle[]` — SvelteKit `Handle[]` array that initializes the CMS, generates runtime
|
|
279
|
+
- `parseFormDataForSubmission(formData: FormData, fields: FormField[]): Promise<Record<string, unknown>>` — Parses multipart `FormData` into a typed record of field values, handling
|
|
279
280
|
- `type PopulateConfig = { /** Hard cap on relation depth. Default 5. Use `0` to keep all relations as raw IDs. */ maxDept...` — Recursion + per-field opt-out config for relation population.
|
|
280
281
|
- `resolveEntries(opts: ResolveEntriesOptions): Promise<Entry[]>` — Fetch a list of populated Entries from a collection (or singleton with multiple instances).
|
|
281
282
|
- `interface ResolveEntriesOptions`
|
|
@@ -386,7 +387,7 @@ Tags:
|
|
|
386
387
|
### `includio-cms/files-local`
|
|
387
388
|
|
|
388
389
|
- `const fullDir: <inferred>`
|
|
389
|
-
- `local(config?: LocalFilesConfig): FilesAdapter` — Local-disk files adapter. Stores uploads under `./static/uploads` (dev) or
|
|
390
|
+
- `local(config?: LocalFilesConfig): FilesAdapter` — Local-disk files adapter. Stores uploads under `./static/uploads` (dev) or
|
|
390
391
|
- `interface LocalFilesConfig`
|
|
391
392
|
|
|
392
393
|
### `includio-cms/email-nodemailer`
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,153 @@
|
|
|
3
3
|
All notable changes to includio-cms are documented here.
|
|
4
4
|
Generated from `src/lib/updates/` — do not edit manually.
|
|
5
5
|
|
|
6
|
+
## 0.22.0 — 2026-04-30
|
|
7
|
+
|
|
8
|
+
Faza 9 — DX & config validation pass. `defineConfig()` waliduje config strict Zodem z czytelnymi błędami (path + hint), resolvery / operacje throwują typowane `CmsError` z `code` + `context`, CLI ma `--help` per subcommand i `--version`, README przepisany pod nowych userów (system requirements + 5-min quickstart), `.env.example` rozszerzony o wszystkie `INCLUDIO_*` envy, JSDoc na każdym `@public` symbolu (opis + `@param` + `@returns` + `@example`).
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `defineConfig()` (`includio-cms/sveltekit`) — runtime Zod validation z agregacją wszystkich błędów. Invalid config → `ConfigValidationError` (extends `CmsError`) z `issues[]` (path + message + hint), bullet-list message ready-to-paste do PR. `superRefine`: unique slugs (collections/singles/forms), default locale ≤ 1, relation targets w deklarowanych kolekcjach, `apiKeys[].permissions` referuje istniejące collection slugs.
|
|
12
|
+
- `CmsError` + `ConfigValidationError` (`includio-cms` → `core/errors`) — bazowa klasa z `code` (SCREAMING_SNAKE_CASE), `context: Record<string, unknown>`, `cause?`. Stabilne kody: `ENTRY_NOT_FOUND`, `ENTRY_VERSION_NOT_FOUND`, `INVALID_DATA`, `MISSING_REQUIRED_PARAM`, `CONFIG_VALIDATION_FAILED`. `toString()` = `[CODE] message (k=v, k=v)`.
|
|
13
|
+
- `formatZodDataIssues(error)` helper — render Zod errora z entry/form data jako lista `path: message`. Używany w `createEntryVersion`, `updateEntryVersion`, `createFormSubmission`.
|
|
14
|
+
- CLI: `includio --help`, `includio scaffold admin --help`, `includio install-peers --help`, `includio create-user --help` — exit 0 + Usage/Options/Example block. `includio --version` / `-v` z `package.json`. Unknown command → exit 1 + top-level help.
|
|
15
|
+
- `.env.example` rozszerzony — 11 envów z komentarzami i sekcjami (Required / Media & Uploads / Security & Rate Limits). Demo vars usunięte (były tylko dla `src/lib/demo/` skasowanego w Fazie 1).
|
|
16
|
+
- README rebuild: TOC, sekcja **System requirements** (Node 18+, PG 14+, ffmpeg optional), 5-step **Quick start** (install → .env → config → scaffold → create-user → dev), poprawiony link do `DOCS.md`, dodana tabelka adapterów, sekcja CLI z subcommandami.
|
|
17
|
+
- JSDoc bodies (opis + `@param` + `@returns` + `@example`) na każdym `@public` symbolu — `defineConfig`, `defineCollection`, `defineSingle`, `defineForm`, `defineObject`, `getCMS`, `resolveEntry`, `resolveEntries`, `countEntries`, `createFormSubmission`, `parseFormDataForSubmission`, `createConsentLog`, `resolveMediaWithStyles`, `createEntityAPI`, `createRestApiHandler`, `generateApiKey`, `getAuth`, `includioCMS`, `getPreviewEntry`, `cmsLayoutLoad`, factory adapterów (`pg`, `local`, `nodemailerAdapter`, `openAIAdapter`, `claudeAdapter`).
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- `MISSING_REQUIRED_PARAM` zamiast `throw new Error('... is required')` w `resolveEntry` / `resolveEntries` / `countEntries`. Context zawiera `op` + `missing`.
|
|
21
|
+
- `INVALID_DATA` zamiast `throw Error('Invalid data: ' + JSON.stringify(parsedData.error.flatten()))` w `createEntryVersion` / `updateEntryVersion` / `createFormSubmission`. Context zawiera `collection`/`entryId`/`lang` (entries) lub `formSlug` (forms). Body errora to czytelna lista `field.path: message`.
|
|
22
|
+
- `ENTRY_NOT_FOUND` z `{ collection, id }` w `_getDbEntryOrThrow` / `_getRawEntryOrThrow`; `ENTRY_VERSION_NOT_FOUND` z `{ versionId, entryId, lang }` w `_getDbEntryVersionOrThrow`.
|
|
23
|
+
|
|
24
|
+
### Breaking
|
|
25
|
+
- **Strict config validation w `defineConfig`** — invalid configs które wcześniej "działały" w runtime (np. duplicate slug, brak default locale, relation do nieistniejącej kolekcji, brak metody w adapterze) **teraz throwują `ConfigValidationError` na starcie**. Najczęstsze przypadki + fix:
|
|
26
|
+
- **Duplicate slug**: dwie kolekcje/singles/forms z tym samym `slug` → unique slug per kategoria.
|
|
27
|
+
- **Invalid language code**: `code: 'ENGLISH'` → `code: 'en'` (ISO-639-1, opcjonalnie z regionem `'pl-PL'`).
|
|
28
|
+
- **Multiple default locales**: dwa `{ default: true }` → tylko jeden.
|
|
29
|
+
- **Missing adapter method**: stub adaptera bez `getEntries` etc. → zaimportuj pełen `pg()` / `local()` lub doimplementuj brakujące metody.
|
|
30
|
+
- **Invalid relation target**: `{ type: 'relation', collection: 'authors' }` przy braku kolekcji `authors` → dodaj kolekcję lub popraw target.
|
|
31
|
+
- **Resolver / operation error format** — błędy z `resolveEntry` / `resolveEntries` / `countEntries` / `createEntryVersion` / `updateEntryVersion` / `createFormSubmission` rzucają teraz `CmsError` (extends `Error`). `error.message` zachowuje stary opis (przy `toString()` poprzedzony `[CODE]`) — string-matching `error.message === 'Entry not found'` może się rozjechać. **Migracja:**
|
|
32
|
+
```ts
|
|
33
|
+
import { CmsError } from 'includio-cms';
|
|
34
|
+
|
|
35
|
+
try { await resolveEntry({ id }); }
|
|
36
|
+
catch (e) {
|
|
37
|
+
if (e instanceof CmsError && e.code === 'ENTRY_NOT_FOUND') { ... }
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
- **`.env.example` reformatted** — `DEMO_USER_EMAIL`, `DEMO_USER_PASSWORD`, `DEMO_RESET_KEY` usunięte (były tylko dla wewnętrznego demo skasowanego w Fazie 1). Jeśli polegałeś na nich → trzymaj w lokalnym `.env`, nie w `.env.example`.
|
|
41
|
+
|
|
42
|
+
### Notes
|
|
43
|
+
|
|
44
|
+
## Czytelność błędów config
|
|
45
|
+
|
|
46
|
+
Po update — invalid config wyrzuci na starcie:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
ConfigValidationError: CMSConfig validation failed (2 issues):
|
|
50
|
+
- languages[0]: must be a 2-letter ISO code (e.g. 'en' or 'pl-PL')
|
|
51
|
+
- collections[1].slug: duplicate collection slug 'posts' — Hint: each collection/single/form must have a unique `slug`
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Każdy issue ma `path` (JS-notation: `collections[1].slug`, `apiKeys[0].permissions[2]`) i `message`. Tam gdzie da się rozsądnie zgadnąć, dochodzi `Hint:` z fix-suggestion.
|
|
55
|
+
|
|
56
|
+
## Pattern: branchowanie po error code
|
|
57
|
+
|
|
58
|
+
Stary kod string-matchujący po `error.message` musi się przepisać:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// PRZED
|
|
62
|
+
catch (e) {
|
|
63
|
+
if ((e as Error).message === 'Entry not found') { /* 404 */ }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// PO
|
|
67
|
+
import { CmsError } from 'includio-cms';
|
|
68
|
+
catch (e) {
|
|
69
|
+
if (e instanceof CmsError && e.code === 'ENTRY_NOT_FOUND') { /* 404 */ }
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Dostępne kody w v0.22.0: `ENTRY_NOT_FOUND`, `ENTRY_VERSION_NOT_FOUND`, `INVALID_DATA`, `MISSING_REQUIRED_PARAM`, `CONFIG_VALIDATION_FAILED`.
|
|
74
|
+
|
|
75
|
+
## Nowe envy (opt-in)
|
|
76
|
+
|
|
77
|
+
Skopiuj `node_modules/includio-cms/.env.example` do swojego `.env` — domyślne wartości działają OOTB:
|
|
78
|
+
|
|
79
|
+
- `INCLUDIO_SHARP_TIMEOUT_MS` (default 30000)
|
|
80
|
+
- `INCLUDIO_RATE_LIMIT_MAX` / `INCLUDIO_RATE_LIMIT_WINDOW_MS` (default 200/60000)
|
|
81
|
+
- `INCLUDIO_FORM_RATE_LIMIT_MAX` / `INCLUDIO_FORM_RATE_LIMIT_WINDOW_MS` (default 5/3600000)
|
|
82
|
+
- `INCLUDIO_CSRF_ALLOWED_ORIGINS` (default: same-origin only)
|
|
83
|
+
|
|
84
|
+
## CLI helpy
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
includio --help
|
|
88
|
+
includio scaffold admin --help
|
|
89
|
+
includio install-peers --help
|
|
90
|
+
includio create-user --help
|
|
91
|
+
includio --version
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Brak SQL migration.
|
|
95
|
+
|
|
96
|
+
## 0.21.0 — 2026-04-30
|
|
97
|
+
|
|
98
|
+
Faza 5 część 2 — security finish: form rate-limit DRY refactor, API keys `expiresAt` / `rotatedAt`, sharp timeout 30s, `{@html}` audit close. `KNOWN-RISKS.md` w root jako single source of truth dla zaakceptowanych ryzyk v1.0. Plus: faza 6 setup — vitest coverage (info-only), docker test profile, integration test scaffolding (`tests/`).
|
|
99
|
+
|
|
100
|
+
### Added
|
|
101
|
+
- `KNOWN-RISKS.md` w root paczki — 5 ryzyk udokumentowanych: CSP `'unsafe-inline'`, API key rotation opt-in, in-memory rate-limit, sharp 30s timeout, ffmpeg/sharp args audit. Każde z mitygacją i triggerem fix.
|
|
102
|
+
- `ApiKeyConfig.expiresAt?: string` (ISO-8601) — opt-in expiry, enforced w `validateApiKey()`. Wygasły klucz → 401 generic (no leak). Brak `expiresAt` = klucz nigdy nie wygasa (backward compat).
|
|
103
|
+
- `ApiKeyConfig.rotatedAt?: string` — info-only audit-trail, nie enforced.
|
|
104
|
+
- `generateApiKey()` (`includio-cms/sveltekit/server`) — crypto-random 32B base64url. Pełna rotacja = update `cms.config.ts` + redeploy (statyczny model, patrz KNOWN-RISKS §2).
|
|
105
|
+
- `withTimeout<T>(promise, ms, label)` + `TimeoutError` (`$lib/server/utils/withTimeout`). Wrap dookoła wszystkich sharp calls (metadata, toBuffer, blur, admin thumbnail, downscale resize). Default 30s, env override `INCLUDIO_SHARP_TIMEOUT_MS`.
|
|
106
|
+
- Form submit rate-limit: refaktor `src/routes/api/forms/[slug]/submit/+server.ts` — używa shared `MemoryRateLimitStore` z `$lib/server/security/rate-limit.js` (DRY z `/admin/api/*` rate-limit). Limity 5/h per IP zachowane. Env: `INCLUDIO_FORM_RATE_LIMIT_MAX`, `INCLUDIO_FORM_RATE_LIMIT_WINDOW_MS`.
|
|
107
|
+
- Faza 6 setup: docker `test` profile (`db_test` na porcie 5434, tmpfs, `fsync=off`), `tests/helpers/{db,api,auth}.ts`, `tests/setup.ts` z TRUNCATE strategy, vitest project `integration` (sequential, `singleFork`), sanity test `tests/integration/db-sanity.spec.ts`.
|
|
108
|
+
- Vitest coverage config (info-only, bez threshold) — `pnpm test:coverage` generuje `coverage/`. Reporter: text/json/html. Devdep: `@vitest/coverage-v8`.
|
|
109
|
+
|
|
110
|
+
### Fixed
|
|
111
|
+
- `{@html}` w `src/lib/admin/client/users/delete-user-dialog.svelte` (linie 85, 93) zastąpione safe Svelte template `{...}<strong>{...}</strong>{...}`. Defense-in-depth — content był admin-controlled, ale eliminuje wektor regresji XSS gdyby ktoś kiedyś dodał user-supplied input do tych komunikatów.
|
|
112
|
+
|
|
113
|
+
### Breaking
|
|
114
|
+
- Brak hard breakages. `ApiKeyConfig.expiresAt` / `rotatedAt` są opcjonalne — istniejące configi działają bez zmian.
|
|
115
|
+
- `usersLang.deleteWarningDesc` zmienia sygnaturę z `(name: string) => string` na statyczny obiekt `{ before: string; after: string }`. `usersLang.deleteConfirmType` analogicznie ze stringa na `{ before: string; after: string }`. Wpływ tylko na fork'i admin UI z customowym lang — szybki `pnpm check` zwróci błąd typu, fix = przepisz wartości w lang.
|
|
116
|
+
|
|
117
|
+
### Notes
|
|
118
|
+
|
|
119
|
+
## Setup integration tests (opt-in)
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
docker compose --profile test up -d db_test
|
|
123
|
+
pnpm prepack # buduje dist/ wymagany przez drizzle-kit push schema
|
|
124
|
+
pnpm db:test:migrate # drizzle-kit push do test DB
|
|
125
|
+
pnpm test:integration # uruchamia tests/integration/**
|
|
126
|
+
docker compose --profile test down
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## API key expiry (opt-in)
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// cms.config.ts
|
|
133
|
+
apiKeys: [
|
|
134
|
+
{ key: 'sk-...', name: 'ci', role: 'admin', expiresAt: '2027-01-01T00:00:00Z' }
|
|
135
|
+
]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Generowanie nowego klucza:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { generateApiKey } from 'includio-cms/sveltekit/server';
|
|
142
|
+
console.log(generateApiKey()); // → 43-char base64url, 32B entropii
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Pełna rotacja wymaga update'u `cms.config.ts` + redeploy. Pole `rotatedAt` jest info-only (audit trail) — admin ustawia ręcznie przy rotacji.
|
|
146
|
+
|
|
147
|
+
## Known risks
|
|
148
|
+
|
|
149
|
+
Lista zaakceptowanych ryzyk v1.0 — `KNOWN-RISKS.md` w root paczki. 5 sekcji: CSP `unsafe-inline`, API key rotation opt-in, in-memory rate-limit (multi-node = wymaga Redis adapter), sharp 30s timeout, ffmpeg/sharp shell audit (SAFE).
|
|
150
|
+
|
|
151
|
+
Brak SQL migration.
|
|
152
|
+
|
|
6
153
|
## 0.20.0 — 2026-04-29
|
|
7
154
|
|
|
8
155
|
API Surface Lock — exports trim 26→15, JSDoc tagi (`@public`/`@experimental`/`@internal`), autogenerowany `API.md`. Powierzchnia publiczna zamrożona w v1.0 — w 1.x tylko `@experimental` może się zmienić bez breaking semver.
|
package/DOCS.md
CHANGED
package/README.md
CHANGED
|
@@ -1,57 +1,149 @@
|
|
|
1
|
-
#
|
|
1
|
+
# includio-cms
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/includio/includio-cms/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A headless CMS built for SvelteKit. Type-safe, extensible, with a modern admin interface and pluggable adapters for database, files, email, and AI.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- [Features](#features)
|
|
10
|
+
- [System requirements](#system-requirements)
|
|
11
|
+
- [Quick start (5 minutes)](#quick-start-5-minutes)
|
|
12
|
+
- [Configuration](#configuration)
|
|
13
|
+
- [Adapters](#adapters)
|
|
14
|
+
- [Writing your own adapter](#writing-your-own-adapter)
|
|
15
|
+
- [Optional peer dependencies](#optional-peer-dependencies)
|
|
16
|
+
- [CLI](#cli)
|
|
17
|
+
- [Links](#links)
|
|
18
|
+
- [For AI assistants](#for-ai-assistants)
|
|
19
|
+
- [License](#license)
|
|
4
20
|
|
|
5
21
|
## Features
|
|
6
22
|
|
|
7
|
-
- **
|
|
23
|
+
- **20 field types** — text, structured content, media, relations, blocks, SEO, URL, custom plugins, and more
|
|
8
24
|
- **Structured content** — ProseMirror-based rich text with inline blocks, tables, media embeds
|
|
9
25
|
- **Media management** — image styles (Sharp), video transcoding (ffmpeg), focal points, blur placeholders
|
|
10
|
-
- **Per-language versioning** — independent draft/published/scheduled per language
|
|
26
|
+
- **Per-language versioning** — independent draft / published / scheduled per language
|
|
11
27
|
- **Layout DSL** — organize admin editor with sections, columns, cards, accordions
|
|
12
28
|
- **Frontend components** — `<Image>`, `<Video>`, `<StructuredContent>`, `<Media>` for SvelteKit
|
|
13
29
|
- **REST API** — API key auth, CRUD endpoints, schema introspection
|
|
14
30
|
- **Entity API** — server-side programmatic CRUD for scripts, migrations, webhooks
|
|
15
31
|
- **Code generation** — TypeScript types and Zod schemas from your content schema
|
|
16
|
-
- **Pluggable adapters** — swap database, file storage, email,
|
|
32
|
+
- **Pluggable adapters** — swap database, file storage, email, AI provider
|
|
17
33
|
- **Plugin system** — lifecycle hooks and custom field types
|
|
18
|
-
- **
|
|
34
|
+
- **Strict config validation** — invalid configs throw a `ConfigValidationError` listing every issue with a path and a fix hint
|
|
35
|
+
|
|
36
|
+
## System requirements
|
|
37
|
+
|
|
38
|
+
- **Node.js** 18+ (20 LTS recommended)
|
|
39
|
+
- **PostgreSQL** 14+ — required by the bundled `db-postgres` adapter; or write your own [`DatabaseAdapter`](#writing-your-own-adapter)
|
|
40
|
+
- **ffmpeg + ffprobe** — optional, only required for video transcoding (set `FFMPEG_PATH` / `FFPROBE_PATH` if not on `PATH`)
|
|
41
|
+
- A SvelteKit project (Kit ≥ 2.48, Svelte ≥ 5.43, Vite ≥ 7)
|
|
42
|
+
|
|
43
|
+
## Quick start (5 minutes)
|
|
19
44
|
|
|
20
|
-
|
|
45
|
+
The five steps below take you from `pnpm add` to a running admin at `/admin`.
|
|
46
|
+
|
|
47
|
+
**1. Install:**
|
|
21
48
|
|
|
22
49
|
```bash
|
|
23
50
|
pnpm add includio-cms
|
|
51
|
+
pnpm dlx includio install-peers
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**2. Configure environment:**
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cp node_modules/includio-cms/.env.example .env
|
|
58
|
+
# edit .env — set DATABASE_URL and BETTER_AUTH_SECRET
|
|
24
59
|
```
|
|
25
60
|
|
|
26
|
-
|
|
61
|
+
Generate a secret:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
openssl rand -hex 32
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**3. Create `src/lib/cms/cms.config.ts`:**
|
|
68
|
+
|
|
69
|
+
```ts
|
|
27
70
|
import { defineConfig } from 'includio-cms/sveltekit';
|
|
28
71
|
import { pg } from 'includio-cms/db-postgres';
|
|
29
72
|
import { local } from 'includio-cms/files-local';
|
|
30
73
|
|
|
31
74
|
export default defineConfig({
|
|
32
75
|
languages: ['en'],
|
|
33
|
-
db: pg({ databaseUrl: process.env.DATABASE_URL }),
|
|
76
|
+
db: pg({ databaseUrl: process.env.DATABASE_URL! }),
|
|
34
77
|
files: local(),
|
|
35
|
-
|
|
78
|
+
auth: { secret: process.env.BETTER_AUTH_SECRET! },
|
|
79
|
+
collections: []
|
|
36
80
|
});
|
|
37
81
|
```
|
|
38
82
|
|
|
39
|
-
|
|
83
|
+
**4. Scaffold admin routes + create a user:**
|
|
40
84
|
|
|
41
|
-
|
|
85
|
+
```bash
|
|
86
|
+
pnpm dlx includio scaffold admin
|
|
87
|
+
pnpm dlx includio create-user
|
|
88
|
+
```
|
|
42
89
|
|
|
43
|
-
|
|
90
|
+
**5. Run the dev server:**
|
|
44
91
|
|
|
45
|
-
|
|
92
|
+
```bash
|
|
93
|
+
pnpm dev
|
|
94
|
+
```
|
|
46
95
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
96
|
+
Open `http://localhost:5173/admin` and sign in with the user you just created.
|
|
97
|
+
|
|
98
|
+
> Need more? Full reference in [`DOCS.md`](./DOCS.md). API surface in [`API.md`](./API.md).
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
`defineConfig({ ... })` is your single source of truth. The shape (abridged):
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
defineConfig({
|
|
106
|
+
languages: ['en', 'pl'], // first entry is the default locale
|
|
107
|
+
db, files, // required adapters
|
|
108
|
+
email, ai, // optional adapters
|
|
109
|
+
auth: { secret }, // required for /admin
|
|
110
|
+
collections: [postsCollection],
|
|
111
|
+
singles: [siteSettings],
|
|
112
|
+
forms: [contactForm],
|
|
113
|
+
apiKeys: [{ key: '...', role: 'admin' }],
|
|
114
|
+
media: { /* sharp / video / maintenance options */ },
|
|
115
|
+
typography: { fixOrphans: true },
|
|
116
|
+
shop, cmp // optional modules
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Invalid configs throw immediately at startup with every issue listed:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
ConfigValidationError: CMSConfig validation failed (2 issues):
|
|
124
|
+
- languages[0]: must be a 2-letter ISO code (e.g. 'en' or 'pl-PL')
|
|
125
|
+
- collections[1].slug: duplicate collection slug 'posts'
|
|
126
|
+
— Hint: each collection/single/form must have a unique `slug`
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
See [`DOCS.md`](./DOCS.md) for every field type and option.
|
|
130
|
+
|
|
131
|
+
## Adapters
|
|
132
|
+
|
|
133
|
+
`includio-cms` ships four pluggable contracts. Each has a default implementation; bring your own when you need to.
|
|
134
|
+
|
|
135
|
+
| Contract | Default | Lazy peer dep |
|
|
136
|
+
|--------------------|------------------------|------------------------------|
|
|
137
|
+
| `DatabaseAdapter` | `includio-cms/db-postgres` | `postgres`, `drizzle-orm` |
|
|
138
|
+
| `FilesAdapter` | `includio-cms/files-local` | — |
|
|
139
|
+
| `EmailAdapter` | `includio-cms/email-nodemailer` | `nodemailer` |
|
|
140
|
+
| `AIAdapter` | `includio-cms/ai-openai`, `includio-cms/ai-claude` | `openai`, `@anthropic-ai/sdk` |
|
|
51
141
|
|
|
52
142
|
Source of truth: [`src/lib/types/adapters/`](src/lib/types/adapters/).
|
|
53
143
|
|
|
54
|
-
###
|
|
144
|
+
### Writing your own adapter
|
|
145
|
+
|
|
146
|
+
Each contract is a TypeScript interface — implement every required method (optionals are marked `?`). TypeScript will enforce the surface.
|
|
55
147
|
|
|
56
148
|
```ts
|
|
57
149
|
import type { DatabaseAdapter } from 'includio-cms/types';
|
|
@@ -68,7 +160,7 @@ export function myDbAdapter(config: { connection: string }): DatabaseAdapter {
|
|
|
68
160
|
createEntryVersion: async (data) => { /* ... */ },
|
|
69
161
|
updateEntryVersion: async (data) => { /* ... */ },
|
|
70
162
|
getEntryVersions: async (data) => { /* ... */ },
|
|
71
|
-
deleteEntryVersion: async (data) => { /* ... */ }
|
|
163
|
+
deleteEntryVersion: async (data) => { /* ... */ }
|
|
72
164
|
// ...form submissions, media files, image/video styles, tags, consent logs...
|
|
73
165
|
};
|
|
74
166
|
}
|
|
@@ -90,25 +182,39 @@ Reference implementation: [`src/lib/db-postgres/index.ts`](src/lib/db-postgres/i
|
|
|
90
182
|
|
|
91
183
|
### Optional peer dependencies
|
|
92
184
|
|
|
93
|
-
The default email + AI adapters depend on third-party SDKs that **are not installed by default**.
|
|
185
|
+
The default email + AI adapters depend on third-party SDKs that **are not installed by default**. Install the peer once you use the adapter:
|
|
186
|
+
|
|
187
|
+
| Adapter | Required peer | Install |
|
|
188
|
+
|--------------------|---------------------|-------------------------------|
|
|
189
|
+
| `email-nodemailer` | `nodemailer` | `pnpm add nodemailer` |
|
|
190
|
+
| `ai-openai` | `openai` | `pnpm add openai` |
|
|
191
|
+
| `ai-claude` | `@anthropic-ai/sdk` | `pnpm add @anthropic-ai/sdk` |
|
|
192
|
+
|
|
193
|
+
Each SDK is loaded lazily on first call. A missing peer throws a clear error at runtime — install it and the adapter wakes up. `pnpm dlx includio install-peers` automates this for the parts of the CMS you have configured.
|
|
194
|
+
|
|
195
|
+
## CLI
|
|
196
|
+
|
|
197
|
+
The `includio` binary ships with three subcommands. Run any of them with `--help` for details.
|
|
94
198
|
|
|
95
|
-
|
|
|
96
|
-
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
199
|
+
| Command | What it does |
|
|
200
|
+
|--------------------------|-----------------------------------------------------------|
|
|
201
|
+
| `includio scaffold admin`| Generates `/admin` and `/api/admin` route files |
|
|
202
|
+
| `includio install-peers` | Installs missing optional peer SDKs based on your config |
|
|
203
|
+
| `includio create-user` | Creates an admin/user account interactively |
|
|
100
204
|
|
|
101
|
-
|
|
205
|
+
Global flags: `--help, -h` and `--version, -v`.
|
|
102
206
|
|
|
103
207
|
## Links
|
|
104
208
|
|
|
105
|
-
- [Full
|
|
106
|
-
- [
|
|
107
|
-
- [
|
|
209
|
+
- [Full documentation](./DOCS.md)
|
|
210
|
+
- [Public API surface](./API.md)
|
|
211
|
+
- [Changelog](./CHANGELOG.md)
|
|
212
|
+
- [Roadmap](./ROADMAP.md)
|
|
213
|
+
- [Known risks](./KNOWN-RISKS.md)
|
|
108
214
|
|
|
109
|
-
## For AI
|
|
215
|
+
## For AI assistants
|
|
110
216
|
|
|
111
|
-
Full documentation is in [`DOCS.md`](DOCS.md) (shipped with the npm package). When working on a project using includio-cms, read `node_modules/includio-cms/DOCS.md` for the complete API reference, available components, and migration guides.
|
|
217
|
+
Full documentation is in [`DOCS.md`](./DOCS.md) (shipped with the npm package). When working on a project using includio-cms, read `node_modules/includio-cms/DOCS.md` for the complete API reference, available components, and migration guides.
|
|
112
218
|
|
|
113
219
|
## License
|
|
114
220
|
|
package/ROADMAP.md
CHANGED
|
@@ -339,6 +339,10 @@
|
|
|
339
339
|
> 13 faz w ~16 sesjach, droga 0.16 → 1.0.0. Lean scope: stabilizacja, security, testy, docs, audit + polish shopa. Bez nowych feature'ów.
|
|
340
340
|
|
|
341
341
|
- [x] `[breaking]` `[P0]` API surface lock — exports trim 26→15, JSDoc tagi (@public/@internal/@experimental), `API.md` autogenerated (0.20.0) <!-- files: package.json, scripts/generate-api-md.ts -->
|
|
342
|
+
- [x] `[chore]` `[P1]` Faza 6 — Test foundation: docker postgres + integration project + 43 cases (30 REST + 6 shop checkout + 7 webhook signature/idempotency dla PayU/InPost), coverage report <!-- files: tests/integration/rest/, tests/helpers/{rest,shop}.ts, tests/integration/fixtures/shopConfig.ts, tests/setup.ts -->
|
|
343
|
+
- [x] `[chore]` `[P1]` Faza 7 — E2E foundation: Playwright config (storageState reuse, setup project), `db_test_e2e` na 5435, `e2e/setup/global-setup.ts` (drizzle push), `e2e/admin/auth.setup.ts` (sign-up + admin role + login), 4 specy zielone w ~23s: login, collection-crud (blog-post create→edit→archive→permanent-delete), media-upload, user-mgmt (create→delete); data-testid na critical buttons admin <!-- files: playwright.config.ts, e2e/admin/*, src/lib/admin/client/{collection,entry,users}/, src/lib/admin/components/media/file-upload.svelte -->
|
|
344
|
+
- [x] `[chore]` `[P1]` Faza 8 — CI na PR: `.github/workflows/ci.yml` z jobs lint/typecheck/unit/integration (postgres :5434)/e2e (postgres :5435 + playwright cache)/build, pnpm cache, trace artifacts on fail, README CI badge <!-- files: .github/workflows/ci.yml, README.md -->
|
|
345
|
+
- [x] `[breaking]` `[P0]` Faza 9 — DX & config validation (0.22.0): `defineConfig` Zod strict z field path + hint, `CmsError` + `ConfigValidationError` z code/context, resolver throw sites zmigrowane, CLI `--help` per subcommand + `--version`, `.env.example` rozszerzony o `INCLUDIO_*`, README rebuild (TOC + system req + 5-min quickstart), JSDoc body (opis + @param + @returns + @example) na każdym `@public` <!-- files: src/lib/core/errors.ts, src/lib/types/cms.schema.ts, src/lib/sveltekit/config.ts, src/lib/cli/index.ts, README.md, .env.example, src/lib/updates/0.22.0/ -->
|
|
342
346
|
- [ ] `[fix]` `[P1]` Select field — `defaultValue` propagacja do zod schema (full repro: `ideas/post-v1/select-field-defaultvalue-bug.md`); fix planowany w Fazie 12 (RC)
|
|
343
347
|
|
|
344
348
|
## v1.x — Post-v1.0 deferred
|
|
@@ -354,10 +358,13 @@
|
|
|
354
358
|
## Security hardening
|
|
355
359
|
|
|
356
360
|
- [ ] `[feature]` `[P1]` `sanitizeHTML` utility — general HTML sanitization outside richtext (text fields, SEO fields)
|
|
357
|
-
- [
|
|
358
|
-
- [
|
|
359
|
-
- [x] `[feature]` `[P1]` Rate limiting — form submit
|
|
360
|
-
- [x] `[chore]` `[P1]` Security audit — timing attacks, MIME
|
|
361
|
+
- [x] `[feature]` `[P1]` CSP headers — Content-Security-Policy middleware (0.20.0/0.21.0; `unsafe-inline` zaakceptowany — KNOWN-RISKS §1)
|
|
362
|
+
- [x] `[feature]` `[P1]` CSRF protection — origin/referer guard na `/admin/api/*` (0.20.0/0.21.0)
|
|
363
|
+
- [x] `[feature]` `[P1]` Rate limiting — form submit + `/admin/api/*` (form 0.13.3, admin 0.20.0, DRY refactor 0.21.0)
|
|
364
|
+
- [x] `[chore]` `[P1]` Security audit — timing attacks, MIME, demo endpoint, rate limiting, `{@html}` w admin UI, ffmpeg/sharp shell args (0.13.3 + 0.21.0)
|
|
365
|
+
- [x] `[feature]` `[P1]` API key expiry (opt-in `expiresAt`) + `generateApiKey()` helper (0.21.0; pełna rotacja = manual config + redeploy, KNOWN-RISKS §2)
|
|
366
|
+
- [x] `[feature]` `[P1]` Sharp timeout 30s (`withTimeout` + `INCLUDIO_SHARP_TIMEOUT_MS`, 0.21.0)
|
|
367
|
+
- [x] `[chore]` `[P0]` `KNOWN-RISKS.md` — single source of truth dla zaakceptowanych ryzyk v1.0 (0.21.0)
|
|
361
368
|
- [ ] `[chore]` `[P1]` Input sanitization audit — review all fields for XSS
|
|
362
369
|
|
|
363
370
|
## Backlog
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { type RequestHandler } from '@sveltejs/kit';
|
|
2
2
|
/**
|
|
3
|
-
* REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
3
|
+
* REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
4
|
+
* `RequestHandler`s authenticated via the `x-api-key` header. Mount in
|
|
5
|
+
* `src/routes/admin/api/rest/[...restPath]/+server.ts`.
|
|
6
|
+
*
|
|
7
|
+
* @returns An object with `GET`, `POST`, `PUT`, `DELETE` SvelteKit
|
|
8
|
+
* `RequestHandler`s, ready to re-export from a `+server.ts` route.
|
|
4
9
|
* @public
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // src/routes/admin/api/rest/[...restPath]/+server.ts
|
|
13
|
+
* import { createRestApiHandler } from 'includio-cms/admin/remote';
|
|
14
|
+
*
|
|
15
|
+
* export const { GET, POST, PUT, DELETE } = createRestApiHandler();
|
|
16
|
+
* ```
|
|
5
17
|
*/
|
|
6
18
|
export declare function createRestApiHandler(): {
|
|
7
19
|
GET: RequestHandler;
|
|
@@ -50,8 +50,20 @@ function matchRoute(method, path) {
|
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
53
|
+
* REST API handler factory. Returns `{ GET, POST, PUT, DELETE }`
|
|
54
|
+
* `RequestHandler`s authenticated via the `x-api-key` header. Mount in
|
|
55
|
+
* `src/routes/admin/api/rest/[...restPath]/+server.ts`.
|
|
56
|
+
*
|
|
57
|
+
* @returns An object with `GET`, `POST`, `PUT`, `DELETE` SvelteKit
|
|
58
|
+
* `RequestHandler`s, ready to re-export from a `+server.ts` route.
|
|
54
59
|
* @public
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* // src/routes/admin/api/rest/[...restPath]/+server.ts
|
|
63
|
+
* import { createRestApiHandler } from 'includio-cms/admin/remote';
|
|
64
|
+
*
|
|
65
|
+
* export const { GET, POST, PUT, DELETE } = createRestApiHandler();
|
|
66
|
+
* ```
|
|
55
67
|
*/
|
|
56
68
|
export function createRestApiHandler() {
|
|
57
69
|
function handle(method) {
|
|
@@ -14,7 +14,15 @@ function safeEqual(a, b) {
|
|
|
14
14
|
}
|
|
15
15
|
export function validateApiKey(key) {
|
|
16
16
|
const cms = getCMS();
|
|
17
|
-
|
|
17
|
+
const found = cms.apiKeys.find((k) => safeEqual(k.key, key));
|
|
18
|
+
if (!found)
|
|
19
|
+
return null;
|
|
20
|
+
if (found.expiresAt) {
|
|
21
|
+
const exp = Date.parse(found.expiresAt);
|
|
22
|
+
if (Number.isFinite(exp) && exp < Date.now())
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return found;
|
|
18
26
|
}
|
|
19
27
|
export function setSyntheticUser(event, apiKey) {
|
|
20
28
|
const name = apiKey.name || 'api';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a cryptographically random API key (32 bytes, base64url-encoded).
|
|
3
|
+
* Use the result for `ApiKeyConfig.key`. Full rotation requires updating
|
|
4
|
+
* `cms.config.ts` and redeploying — see `KNOWN-RISKS.md` §2.
|
|
5
|
+
*
|
|
6
|
+
* @returns A 43-character base64url string (no padding).
|
|
7
|
+
* @public
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { generateApiKey } from 'includio-cms/admin/remote';
|
|
11
|
+
*
|
|
12
|
+
* const key = generateApiKey();
|
|
13
|
+
* console.log(key); // 'A1b2C3...xYz'
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateApiKey(): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a cryptographically random API key (32 bytes, base64url-encoded).
|
|
4
|
+
* Use the result for `ApiKeyConfig.key`. Full rotation requires updating
|
|
5
|
+
* `cms.config.ts` and redeploying — see `KNOWN-RISKS.md` §2.
|
|
6
|
+
*
|
|
7
|
+
* @returns A 43-character base64url string (no padding).
|
|
8
|
+
* @public
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { generateApiKey } from 'includio-cms/admin/remote';
|
|
12
|
+
*
|
|
13
|
+
* const key = generateApiKey();
|
|
14
|
+
* console.log(key); // 'A1b2C3...xYz'
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function generateApiKey() {
|
|
18
|
+
return randomBytes(32).toString('base64url');
|
|
19
|
+
}
|
|
@@ -897,7 +897,7 @@
|
|
|
897
897
|
>
|
|
898
898
|
<AlertDialog.Footer>
|
|
899
899
|
<AlertDialog.Cancel>{lang[interfaceLanguage.current].cancel}</AlertDialog.Cancel>
|
|
900
|
-
<AlertDialog.Action onclick={confirmDelete}
|
|
900
|
+
<AlertDialog.Action onclick={confirmDelete} data-testid="confirm-delete-entry"
|
|
901
901
|
>{lang[interfaceLanguage.current].delete}</AlertDialog.Action
|
|
902
902
|
>
|
|
903
903
|
</AlertDialog.Footer>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<h3 class="text-lg font-bold text-foreground mb-2">{title}</h3>
|
|
21
21
|
<p class="text-sm text-muted-foreground max-w-[360px] mb-6">{description}</p>
|
|
22
22
|
{#if ctaLabel && onCta}
|
|
23
|
-
<Button variant="default" onclick={onCta}>
|
|
23
|
+
<Button variant="default" onclick={onCta} data-testid="create-entry-button">
|
|
24
24
|
<Plus class="size-4 mr-1.5" />
|
|
25
25
|
{ctaLabel}
|
|
26
26
|
</Button>
|