create-dacosta-proj 1.0.13 → 1.0.15
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/package.json
CHANGED
package/template/package.json
CHANGED
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
"express": "^5.2.1",
|
|
14
14
|
"module-alias": "^2.3.4",
|
|
15
15
|
"nanoid": "^5.1.6",
|
|
16
|
+
"rate-limiter-flexible": "^9.1.1",
|
|
16
17
|
"redis": "^5.11.0",
|
|
17
|
-
"simple-supabase": "^2.0.5"
|
|
18
|
+
"simple-supabase": "^2.0.5",
|
|
19
|
+
"zod": "^4.3.6"
|
|
18
20
|
},
|
|
19
21
|
"devDependencies": {
|
|
20
22
|
"eslint": "^9.39.3",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const { global } = require('@/config/global');
|
|
5
|
+
const { default: zod } = require('zod');
|
|
5
6
|
|
|
6
7
|
async function GET({ headers }) {
|
|
7
8
|
|
|
@@ -11,11 +12,9 @@ async function GET({ headers }) {
|
|
|
11
12
|
const userId = headers['user-id'];
|
|
12
13
|
const accessToken = headers['access-token'];
|
|
13
14
|
|
|
14
|
-
if (!userId || !accessToken) return { code: 400, json: { error: 'Missing params!' } };
|
|
15
|
-
|
|
16
15
|
// Format Signature
|
|
17
16
|
|
|
18
|
-
const expires = Date.now() + (1_000 * 60 * 60 *
|
|
17
|
+
const expires = Date.now() + (1_000 * 60 * 60 * 1); // 1 Hour
|
|
19
18
|
|
|
20
19
|
const hmac = crypto.createHmac('sha256', process.env.SIGNATURE_KEY)
|
|
21
20
|
.update(`${userId}:${accessToken}:${expires}:${process.env.SIGNATURE_REF}`)
|
|
@@ -28,6 +27,19 @@ async function GET({ headers }) {
|
|
|
28
27
|
json: { signature }
|
|
29
28
|
};
|
|
30
29
|
}
|
|
31
|
-
catch { return { code: 500, json: { error: 'Failed to generate signature!' } }; }
|
|
30
|
+
catch (err) { console.log('Signature Generation Failed', err); return { code: 500, json: { error: 'Failed to generate signature!' } }; }
|
|
32
31
|
}
|
|
33
|
-
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
GET,
|
|
35
|
+
settings: {
|
|
36
|
+
requires_bypass: true
|
|
37
|
+
},
|
|
38
|
+
schemas: {
|
|
39
|
+
headers: zod.object({
|
|
40
|
+
'user-id': zod.string(),
|
|
41
|
+
'access-token': zod.string(),
|
|
42
|
+
'bypass': zod.string()
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -16,7 +16,6 @@ module.exports = (signature, userId, accessToken) => {
|
|
|
16
16
|
.update(`${userId}:${accessToken}:${sigExpires}:${process.env.SIGNATURE_REF}`)
|
|
17
17
|
.digest('hex');
|
|
18
18
|
|
|
19
|
-
if (sigHmac.length !== expectedHmac.length) return false;
|
|
20
19
|
const isValid = crypto.timingSafeEqual(
|
|
21
20
|
Uint8Array.from([...sigHmac].map(c => c.charCodeAt(0))),
|
|
22
21
|
Uint8Array.from([...expectedHmac].map(c => c.charCodeAt(0)))
|
|
@@ -25,5 +24,5 @@ module.exports = (signature, userId, accessToken) => {
|
|
|
25
24
|
|
|
26
25
|
return true;
|
|
27
26
|
}
|
|
28
|
-
catch { return false; }
|
|
27
|
+
catch (err) { console.log('Signature Verification Failed', err); return false; }
|
|
29
28
|
};
|
package/template/src/index.js
CHANGED
|
@@ -10,6 +10,7 @@ const { SimpleSupabase } = require('simple-supabase');
|
|
|
10
10
|
const { createClient: createRedisClient } = require('redis');
|
|
11
11
|
const Cryptr = require('cryptr');
|
|
12
12
|
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
13
14
|
|
|
14
15
|
// Start System
|
|
15
16
|
|
|
@@ -17,8 +18,11 @@ const fs = require('fs');
|
|
|
17
18
|
|
|
18
19
|
// Handle Errors
|
|
19
20
|
|
|
20
|
-
process.on('unhandledRejection',
|
|
21
|
-
process.on('uncaughtException',
|
|
21
|
+
process.on('unhandledRejection', (error) => console.log('Unhandled Rejection', error));
|
|
22
|
+
process.on('uncaughtException', (error) => {
|
|
23
|
+
console.log('Uncaught Exception', error);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
22
26
|
|
|
23
27
|
// Database / Redis
|
|
24
28
|
|
|
@@ -47,9 +51,9 @@ const fs = require('fs');
|
|
|
47
51
|
|
|
48
52
|
// Start Services
|
|
49
53
|
|
|
50
|
-
fs.readdirSync('
|
|
54
|
+
fs.readdirSync(path.join(__dirname, 'services'))
|
|
51
55
|
.filter(f => f.endsWith('.js') && !f.startsWith('_'))
|
|
52
|
-
.
|
|
56
|
+
.forEach(service => {
|
|
53
57
|
const serviceFile = require(`@/services/${service}`);
|
|
54
58
|
serviceFile();
|
|
55
59
|
});
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// Packages / Helpers
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
4
5
|
const express = require('express');
|
|
5
6
|
const cors = require('cors');
|
|
7
|
+
const { RateLimiterRedis } = require('rate-limiter-flexible');
|
|
6
8
|
const verifySignature = require('@/helpers/verifySignature');
|
|
9
|
+
const { global } = require('@/config/global');
|
|
10
|
+
const { config } = require('@/config/config');
|
|
7
11
|
|
|
8
12
|
module.exports = async () => {
|
|
9
13
|
|
|
@@ -20,34 +24,69 @@ module.exports = async () => {
|
|
|
20
24
|
|
|
21
25
|
api.use(cors({
|
|
22
26
|
origin: (origin, callback) => {
|
|
23
|
-
if (allowedOrigins.includes(origin)) return callback(null, true);
|
|
27
|
+
if (!origin || allowedOrigins.includes(origin)) return callback(null, true);
|
|
24
28
|
return callback(new Error('Not allowed by CORS'));
|
|
25
29
|
}
|
|
26
30
|
}));
|
|
27
31
|
|
|
28
32
|
// Get Endpoints
|
|
29
33
|
|
|
30
|
-
const endpoints = fs.readdirSync('
|
|
34
|
+
const endpoints = fs.readdirSync(path.join(__dirname, '..', 'api'), { recursive: true })
|
|
31
35
|
.filter(f => f.endsWith('.js') && !f.startsWith('_'))
|
|
32
36
|
.map(endpoint => endpoint.replace('.js', ''));
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
const slashEndpoints = endpoints.map(e => `/${e}`);
|
|
35
39
|
|
|
40
|
+
// Endpoint Data
|
|
41
|
+
|
|
42
|
+
let endpointRateLimiter = {};
|
|
36
43
|
let endpointSettings = {};
|
|
44
|
+
let endpointSchemas = {};
|
|
37
45
|
|
|
38
|
-
endpoints.
|
|
39
|
-
try {
|
|
40
|
-
|
|
46
|
+
endpoints.forEach(endpoint => {
|
|
47
|
+
try {
|
|
48
|
+
const data = require(`@/api/${endpoint}`);
|
|
49
|
+
endpointRateLimiter[`/${endpoint}`] = new RateLimiterRedis({
|
|
50
|
+
storeClient: global.redis,
|
|
51
|
+
keyPrefix: `${process.env.PROJECT_ID}_request_rate_limit_${endpoint}`,
|
|
52
|
+
...(data?.rate_limit || { points: 1, duration: 2 }),
|
|
53
|
+
useRedisPackage: true
|
|
54
|
+
});
|
|
55
|
+
endpointSettings[`/${endpoint}`] = data?.settings || {};
|
|
56
|
+
endpointSchemas[`/${endpoint}`] = data?.schemas || {};
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
console.log('API Endpoint Data Listing Error', `Endpoint: ${endpoint}`, err);
|
|
60
|
+
endpointSettings[`/${endpoint}`] = {};
|
|
61
|
+
endpointSchemas[`/${endpoint}`] = {};
|
|
62
|
+
}
|
|
41
63
|
});
|
|
42
64
|
|
|
43
65
|
// API Authentication
|
|
44
66
|
|
|
45
67
|
api.use('/', async (req, res, next) => {
|
|
46
68
|
|
|
47
|
-
// Get Endpoint
|
|
69
|
+
// Get Endpoint Data
|
|
48
70
|
|
|
49
71
|
const endpoint = req?.path || '';
|
|
72
|
+
const rateLimiter = endpointRateLimiter[endpoint];
|
|
50
73
|
const settings = endpointSettings[endpoint];
|
|
74
|
+
const schemas = endpointSchemas[endpoint];
|
|
75
|
+
|
|
76
|
+
// Check Endpoint
|
|
77
|
+
|
|
78
|
+
if (!slashEndpoints.includes(endpoint)) return res.sendStatus(404);
|
|
79
|
+
|
|
80
|
+
// Check Schemas
|
|
81
|
+
|
|
82
|
+
const invalidSchema = ['query','headers','body']
|
|
83
|
+
.filter(schema => schemas[schema])
|
|
84
|
+
.map(schema => {
|
|
85
|
+
return { id: schema, parsed: schemas[schema].safeParse(req[schema] || {}) };
|
|
86
|
+
})
|
|
87
|
+
.find(schema => !schema?.parsed?.success);
|
|
88
|
+
|
|
89
|
+
if (invalidSchema?.id) return res.status(400).json({ error: 'Missing params!' });
|
|
51
90
|
|
|
52
91
|
// Check Bypass
|
|
53
92
|
|
|
@@ -58,7 +97,7 @@ module.exports = async () => {
|
|
|
58
97
|
|
|
59
98
|
// Public Endpoint
|
|
60
99
|
|
|
61
|
-
if (endpoint.includes(
|
|
100
|
+
if (endpoint.includes(`/v${config.api_version}/public`)) return next();
|
|
62
101
|
|
|
63
102
|
// Private Endpoint
|
|
64
103
|
|
|
@@ -71,6 +110,11 @@ module.exports = async () => {
|
|
|
71
110
|
|
|
72
111
|
if (!userId || !accessToken || !signature) return res.sendStatus(401);
|
|
73
112
|
|
|
113
|
+
// Check Rate Limit
|
|
114
|
+
|
|
115
|
+
try { await rateLimiter.consume(userId, 1); }
|
|
116
|
+
catch (err) { return res.status(429).json({ error: 'Too many requests!', resets_at: new Date(Date.now() + (err?.msBeforeNext || 0)).toISOString() }); }
|
|
117
|
+
|
|
74
118
|
// Verify Signature
|
|
75
119
|
|
|
76
120
|
const signatureIsValid = verifySignature(signature, userId, accessToken);
|
|
@@ -84,12 +128,12 @@ module.exports = async () => {
|
|
|
84
128
|
|
|
85
129
|
return next();
|
|
86
130
|
}
|
|
87
|
-
catch { return res.sendStatus(500); }
|
|
131
|
+
catch (err) { console.log('API Middleware Error', `Endpoint: ${endpoint}`, err); return res.sendStatus(500); }
|
|
88
132
|
});
|
|
89
133
|
|
|
90
134
|
// Start API Endpoints
|
|
91
135
|
|
|
92
|
-
endpoints.
|
|
136
|
+
endpoints.forEach(endpoint => {
|
|
93
137
|
|
|
94
138
|
let endpointMethods = require(`@/api/${endpoint}`);
|
|
95
139
|
|
|
@@ -100,11 +144,12 @@ module.exports = async () => {
|
|
|
100
144
|
|
|
101
145
|
try {
|
|
102
146
|
const result = await method({ userId: req.headers['user-id'], req, res, endpoint, headers: req.headers || {}, body: req?.body || {}, query: req?.query || {} });
|
|
103
|
-
return res.status(result?.code || 200).json(result?.json
|
|
147
|
+
return res.status(result?.code || 200).json(result?.json);
|
|
104
148
|
}
|
|
105
|
-
catch {
|
|
149
|
+
catch (err) {
|
|
150
|
+
console.log('API Error', `Endpoint: ${endpoint}`, err);
|
|
106
151
|
try { return res.sendStatus(500); }
|
|
107
|
-
catch
|
|
152
|
+
catch {}
|
|
108
153
|
}
|
|
109
154
|
});
|
|
110
155
|
});
|