@reverso/api 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.
- package/README.md +47 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/auth.d.ts +72 -0
- package/dist/plugins/auth.d.ts.map +1 -0
- package/dist/plugins/auth.js +173 -0
- package/dist/plugins/auth.js.map +1 -0
- package/dist/plugins/database.d.ts +19 -0
- package/dist/plugins/database.d.ts.map +1 -0
- package/dist/plugins/database.js +23 -0
- package/dist/plugins/database.js.map +1 -0
- package/dist/plugins/index.d.ts +5 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +5 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/routes/auth.d.ts +6 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +258 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/content.d.ts +8 -0
- package/dist/routes/content.d.ts.map +1 -0
- package/dist/routes/content.js +339 -0
- package/dist/routes/content.js.map +1 -0
- package/dist/routes/forms.d.ts +8 -0
- package/dist/routes/forms.d.ts.map +1 -0
- package/dist/routes/forms.js +953 -0
- package/dist/routes/forms.js.map +1 -0
- package/dist/routes/index.d.ts +19 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +31 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/media.d.ts +8 -0
- package/dist/routes/media.d.ts.map +1 -0
- package/dist/routes/media.js +400 -0
- package/dist/routes/media.js.map +1 -0
- package/dist/routes/pages.d.ts +8 -0
- package/dist/routes/pages.d.ts.map +1 -0
- package/dist/routes/pages.js +220 -0
- package/dist/routes/pages.js.map +1 -0
- package/dist/routes/redirects.d.ts +8 -0
- package/dist/routes/redirects.d.ts.map +1 -0
- package/dist/routes/redirects.js +462 -0
- package/dist/routes/redirects.js.map +1 -0
- package/dist/routes/schema.d.ts +8 -0
- package/dist/routes/schema.d.ts.map +1 -0
- package/dist/routes/schema.js +151 -0
- package/dist/routes/schema.js.map +1 -0
- package/dist/routes/sitemap.d.ts +8 -0
- package/dist/routes/sitemap.d.ts.map +1 -0
- package/dist/routes/sitemap.js +144 -0
- package/dist/routes/sitemap.js.map +1 -0
- package/dist/server.d.ts +47 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +218 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/security.d.ts +32 -0
- package/dist/utils/security.d.ts.map +1 -0
- package/dist/utils/security.js +154 -0
- package/dist/utils/security.js.map +1 -0
- package/dist/validation.d.ts +402 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +308 -0
- package/dist/validation.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sitemap route.
|
|
3
|
+
* Generates dynamic XML sitemap for SEO.
|
|
4
|
+
*/
|
|
5
|
+
import { getFormsByStatus, getPages } from '@reverso/db';
|
|
6
|
+
/**
|
|
7
|
+
* Build XML sitemap from URLs.
|
|
8
|
+
*/
|
|
9
|
+
function buildSitemapXml(urls) {
|
|
10
|
+
const urlEntries = urls
|
|
11
|
+
.map((url) => {
|
|
12
|
+
const parts = [` <url>`, ` <loc>${escapeXml(url.loc)}</loc>`];
|
|
13
|
+
if (url.lastmod) {
|
|
14
|
+
parts.push(` <lastmod>${url.lastmod}</lastmod>`);
|
|
15
|
+
}
|
|
16
|
+
if (url.changefreq) {
|
|
17
|
+
parts.push(` <changefreq>${url.changefreq}</changefreq>`);
|
|
18
|
+
}
|
|
19
|
+
if (url.priority !== undefined) {
|
|
20
|
+
parts.push(` <priority>${url.priority.toFixed(1)}</priority>`);
|
|
21
|
+
}
|
|
22
|
+
parts.push(` </url>`);
|
|
23
|
+
return parts.join('\n');
|
|
24
|
+
})
|
|
25
|
+
.join('\n');
|
|
26
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
27
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
28
|
+
${urlEntries}
|
|
29
|
+
</urlset>`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Escape special XML characters.
|
|
33
|
+
*/
|
|
34
|
+
function escapeXml(str) {
|
|
35
|
+
return str
|
|
36
|
+
.replace(/&/g, '&')
|
|
37
|
+
.replace(/</g, '<')
|
|
38
|
+
.replace(/>/g, '>')
|
|
39
|
+
.replace(/"/g, '"')
|
|
40
|
+
.replace(/'/g, ''');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Format date for sitemap (ISO 8601 date format).
|
|
44
|
+
*/
|
|
45
|
+
function formatDate(date) {
|
|
46
|
+
if (!date)
|
|
47
|
+
return undefined;
|
|
48
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
49
|
+
return d.toISOString().split('T')[0];
|
|
50
|
+
}
|
|
51
|
+
// Cache for sitemap (1 hour)
|
|
52
|
+
let sitemapCache = null;
|
|
53
|
+
const CACHE_TTL = 60 * 60 * 1000; // 1 hour in milliseconds
|
|
54
|
+
const sitemapRoutes = async (fastify) => {
|
|
55
|
+
/**
|
|
56
|
+
* GET /sitemap.xml
|
|
57
|
+
* Generate dynamic sitemap.
|
|
58
|
+
*/
|
|
59
|
+
fastify.get('/sitemap.xml', async (request, reply) => {
|
|
60
|
+
try {
|
|
61
|
+
// Check cache
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
if (sitemapCache && now - sitemapCache.timestamp < CACHE_TTL) {
|
|
64
|
+
reply.header('Content-Type', 'application/xml');
|
|
65
|
+
reply.header('Cache-Control', 'public, max-age=3600');
|
|
66
|
+
return sitemapCache.xml;
|
|
67
|
+
}
|
|
68
|
+
const db = request.db;
|
|
69
|
+
// Get base URL from request or config
|
|
70
|
+
const protocol = request.protocol || 'https';
|
|
71
|
+
const host = request.hostname || 'localhost';
|
|
72
|
+
const baseUrl = `${protocol}://${host}`;
|
|
73
|
+
const urls = [];
|
|
74
|
+
// Add homepage
|
|
75
|
+
urls.push({
|
|
76
|
+
loc: baseUrl,
|
|
77
|
+
changefreq: 'daily',
|
|
78
|
+
priority: 1.0,
|
|
79
|
+
});
|
|
80
|
+
// Get all pages
|
|
81
|
+
const pages = await getPages(db);
|
|
82
|
+
for (const page of pages) {
|
|
83
|
+
// Skip home page (already added)
|
|
84
|
+
if (page.slug === 'home' || page.slug === 'index') {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
urls.push({
|
|
88
|
+
loc: `${baseUrl}/${page.slug}`,
|
|
89
|
+
lastmod: formatDate(page.updatedAt),
|
|
90
|
+
changefreq: 'weekly',
|
|
91
|
+
priority: 0.8,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// Get published forms (for public form pages)
|
|
95
|
+
const publishedForms = await getFormsByStatus(db, 'published');
|
|
96
|
+
for (const form of publishedForms) {
|
|
97
|
+
urls.push({
|
|
98
|
+
loc: `${baseUrl}/forms/${form.slug}`,
|
|
99
|
+
lastmod: formatDate(form.updatedAt),
|
|
100
|
+
changefreq: 'monthly',
|
|
101
|
+
priority: 0.6,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// Build XML
|
|
105
|
+
const xml = buildSitemapXml(urls);
|
|
106
|
+
// Update cache
|
|
107
|
+
sitemapCache = { xml, timestamp: now };
|
|
108
|
+
reply.header('Content-Type', 'application/xml');
|
|
109
|
+
reply.header('Cache-Control', 'public, max-age=3600');
|
|
110
|
+
return xml;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
fastify.log.error(error, 'Failed to generate sitemap');
|
|
114
|
+
return reply.status(500).send({
|
|
115
|
+
success: false,
|
|
116
|
+
error: 'Internal error',
|
|
117
|
+
message: 'Failed to generate sitemap',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
/**
|
|
122
|
+
* POST /sitemap/invalidate
|
|
123
|
+
* Invalidate sitemap cache (admin only).
|
|
124
|
+
*/
|
|
125
|
+
fastify.post('/sitemap/invalidate', async (request, reply) => {
|
|
126
|
+
try {
|
|
127
|
+
sitemapCache = null;
|
|
128
|
+
return {
|
|
129
|
+
success: true,
|
|
130
|
+
message: 'Sitemap cache invalidated',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
fastify.log.error(error, 'Failed to invalidate sitemap cache');
|
|
135
|
+
return reply.status(500).send({
|
|
136
|
+
success: false,
|
|
137
|
+
error: 'Internal error',
|
|
138
|
+
message: 'Failed to invalidate sitemap cache',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
export default sitemapRoutes;
|
|
144
|
+
//# sourceMappingURL=sitemap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../../src/routes/sitemap.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fastify server setup.
|
|
3
|
+
*/
|
|
4
|
+
import { type FastifyInstance } from 'fastify';
|
|
5
|
+
export interface ServerConfig {
|
|
6
|
+
/** Server port */
|
|
7
|
+
port?: number;
|
|
8
|
+
/** Server host */
|
|
9
|
+
host?: string;
|
|
10
|
+
/** Enable CORS */
|
|
11
|
+
cors?: boolean | CorsOptions;
|
|
12
|
+
/** Cookie secret */
|
|
13
|
+
cookieSecret?: string;
|
|
14
|
+
/** Uploads directory */
|
|
15
|
+
uploadsDir?: string;
|
|
16
|
+
/** Enable request logging */
|
|
17
|
+
logger?: boolean;
|
|
18
|
+
/** API prefix */
|
|
19
|
+
prefix?: string;
|
|
20
|
+
/** API key for authentication */
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
/** Enable authentication (default: true in production) */
|
|
23
|
+
authEnabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface CorsOptions {
|
|
26
|
+
origin?: string | string[] | boolean;
|
|
27
|
+
methods?: string[];
|
|
28
|
+
credentials?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create a Fastify server instance.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createServer(config?: ServerConfig): Promise<FastifyInstance>;
|
|
34
|
+
/**
|
|
35
|
+
* Start the server.
|
|
36
|
+
*/
|
|
37
|
+
export declare function startServer(server: FastifyInstance, config?: ServerConfig): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Stop the server gracefully.
|
|
40
|
+
*/
|
|
41
|
+
export declare function stopServer(server: FastifyInstance): Promise<void>;
|
|
42
|
+
declare module 'fastify' {
|
|
43
|
+
interface FastifyInstance {
|
|
44
|
+
config: Required<ServerConfig>;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,OAAgB,EAAE,KAAK,eAAe,EAA6B,MAAM,SAAS,CAAC;AAGnF,MAAM,WAAW,YAAY;IAC3B,kBAAkB;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,IAAI,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;IAC7B,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wBAAwB;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iBAAiB;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAgCD;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,GAAE,YAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CA2KtF;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,eAAe,EACvB,MAAM,GAAE,YAAiB,GACxB,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;AAGD,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,eAAe;QACvB,MAAM,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;KAChC;CACF"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fastify server setup.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import cookie from '@fastify/cookie';
|
|
7
|
+
import cors from '@fastify/cors';
|
|
8
|
+
import csrfProtection from '@fastify/csrf-protection';
|
|
9
|
+
import helmet from '@fastify/helmet';
|
|
10
|
+
import multipart from '@fastify/multipart';
|
|
11
|
+
import rateLimit from '@fastify/rate-limit';
|
|
12
|
+
import fastifyStatic from '@fastify/static';
|
|
13
|
+
import Fastify, {} from 'fastify';
|
|
14
|
+
import authPlugin, {} from './plugins/auth.js';
|
|
15
|
+
/**
|
|
16
|
+
* Get cookie secret from environment or use development fallback.
|
|
17
|
+
* In production, REVERSO_COOKIE_SECRET must be set.
|
|
18
|
+
*/
|
|
19
|
+
function getCookieSecret() {
|
|
20
|
+
const envSecret = process.env.REVERSO_COOKIE_SECRET;
|
|
21
|
+
if (envSecret) {
|
|
22
|
+
return envSecret;
|
|
23
|
+
}
|
|
24
|
+
// Only allow fallback in non-production environments
|
|
25
|
+
if (process.env.NODE_ENV === 'production') {
|
|
26
|
+
throw new Error('REVERSO_COOKIE_SECRET environment variable must be set in production');
|
|
27
|
+
}
|
|
28
|
+
return 'reverso-dev-secret-do-not-use-in-production';
|
|
29
|
+
}
|
|
30
|
+
const defaultConfig = {
|
|
31
|
+
port: 3001,
|
|
32
|
+
host: '0.0.0.0',
|
|
33
|
+
cors: true,
|
|
34
|
+
cookieSecret: getCookieSecret(),
|
|
35
|
+
uploadsDir: '.reverso/uploads',
|
|
36
|
+
logger: true,
|
|
37
|
+
prefix: '/api/reverso',
|
|
38
|
+
apiKey: process.env.REVERSO_API_KEY || '',
|
|
39
|
+
authEnabled: process.env.NODE_ENV === 'production',
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Create a Fastify server instance.
|
|
43
|
+
*/
|
|
44
|
+
export async function createServer(config = {}) {
|
|
45
|
+
const opts = { ...defaultConfig, ...config };
|
|
46
|
+
const fastifyOptions = {
|
|
47
|
+
logger: opts.logger
|
|
48
|
+
? {
|
|
49
|
+
transport: {
|
|
50
|
+
target: 'pino-pretty',
|
|
51
|
+
options: {
|
|
52
|
+
translateTime: 'HH:MM:ss Z',
|
|
53
|
+
ignore: 'pid,hostname',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
: false,
|
|
58
|
+
};
|
|
59
|
+
const server = Fastify(fastifyOptions);
|
|
60
|
+
// Register CORS
|
|
61
|
+
if (opts.cors) {
|
|
62
|
+
// In production, require explicit origin configuration
|
|
63
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
64
|
+
const defaultOrigin = isProduction
|
|
65
|
+
? process.env.REVERSO_CORS_ORIGIN || 'http://localhost:3000'
|
|
66
|
+
: true;
|
|
67
|
+
const corsOptions = typeof opts.cors === 'object'
|
|
68
|
+
? {
|
|
69
|
+
origin: opts.cors.origin ?? defaultOrigin,
|
|
70
|
+
methods: opts.cors.methods ?? ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
|
71
|
+
credentials: opts.cors.credentials ?? true,
|
|
72
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
|
|
73
|
+
}
|
|
74
|
+
: {
|
|
75
|
+
origin: defaultOrigin,
|
|
76
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
|
77
|
+
credentials: true,
|
|
78
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
|
|
79
|
+
};
|
|
80
|
+
await server.register(cors, corsOptions);
|
|
81
|
+
}
|
|
82
|
+
// Register security headers (helmet)
|
|
83
|
+
await server.register(helmet, {
|
|
84
|
+
contentSecurityPolicy: {
|
|
85
|
+
directives: {
|
|
86
|
+
defaultSrc: ["'self'"],
|
|
87
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
88
|
+
scriptSrc: ["'self'"],
|
|
89
|
+
imgSrc: ["'self'", 'data:', 'blob:'],
|
|
90
|
+
fontSrc: ["'self'"],
|
|
91
|
+
objectSrc: ["'none'"],
|
|
92
|
+
upgradeInsecureRequests: process.env.NODE_ENV === 'production' ? [] : null,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
crossOriginEmbedderPolicy: false, // Disable for media uploads
|
|
96
|
+
crossOriginResourcePolicy: { policy: 'cross-origin' }, // Allow cross-origin for assets
|
|
97
|
+
});
|
|
98
|
+
// Register cookies
|
|
99
|
+
await server.register(cookie, {
|
|
100
|
+
secret: opts.cookieSecret,
|
|
101
|
+
parseOptions: {},
|
|
102
|
+
});
|
|
103
|
+
// Register CSRF protection (enabled by default, can be disabled in dev with env var)
|
|
104
|
+
const csrfEnabled = process.env.REVERSO_CSRF_DISABLED !== 'true';
|
|
105
|
+
if (csrfEnabled) {
|
|
106
|
+
await server.register(csrfProtection, {
|
|
107
|
+
sessionPlugin: '@fastify/cookie',
|
|
108
|
+
cookieOpts: {
|
|
109
|
+
signed: true,
|
|
110
|
+
httpOnly: true,
|
|
111
|
+
sameSite: 'strict',
|
|
112
|
+
secure: process.env.NODE_ENV === 'production',
|
|
113
|
+
path: '/',
|
|
114
|
+
},
|
|
115
|
+
// Skip CSRF for public endpoints and API key authentication
|
|
116
|
+
getToken: (request) => {
|
|
117
|
+
return (request.headers['x-csrf-token']?.toString() ||
|
|
118
|
+
request.headers['csrf-token']?.toString());
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
// Add hook to skip CSRF for safe methods and API key auth
|
|
122
|
+
server.addHook('onRequest', async (request, reply) => {
|
|
123
|
+
const safeMethods = ['GET', 'HEAD', 'OPTIONS'];
|
|
124
|
+
if (safeMethods.includes(request.method))
|
|
125
|
+
return;
|
|
126
|
+
// Skip CSRF if API key is provided
|
|
127
|
+
if (request.headers['x-api-key'])
|
|
128
|
+
return;
|
|
129
|
+
// Skip CSRF for public form submissions (they have their own validation)
|
|
130
|
+
if (request.url.startsWith('/api/reverso/public/'))
|
|
131
|
+
return;
|
|
132
|
+
});
|
|
133
|
+
// Add CSRF token generation endpoint
|
|
134
|
+
server.get('/api/csrf-token', async (request, reply) => {
|
|
135
|
+
const token = await reply.generateCsrf();
|
|
136
|
+
return { csrfToken: token };
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// Register rate limiting
|
|
140
|
+
await server.register(rateLimit, {
|
|
141
|
+
max: 100, // Max 100 requests per window
|
|
142
|
+
timeWindow: '1 minute',
|
|
143
|
+
// Stricter limits for specific routes
|
|
144
|
+
keyGenerator: (request) => {
|
|
145
|
+
// Use API key or IP for rate limiting
|
|
146
|
+
return (request.headers['x-api-key']?.toString() ||
|
|
147
|
+
request.headers['x-forwarded-for']?.toString() ||
|
|
148
|
+
request.ip);
|
|
149
|
+
},
|
|
150
|
+
// Skip rate limiting for health checks
|
|
151
|
+
allowList: ['/health'],
|
|
152
|
+
// Custom error response
|
|
153
|
+
errorResponseBuilder: () => ({
|
|
154
|
+
success: false,
|
|
155
|
+
error: 'Too Many Requests',
|
|
156
|
+
message: 'Rate limit exceeded. Please try again later.',
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
// Register multipart for file uploads
|
|
160
|
+
await server.register(multipart, {
|
|
161
|
+
limits: {
|
|
162
|
+
fileSize: 50 * 1024 * 1024, // 50MB
|
|
163
|
+
files: 10,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
// Register authentication plugin
|
|
167
|
+
await server.register(authPlugin, {
|
|
168
|
+
apiKey: opts.apiKey,
|
|
169
|
+
enabled: opts.authEnabled,
|
|
170
|
+
publicPaths: [
|
|
171
|
+
/^\/api\/reverso\/public\//,
|
|
172
|
+
/^\/sitemap\.xml$/,
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
// Ensure uploads directory exists
|
|
176
|
+
if (!existsSync(opts.uploadsDir)) {
|
|
177
|
+
mkdirSync(opts.uploadsDir, { recursive: true });
|
|
178
|
+
}
|
|
179
|
+
// Serve static files from uploads directory
|
|
180
|
+
await server.register(fastifyStatic, {
|
|
181
|
+
root: join(process.cwd(), opts.uploadsDir),
|
|
182
|
+
prefix: '/uploads/',
|
|
183
|
+
decorateReply: false,
|
|
184
|
+
});
|
|
185
|
+
// Health check endpoint
|
|
186
|
+
server.get('/health', async () => ({
|
|
187
|
+
status: 'ok',
|
|
188
|
+
timestamp: new Date().toISOString(),
|
|
189
|
+
}));
|
|
190
|
+
// Store config in server instance
|
|
191
|
+
server.decorate('config', opts);
|
|
192
|
+
return server;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Start the server.
|
|
196
|
+
*/
|
|
197
|
+
export async function startServer(server, config = {}) {
|
|
198
|
+
// Use server's stored config as base, then override with passed config
|
|
199
|
+
const opts = { ...defaultConfig, ...server.config, ...config };
|
|
200
|
+
try {
|
|
201
|
+
const address = await server.listen({
|
|
202
|
+
port: opts.port,
|
|
203
|
+
host: opts.host,
|
|
204
|
+
});
|
|
205
|
+
return address;
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
server.log.error(err);
|
|
209
|
+
throw err;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Stop the server gracefully.
|
|
214
|
+
*/
|
|
215
|
+
export async function stopServer(server) {
|
|
216
|
+
await server.close();
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API type definitions.
|
|
3
|
+
*/
|
|
4
|
+
import type { ContentValue, ProjectSchema } from '@reverso/core';
|
|
5
|
+
import type { DrizzleDatabase } from '@reverso/db';
|
|
6
|
+
import type { FastifyRequest } from 'fastify';
|
|
7
|
+
import type { AuthUser } from './plugins/auth.js';
|
|
8
|
+
export type { AuthUser };
|
|
9
|
+
/**
|
|
10
|
+
* Extended FastifyRequest with database.
|
|
11
|
+
*/
|
|
12
|
+
export interface ApiRequest extends FastifyRequest {
|
|
13
|
+
db: DrizzleDatabase;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* API error response.
|
|
17
|
+
*/
|
|
18
|
+
export interface ApiError {
|
|
19
|
+
error: string;
|
|
20
|
+
message: string;
|
|
21
|
+
statusCode: number;
|
|
22
|
+
details?: unknown;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* API success response.
|
|
26
|
+
*/
|
|
27
|
+
export interface ApiResponse<T = unknown> {
|
|
28
|
+
success: boolean;
|
|
29
|
+
data?: T;
|
|
30
|
+
meta?: {
|
|
31
|
+
total?: number;
|
|
32
|
+
page?: number;
|
|
33
|
+
limit?: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Schema sync request body.
|
|
38
|
+
*/
|
|
39
|
+
export interface SchemaSyncBody {
|
|
40
|
+
schema: ProjectSchema;
|
|
41
|
+
deleteRemoved?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Content update request body.
|
|
45
|
+
*/
|
|
46
|
+
export interface ContentUpdateBody {
|
|
47
|
+
value: ContentValue;
|
|
48
|
+
locale?: string;
|
|
49
|
+
publish?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Bulk content update request body.
|
|
53
|
+
*/
|
|
54
|
+
export interface BulkContentUpdateBody {
|
|
55
|
+
updates: Array<{
|
|
56
|
+
path: string;
|
|
57
|
+
value: ContentValue;
|
|
58
|
+
locale?: string;
|
|
59
|
+
}>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Media upload result.
|
|
63
|
+
*/
|
|
64
|
+
export interface MediaUploadResult {
|
|
65
|
+
id: string;
|
|
66
|
+
url: string;
|
|
67
|
+
filename: string;
|
|
68
|
+
mimeType: string;
|
|
69
|
+
size: number;
|
|
70
|
+
width?: number;
|
|
71
|
+
height?: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Pagination params.
|
|
75
|
+
*/
|
|
76
|
+
export interface PaginationParams {
|
|
77
|
+
page?: number;
|
|
78
|
+
limit?: number;
|
|
79
|
+
offset?: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* List response with pagination.
|
|
83
|
+
*/
|
|
84
|
+
export interface ListResponse<T> {
|
|
85
|
+
items: T[];
|
|
86
|
+
total: number;
|
|
87
|
+
page: number;
|
|
88
|
+
limit: number;
|
|
89
|
+
totalPages: number;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGlD,YAAY,EAAE,QAAQ,EAAE,CAAC;AAEzB;;GAEG;AACH,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD,EAAE,EAAE,eAAe,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,YAAY,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security utilities for the API.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Check if a URL is safe for server-side requests (SSRF protection).
|
|
6
|
+
* Blocks private IPs, localhost, and cloud metadata endpoints.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isUrlSafeForSSRF(urlString: string): {
|
|
9
|
+
safe: boolean;
|
|
10
|
+
reason?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Check if a redirect URL is safe (prevents open redirect attacks).
|
|
14
|
+
* Only allows relative URLs or URLs to whitelisted domains.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isRedirectUrlSafe(urlString: string | undefined | null): {
|
|
17
|
+
safe: boolean;
|
|
18
|
+
reason?: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Generate HMAC-SHA256 signature for webhook payloads.
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateWebhookSignature(payload: string, secret: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Verify HMAC-SHA256 signature for webhook payloads.
|
|
26
|
+
*/
|
|
27
|
+
export declare function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Validate token format and length.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isValidTokenFormat(token: string): boolean;
|
|
32
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/utils/security.ts"],"names":[],"mappings":"AAAA;;GAEG;AAmCH;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA+BtF;AAcD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAuC1G;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIhF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAQlG;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAkBzD"}
|