webpeel 0.1.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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +415 -0
  3. package/dist/cli.d.ts +16 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +140 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/core/fetcher.d.ts +32 -0
  8. package/dist/core/fetcher.d.ts.map +1 -0
  9. package/dist/core/fetcher.js +479 -0
  10. package/dist/core/fetcher.js.map +1 -0
  11. package/dist/core/markdown.d.ts +17 -0
  12. package/dist/core/markdown.d.ts.map +1 -0
  13. package/dist/core/markdown.js +143 -0
  14. package/dist/core/markdown.js.map +1 -0
  15. package/dist/core/metadata.d.ts +17 -0
  16. package/dist/core/metadata.d.ts.map +1 -0
  17. package/dist/core/metadata.js +159 -0
  18. package/dist/core/metadata.js.map +1 -0
  19. package/dist/core/strategies.d.ts +30 -0
  20. package/dist/core/strategies.d.ts.map +1 -0
  21. package/dist/core/strategies.js +67 -0
  22. package/dist/core/strategies.js.map +1 -0
  23. package/dist/index.d.ts +31 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +81 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/mcp/server.d.ts +7 -0
  28. package/dist/mcp/server.d.ts.map +1 -0
  29. package/dist/mcp/server.js +248 -0
  30. package/dist/mcp/server.js.map +1 -0
  31. package/dist/server/app.d.ts +13 -0
  32. package/dist/server/app.d.ts.map +1 -0
  33. package/dist/server/app.js +89 -0
  34. package/dist/server/app.js.map +1 -0
  35. package/dist/server/auth-store.d.ts +28 -0
  36. package/dist/server/auth-store.d.ts.map +1 -0
  37. package/dist/server/auth-store.js +87 -0
  38. package/dist/server/auth-store.js.map +1 -0
  39. package/dist/server/middleware/auth.d.ts +18 -0
  40. package/dist/server/middleware/auth.d.ts.map +1 -0
  41. package/dist/server/middleware/auth.js +55 -0
  42. package/dist/server/middleware/auth.js.map +1 -0
  43. package/dist/server/middleware/rate-limit.d.ts +23 -0
  44. package/dist/server/middleware/rate-limit.d.ts.map +1 -0
  45. package/dist/server/middleware/rate-limit.js +85 -0
  46. package/dist/server/middleware/rate-limit.js.map +1 -0
  47. package/dist/server/routes/fetch.d.ts +7 -0
  48. package/dist/server/routes/fetch.d.ts.map +1 -0
  49. package/dist/server/routes/fetch.js +127 -0
  50. package/dist/server/routes/fetch.js.map +1 -0
  51. package/dist/server/routes/health.d.ts +6 -0
  52. package/dist/server/routes/health.d.ts.map +1 -0
  53. package/dist/server/routes/health.js +19 -0
  54. package/dist/server/routes/health.js.map +1 -0
  55. package/dist/server/routes/search.d.ts +7 -0
  56. package/dist/server/routes/search.d.ts.map +1 -0
  57. package/dist/server/routes/search.js +124 -0
  58. package/dist/server/routes/search.js.map +1 -0
  59. package/dist/types.d.ts +59 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +30 -0
  62. package/dist/types.js.map +1 -0
  63. package/llms.txt +60 -0
  64. package/package.json +80 -0
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server for WebPeel
4
+ * Provides webpeel_fetch and webpeel_search tools for Claude Desktop / Cursor
5
+ */
6
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
9
+ import { peel } from '../index.js';
10
+ import { fetch as undiciFetch } from 'undici';
11
+ import { load } from 'cheerio';
12
+ const server = new Server({
13
+ name: 'webpeel',
14
+ version: '1.0.0',
15
+ }, {
16
+ capabilities: {
17
+ tools: {},
18
+ },
19
+ });
20
+ /**
21
+ * Search DuckDuckGo HTML and return structured results
22
+ */
23
+ async function searchWeb(query, count = 5) {
24
+ const searchUrl = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
25
+ try {
26
+ const response = await undiciFetch(searchUrl, {
27
+ headers: {
28
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
29
+ },
30
+ });
31
+ if (!response.ok) {
32
+ throw new Error(`Search failed: HTTP ${response.status}`);
33
+ }
34
+ const html = await response.text();
35
+ const $ = load(html);
36
+ const results = [];
37
+ $('.result').each((_i, elem) => {
38
+ if (results.length >= count)
39
+ return;
40
+ const $result = $(elem);
41
+ let title = $result.find('.result__title').text().trim();
42
+ let url = $result.find('.result__url').attr('href') || '';
43
+ let snippet = $result.find('.result__snippet').text().trim();
44
+ // SECURITY: Validate and sanitize results
45
+ if (!title || !url)
46
+ return;
47
+ // Only allow HTTP/HTTPS URLs
48
+ try {
49
+ const parsed = new URL(url);
50
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
51
+ return;
52
+ }
53
+ }
54
+ catch {
55
+ return;
56
+ }
57
+ // Limit text lengths to prevent bloat
58
+ title = title.slice(0, 200);
59
+ snippet = snippet.slice(0, 500);
60
+ results.push({ title, url, snippet });
61
+ });
62
+ return results;
63
+ }
64
+ catch (error) {
65
+ const err = error;
66
+ throw new Error(`Search failed: ${err.message}`);
67
+ }
68
+ }
69
+ const tools = [
70
+ {
71
+ name: 'webpeel_fetch',
72
+ description: 'Fetch a URL and return clean, AI-ready markdown content. Handles JavaScript rendering and anti-bot protections automatically. Use this when you need to read the content of a web page.',
73
+ inputSchema: {
74
+ type: 'object',
75
+ properties: {
76
+ url: {
77
+ type: 'string',
78
+ description: 'The URL to fetch',
79
+ },
80
+ render: {
81
+ type: 'boolean',
82
+ description: 'Force browser rendering (slower but handles JavaScript-heavy sites)',
83
+ default: false,
84
+ },
85
+ wait: {
86
+ type: 'number',
87
+ description: 'Milliseconds to wait for dynamic content (only used with render=true)',
88
+ default: 0,
89
+ },
90
+ format: {
91
+ type: 'string',
92
+ enum: ['markdown', 'text', 'html'],
93
+ description: 'Output format: markdown (default), text, or html',
94
+ default: 'markdown',
95
+ },
96
+ },
97
+ required: ['url'],
98
+ },
99
+ },
100
+ {
101
+ name: 'webpeel_search',
102
+ description: 'Search the web using DuckDuckGo and return results with titles, URLs, and snippets. Use this to find relevant web pages before fetching them.',
103
+ inputSchema: {
104
+ type: 'object',
105
+ properties: {
106
+ query: {
107
+ type: 'string',
108
+ description: 'Search query',
109
+ },
110
+ count: {
111
+ type: 'number',
112
+ description: 'Number of results to return (1-10)',
113
+ default: 5,
114
+ minimum: 1,
115
+ maximum: 10,
116
+ },
117
+ },
118
+ required: ['query'],
119
+ },
120
+ },
121
+ ];
122
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
123
+ tools,
124
+ }));
125
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
126
+ const { name, arguments: args } = request.params;
127
+ try {
128
+ if (name === 'webpeel_fetch') {
129
+ const { url, render, wait, format } = args;
130
+ // SECURITY: Validate input parameters
131
+ if (!url || typeof url !== 'string') {
132
+ throw new Error('Invalid URL parameter');
133
+ }
134
+ if (url.length > 2048) {
135
+ throw new Error('URL too long (max 2048 characters)');
136
+ }
137
+ if (wait !== undefined) {
138
+ if (typeof wait !== 'number' || isNaN(wait) || wait < 0 || wait > 60000) {
139
+ throw new Error('Invalid wait parameter: must be between 0 and 60000ms');
140
+ }
141
+ }
142
+ if (format !== undefined && !['markdown', 'text', 'html'].includes(format)) {
143
+ throw new Error('Invalid format parameter: must be "markdown", "text", or "html"');
144
+ }
145
+ const options = {
146
+ render: render || false,
147
+ wait: wait || 0,
148
+ format: format || 'markdown',
149
+ };
150
+ // SECURITY: Wrap in timeout (60 seconds max)
151
+ const timeoutPromise = new Promise((_, reject) => {
152
+ setTimeout(() => reject(new Error('MCP operation timed out after 60s')), 60000);
153
+ });
154
+ const result = await Promise.race([
155
+ peel(url, options),
156
+ timeoutPromise,
157
+ ]);
158
+ // SECURITY: Handle JSON serialization errors
159
+ let resultText;
160
+ try {
161
+ resultText = JSON.stringify(result, null, 2);
162
+ }
163
+ catch (jsonError) {
164
+ resultText = JSON.stringify({
165
+ error: 'serialization_error',
166
+ message: 'Failed to serialize result',
167
+ });
168
+ }
169
+ return {
170
+ content: [
171
+ {
172
+ type: 'text',
173
+ text: resultText,
174
+ },
175
+ ],
176
+ };
177
+ }
178
+ if (name === 'webpeel_search') {
179
+ const { query, count } = args;
180
+ // SECURITY: Validate input parameters
181
+ if (!query || typeof query !== 'string') {
182
+ throw new Error('Invalid query parameter');
183
+ }
184
+ if (query.length > 500) {
185
+ throw new Error('Query too long (max 500 characters)');
186
+ }
187
+ if (count !== undefined) {
188
+ if (typeof count !== 'number' || isNaN(count) || count < 1 || count > 10) {
189
+ throw new Error('Invalid count parameter: must be between 1 and 10');
190
+ }
191
+ }
192
+ const resultCount = Math.min(Math.max(count || 5, 1), 10);
193
+ // SECURITY: Wrap in timeout (30 seconds max)
194
+ const timeoutPromise = new Promise((_, reject) => {
195
+ setTimeout(() => reject(new Error('Search operation timed out after 30s')), 30000);
196
+ });
197
+ const results = await Promise.race([
198
+ searchWeb(query, resultCount),
199
+ timeoutPromise,
200
+ ]);
201
+ // SECURITY: Handle JSON serialization errors
202
+ let resultText;
203
+ try {
204
+ resultText = JSON.stringify(results, null, 2);
205
+ }
206
+ catch (jsonError) {
207
+ resultText = JSON.stringify({
208
+ error: 'serialization_error',
209
+ message: 'Failed to serialize results',
210
+ });
211
+ }
212
+ return {
213
+ content: [
214
+ {
215
+ type: 'text',
216
+ text: resultText,
217
+ },
218
+ ],
219
+ };
220
+ }
221
+ throw new Error(`Unknown tool: ${name}`);
222
+ }
223
+ catch (error) {
224
+ const err = error;
225
+ return {
226
+ content: [
227
+ {
228
+ type: 'text',
229
+ text: JSON.stringify({
230
+ error: err.name || 'Error',
231
+ message: err.message || 'Unknown error occurred',
232
+ }, null, 2),
233
+ },
234
+ ],
235
+ isError: true,
236
+ };
237
+ }
238
+ });
239
+ async function main() {
240
+ const transport = new StdioServerTransport();
241
+ await server.connect(transport);
242
+ console.error('WebPeel MCP server running on stdio');
243
+ }
244
+ main().catch((error) => {
245
+ console.error('Fatal error:', error);
246
+ process.exit(1);
247
+ });
248
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * WebPeel API Server
3
+ * Express-based REST API for hosted deployments
4
+ */
5
+ import { Express } from 'express';
6
+ export interface ServerConfig {
7
+ port?: number;
8
+ corsOrigins?: string[];
9
+ rateLimitWindowMs?: number;
10
+ }
11
+ export declare function createApp(config?: ServerConfig): Express;
12
+ export declare function startServer(config?: ServerConfig): void;
13
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAgB,EAAE,OAAO,EAAmC,MAAM,SAAS,CAAC;AAS5E,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,SAAS,CAAC,MAAM,GAAE,YAAiB,GAAG,OAAO,CA0D5D;AAED,wBAAgB,WAAW,CAAC,MAAM,GAAE,YAAiB,GAAG,IAAI,CA4B3D"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * WebPeel API Server
3
+ * Express-based REST API for hosted deployments
4
+ */
5
+ import express from 'express';
6
+ import cors from 'cors';
7
+ import { InMemoryAuthStore } from './auth-store.js';
8
+ import { createAuthMiddleware } from './middleware/auth.js';
9
+ import { createRateLimitMiddleware, RateLimiter } from './middleware/rate-limit.js';
10
+ import { createHealthRouter } from './routes/health.js';
11
+ import { createFetchRouter } from './routes/fetch.js';
12
+ import { createSearchRouter } from './routes/search.js';
13
+ export function createApp(config = {}) {
14
+ const app = express();
15
+ // Middleware
16
+ // SECURITY: Limit request body size to prevent DoS
17
+ app.use(express.json({ limit: '1mb' }));
18
+ // SECURITY: Restrict CORS - require explicit origin whitelist
19
+ const corsOrigins = config.corsOrigins || [];
20
+ app.use(cors({
21
+ origin: corsOrigins.length > 0 ? corsOrigins : false,
22
+ credentials: true,
23
+ }));
24
+ // Trust proxy (for rate limiting by IP in production)
25
+ app.set('trust proxy', 1);
26
+ // Auth store (in-memory for now, swap to PostgreSQL later)
27
+ const authStore = new InMemoryAuthStore();
28
+ // Rate limiter
29
+ const rateLimiter = new RateLimiter(config.rateLimitWindowMs || 60000);
30
+ // Clean up rate limiter every 5 minutes
31
+ setInterval(() => {
32
+ rateLimiter.cleanup();
33
+ }, 5 * 60 * 1000);
34
+ // Apply auth middleware globally
35
+ app.use(createAuthMiddleware(authStore));
36
+ // Apply rate limiting middleware globally
37
+ app.use(createRateLimitMiddleware(rateLimiter));
38
+ // Routes
39
+ app.use(createHealthRouter());
40
+ app.use(createFetchRouter(authStore));
41
+ app.use(createSearchRouter(authStore));
42
+ // 404 handler
43
+ app.use((req, res) => {
44
+ res.status(404).json({
45
+ error: 'not_found',
46
+ message: `Route not found: ${req.method} ${req.path}`,
47
+ });
48
+ });
49
+ // Error handler
50
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
51
+ app.use((err, _req, res, _next) => {
52
+ console.error('Unhandled error:', err);
53
+ res.status(500).json({
54
+ error: 'internal_error',
55
+ message: err.message || 'An unexpected error occurred',
56
+ });
57
+ });
58
+ return app;
59
+ }
60
+ export function startServer(config = {}) {
61
+ const app = createApp(config);
62
+ const port = config.port || parseInt(process.env.PORT || '3000', 10);
63
+ const server = app.listen(port, () => {
64
+ console.log(`WebPeel API server listening on port ${port}`);
65
+ console.log(`Health check: http://localhost:${port}/health`);
66
+ console.log(`Fetch: http://localhost:${port}/v1/fetch?url=<url>`);
67
+ console.log(`Search: http://localhost:${port}/v1/search?q=<query>`);
68
+ });
69
+ // Graceful shutdown
70
+ const shutdown = () => {
71
+ console.log('\nShutting down gracefully...');
72
+ server.close(() => {
73
+ console.log('Server closed');
74
+ process.exit(0);
75
+ });
76
+ // Force shutdown after 10 seconds
77
+ setTimeout(() => {
78
+ console.error('Forced shutdown after timeout');
79
+ process.exit(1);
80
+ }, 10000);
81
+ };
82
+ process.on('SIGTERM', shutdown);
83
+ process.on('SIGINT', shutdown);
84
+ }
85
+ // Start server if run directly
86
+ if (import.meta.url === `file://${process.argv[1]}`) {
87
+ startServer();
88
+ }
89
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Auth store abstraction for API key validation and usage tracking
3
+ * Designed to easily swap from in-memory to PostgreSQL
4
+ */
5
+ export interface ApiKeyInfo {
6
+ key: string;
7
+ tier: 'free' | 'starter' | 'pro' | 'enterprise';
8
+ rateLimit: number;
9
+ accountId?: string;
10
+ createdAt: Date;
11
+ }
12
+ export interface AuthStore {
13
+ validateKey(key: string): Promise<ApiKeyInfo | null>;
14
+ trackUsage(key: string, credits: number): Promise<void>;
15
+ }
16
+ /**
17
+ * In-memory auth store for development and self-hosted deployments
18
+ */
19
+ export declare class InMemoryAuthStore implements AuthStore {
20
+ private keys;
21
+ private usage;
22
+ constructor();
23
+ validateKey(key: string): Promise<ApiKeyInfo | null>;
24
+ trackUsage(key: string, credits: number): Promise<void>;
25
+ addKey(keyInfo: ApiKeyInfo): void;
26
+ getUsage(key: string): number;
27
+ }
28
+ //# sourceMappingURL=auth-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-store.d.ts","sourceRoot":"","sources":["../../src/server/auth-store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,YAAY,CAAC;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACrD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAwCD;;GAEG;AACH,qBAAa,iBAAkB,YAAW,SAAS;IACjD,OAAO,CAAC,IAAI,CAAiC;IAC7C,OAAO,CAAC,KAAK,CAA6B;;IAepC,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAiBpD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7D,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI;IAQjC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAG9B"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Auth store abstraction for API key validation and usage tracking
3
+ * Designed to easily swap from in-memory to PostgreSQL
4
+ */
5
+ import { timingSafeEqual } from 'crypto';
6
+ /**
7
+ * Validate API key format and strength
8
+ * SECURITY: Enforce minimum complexity
9
+ */
10
+ function validateKeyFormat(key) {
11
+ // Minimum 32 characters
12
+ if (key.length < 32) {
13
+ return false;
14
+ }
15
+ // Must contain alphanumeric characters
16
+ if (!/^[a-zA-Z0-9_-]+$/.test(key)) {
17
+ return false;
18
+ }
19
+ return true;
20
+ }
21
+ /**
22
+ * Timing-safe key comparison
23
+ * SECURITY: Prevent timing attacks on key validation
24
+ */
25
+ function timingSafeKeyCompare(a, b) {
26
+ // Ensure equal length for comparison
27
+ if (a.length !== b.length) {
28
+ // Compare against dummy to prevent timing leak
29
+ const dummy = 'x'.repeat(Math.max(a.length, b.length));
30
+ timingSafeEqual(Buffer.from(dummy), Buffer.from(dummy));
31
+ return false;
32
+ }
33
+ try {
34
+ return timingSafeEqual(Buffer.from(a), Buffer.from(b));
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ /**
41
+ * In-memory auth store for development and self-hosted deployments
42
+ */
43
+ export class InMemoryAuthStore {
44
+ keys = new Map();
45
+ usage = new Map();
46
+ constructor() {
47
+ // SECURITY: Demo key only in development mode
48
+ // Removed hardcoded demo key - use addKey() or environment variables
49
+ if (process.env.NODE_ENV === 'development' && process.env.DEMO_KEY) {
50
+ this.addKey({
51
+ key: process.env.DEMO_KEY,
52
+ tier: 'pro',
53
+ rateLimit: 300,
54
+ createdAt: new Date(),
55
+ });
56
+ }
57
+ }
58
+ async validateKey(key) {
59
+ // Basic validation
60
+ if (!key || typeof key !== 'string') {
61
+ return null;
62
+ }
63
+ // SECURITY: Timing-safe comparison to prevent timing attacks
64
+ for (const [storedKey, keyInfo] of this.keys.entries()) {
65
+ if (timingSafeKeyCompare(key, storedKey)) {
66
+ return keyInfo;
67
+ }
68
+ }
69
+ // Constant-time operation for invalid key
70
+ return null;
71
+ }
72
+ async trackUsage(key, credits) {
73
+ const current = this.usage.get(key) || 0;
74
+ this.usage.set(key, current + credits);
75
+ }
76
+ addKey(keyInfo) {
77
+ // SECURITY: Validate key format before adding
78
+ if (!validateKeyFormat(keyInfo.key)) {
79
+ throw new Error('Invalid API key format: must be at least 32 characters, alphanumeric with - or _');
80
+ }
81
+ this.keys.set(keyInfo.key, keyInfo);
82
+ }
83
+ getUsage(key) {
84
+ return this.usage.get(key) || 0;
85
+ }
86
+ }
87
+ //# sourceMappingURL=auth-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-store.js","sourceRoot":"","sources":["../../src/server/auth-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * API key authentication middleware
3
+ */
4
+ import { Request, Response, NextFunction } from 'express';
5
+ import { AuthStore, ApiKeyInfo } from '../auth-store.js';
6
+ declare global {
7
+ namespace Express {
8
+ interface Request {
9
+ auth?: {
10
+ keyInfo: ApiKeyInfo | null;
11
+ tier: 'free' | 'starter' | 'pro' | 'enterprise';
12
+ rateLimit: number;
13
+ };
14
+ }
15
+ }
16
+ }
17
+ export declare function createAuthMiddleware(authStore: AuthStore): (req: Request, res: Response, next: NextFunction) => Promise<void>;
18
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/server/middleware/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,IAAI,CAAC,EAAE;gBACL,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;gBAC3B,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,YAAY,CAAC;gBAChD,SAAS,EAAE,MAAM,CAAC;aACnB,CAAC;SACH;KACF;CACF;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,SAAS,IACzC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,mBAsD9D"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * API key authentication middleware
3
+ */
4
+ export function createAuthMiddleware(authStore) {
5
+ return async (req, res, next) => {
6
+ try {
7
+ // Extract API key from Authorization header or X-API-Key header
8
+ const authHeader = req.headers.authorization;
9
+ const apiKeyHeader = req.headers['x-api-key'];
10
+ let apiKey = null;
11
+ if (authHeader?.startsWith('Bearer ')) {
12
+ apiKey = authHeader.slice(7);
13
+ }
14
+ else if (apiKeyHeader && typeof apiKeyHeader === 'string') {
15
+ apiKey = apiKeyHeader;
16
+ }
17
+ // SECURITY: Require API key for all non-health endpoints
18
+ const isHealthEndpoint = req.path === '/health';
19
+ if (!apiKey && !isHealthEndpoint) {
20
+ res.status(401).json({
21
+ error: 'missing_key',
22
+ message: 'API key is required. Provide via Authorization: Bearer <key> or X-API-Key header.',
23
+ });
24
+ return;
25
+ }
26
+ // Validate API key if provided
27
+ let keyInfo = null;
28
+ if (apiKey) {
29
+ keyInfo = await authStore.validateKey(apiKey);
30
+ if (!keyInfo) {
31
+ res.status(401).json({
32
+ error: 'invalid_key',
33
+ message: 'Invalid API key',
34
+ });
35
+ return;
36
+ }
37
+ }
38
+ // Set auth context on request
39
+ req.auth = {
40
+ keyInfo,
41
+ tier: keyInfo?.tier || 'free',
42
+ rateLimit: keyInfo?.rateLimit || 10,
43
+ };
44
+ next();
45
+ }
46
+ catch (error) {
47
+ const err = error;
48
+ res.status(500).json({
49
+ error: 'auth_error',
50
+ message: err.message || 'Authentication failed',
51
+ });
52
+ }
53
+ };
54
+ }
55
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/server/middleware/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAiBH,MAAM,UAAU,oBAAoB,CAAC,SAAoB;IACvD,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/D,IAAI,CAAC;YACH,gEAAgE;YAChE,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;YAC7C,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YAE9C,IAAI,MAAM,GAAkB,IAAI,CAAC;YAEjC,IAAI,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBACtC,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC/B,CAAC;iBAAM,IAAI,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;gBAC5D,MAAM,GAAG,YAAY,CAAC;YACxB,CAAC;YAED,yDAAyD;YACzD,MAAM,gBAAgB,GAAG,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC;YAEhD,IAAI,CAAC,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACjC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnB,KAAK,EAAE,aAAa;oBACpB,OAAO,EAAE,mFAAmF;iBAC7F,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,+BAA+B;YAC/B,IAAI,OAAO,GAAsB,IAAI,CAAC;YACtC,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;gBAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;wBACnB,KAAK,EAAE,aAAa;wBACpB,OAAO,EAAE,iBAAiB;qBAC3B,CAAC,CAAC;oBACH,OAAO;gBACT,CAAC;YACH,CAAC;YAED,8BAA8B;YAC9B,GAAG,CAAC,IAAI,GAAG;gBACT,OAAO;gBACP,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,MAAM;gBAC7B,SAAS,EAAE,OAAO,EAAE,SAAS,IAAI,EAAE;aACpC,CAAC;YAEF,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,KAAc,CAAC;YAC3B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,YAAY;gBACnB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,uBAAuB;aAChD,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Sliding window rate limiting middleware
3
+ */
4
+ import { Request, Response, NextFunction } from 'express';
5
+ export declare class RateLimiter {
6
+ private store;
7
+ private windowMs;
8
+ constructor(windowMs?: number);
9
+ /**
10
+ * Check if request is allowed under rate limit
11
+ */
12
+ checkLimit(identifier: string, limit: number): {
13
+ allowed: boolean;
14
+ remaining: number;
15
+ retryAfter?: number;
16
+ };
17
+ /**
18
+ * Clean up old entries (call periodically)
19
+ */
20
+ cleanup(): void;
21
+ }
22
+ export declare function createRateLimitMiddleware(limiter: RateLimiter): (req: Request, res: Response, next: NextFunction) => void;
23
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/server/middleware/rate-limit.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAM1D,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,GAAE,MAAc;IAIpC;;OAEG;IACH,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;QAC7C,OAAO,EAAE,OAAO,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB;IAmCD;;OAEG;IACH,OAAO,IAAI,IAAI;CAWhB;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,WAAW,IACpD,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,UA+BxD"}