@fydemy/cms 1.0.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/CHANGELOG.md +50 -0
- package/LICENSE +21 -0
- package/dist/index.d.mts +431 -0
- package/dist/index.d.ts +431 -0
- package/dist/index.js +934 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +862 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/rate-limit.ts","../src/content/markdown.ts","../src/content/storage.ts","../src/utils/validation.ts","../src/content/directory.ts","../src/content/collection.ts","../src/content/upload.ts","../src/auth/session.ts","../src/auth/login.ts","../src/api/handlers.ts","../src/api/upload.ts","../src/middleware/auth.ts","../src/init/setup.ts","../src/index.ts"],"sourcesContent":["/**\n * Simple in-memory rate limiter for login attempts\n */\n\ninterface RateLimitEntry {\n count: number;\n resetTime: number;\n}\n\nconst rateLimitStore = new Map<string, RateLimitEntry>();\n\n// Configuration\nconst MAX_ATTEMPTS = 5; // Maximum login attempts\nconst WINDOW_MS = 15 * 60 * 1000; // 15 minutes\n\n/**\n * Check if IP/identifier is rate limited\n * @param identifier - IP address or other unique identifier\n * @returns Object with isLimited and remaining attempts\n */\nexport function checkRateLimit(identifier: string): {\n isLimited: boolean;\n remaining: number;\n resetTime: number;\n} {\n const now = Date.now();\n const entry = rateLimitStore.get(identifier);\n\n // No entry or expired entry\n if (!entry || now > entry.resetTime) {\n rateLimitStore.set(identifier, {\n count: 0,\n resetTime: now + WINDOW_MS,\n });\n\n return {\n isLimited: false,\n remaining: MAX_ATTEMPTS,\n resetTime: now + WINDOW_MS,\n };\n }\n\n // Check if limit exceeded\n if (entry.count >= MAX_ATTEMPTS) {\n return {\n isLimited: true,\n remaining: 0,\n resetTime: entry.resetTime,\n };\n }\n\n return {\n isLimited: false,\n remaining: MAX_ATTEMPTS - entry.count,\n resetTime: entry.resetTime,\n };\n}\n\n/**\n * Increment rate limit counter for identifier\n * @param identifier - IP address or other unique identifier\n */\nexport function incrementRateLimit(identifier: string): void {\n const now = Date.now();\n const entry = rateLimitStore.get(identifier);\n\n if (!entry || now > entry.resetTime) {\n rateLimitStore.set(identifier, {\n count: 1,\n resetTime: now + WINDOW_MS,\n });\n } else {\n entry.count++;\n }\n}\n\n/**\n * Reset rate limit for identifier (use after successful login)\n * @param identifier - IP address or other unique identifier\n */\nexport function resetRateLimit(identifier: string): void {\n rateLimitStore.delete(identifier);\n}\n\n/**\n * Clean up expired entries (should be called periodically)\n */\nexport function cleanupRateLimitStore(): void {\n const now = Date.now();\n for (const [identifier, entry] of rateLimitStore.entries()) {\n if (now > entry.resetTime) {\n rateLimitStore.delete(identifier);\n }\n }\n}\n\n// Cleanup expired entries every 5 minutes\nif (typeof setInterval !== \"undefined\") {\n setInterval(cleanupRateLimitStore, 5 * 60 * 1000);\n}\n","import matter from \"gray-matter\";\nimport { getStorageProvider } from \"./storage\";\nimport {\n validateFilePath,\n sanitizeFrontmatter,\n MAX_FILE_SIZE,\n} from \"../utils/validation\";\n\nexport interface MarkdownData {\n content: string;\n data: Record<string, any>;\n}\n\n/**\n * Parse markdown content with frontmatter\n * @param rawContent - Raw markdown content to parse\n * @returns Parsed markdown data with frontmatter and content\n */\nexport function parseMarkdown(rawContent: string): MarkdownData {\n const { data, content } = matter(rawContent);\n return { data, content };\n}\n\n/**\n * Convert data and content back to markdown with frontmatter\n */\nexport function stringifyMarkdown(\n data: Record<string, any>,\n content: string\n): string {\n return matter.stringify(content, data);\n}\n\n/**\n * Read and parse a markdown file\n * @param filePath - Path to the markdown file\n * @returns Parsed markdown data\n * @throws Error if file path is invalid or file is too large\n */\nexport async function getMarkdownContent(\n filePath: string\n): Promise<MarkdownData> {\n // Validate file path\n const validatedPath = validateFilePath(filePath);\n\n const storage = getStorageProvider();\n const rawContent = await storage.readFile(validatedPath);\n\n // Check file size\n const size = Buffer.byteLength(rawContent, \"utf-8\");\n if (size > MAX_FILE_SIZE) {\n throw new Error(\n `File size (${size} bytes) exceeds maximum allowed size (${MAX_FILE_SIZE} bytes)`\n );\n }\n\n return parseMarkdown(rawContent);\n}\n\n/**\n * Write markdown file with frontmatter\n * @param filePath - Path to the markdown file\n * @param data - Frontmatter data object\n * @param content - Markdown content\n * @throws Error if file path is invalid or content is too large\n */\nexport async function saveMarkdownContent(\n filePath: string,\n data: Record<string, any>,\n content: string\n): Promise<void> {\n // Validate file path\n const validatedPath = validateFilePath(filePath);\n\n // Sanitize frontmatter\n const sanitizedData = sanitizeFrontmatter(data);\n\n const markdown = stringifyMarkdown(sanitizedData, content);\n\n // Check size before writing\n const size = Buffer.byteLength(markdown, \"utf-8\");\n if (size > MAX_FILE_SIZE) {\n throw new Error(\n `Content size (${size} bytes) exceeds maximum allowed size (${MAX_FILE_SIZE} bytes)`\n );\n }\n\n const storage = getStorageProvider();\n await storage.writeFile(validatedPath, markdown);\n}\n\n/**\n * Delete a markdown file\n * @param filePath - Path to the markdown file\n * @throws Error if file path is invalid\n */\nexport async function deleteMarkdownContent(filePath: string): Promise<void> {\n // Validate file path\n const validatedPath = validateFilePath(filePath);\n\n const storage = getStorageProvider();\n await storage.deleteFile(validatedPath);\n}\n\n/**\n * List all markdown files in a directory\n * @param directory - Directory path (optional, defaults to root)\n * @returns Array of file paths\n * @throws Error if directory path is invalid\n */\nexport async function listMarkdownFiles(\n directory: string = \"\"\n): Promise<string[]> {\n // Validate directory path if provided\n const validatedDir = directory ? validateFilePath(directory) : \"\";\n\n const storage = getStorageProvider();\n const entries = await storage.listFiles(validatedDir);\n return entries\n .filter((entry) => entry.type === \"file\")\n .map((entry) => entry.path);\n}\n\n/**\n * Check if a markdown file exists\n * @param filePath - Path to the markdown file\n * @returns True if file exists, false otherwise\n * @throws Error if file path is invalid\n */\nexport async function markdownFileExists(filePath: string): Promise<boolean> {\n // Validate file path\n const validatedPath = validateFilePath(filePath);\n\n const storage = getStorageProvider();\n return storage.exists(validatedPath);\n}\n","import fs from \"fs/promises\";\nimport path from \"path\";\nimport { Octokit } from \"@octokit/rest\";\n\n/**\n * Represents a file or directory entry\n */\nexport interface FileEntry {\n /** Relative path from base content directory */\n path: string;\n /** File or directory name */\n name: string;\n /** Type of entry */\n type: \"file\" | \"directory\";\n}\n\n/**\n * Interface for file storage operations\n */\nexport interface StorageProvider {\n /** Read file content as string */\n readFile(filePath: string): Promise<string>;\n /** Write content to file, creating parent directories if needed */\n writeFile(filePath: string, content: string): Promise<void>;\n /** Delete a file */\n deleteFile(filePath: string): Promise<void>;\n /** List files in a directory */\n listFiles(directory: string): Promise<FileEntry[]>;\n /** Check if a file exists */\n exists(filePath: string): Promise<boolean>;\n /** Upload a binary file */\n uploadFile(filePath: string, buffer: Buffer): Promise<string>; // Returns public URL\n}\n\n/**\n * Local file system storage (for development)\n */\nexport class LocalStorage implements StorageProvider {\n private baseDir: string;\n\n constructor(baseDir: string = \"public/content\") {\n this.baseDir = baseDir;\n }\n\n private getFullPath(filePath: string): string {\n return path.join(process.cwd(), this.baseDir, filePath);\n }\n\n async readFile(filePath: string): Promise<string> {\n const fullPath = this.getFullPath(filePath);\n return fs.readFile(fullPath, \"utf-8\");\n }\n\n async writeFile(filePath: string, content: string): Promise<void> {\n const fullPath = this.getFullPath(filePath);\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\n await fs.writeFile(fullPath, content, \"utf-8\");\n }\n\n async deleteFile(filePath: string): Promise<void> {\n const fullPath = this.getFullPath(filePath);\n await fs.unlink(fullPath);\n }\n\n async listFiles(directory: string): Promise<FileEntry[]> {\n const fullPath = this.getFullPath(directory);\n try {\n const entries = await fs.readdir(fullPath, { withFileTypes: true });\n return entries\n .map((entry) => ({\n path: path.join(directory, entry.name),\n name: entry.name,\n type: entry.isDirectory()\n ? (\"directory\" as const)\n : (\"file\" as const),\n }))\n .filter(\n (entry) => entry.type === \"directory\" || entry.name.endsWith(\".md\")\n );\n } catch (error) {\n return [];\n }\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n const fullPath = this.getFullPath(filePath);\n await fs.access(fullPath);\n return true;\n } catch {\n return false;\n }\n }\n\n async uploadFile(filePath: string, buffer: Buffer): Promise<string> {\n // Store uploads in /public/uploads directory\n const uploadPath = path.join(process.cwd(), \"public\", \"uploads\", filePath);\n await fs.mkdir(path.dirname(uploadPath), { recursive: true });\n await fs.writeFile(uploadPath, buffer);\n\n // Return public URL path\n return `/uploads/${filePath}`;\n }\n}\n\n/**\n * GitHub storage (for production)\n */\nexport class GitHubStorage implements StorageProvider {\n private octokit: Octokit;\n private owner: string;\n private repo: string;\n private branch: string;\n private baseDir: string;\n\n constructor() {\n const token = process.env.GITHUB_TOKEN;\n const repoInfo = process.env.GITHUB_REPO;\n\n if (!token || !repoInfo) {\n throw new Error(\n \"GITHUB_TOKEN and GITHUB_REPO must be set for production\"\n );\n }\n\n const [owner, repo] = repoInfo.split(\"/\");\n if (!owner || !repo) {\n throw new Error('GITHUB_REPO must be in format \"owner/repo\"');\n }\n\n this.octokit = new Octokit({ auth: token });\n this.owner = owner;\n this.repo = repo;\n this.branch = process.env.GITHUB_BRANCH || \"main\";\n this.baseDir = \"public/content\";\n }\n\n private getGitHubPath(filePath: string): string {\n return `${this.baseDir}/${filePath}`;\n }\n\n async readFile(filePath: string): Promise<string> {\n const { data } = await this.octokit.repos.getContent({\n owner: this.owner,\n repo: this.repo,\n path: this.getGitHubPath(filePath),\n ref: this.branch,\n });\n\n if (\"content\" in data) {\n return Buffer.from(data.content, \"base64\").toString(\"utf-8\");\n }\n throw new Error(\"File not found\");\n }\n\n async writeFile(filePath: string, content: string): Promise<void> {\n const githubPath = this.getGitHubPath(filePath);\n let sha: string | undefined;\n\n // Try to get existing file SHA\n try {\n const { data } = await this.octokit.repos.getContent({\n owner: this.owner,\n repo: this.repo,\n path: githubPath,\n ref: this.branch,\n });\n if (\"sha\" in data) {\n sha = data.sha;\n }\n } catch (error) {\n // File doesn't exist, that's ok\n }\n\n await this.octokit.repos.createOrUpdateFileContents({\n owner: this.owner,\n repo: this.repo,\n path: githubPath,\n message: `Update ${filePath}`,\n content: Buffer.from(content).toString(\"base64\"),\n branch: this.branch,\n sha,\n });\n }\n\n async deleteFile(filePath: string): Promise<void> {\n const githubPath = this.getGitHubPath(filePath);\n\n // Get file SHA\n const { data } = await this.octokit.repos.getContent({\n owner: this.owner,\n repo: this.repo,\n path: githubPath,\n ref: this.branch,\n });\n\n if (\"sha\" in data) {\n await this.octokit.repos.deleteFile({\n owner: this.owner,\n repo: this.repo,\n path: githubPath,\n message: `Delete ${filePath}`,\n sha: data.sha,\n branch: this.branch,\n });\n }\n }\n\n async listFiles(directory: string): Promise<FileEntry[]> {\n const githubPath = this.getGitHubPath(directory);\n\n try {\n const { data } = await this.octokit.repos.getContent({\n owner: this.owner,\n repo: this.repo,\n path: githubPath,\n ref: this.branch,\n });\n\n if (Array.isArray(data)) {\n return data\n .map((item) => ({\n path: path.join(directory, item.name),\n name: item.name,\n type:\n item.type === \"dir\" ? (\"directory\" as const) : (\"file\" as const),\n }))\n .filter(\n (entry) => entry.type === \"directory\" || entry.name.endsWith(\".md\")\n );\n }\n } catch (error) {\n // Directory doesn't exist\n }\n\n return [];\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n await this.octokit.repos.getContent({\n owner: this.owner,\n repo: this.repo,\n path: this.getGitHubPath(filePath),\n ref: this.branch,\n });\n return true;\n } catch {\n return false;\n }\n }\n\n async uploadFile(filePath: string, buffer: Buffer): Promise<string> {\n // Store uploads in public/uploads directory in GitHub\n const uploadPath = `public/uploads/${filePath}`;\n\n await this.octokit.repos.createOrUpdateFileContents({\n owner: this.owner,\n repo: this.repo,\n path: uploadPath,\n message: `Upload file: ${filePath}`,\n content: buffer.toString(\"base64\"),\n branch: this.branch,\n });\n\n // Return public URL path\n return `/uploads/${filePath}`;\n }\n}\n\n/**\n * Get the appropriate storage provider based on environment\n */\nexport function getStorageProvider(): StorageProvider {\n if (process.env.NODE_ENV === \"production\" && process.env.GITHUB_TOKEN) {\n return new GitHubStorage();\n }\n return new LocalStorage();\n}\n","import path from \"path\";\n\n/**\n * Maximum username length (prevents DoS)\n */\nexport const MAX_USERNAME_LENGTH = 100;\n\n/**\n * Maximum password length (prevents DoS)\n */\nexport const MAX_PASSWORD_LENGTH = 1000;\n\n/**\n * Maximum file size in bytes (10MB)\n */\nexport const MAX_FILE_SIZE = 10 * 1024 * 1024;\n\n/**\n * Validate and sanitize file path to prevent directory traversal\n * @param filePath - The file path to validate\n * @returns Sanitized file path\n * @throws Error if path is invalid or attempts directory traversal\n */\nexport function validateFilePath(filePath: string): string {\n if (!filePath || typeof filePath !== \"string\") {\n throw new Error(\"Invalid file path\");\n }\n\n // Remove any null bytes\n if (filePath.includes(\"\\x00\")) {\n throw new Error(\"Invalid file path: null byte detected\");\n }\n\n // Check for directory traversal attempts BEFORE normalization\n if (filePath.includes(\"..\")) {\n throw new Error(\"Invalid file path: directory traversal detected\");\n }\n\n // Normalize the path\n const normalized = path.normalize(filePath).replace(/^(\\.\\.(\\/|\\\\|$))+/, \"\");\n\n // Check if normalized path is different and contains dangerous patterns\n if (normalized.startsWith(\"/\") || normalized.startsWith(\"\\\\\")) {\n throw new Error(\"Invalid file path: directory traversal detected\");\n }\n\n // Ensure path doesn't contain dangerous patterns\n const dangerousPatterns = [\n /\\.\\./,\n /^[/\\\\]/,\n /[<>:\"|?*]/,\n /\\x00/,\n /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\\..*)?$/i, // Windows reserved names\n ];\n\n for (const pattern of dangerousPatterns) {\n if (pattern.test(normalized)) {\n throw new Error(\"Invalid file path: contains dangerous characters\");\n }\n }\n\n return normalized;\n}\n\n/**\n * Validate username input\n * @param username - The username to validate\n * @returns True if valid\n * @throws Error if invalid\n */\nexport function validateUsername(username: string): boolean {\n if (!username || typeof username !== \"string\") {\n throw new Error(\"Username is required\");\n }\n\n if (username.length > MAX_USERNAME_LENGTH) {\n throw new Error(\n `Username must be ${MAX_USERNAME_LENGTH} characters or less`\n );\n }\n\n // Only allow alphanumeric, underscore, hyphen\n if (!/^[a-zA-Z0-9_-]+$/.test(username)) {\n throw new Error(\n \"Username must contain only letters, numbers, underscores, and hyphens\"\n );\n }\n\n return true;\n}\n\n/**\n * Validate password input\n * @param password - The password to validate\n * @returns True if valid\n * @throws Error if invalid\n */\nexport function validatePassword(password: string): boolean {\n if (!password || typeof password !== \"string\") {\n throw new Error(\"Password is required\");\n }\n\n if (password.length > MAX_PASSWORD_LENGTH) {\n throw new Error(\n `Password must be ${MAX_PASSWORD_LENGTH} characters or less`\n );\n }\n\n return true;\n}\n\n/**\n * Validate file size\n * @param size - File size in bytes\n * @returns True if valid\n * @throws Error if too large\n */\nexport function validateFileSize(size: number): boolean {\n if (size > MAX_FILE_SIZE) {\n throw new Error(\n `File size exceeds maximum allowed size of ${\n MAX_FILE_SIZE / 1024 / 1024\n }MB`\n );\n }\n\n return true;\n}\n\n/**\n * Sanitize frontmatter data to prevent injection\n * @param data - The data object to sanitize\n * @returns Sanitized data\n */\nexport function sanitizeFrontmatter(\n data: Record<string, any>\n): Record<string, any> {\n const sanitized: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(data)) {\n // Validate key\n if (!/^[a-zA-Z0-9_-]+$/.test(key)) {\n continue; // Skip invalid keys\n }\n\n // Sanitize value based on type\n if (typeof value === \"string\") {\n // Remove null bytes\n sanitized[key] = value.replace(/\\x00/g, \"\");\n } else if (\n typeof value === \"number\" ||\n typeof value === \"boolean\" ||\n value === null\n ) {\n sanitized[key] = value;\n } else if (Array.isArray(value)) {\n sanitized[key] = value.map((item) =>\n typeof item === \"string\" ? item.replace(/\\x00/g, \"\") : item\n );\n } else if (typeof value === \"object\") {\n sanitized[key] = sanitizeFrontmatter(value);\n }\n }\n\n return sanitized;\n}\n","import { getStorageProvider, FileEntry } from \"./storage\";\n\n/**\n * List all files and directories in a directory\n * @param directory - Relative path to the directory (default: root)\n * @returns Array of file entries (files and directories)\n */\nexport async function listDirectory(\n directory: string = \"\"\n): Promise<FileEntry[]> {\n const storage = getStorageProvider();\n return storage.listFiles(directory);\n}\n","import { getStorageProvider } from \"./storage\";\nimport { parseMarkdown } from \"./markdown\";\n\n/**\n * Base interface for collection items with flexible frontmatter\n */\nexport interface CollectionItem<T = Record<string, any>> {\n /** The markdown content body */\n content: string;\n /** Frontmatter data with dynamic fields */\n data: T;\n /** File slug (filename without extension) */\n slug: string;\n}\n\n/**\n * Fetch all markdown files from a specific folder/collection\n * @param folderName - The folder name under public/content (e.g., \"blog\", \"pages\")\n * @returns Array of collection items with parsed frontmatter and content\n *\n * @example\n * ```ts\n * // Fetch all blog posts\n * const posts = await getCollectionItems(\"blog\");\n * posts.forEach(post => {\n * console.log(post.data.title); // Access any frontmatter field\n * console.log(post.slug); // Filename without .md\n * });\n *\n * // With type inference for known fields\n * interface BlogPost {\n * title: string;\n * date: string;\n * author?: string;\n * [key: string]: any; // Allow any other fields\n * }\n * const typedPosts = await getCollectionItems<BlogPost>(\"blog\");\n * ```\n */\nexport async function getCollectionItems<T = Record<string, any>>(\n folderName: string\n): Promise<CollectionItem<T>[]> {\n const storage = getStorageProvider();\n\n // List all files in the collection folder\n const entries = await storage.listFiles(folderName);\n\n // Filter for markdown files only\n const mdFiles = entries.filter(\n (entry) => entry.type === \"file\" && entry.path.endsWith(\".md\")\n );\n\n // Read and parse each markdown file\n const items = await Promise.all(\n mdFiles.map(async (file) => {\n const rawContent = await storage.readFile(file.path);\n const { data, content } = parseMarkdown(rawContent);\n\n // Extract slug from filename (remove .md extension)\n const slug = file.path.split(\"/\").pop()!.replace(/\\.md$/, \"\");\n\n return {\n content,\n data: data as T,\n slug,\n };\n })\n );\n\n return items;\n}\n\n/**\n * Fetch a single item from a collection by slug\n * @param folderName - The folder name under public/content\n * @param slug - The filename without .md extension\n * @returns Single collection item or null if not found\n *\n * @example\n * ```ts\n * // Fetch a specific blog post\n * const post = await getCollectionItem(\"blog\", \"my-first-post\");\n * if (post) {\n * console.log(post.data.title);\n * }\n * ```\n */\nexport async function getCollectionItem<T = Record<string, any>>(\n folderName: string,\n slug: string\n): Promise<CollectionItem<T> | null> {\n const storage = getStorageProvider();\n const filePath = `${folderName}/${slug}.md`;\n\n try {\n const exists = await storage.exists(filePath);\n if (!exists) {\n return null;\n }\n\n const rawContent = await storage.readFile(filePath);\n const { data, content } = parseMarkdown(rawContent);\n\n return {\n content,\n data: data as T,\n slug,\n };\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Get all unique folder names (collections) in the content directory\n * @param baseDir - Base directory to scan (default: \"\")\n * @returns Array of folder names\n *\n * @example\n * ```ts\n * const collections = await getCollections();\n * // Returns: [\"blog\", \"pages\", \"docs\", ...]\n * ```\n */\nexport async function getCollections(baseDir: string = \"\"): Promise<string[]> {\n const storage = getStorageProvider();\n const entries = await storage.listFiles(baseDir);\n\n return entries\n .filter((entry) => entry.type === \"directory\")\n .map((entry) => entry.path.split(\"/\").pop()!)\n .filter(Boolean);\n}\n","import { getStorageProvider } from \"./storage\";\n\n/**\n * Upload a file and return its public URL\n * @param fileName - Original filename\n * @param buffer - File content buffer\n * @returns Public URL path to the uploaded file\n */\nexport async function uploadFile(\n fileName: string,\n buffer: Buffer\n): Promise<string> {\n const storage = getStorageProvider();\n\n // Generate unique filename with timestamp to avoid conflicts\n const timestamp = Date.now();\n const ext = fileName.split(\".\").pop();\n const nameWithoutExt = fileName.replace(`.${ext}`, \"\");\n const uniqueFileName = `${nameWithoutExt}-${timestamp}.${ext}`;\n\n return storage.uploadFile(uniqueFileName, buffer);\n}\n","import { SignJWT, jwtVerify } from \"jose\";\nimport { cookies } from \"next/headers\";\n\nconst SESSION_COOKIE_NAME = \"cms-session\";\nconst SESSION_DURATION = 60 * 60 * 24 * 7; // 7 days in seconds\n\n/**\n * Session payload structure for JWT\n */\ninterface SessionPayload {\n /** Username of the authenticated user */\n username: string;\n /** Expiration timestamp (Unix time) */\n exp: number;\n}\n\n/**\n * Get the secret key for JWT signing\n */\nfunction getSecretKey(): Uint8Array {\n const secret = process.env.CMS_SESSION_SECRET;\n if (!secret || secret.length < 32) {\n throw new Error(\"CMS_SESSION_SECRET must be at least 32 characters\");\n }\n return new TextEncoder().encode(secret);\n}\n\n/**\n * Create a new session token for the given username\n * @param username - The username to create a session for\n * @returns Signed JWT string\n */\nexport async function createSession(username: string): Promise<string> {\n const payload: SessionPayload = {\n username,\n exp: Math.floor(Date.now() / 1000) + SESSION_DURATION,\n };\n\n const token = await new SignJWT(payload as any)\n .setProtectedHeader({ alg: \"HS256\" })\n .setExpirationTime(\"7d\")\n .sign(getSecretKey());\n\n return token;\n}\n\n/**\n * Verify and decode a session token\n * @param token - The JWT token to verify\n * @returns Decoded payload if valid, null otherwise\n */\nexport async function verifySession(\n token: string\n): Promise<SessionPayload | null> {\n try {\n const { payload } = await jwtVerify(token, getSecretKey());\n\n // Validate payload structure\n if (\n typeof payload.username === \"string\" &&\n typeof payload.exp === \"number\"\n ) {\n return payload as unknown as SessionPayload;\n }\n\n return null;\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Get session from Next.js request cookies\n */\nexport async function getSessionFromCookies(): Promise<SessionPayload | null> {\n const cookieStore = await cookies();\n const token = cookieStore.get(SESSION_COOKIE_NAME)?.value;\n\n if (!token) {\n return null;\n }\n\n return verifySession(token);\n}\n\n/**\n * Set session cookie\n */\nexport async function setSessionCookie(token: string): Promise<void> {\n const cookieStore = await cookies();\n cookieStore.set(SESSION_COOKIE_NAME, token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === \"production\",\n sameSite: \"lax\",\n maxAge: SESSION_DURATION,\n path: \"/\",\n });\n}\n\n/**\n * Clear session cookie\n */\nexport async function clearSessionCookie(): Promise<void> {\n const cookieStore = await cookies();\n cookieStore.delete(SESSION_COOKIE_NAME);\n}\n","import crypto from \"crypto\";\nimport { validateUsername, validatePassword } from \"../utils/validation\";\n\n/**\n * Validate username and password against environment variables using timing-safe comparison\n * @param username - Username to validate\n * @param password - Password to validate\n * @returns True if credentials are valid, false otherwise\n * @throws Error if environment variables are not configured\n */\nexport function validateCredentials(\n username: string,\n password: string\n): boolean {\n // Validate input format first\n try {\n validateUsername(username);\n validatePassword(password);\n } catch (error) {\n // Return false for invalid input format (don't leak validation details)\n return false;\n }\n\n const envUsername = process.env.CMS_ADMIN_USERNAME;\n const envPassword = process.env.CMS_ADMIN_PASSWORD;\n\n if (!envUsername || !envPassword) {\n throw new Error(\n \"CMS_ADMIN_USERNAME and CMS_ADMIN_PASSWORD must be set in environment variables\"\n );\n }\n\n // Use timing-safe comparison to prevent timing attacks\n // Both comparisons must be done to prevent timing analysis\n const usernameMatch = timingSafeEqual(username, envUsername);\n const passwordMatch = timingSafeEqual(password, envPassword);\n\n return usernameMatch && passwordMatch;\n}\n\n/**\n * Timing-safe string comparison using crypto.timingSafeEqual\n * @param a - First string\n * @param b - Second string\n * @returns True if strings match\n */\nfunction timingSafeEqual(a: string, b: string): boolean {\n try {\n // Convert to buffers with consistent encoding\n const bufferA = Buffer.from(a, \"utf-8\");\n const bufferB = Buffer.from(b, \"utf-8\");\n\n // If lengths differ, compare dummy buffers of same length\n // This prevents length-based timing attacks\n if (bufferA.length !== bufferB.length) {\n const dummyBuffer = Buffer.alloc(bufferA.length);\n crypto.timingSafeEqual(bufferA, dummyBuffer);\n return false;\n }\n\n return crypto.timingSafeEqual(bufferA, bufferB);\n } catch {\n return false;\n }\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { validateCredentials } from \"../auth/login\";\nimport { createSession, getSessionFromCookies } from \"../auth/session\";\nimport {\n getMarkdownContent,\n saveMarkdownContent,\n deleteMarkdownContent,\n} from \"../content/markdown\";\nimport { listDirectory } from \"../content/directory\";\n\n/**\n * Login endpoint with rate limiting\n */\nexport async function handleLogin(request: NextRequest) {\n try {\n // Get IP address for rate limiting\n const ip =\n request.headers.get(\"x-forwarded-for\")?.split(\",\")[0] ||\n request.headers.get(\"x-real-ip\") ||\n \"unknown\";\n\n // Check rate limit\n const { checkRateLimit, incrementRateLimit, resetRateLimit } = await import(\n \"../utils/rate-limit\"\n );\n const rateLimit = checkRateLimit(ip);\n\n if (rateLimit.isLimited) {\n const retryAfter = Math.ceil((rateLimit.resetTime - Date.now()) / 1000);\n return NextResponse.json(\n {\n error: \"Too many login attempts. Please try again later.\",\n retryAfter,\n },\n {\n status: 429,\n headers: {\n \"Retry-After\": retryAfter.toString(),\n },\n }\n );\n }\n\n const body = (await request.json()) as {\n username?: string;\n password?: string;\n };\n const { username, password } = body;\n\n if (!username || !password) {\n return NextResponse.json(\n { error: \"Username and password are required\" },\n { status: 400 }\n );\n }\n\n // Validate credentials\n const isValid = validateCredentials(username, password);\n\n if (!isValid) {\n // Increment rate limit on failed attempt\n incrementRateLimit(ip);\n\n // Generic error message to avoid username enumeration\n return NextResponse.json(\n { error: \"Invalid credentials\" },\n { status: 401 }\n );\n }\n\n // Reset rate limit on successful login\n resetRateLimit(ip);\n\n const token = await createSession(username);\n const response = NextResponse.json({ success: true });\n\n // Set cookie in response\n response.cookies.set(\"cms-session\", token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === \"production\",\n sameSite: \"lax\",\n maxAge: 60 * 60 * 24 * 7, // 7 days\n path: \"/\",\n });\n\n return response;\n } catch (error) {\n console.error(\"Login error:\", error);\n return NextResponse.json(\n { error: \"Authentication service temporarily unavailable\" },\n { status: 500 }\n );\n }\n}\n\n/**\n * Logout endpoint\n */\nexport async function handleLogout() {\n const response = NextResponse.json({ success: true });\n response.cookies.delete(\"cms-session\");\n return response;\n}\n\n/**\n * Get content endpoint\n */\nexport async function handleGetContent(\n _request: NextRequest,\n filePath: string\n) {\n try {\n const session = await getSessionFromCookies();\n if (!session) {\n return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n }\n\n const content = await getMarkdownContent(filePath);\n return NextResponse.json(content);\n } catch (error) {\n console.error(\"Get content error:\", error);\n return NextResponse.json(\n { error: \"Failed to read content\" },\n { status: 500 }\n );\n }\n}\n\n/**\n * Save content endpoint\n */\nexport async function handleSaveContent(\n request: NextRequest,\n filePath: string\n) {\n try {\n const session = await getSessionFromCookies();\n if (!session) {\n return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n }\n\n const body = (await request.json()) as {\n data?: Record<string, any>;\n content?: string;\n };\n const { data, content } = body;\n\n // Content is optional now - default to empty string\n await saveMarkdownContent(filePath, data || {}, content || \"\");\n return NextResponse.json({ success: true });\n } catch (error) {\n console.error(\"Save content error:\", error);\n return NextResponse.json(\n { error: \"Failed to save content\" },\n { status: 500 }\n );\n }\n}\n\n/**\n * Delete content endpoint\n */\nexport async function handleDeleteContent(\n _request: NextRequest,\n filePath: string\n) {\n try {\n const session = await getSessionFromCookies();\n if (!session) {\n return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n }\n\n await deleteMarkdownContent(filePath);\n return NextResponse.json({ success: true });\n } catch (error) {\n console.error(\"Delete content error:\", error);\n return NextResponse.json(\n { error: \"Failed to delete content\" },\n { status: 500 }\n );\n }\n}\n\n/**\n * List files endpoint\n */\nexport async function handleListFiles(directory: string = \"\") {\n try {\n const session = await getSessionFromCookies();\n if (!session) {\n return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n }\n\n const entries = await listDirectory(directory);\n return NextResponse.json({ entries });\n } catch (error) {\n console.error(\"List files error:\", error);\n return NextResponse.json(\n { error: \"Failed to list files\" },\n { status: 500 }\n );\n }\n}\n\n/**\n * Create API route handlers for Next.js App Router\n */\nexport function createContentApiHandlers() {\n return {\n async GET(\n request: NextRequest,\n { params }: { params: { path: string[] } }\n ) {\n const filePath = params.path.join(\"/\");\n return handleGetContent(request, filePath);\n },\n async POST(\n request: NextRequest,\n { params }: { params: { path: string[] } }\n ) {\n const filePath = params.path.join(\"/\");\n return handleSaveContent(request, filePath);\n },\n async DELETE(\n request: NextRequest,\n { params }: { params: { path: string[] } }\n ) {\n const filePath = params.path.join(\"/\");\n return handleDeleteContent(request, filePath);\n },\n };\n}\n\n/**\n * Create list API handlers\n */\nexport function createListApiHandlers() {\n return {\n async GET(\n _request: NextRequest,\n { params }: { params: { path?: string[] } }\n ) {\n const directory = params?.path?.join(\"/\") || \"\";\n return handleListFiles(directory);\n },\n };\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { getSessionFromCookies } from \"../auth/session\";\nimport { uploadFile } from \"../content/upload\";\n\n/**\n * Handle file upload\n */\nexport async function handleUpload(request: NextRequest) {\n try {\n const session = await getSessionFromCookies();\n if (!session) {\n return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n }\n\n const formData = await request.formData();\n const file = formData.get(\"file\") as File;\n\n if (!file) {\n return NextResponse.json({ error: \"No file provided\" }, { status: 400 });\n }\n\n // Convert file to buffer\n const bytes = await file.arrayBuffer();\n const buffer = Buffer.from(bytes);\n\n // Upload file\n const publicUrl = await uploadFile(file.name, buffer);\n\n return NextResponse.json({\n success: true,\n url: publicUrl,\n filename: file.name,\n });\n } catch (error) {\n console.error(\"Upload error:\", error);\n return NextResponse.json(\n { error: \"Failed to upload file\" },\n { status: 500 }\n );\n }\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { getSessionFromCookies } from \"../auth/session\";\n\n/**\n * Create middleware to protect admin routes\n */\nexport function createAuthMiddleware(\n options: {\n loginPath?: string;\n protectedPaths?: string[];\n } = {}\n) {\n const loginPath = options.loginPath || \"/admin/login\";\n const protectedPaths = options.protectedPaths || [\"/admin\"];\n\n return async function middleware(request: NextRequest) {\n const { pathname } = request.nextUrl;\n\n // Skip if not a protected path\n const isProtected = protectedPaths.some((path) =>\n pathname.startsWith(path)\n );\n if (!isProtected) {\n return NextResponse.next();\n }\n\n // Allow login page\n if (pathname === loginPath) {\n return NextResponse.next();\n }\n\n // Check session\n const session = await getSessionFromCookies();\n if (!session) {\n const url = request.nextUrl.clone();\n url.pathname = loginPath;\n url.searchParams.set(\"from\", pathname);\n return NextResponse.redirect(url);\n }\n\n return NextResponse.next();\n };\n}\n","import fs from \"fs/promises\";\nimport path from \"path\";\n\nexport interface InitCMSConfig {\n /** Directory where content is stored (default: \"public/content\") */\n contentDir?: string;\n}\n\n/**\n * Initialize CMS in a Next.js project\n * Creates the content directory and an example markdown file.\n * @param config - Configuration options\n */\nexport async function initCMS(config: InitCMSConfig = {}) {\n const contentDir = config.contentDir || \"public/content\";\n const fullPath = path.join(process.cwd(), contentDir);\n\n // Create content directory\n await fs.mkdir(fullPath, { recursive: true });\n\n // Create example markdown file\n const exampleContent = `---\ntitle: Example Post\ndescription: This is an example markdown file\ndate: ${new Date().toISOString()}\n---\n\n# Welcome to your CMS!\n\nThis is an example markdown file. You can edit or delete it from the admin dashboard.\n\n## Features\n\n- File-based content storage\n- Markdown with frontmatter\n- GitHub integration for production\n- Simple authentication\n- No database required\n`;\n\n const examplePath = path.join(fullPath, \"example.md\");\n await fs.writeFile(examplePath, exampleContent, \"utf-8\");\n\n console.log(\"✅ CMS initialized successfully!\");\n console.log(`📁 Content directory: ${contentDir}`);\n console.log(`📝 Example file created: ${contentDir}/example.md`);\n console.log(\"\");\n console.log(\"Next steps:\");\n console.log(\"1. Set up environment variables in .env.local:\");\n console.log(\" - CMS_ADMIN_USERNAME\");\n console.log(\" - CMS_ADMIN_PASSWORD\");\n console.log(\" - CMS_SESSION_SECRET (min 32 characters)\");\n console.log(\"2. For production: GITHUB_TOKEN, GITHUB_REPO, GITHUB_BRANCH\");\n console.log(\"3. Import and use the CMS utilities in your Next.js app\");\n}\n","// Content management\nexport {\n getMarkdownContent,\n saveMarkdownContent,\n deleteMarkdownContent,\n listMarkdownFiles,\n markdownFileExists,\n parseMarkdown,\n stringifyMarkdown,\n} from \"./content/markdown\";\n\nexport type { MarkdownData } from \"./content/markdown\";\n\nexport { listDirectory } from \"./content/directory\";\n\nexport {\n getCollectionItems,\n getCollectionItem,\n getCollections,\n} from \"./content/collection\";\n\nexport type { CollectionItem } from \"./content/collection\";\n\nexport { uploadFile } from \"./content/upload\";\n\nexport {\n LocalStorage,\n GitHubStorage,\n getStorageProvider,\n} from \"./content/storage\";\n\nexport type { StorageProvider, FileEntry } from \"./content/storage\";\n\n// Authentication\nexport {\n createSession,\n verifySession,\n getSessionFromCookies,\n setSessionCookie,\n clearSessionCookie,\n} from \"./auth/session\";\n\nexport { validateCredentials } from \"./auth/login\";\n\n// API handlers\nexport {\n handleLogin,\n handleLogout,\n handleGetContent,\n handleSaveContent,\n handleDeleteContent,\n handleListFiles,\n createContentApiHandlers,\n createListApiHandlers,\n} from \"./api/handlers\";\n\nexport { handleUpload } from \"./api/upload\";\n\n// Middleware\nexport { createAuthMiddleware } from \"./middleware/auth\";\n\n// Initialize\nexport { initCMS } from \"./init/setup\";\nexport type { InitCMSConfig } from \"./init/setup\";\n\n// Utilities (for advanced usage)\nexport {\n validateFilePath,\n validateUsername,\n validatePassword,\n validateFileSize,\n sanitizeFrontmatter,\n MAX_USERNAME_LENGTH,\n MAX_PASSWORD_LENGTH,\n MAX_FILE_SIZE,\n} from \"./utils/validation\";\n\nexport {\n checkRateLimit,\n incrementRateLimit,\n resetRateLimit,\n} from \"./utils/rate-limit\";\n"],"mappings":"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","names":["path","checkRateLimit","incrementRateLimit","resetRateLimit","NextResponse","NextResponse","NextResponse","path","NextResponse","fs","path"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fydemy/cms",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Simple file-based CMS for Next.js without database - with GitHub integration for production",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"cms",
|
|
22
|
+
"nextjs",
|
|
23
|
+
"next",
|
|
24
|
+
"markdown",
|
|
25
|
+
"vercel",
|
|
26
|
+
"headless-cms",
|
|
27
|
+
"file-based",
|
|
28
|
+
"github",
|
|
29
|
+
"content-management",
|
|
30
|
+
"static-site",
|
|
31
|
+
"jamstack",
|
|
32
|
+
"typescript"
|
|
33
|
+
],
|
|
34
|
+
"author": {
|
|
35
|
+
"name": "Fydemy",
|
|
36
|
+
"url": "https://fydemy.com"
|
|
37
|
+
},
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/fydemy/cms.git",
|
|
42
|
+
"directory": "packages/core"
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/fydemy/cms/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/fydemy/cms#readme",
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18.0.0"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public",
|
|
53
|
+
"registry": "https://registry.npmjs.org/"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@octokit/rest": "^20.0.2",
|
|
57
|
+
"gray-matter": "^4.0.3",
|
|
58
|
+
"jose": "^5.2.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/node": "^20.10.6",
|
|
62
|
+
"@vitest/coverage-v8": "^1.6.1",
|
|
63
|
+
"tsup": "^8.0.1",
|
|
64
|
+
"typescript": "^5.3.3",
|
|
65
|
+
"vitest": "^1.6.1"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"next": ">=13.0.0"
|
|
69
|
+
},
|
|
70
|
+
"scripts": {
|
|
71
|
+
"build": "tsup",
|
|
72
|
+
"dev": "tsup --watch",
|
|
73
|
+
"type-check": "tsc --noEmit",
|
|
74
|
+
"test": "vitest run",
|
|
75
|
+
"test:watch": "vitest",
|
|
76
|
+
"test:coverage": "vitest run --coverage"
|
|
77
|
+
}
|
|
78
|
+
}
|