@fydemy/cms 1.0.0 → 1.0.1

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.
Files changed (2) hide show
  1. package/README.md +343 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,343 @@
1
+ # @fydemy/cms
2
+
3
+ [![npm version](https://badge.fury.io/js/@fydemy%2Fcms.svg)](https://www.npmjs.com/package/@fydemy/cms)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)
6
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org/)
7
+
8
+ A minimal, secure, file-based CMS for Next.js without database requirements. Store content as markdown files with GitHub integration for production deployments.
9
+
10
+ ## Features
11
+
12
+ - 📝 **File-based Storage** - Markdown files with frontmatter in `/public/content`
13
+ - 🔐 **Secure Authentication** - Timing-safe password comparison, rate limiting, input validation
14
+ - 🚀 **Vercel Compatible** - Deploy without any database setup
15
+ - 🐙 **GitHub Integration** - Automatic file commits in production
16
+ - 📦 **Zero Config** - Minimal setup required
17
+ - 🎯 **TypeScript First** - Full type safety with comprehensive type definitions
18
+ - ⚡ **Lightweight** - Small bundle size (~30KB), minimal dependencies
19
+ - 🛡️ **Security Hardened** - Built with security best practices
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install @fydemy/cms
25
+ # or
26
+ pnpm add @fydemy/cms
27
+ # or
28
+ yarn add @fydemy/cms
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ### 1. Initialize the CMS
34
+
35
+ Create a script to initialize your content directory:
36
+
37
+ ```typescript
38
+ // scripts/init-cms.ts
39
+ import { initCMS } from "@fydemy/cms";
40
+
41
+ initCMS();
42
+ ```
43
+
44
+ Run it:
45
+
46
+ ```bash
47
+ npx tsx scripts/init-cms.ts
48
+ ```
49
+
50
+ ### 2. Set Environment Variables
51
+
52
+ Create `.env.local`:
53
+
54
+ ```env
55
+ # Required for authentication
56
+ CMS_ADMIN_USERNAME=admin
57
+ CMS_ADMIN_PASSWORD=your_secure_password
58
+ CMS_SESSION_SECRET=your-secret-key-must-be-at-least-32-characters-long
59
+
60
+ # Optional: For production (GitHub integration)
61
+ GITHUB_TOKEN=ghp_your_github_token
62
+ GITHUB_REPO=username/repository
63
+ GITHUB_BRANCH=main
64
+ ```
65
+
66
+ > **Security Note**: Use strong passwords and keep `CMS_SESSION_SECRET` at least 32 characters long.
67
+
68
+ ### 3. Set Up API Routes
69
+
70
+ Create the following API routes in your Next.js app:
71
+
72
+ **`app/api/cms/login/route.ts`**
73
+
74
+ ```typescript
75
+ import { handleLogin } from "@fydemy/cms";
76
+ export { handleLogin as POST };
77
+ ```
78
+
79
+ **`app/api/cms/logout/route.ts`**
80
+
81
+ ```typescript
82
+ import { handleLogout } from "@fydemy/cms";
83
+ export { handleLogout as POST };
84
+ ```
85
+
86
+ **`app/api/cms/content/[...path]/route.ts`**
87
+
88
+ ```typescript
89
+ import { createContentApiHandlers } from "@fydemy/cms";
90
+
91
+ const handlers = createContentApiHandlers();
92
+ export const GET = handlers.GET;
93
+ export const POST = handlers.POST;
94
+ export const DELETE = handlers.DELETE;
95
+ ```
96
+
97
+ **`app/api/cms/list/[[...path]]/route.ts`**
98
+
99
+ ```typescript
100
+ import { createListApiHandlers } from "@fydemy/cms";
101
+
102
+ const handlers = createListApiHandlers();
103
+ export const GET = handlers.GET;
104
+ ```
105
+
106
+ ### 4. Add Middleware
107
+
108
+ **`middleware.ts`** (root of your project)
109
+
110
+ ```typescript
111
+ import { createAuthMiddleware } from "@fydemy/cms";
112
+
113
+ export const middleware = createAuthMiddleware({
114
+ loginPath: "/admin/login",
115
+ protectedPaths: ["/admin"],
116
+ });
117
+
118
+ export const config = {
119
+ matcher: ["/admin/:path*"],
120
+ };
121
+ ```
122
+
123
+ ### 5. Read Content in Your App
124
+
125
+ ```typescript
126
+ import { getMarkdownContent } from "@fydemy/cms";
127
+
128
+ export default async function BlogPost({
129
+ params,
130
+ }: {
131
+ params: { slug: string };
132
+ }) {
133
+ const post = await getMarkdownContent(`${params.slug}.md`);
134
+
135
+ return (
136
+ <article>
137
+ <h1>{post.data.title}</h1>
138
+ <p>{post.data.description}</p>
139
+ <div>{post.content}</div>
140
+ </article>
141
+ );
142
+ }
143
+ ```
144
+
145
+ ## Security Features
146
+
147
+ ### Built-in Security
148
+
149
+ - **Timing-Safe Authentication**: Uses `crypto.timingSafeEqual` to prevent timing attacks
150
+ - **Rate Limiting**: 5 login attempts per 15 minutes per IP address
151
+ - **Input Validation**: All inputs validated and sanitized
152
+ - **Path Validation**: Prevents directory traversal attacks
153
+ - **File Size Limits**: Default 10MB maximum file size
154
+ - **Secure Sessions**: httpOnly, sameSite, and secure cookies in production
155
+ - **No Username Enumeration**: Generic error messages
156
+
157
+ ### Security Best Practices
158
+
159
+ 1. **Strong Credentials**: Use strong, unique passwords for `CMS_ADMIN_PASSWORD`
160
+ 2. **Secret Management**: Keep `CMS_SESSION_SECRET` at least 32 characters
161
+ 3. **GitHub Token Security**: Use minimal permissions (only `repo` scope)
162
+ 4. **HTTPS Only**: Always use HTTPS in production
163
+ 5. **Regular Updates**: Keep dependencies up to date
164
+ 6. **Environment Variables**: Never commit `.env` files
165
+
166
+ For more security information, see [SECURITY.md](./SECURITY.md).
167
+
168
+ ## API Reference
169
+
170
+ ### Content Management
171
+
172
+ ```typescript
173
+ // Read markdown file
174
+ const content = await getMarkdownContent("blog/post.md");
175
+ // Returns: { data: {...}, content: "..." }
176
+
177
+ // Write markdown file
178
+ await saveMarkdownContent(
179
+ "blog/post.md",
180
+ { title: "My Post", date: "2024-01-01" },
181
+ "# Hello World"
182
+ );
183
+
184
+ // Delete file
185
+ await deleteMarkdownContent("blog/post.md");
186
+
187
+ // List files
188
+ const files = await listMarkdownFiles("blog");
189
+ // Returns: ['blog/post1.md', 'blog/post2.md']
190
+
191
+ // Check if file exists
192
+ const exists = await markdownFileExists("blog/post.md");
193
+ ```
194
+
195
+ ### Parsing Utilities
196
+
197
+ ```typescript
198
+ import { parseMarkdown, stringifyMarkdown } from "@fydemy/cms";
199
+
200
+ // Parse markdown string
201
+ const { data, content } = parseMarkdown(rawMarkdown);
202
+
203
+ // Convert to markdown
204
+ const markdown = stringifyMarkdown({ title: "Post" }, "Content here");
205
+ ```
206
+
207
+ ### Authentication
208
+
209
+ ```typescript
210
+ import { validateCredentials, createSession } from "@fydemy/cms";
211
+
212
+ // Validate credentials
213
+ const isValid = validateCredentials("admin", "password");
214
+
215
+ // Create session (returns JWT)
216
+ const token = await createSession("admin");
217
+ ```
218
+
219
+ ### Validation Utilities
220
+
221
+ ```typescript
222
+ import {
223
+ validateFilePath,
224
+ validateUsername,
225
+ validatePassword,
226
+ sanitizeFrontmatter,
227
+ } from "@fydemy/cms";
228
+
229
+ // Validate file path (prevents directory traversal)
230
+ const safePath = validateFilePath("blog/post.md");
231
+
232
+ // Validate username
233
+ validateUsername("admin"); // throws if invalid
234
+
235
+ // Sanitize frontmatter data
236
+ const safe = sanitizeFrontmatter({ title: "Test", script: "<script>" });
237
+ ```
238
+
239
+ ## Storage
240
+
241
+ ### Development
242
+
243
+ Files are stored locally in `/public/content` directory.
244
+
245
+ ### Production
246
+
247
+ When `NODE_ENV=production` and `GITHUB_TOKEN` is set, all file operations are performed via GitHub API, creating commits directly to your repository.
248
+
249
+ ## Environment Variables
250
+
251
+ | Variable | Required | Description |
252
+ | -------------------- | ---------- | ------------------------------- |
253
+ | `CMS_ADMIN_USERNAME` | Yes | Admin username |
254
+ | `CMS_ADMIN_PASSWORD` | Yes | Admin password |
255
+ | `CMS_SESSION_SECRET` | Yes | JWT secret (min 32 chars) |
256
+ | `GITHUB_TOKEN` | Production | GitHub personal access token |
257
+ | `GITHUB_REPO` | Production | Repository (format: owner/repo) |
258
+ | `GITHUB_BRANCH` | Production | Branch name (default: main) |
259
+
260
+ ## GitHub Setup
261
+
262
+ 1. Create a GitHub Personal Access Token with `repo` permissions
263
+ 2. Add the token to your environment variables
264
+ 3. Deploy to Vercel and configure the environment variables
265
+
266
+ ## FAQ
267
+
268
+ ### Is this suitable for production?
269
+
270
+ Yes! The package includes security hardening, rate limiting, and has been tested for production use. Make sure to follow security best practices.
271
+
272
+ ### Can I use this with other frameworks?
273
+
274
+ This package is designed for Next.js App Router (13+). For other frameworks, you can use the core utilities but will need to implement your own API routes.
275
+
276
+ ### How do I customize the file size limit?
277
+
278
+ ```typescript
279
+ import { MAX_FILE_SIZE } from "@fydemy/cms";
280
+ // Default is 10MB, you can check this constant
281
+ ```
282
+
283
+ To change it, you'll need to implement your own validation layer.
284
+
285
+ ### Does it support images?
286
+
287
+ Yes! The package includes file upload functionality. Images can be uploaded and stored in `/public/uploads` (local) or via GitHub API (production).
288
+
289
+ ### How do I backup my content?
290
+
291
+ Since content is stored in your GitHub repository (in production), it's automatically backed up with full version history. In development, the `/public/content` directory can be committed to git.
292
+
293
+ ### What about rate limiting in production?
294
+
295
+ The built-in rate limiter is memory-based and resets on server restart. For production with multiple instances, consider implementing Redis-based rate limiting.
296
+
297
+ ### Can I add more admin users?
298
+
299
+ Currently, the package supports a single admin user via environment variables. For multi-user support, you'd need to implement a custom authentication layer.
300
+
301
+ ## Example Admin UI
302
+
303
+ Check the `/apps/dev` directory in this repository for a complete example with:
304
+
305
+ - Login page
306
+ - Admin dashboard
307
+ - File editor
308
+ - File management
309
+
310
+ ## Troubleshooting
311
+
312
+ ### "CMS_SESSION_SECRET must be at least 32 characters"
313
+
314
+ Make sure your session secret is long enough. Generate a secure random string:
315
+
316
+ ```bash
317
+ node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
318
+ ```
319
+
320
+ ### Rate limiting not working across restarts
321
+
322
+ The rate limiter is in-memory. For persistent rate limiting, implement Redis storage.
323
+
324
+ ### GitHub API rate limits
325
+
326
+ GitHub API has rate limits. For high-traffic sites, consider caching content or using a CDN.
327
+
328
+ ## License
329
+
330
+ MIT
331
+
332
+ ## Contributing
333
+
334
+ Contributions welcome! This is a minimal CMS focused on simplicity and maintainability.
335
+
336
+ Please report security vulnerabilities privately to fydemy@gmail.com or via GitHub security advisories.
337
+
338
+ ## Links
339
+
340
+ - [GitHub Repository](https://github.com/fydemy/cms)
341
+ - [npm Package](https://www.npmjs.com/package/@fydemy/cms)
342
+ - [Report Issues](https://github.com/fydemy/cms/issues)
343
+ - [Security Policy](https://github.com/fydemy/cms/blob/main/SECURITY.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fydemy/cms",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Simple file-based CMS for Next.js without database - with GitHub integration for production",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",