create-dacosta-proj 1.0.15 → 1.0.16
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 +1 -1
- package/template/.env.template +4 -12
- package/template/jsconfig.json +1 -0
- package/template/package.json +1 -5
- package/template/src/config/global.js +0 -2
- package/template/src/index.js +5 -11
- package/template/src/api/v1/public/signature.js +0 -45
- package/template/src/config/config.js +0 -4
- package/template/src/helpers/verifySignature.js +0 -28
- package/template/src/services/api.js +0 -160
package/package.json
CHANGED
package/template/.env.template
CHANGED
|
@@ -7,16 +7,8 @@ PROJECT_WEB_PORT=
|
|
|
7
7
|
|
|
8
8
|
# Supabase
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# API
|
|
14
|
-
|
|
15
|
-
API_PORT=
|
|
16
|
-
API_BYPASS_TOKEN=
|
|
10
|
+
SUPABASE_PROJECT_URL_DEV=
|
|
11
|
+
SUPABASE_SECRET_KEY_DEV=
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
ENCRYPTION_KEY=
|
|
21
|
-
SIGNATURE_KEY=
|
|
22
|
-
SIGNATURE_REF=
|
|
13
|
+
SUPABASE_PROJECT_URL=
|
|
14
|
+
SUPABASE_SECRET_KEY=
|
package/template/jsconfig.json
CHANGED
package/template/package.json
CHANGED
|
@@ -8,15 +8,11 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"cors": "^2.8.6",
|
|
11
|
-
"cryptr": "^6.4.0",
|
|
12
11
|
"dotenv": "^17.3.1",
|
|
13
|
-
"express": "^5.2.1",
|
|
14
12
|
"module-alias": "^2.3.4",
|
|
15
13
|
"nanoid": "^5.1.6",
|
|
16
|
-
"rate-limiter-flexible": "^9.1.1",
|
|
17
14
|
"redis": "^5.11.0",
|
|
18
|
-
"simple-supabase": "^2.0.
|
|
19
|
-
"zod": "^4.3.6"
|
|
15
|
+
"simple-supabase": "^2.0.10"
|
|
20
16
|
},
|
|
21
17
|
"devDependencies": {
|
|
22
18
|
"eslint": "^9.39.3",
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
* @type {{
|
|
3
3
|
* db: import('simple-supabase').DB | null,
|
|
4
4
|
* redis: import('redis').RedisClientType | null,
|
|
5
|
-
* cryptr: import('cryptr'),
|
|
6
5
|
* }}
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
const global = {
|
|
10
9
|
db: null,
|
|
11
10
|
redis: null,
|
|
12
|
-
cryptr: null,
|
|
13
11
|
};
|
|
14
12
|
module.exports = { global };
|
package/template/src/index.js
CHANGED
|
@@ -5,10 +5,10 @@ require('module-alias/register');
|
|
|
5
5
|
|
|
6
6
|
// Packages / Helpers
|
|
7
7
|
|
|
8
|
+
const dev = require('@/../devref.json');
|
|
8
9
|
const { global } = require('@/config/global');
|
|
9
10
|
const { SimpleSupabase } = require('simple-supabase');
|
|
10
11
|
const { createClient: createRedisClient } = require('redis');
|
|
11
|
-
const Cryptr = require('cryptr');
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
|
|
@@ -26,11 +26,11 @@ const path = require('path');
|
|
|
26
26
|
|
|
27
27
|
// Database / Redis
|
|
28
28
|
|
|
29
|
+
const projectUrl = process.env[`SUPABASE_PROJECT_URL${dev ? '_DEV' : ''}`]
|
|
30
|
+
const serviceKey = process.env[`SUPABASE_SECRET_KEY${dev ? '_DEV' : ''}`]
|
|
31
|
+
|
|
29
32
|
let db = await SimpleSupabase({
|
|
30
|
-
credentials: {
|
|
31
|
-
projectUrl: process.env.SUPABASE_PROJECT_URL,
|
|
32
|
-
serviceKey: process.env.SUPABASE_SECRET_KEY
|
|
33
|
-
},
|
|
33
|
+
credentials: { projectUrl, serviceKey },
|
|
34
34
|
redisPrefix: process.env.PROJECT_ID
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -43,12 +43,6 @@ const path = require('path');
|
|
|
43
43
|
|
|
44
44
|
global.redis = redis;
|
|
45
45
|
|
|
46
|
-
// Cryptr
|
|
47
|
-
|
|
48
|
-
const cryptr = new Cryptr(process.env.ENCRYPTION_KEY);
|
|
49
|
-
|
|
50
|
-
global.cryptr = cryptr;
|
|
51
|
-
|
|
52
46
|
// Start Services
|
|
53
47
|
|
|
54
48
|
fs.readdirSync(path.join(__dirname, 'services'))
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// Packages / Helpers
|
|
2
|
-
|
|
3
|
-
const crypto = require('crypto');
|
|
4
|
-
const { global } = require('@/config/global');
|
|
5
|
-
const { default: zod } = require('zod');
|
|
6
|
-
|
|
7
|
-
async function GET({ headers }) {
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
// Get Details
|
|
11
|
-
|
|
12
|
-
const userId = headers['user-id'];
|
|
13
|
-
const accessToken = headers['access-token'];
|
|
14
|
-
|
|
15
|
-
// Format Signature
|
|
16
|
-
|
|
17
|
-
const expires = Date.now() + (1_000 * 60 * 60 * 1); // 1 Hour
|
|
18
|
-
|
|
19
|
-
const hmac = crypto.createHmac('sha256', process.env.SIGNATURE_KEY)
|
|
20
|
-
.update(`${userId}:${accessToken}:${expires}:${process.env.SIGNATURE_REF}`)
|
|
21
|
-
.digest('hex');
|
|
22
|
-
|
|
23
|
-
const signature = global.cryptr.encrypt(`${expires}.${hmac}`);
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
code: 200,
|
|
27
|
-
json: { signature }
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
catch (err) { console.log('Signature Generation Failed', err); return { code: 500, json: { error: 'Failed to generate signature!' } }; }
|
|
31
|
-
}
|
|
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
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
const crypto = require('crypto');
|
|
2
|
-
const { global } = require('@/config/global');
|
|
3
|
-
|
|
4
|
-
module.exports = (signature, userId, accessToken) => {
|
|
5
|
-
|
|
6
|
-
try {
|
|
7
|
-
signature = global.cryptr.decrypt(signature);
|
|
8
|
-
|
|
9
|
-
const parts = signature.split('.');
|
|
10
|
-
if (parts.length !== 2) return false;
|
|
11
|
-
const [sigExpires, sigHmac] = parts;
|
|
12
|
-
|
|
13
|
-
if (Date.now() > Number(sigExpires)) return false;
|
|
14
|
-
|
|
15
|
-
const expectedHmac = crypto.createHmac('sha256', process.env.SIGNATURE_KEY)
|
|
16
|
-
.update(`${userId}:${accessToken}:${sigExpires}:${process.env.SIGNATURE_REF}`)
|
|
17
|
-
.digest('hex');
|
|
18
|
-
|
|
19
|
-
const isValid = crypto.timingSafeEqual(
|
|
20
|
-
Uint8Array.from([...sigHmac].map(c => c.charCodeAt(0))),
|
|
21
|
-
Uint8Array.from([...expectedHmac].map(c => c.charCodeAt(0)))
|
|
22
|
-
);
|
|
23
|
-
if (!isValid) return false;
|
|
24
|
-
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
catch (err) { console.log('Signature Verification Failed', err); return false; }
|
|
28
|
-
};
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
// Packages / Helpers
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const express = require('express');
|
|
6
|
-
const cors = require('cors');
|
|
7
|
-
const { RateLimiterRedis } = require('rate-limiter-flexible');
|
|
8
|
-
const verifySignature = require('@/helpers/verifySignature');
|
|
9
|
-
const { global } = require('@/config/global');
|
|
10
|
-
const { config } = require('@/config/config');
|
|
11
|
-
|
|
12
|
-
module.exports = async () => {
|
|
13
|
-
|
|
14
|
-
// Start API
|
|
15
|
-
|
|
16
|
-
const api = express();
|
|
17
|
-
api.use(express.urlencoded({ limit: '10mb', extended: true }));
|
|
18
|
-
api.use(express.json({ limit: '10mb' }));
|
|
19
|
-
|
|
20
|
-
const allowedOrigins = [
|
|
21
|
-
`https://${process.env.PROJECT_WEB_DOMAIN}`,
|
|
22
|
-
`http://localhost:${process.env.PROJECT_WEB_PORT}`
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
api.use(cors({
|
|
26
|
-
origin: (origin, callback) => {
|
|
27
|
-
if (!origin || allowedOrigins.includes(origin)) return callback(null, true);
|
|
28
|
-
return callback(new Error('Not allowed by CORS'));
|
|
29
|
-
}
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
// Get Endpoints
|
|
33
|
-
|
|
34
|
-
const endpoints = fs.readdirSync(path.join(__dirname, '..', 'api'), { recursive: true })
|
|
35
|
-
.filter(f => f.endsWith('.js') && !f.startsWith('_'))
|
|
36
|
-
.map(endpoint => endpoint.replace('.js', ''));
|
|
37
|
-
|
|
38
|
-
const slashEndpoints = endpoints.map(e => `/${e}`);
|
|
39
|
-
|
|
40
|
-
// Endpoint Data
|
|
41
|
-
|
|
42
|
-
let endpointRateLimiter = {};
|
|
43
|
-
let endpointSettings = {};
|
|
44
|
-
let endpointSchemas = {};
|
|
45
|
-
|
|
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
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// API Authentication
|
|
66
|
-
|
|
67
|
-
api.use('/', async (req, res, next) => {
|
|
68
|
-
|
|
69
|
-
// Get Endpoint Data
|
|
70
|
-
|
|
71
|
-
const endpoint = req?.path || '';
|
|
72
|
-
const rateLimiter = endpointRateLimiter[endpoint];
|
|
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!' });
|
|
90
|
-
|
|
91
|
-
// Check Bypass
|
|
92
|
-
|
|
93
|
-
if (settings?.requires_bypass) {
|
|
94
|
-
const bypass = req.headers['bypass'];
|
|
95
|
-
if (bypass !== process.env.API_BYPASS_TOKEN) return res.sendStatus(401);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Public Endpoint
|
|
99
|
-
|
|
100
|
-
if (endpoint.includes(`/v${config.api_version}/public`)) return next();
|
|
101
|
-
|
|
102
|
-
// Private Endpoint
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
// Get Request Details
|
|
106
|
-
|
|
107
|
-
const userId = req.headers['user-id'];
|
|
108
|
-
const accessToken = req.headers['access-token'];
|
|
109
|
-
const signature = req.headers['signature'];
|
|
110
|
-
|
|
111
|
-
if (!userId || !accessToken || !signature) return res.sendStatus(401);
|
|
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
|
-
|
|
118
|
-
// Verify Signature
|
|
119
|
-
|
|
120
|
-
const signatureIsValid = verifySignature(signature, userId, accessToken);
|
|
121
|
-
if (!signatureIsValid) return res.sendStatus(401);
|
|
122
|
-
|
|
123
|
-
// Verify User
|
|
124
|
-
|
|
125
|
-
// TODO: Create user verification logic!
|
|
126
|
-
|
|
127
|
-
// Return
|
|
128
|
-
|
|
129
|
-
return next();
|
|
130
|
-
}
|
|
131
|
-
catch (err) { console.log('API Middleware Error', `Endpoint: ${endpoint}`, err); return res.sendStatus(500); }
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Start API Endpoints
|
|
135
|
-
|
|
136
|
-
endpoints.forEach(endpoint => {
|
|
137
|
-
|
|
138
|
-
let endpointMethods = require(`@/api/${endpoint}`);
|
|
139
|
-
|
|
140
|
-
api.all(`/${endpoint}`, async (req, res) => {
|
|
141
|
-
|
|
142
|
-
const method = endpointMethods[(req?.method || 'GET').toUpperCase()];
|
|
143
|
-
if (!method) return res.sendStatus(405);
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
const result = await method({ userId: req.headers['user-id'], req, res, endpoint, headers: req.headers || {}, body: req?.body || {}, query: req?.query || {} });
|
|
147
|
-
return res.status(result?.code || 200).json(result?.json);
|
|
148
|
-
}
|
|
149
|
-
catch (err) {
|
|
150
|
-
console.log('API Error', `Endpoint: ${endpoint}`, err);
|
|
151
|
-
try { return res.sendStatus(500); }
|
|
152
|
-
catch {}
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// Listen
|
|
158
|
-
|
|
159
|
-
api.listen(Number(process.env.API_PORT), () => console.log(`[API] Running on port ${process.env.API_PORT}`));
|
|
160
|
-
};
|