@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.
- package/README.md +343 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# @fydemy/cms
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@fydemy/cms)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](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