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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-dacosta-proj",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "bin": {
5
5
  "create-dacosta-proj": "./index.js"
6
6
  }
@@ -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 * 24); // 24 Hours
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
- module.exports = { GET, settings: { requires_bypass: true } };
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
+ };
@@ -0,0 +1,4 @@
1
+ const config = {
2
+ api_version: 1
3
+ };
4
+ module.exports = { config };
@@ -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
  };
@@ -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', async (error) => console.log('Unhandled Rejection', error));
21
- process.on('uncaughtException', async (error) => console.log('Uncaught Exception', error));
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('src/services')
54
+ fs.readdirSync(path.join(__dirname, 'services'))
51
55
  .filter(f => f.endsWith('.js') && !f.startsWith('_'))
52
- .map(service => {
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('src/api', { recursive: true })
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
- // Endpoint Settings
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.map(endpoint => {
39
- try { endpointSettings[`/${endpoint}`] = (require(`@/api/${endpoint}`))?.settings || {}; }
40
- catch { endpointSettings[`/${endpoint}`] = {}; }
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 Settings
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('/public')) return next();
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.map(endpoint => {
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 (err) { console.log(err); }
152
+ catch {}
108
153
  }
109
154
  });
110
155
  });