chadstart 1.0.3 → 1.0.4
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/core/api-generator.js +2 -2
- package/core/auth.js +1 -4
- package/core/db.js +1 -0
- package/core/file-storage.js +13 -15
- package/core/migrations.js +1 -52
- package/core/oauth.js +2 -8
- package/core/seeder.js +2 -2
- package/package.json +1 -1
package/core/api-generator.js
CHANGED
|
@@ -310,10 +310,10 @@ function _apiKeyPermGuard(operation, entity) {
|
|
|
310
310
|
return (req, res, next) => {
|
|
311
311
|
if (!req._apiKeyPermissions) return next();
|
|
312
312
|
const { operations, entities: keyEntities } = req._apiKeyPermissions;
|
|
313
|
-
if (operations
|
|
313
|
+
if (operations?.length && !operations.includes(operation)) {
|
|
314
314
|
return res.status(403).json({ error: 'API key does not have permission for this operation' });
|
|
315
315
|
}
|
|
316
|
-
if (keyEntities
|
|
316
|
+
if (keyEntities?.length && !keyEntities.includes(entity.slug)) {
|
|
317
317
|
return res.status(403).json({ error: 'API key does not have access to this entity' });
|
|
318
318
|
}
|
|
319
319
|
next();
|
package/core/auth.js
CHANGED
|
@@ -16,6 +16,7 @@ const crypto = require('crypto');
|
|
|
16
16
|
const jwt = require('jsonwebtoken');
|
|
17
17
|
const bcrypt = require('bcryptjs');
|
|
18
18
|
const db = require('./db');
|
|
19
|
+
const { q: _q, DB_ENGINE: _DB_ENGINE } = db;
|
|
19
20
|
const logger = require('../utils/logger');
|
|
20
21
|
|
|
21
22
|
const API_KEY_PREFIX = 'cs_';
|
|
@@ -29,10 +30,6 @@ const JWT_SECRET = process.env.JWT_SECRET || process.env.TOKEN_SECRET_KEY || (()
|
|
|
29
30
|
const JWT_EXPIRES = process.env.JWT_EXPIRES || '7d';
|
|
30
31
|
const BCRYPT_ROUNDS = 10;
|
|
31
32
|
|
|
32
|
-
// Quote an identifier for the current database engine (mirrors db.js helper)
|
|
33
|
-
const _DB_ENGINE = (process.env.DB_ENGINE || 'sqlite').toLowerCase();
|
|
34
|
-
function _q(name) { return _DB_ENGINE === 'mysql' ? `\`${name}\`` : `"${name}"`; }
|
|
35
|
-
|
|
36
33
|
// Column types for the API keys table (must be indexable in all engines)
|
|
37
34
|
const _ID_T = _DB_ENGINE === 'mysql' ? 'VARCHAR(36)' : 'TEXT';
|
|
38
35
|
const _HASH_T = _DB_ENGINE === 'mysql' ? 'VARCHAR(64)' : 'TEXT';
|
package/core/db.js
CHANGED
|
@@ -552,6 +552,7 @@ async function closeDb() {
|
|
|
552
552
|
}
|
|
553
553
|
|
|
554
554
|
module.exports = {
|
|
555
|
+
DB_ENGINE, q, sqlType, idColType, authStrType, toPgPlaceholders,
|
|
555
556
|
initDb, syncSchema, getDb, generateUUID, closeDb,
|
|
556
557
|
exec, queryAll, queryOne, queryRun,
|
|
557
558
|
findAll, findAllSimple, findById, create, update, remove,
|
package/core/file-storage.js
CHANGED
|
@@ -4,6 +4,18 @@ const path = require('path');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const express = require('express');
|
|
6
6
|
const logger = require('../utils/logger');
|
|
7
|
+
const { sanitizeFilename } = require('./upload');
|
|
8
|
+
|
|
9
|
+
// Lazy-load busboy (shared with upload.js)
|
|
10
|
+
function getBusboy() {
|
|
11
|
+
try {
|
|
12
|
+
return require('busboy');
|
|
13
|
+
} catch {
|
|
14
|
+
throw new Error(
|
|
15
|
+
'busboy is required for file uploads. Install it with: npm install busboy'
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
7
19
|
|
|
8
20
|
/**
|
|
9
21
|
* Register file storage routes for all buckets defined in core.files.
|
|
@@ -50,11 +62,7 @@ function registerFileRoutes(app, core) {
|
|
|
50
62
|
file.resume();
|
|
51
63
|
return;
|
|
52
64
|
}
|
|
53
|
-
|
|
54
|
-
const safeName = path
|
|
55
|
-
.basename(filename)
|
|
56
|
-
.replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
57
|
-
.replace(/^\.+/, '_');
|
|
65
|
+
const safeName = sanitizeFilename(filename);
|
|
58
66
|
const dest = path.join(bucketPath, safeName);
|
|
59
67
|
const writeStream = fs.createWriteStream(dest);
|
|
60
68
|
file.pipe(writeStream);
|
|
@@ -84,14 +92,4 @@ function registerFileRoutes(app, core) {
|
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
|
|
87
|
-
function getBusboy() {
|
|
88
|
-
try {
|
|
89
|
-
return require('busboy');
|
|
90
|
-
} catch {
|
|
91
|
-
throw new Error(
|
|
92
|
-
'busboy is required for file uploads. Install it with: npm install busboy'
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
95
|
module.exports = { registerFileRoutes };
|
package/core/migrations.js
CHANGED
|
@@ -7,6 +7,7 @@ const YAML = require('yaml');
|
|
|
7
7
|
const logger = require('../utils/logger');
|
|
8
8
|
|
|
9
9
|
const { buildCore, toSnakeCase } = require('./entity-engine');
|
|
10
|
+
const { DB_ENGINE, q, sqlType, idColType, authStrType } = require('./db');
|
|
10
11
|
|
|
11
12
|
// ─── Git helpers ──────────────────────────────────────────────────────────────
|
|
12
13
|
|
|
@@ -46,58 +47,6 @@ function loadCurrentYaml(yamlPath) {
|
|
|
46
47
|
return YAML.parse(fs.readFileSync(resolved, 'utf8'));
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
// ─── SQL generation helpers ───────────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
const DB_ENGINE = (process.env.DB_ENGINE || 'sqlite').toLowerCase();
|
|
52
|
-
|
|
53
|
-
const SQL_TYPE_SQLITE = {
|
|
54
|
-
text: 'TEXT', string: 'TEXT', richText: 'TEXT',
|
|
55
|
-
integer: 'INTEGER', int: 'INTEGER',
|
|
56
|
-
number: 'REAL', float: 'REAL', real: 'REAL', money: 'REAL',
|
|
57
|
-
boolean: 'INTEGER', bool: 'INTEGER',
|
|
58
|
-
date: 'TEXT', timestamp: 'TEXT', email: 'TEXT', link: 'TEXT',
|
|
59
|
-
password: 'TEXT', choice: 'TEXT', location: 'TEXT',
|
|
60
|
-
file: 'TEXT', image: 'TEXT', group: 'TEXT', json: 'TEXT',
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const SQL_TYPE_PG = {
|
|
64
|
-
text: 'TEXT', string: 'TEXT', richText: 'TEXT',
|
|
65
|
-
integer: 'INTEGER', int: 'INTEGER',
|
|
66
|
-
number: 'NUMERIC', float: 'NUMERIC', real: 'NUMERIC', money: 'NUMERIC',
|
|
67
|
-
boolean: 'BOOLEAN', bool: 'BOOLEAN',
|
|
68
|
-
date: 'TEXT', timestamp: 'TEXT', email: 'TEXT', link: 'TEXT',
|
|
69
|
-
password: 'TEXT', choice: 'TEXT', location: 'TEXT',
|
|
70
|
-
file: 'TEXT', image: 'TEXT', group: 'TEXT', json: 'TEXT',
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const SQL_TYPE_MYSQL = {
|
|
74
|
-
text: 'TEXT', string: 'TEXT', richText: 'TEXT',
|
|
75
|
-
integer: 'INT', int: 'INT',
|
|
76
|
-
number: 'DECIMAL(15,4)', float: 'DECIMAL(15,4)', real: 'DECIMAL(15,4)', money: 'DECIMAL(15,4)',
|
|
77
|
-
boolean: 'TINYINT(1)', bool: 'TINYINT(1)',
|
|
78
|
-
date: 'TEXT', timestamp: 'TEXT', email: 'TEXT', link: 'TEXT',
|
|
79
|
-
password: 'TEXT', choice: 'TEXT', location: 'TEXT',
|
|
80
|
-
file: 'TEXT', image: 'TEXT', group: 'TEXT', json: 'TEXT',
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
function sqlType(type) {
|
|
84
|
-
if (DB_ENGINE === 'postgres') return SQL_TYPE_PG[type] || 'TEXT';
|
|
85
|
-
if (DB_ENGINE === 'mysql') return SQL_TYPE_MYSQL[type] || 'TEXT';
|
|
86
|
-
return SQL_TYPE_SQLITE[type] || 'TEXT';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function idColType() {
|
|
90
|
-
return DB_ENGINE === 'mysql' ? 'VARCHAR(36)' : 'TEXT';
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function authStrType() {
|
|
94
|
-
return DB_ENGINE === 'mysql' ? 'VARCHAR(191)' : 'TEXT';
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function q(name) {
|
|
98
|
-
if (DB_ENGINE === 'mysql') return `\`${name}\``;
|
|
99
|
-
return `"${name}"`;
|
|
100
|
-
}
|
|
101
50
|
|
|
102
51
|
// ─── Diff engine ──────────────────────────────────────────────────────────────
|
|
103
52
|
|
package/core/oauth.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
const crypto = require('crypto');
|
|
16
16
|
const Grant = require('grant').express();
|
|
17
17
|
const rateLimit = require('express-rate-limit');
|
|
18
|
-
const { signToken } = require('./auth');
|
|
18
|
+
const { signToken, omitPassword } = require('./auth');
|
|
19
19
|
const db = require('./db');
|
|
20
20
|
const logger = require('../utils/logger');
|
|
21
21
|
|
|
@@ -195,7 +195,7 @@ function registerOAuthRoutes(app, core, emit) {
|
|
|
195
195
|
return res.redirect(`${successRedirect}${sep}token=${encodeURIComponent(token)}`);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
res.json({ token, user:
|
|
198
|
+
res.json({ token, user: omitPassword(user) });
|
|
199
199
|
} catch (e) {
|
|
200
200
|
logger.error('OAuth callback error:', e.message);
|
|
201
201
|
return _handleError(res, oauthConfig, e.message);
|
|
@@ -245,12 +245,6 @@ async function _findOrCreateOAuthUser(entity, { email, name, provider, providerI
|
|
|
245
245
|
return created;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
function _omitSensitive(user) {
|
|
249
|
-
if (!user) return null;
|
|
250
|
-
const { password: _, ...rest } = user;
|
|
251
|
-
return rest;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
248
|
function _handleError(res, oauthConfig, message) {
|
|
255
249
|
const errorRedirect = oauthConfig.errorRedirect;
|
|
256
250
|
if (errorRedirect) {
|
package/core/seeder.js
CHANGED
|
@@ -44,7 +44,7 @@ function fakeValueForProp(prop, idx, groups = {}) {
|
|
|
44
44
|
const { name, type, options } = prop;
|
|
45
45
|
const n = idx + 1;
|
|
46
46
|
|
|
47
|
-
if (
|
|
47
|
+
if (Array.isArray(options) && options.length) {
|
|
48
48
|
return options[randomInt(0, options.length - 1)];
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -184,7 +184,7 @@ async function seedAll(core) {
|
|
|
184
184
|
for (const rel of entity.belongsTo || []) {
|
|
185
185
|
const parentName = typeof rel === 'string' ? rel : (rel.entity || rel.name);
|
|
186
186
|
const parentEntity = core.entities[parentName];
|
|
187
|
-
if (parentEntity && seededIds[parentName]
|
|
187
|
+
if (parentEntity && seededIds[parentName]?.length) {
|
|
188
188
|
const fk = `${parentEntity.tableName}_id`;
|
|
189
189
|
const parentIds = seededIds[parentName];
|
|
190
190
|
record[fk] = parentIds[randomInt(0, parentIds.length - 1)];
|