zyket 1.2.17 → 1.2.19
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/SECURITY.md +40 -0
- package/bin/cli.js +282 -202
- package/index.js +4 -3
- package/package.json +5 -1
- package/src/extensions/bullboard/index.js +14 -3
- package/src/extensions/interactive-storage/index.js +25 -9
- package/src/extensions/interactive-storage/routes/delete-folder.js +13 -1
- package/src/extensions/interactive-storage/routes/delete.js +14 -3
- package/src/extensions/interactive-storage/routes/download.js +22 -3
- package/src/extensions/interactive-storage/routes/info.js +13 -0
- package/src/services/auth/index.js +66 -42
- package/src/services/express/Express.js +32 -15
- package/src/services/express/RequireAdminMiddleware.js +14 -0
- package/src/services/express/RequireAuthMiddleware.js +46 -0
- package/src/services/express/index.js +7 -5
- package/src/services/socketio/AuthGuard.js +33 -0
- package/src/services/socketio/SocketIO.js +3 -1
- package/src/services/socketio/index.js +2 -1
- package/src/services/template-manager/index.js +1 -0
- package/src/templates/api-rest/.env.example +24 -0
- package/src/templates/api-rest/README.md +50 -0
- package/src/templates/api-rest/index.js +18 -0
- package/src/templates/api-rest/src/models/Task.js +18 -0
- package/src/templates/api-rest/src/routes/tasks/[id].js +42 -0
- package/src/templates/api-rest/src/routes/tasks/index.js +26 -0
- package/src/templates/api-rest/src/services/auth/auth.js +9 -0
- package/src/templates/api-rest/src/services/auth/index.js +23 -0
- package/src/templates/realtime-chat/.env.example +26 -0
- package/src/templates/realtime-chat/README.md +38 -0
- package/src/templates/realtime-chat/frontend/.env.example +3 -0
- package/src/templates/realtime-chat/frontend/index.html +12 -0
- package/src/templates/realtime-chat/frontend/main.jsx +18 -0
- package/src/templates/realtime-chat/frontend/src/hooks/useAuth.jsx +27 -0
- package/src/templates/realtime-chat/frontend/src/hooks/useChatSocket.jsx +29 -0
- package/src/templates/realtime-chat/frontend/src/middlewares/LoggedMiddleware.jsx +12 -0
- package/src/templates/realtime-chat/frontend/src/middlewares/NotLoggedMiddleware.jsx +12 -0
- package/src/templates/realtime-chat/frontend/src/store/storeAuth.jsx +11 -0
- package/src/templates/realtime-chat/frontend/src/views/AuthView.jsx +70 -0
- package/src/templates/realtime-chat/frontend/src/views/ChatView.jsx +69 -0
- package/src/templates/realtime-chat/frontend/styles.css +1 -0
- package/src/templates/realtime-chat/frontend/vite.config.js +7 -0
- package/src/templates/realtime-chat/index.js +14 -0
- package/src/templates/realtime-chat/src/guards/auth.js +3 -0
- package/src/templates/realtime-chat/src/handlers/connection.js +23 -0
- package/src/templates/realtime-chat/src/handlers/message.js +29 -0
- package/src/templates/realtime-chat/src/services/auth/auth.js +8 -0
- package/src/templates/realtime-chat/src/services/auth/index.js +19 -0
- package/src/templates/saas-multitenant/.env.example +22 -0
- package/src/templates/saas-multitenant/README.md +71 -0
- package/src/templates/saas-multitenant/frontend/.env.example +3 -0
- package/src/templates/saas-multitenant/frontend/index.html +12 -0
- package/src/templates/saas-multitenant/frontend/main.jsx +18 -0
- package/src/templates/saas-multitenant/frontend/src/hooks/useAuth.jsx +27 -0
- package/src/templates/saas-multitenant/frontend/src/hooks/useProjects.jsx +41 -0
- package/src/templates/saas-multitenant/frontend/src/middlewares/LoggedMiddleware.jsx +12 -0
- package/src/templates/saas-multitenant/frontend/src/middlewares/NotLoggedMiddleware.jsx +12 -0
- package/src/templates/saas-multitenant/frontend/src/store/storeAuth.jsx +13 -0
- package/src/templates/saas-multitenant/frontend/src/views/AuthView.jsx +70 -0
- package/src/templates/saas-multitenant/frontend/src/views/DashboardView.jsx +131 -0
- package/src/templates/saas-multitenant/frontend/styles.css +1 -0
- package/src/templates/saas-multitenant/frontend/vite.config.js +7 -0
- package/src/templates/saas-multitenant/index.js +14 -0
- package/src/templates/saas-multitenant/src/middlewares/RequireOrganization.js +22 -0
- package/src/templates/saas-multitenant/src/models/Project.js +17 -0
- package/src/templates/saas-multitenant/src/routes/admin/stats.js +15 -0
- package/src/templates/saas-multitenant/src/routes/projects/index.js +34 -0
- package/src/templates/saas-multitenant/src/services/auth/auth.js +8 -0
- package/src/templates/saas-multitenant/src/services/auth/index.js +43 -0
- package/src/utils/EnvManager.js +23 -0
package/SECURITY.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Seguridad — Zyket
|
|
2
|
+
|
|
3
|
+
Auditoría de seguridad del framework y estado de las correcciones.
|
|
4
|
+
Leyenda: `[x]` hecho · `[ ]` pendiente · 🔴 crítico · 🟠 alto · 🟡 medio · 🔵 bajo
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## ✅ Resuelto
|
|
9
|
+
|
|
10
|
+
- [x] 🔴 **`.env` ignorado en git.** Añadido `.env` / `*.sqlite` al `.gitignore` para evitar fuga de `AUTH_SECRET`, claves S3 y credenciales de BD.
|
|
11
|
+
- [x] 🔴 **`AUTH_SECRET` aleatorio + fail-closed.** `#addAuthEnvVariables` genera ahora un secreto con `crypto.randomBytes(32)` por proyecto y lo inyecta en `process.env` para el primer arranque. `#requireAuthSecret()` aborta el boot si el secreto falta o es uno de los valores estáticos conocidos. → [src/services/auth/index.js](src/services/auth/index.js)
|
|
12
|
+
- [x] 🔴 **Autenticación en Storage realmente aplicada.** Antes los `middlewares` del constructor se guardaban pero no se usaban; ahora se aplican a todas las rutas y métodos (auth antes de multer en `upload`). La autenticación se inyecta vía `new InteractiveStorageExtension({ middlewares: [authMiddleware] })`. → [src/extensions/interactive-storage/index.js](src/extensions/interactive-storage/index.js)
|
|
13
|
+
- [x] 🔴 **BullBoard fail-closed.** Si no hay `BULLBOARD_ADMIN_PASSWORD` ni `middlewares`, el panel **no se monta** (antes quedaba público). Soporta `middlewares` por constructor. → [src/extensions/bullboard/index.js](src/extensions/bullboard/index.js)
|
|
14
|
+
- [x] 🟠 **Inyección de cabecera en descarga.** `Content-Disposition` ahora sanea el nombre (basename + ASCII filtrado + `filename*=UTF-8''`) evitando breakout de comillas/CRLF. → [src/extensions/interactive-storage/routes/download.js](src/extensions/interactive-storage/routes/download.js)
|
|
15
|
+
- [x] 🟠 **Path traversal en `normalizePath`.** Se eliminan segmentos `..` / `.` además de normalizar slashes. → [src/extensions/interactive-storage/index.js](src/extensions/interactive-storage/index.js#L150)
|
|
16
|
+
- [x] 🟡 **Límite en borrado masivo.** `delete` ahora rechaza lotes mayores a `maxDeleteBatch` (por defecto 100, configurable en el constructor de la extensión) → evita borrado masivo / agotamiento de recursos en una sola petición. → [src/extensions/interactive-storage/routes/delete.js](src/extensions/interactive-storage/routes/delete.js)
|
|
17
|
+
- [x] 🟡 **`requireEmailVerification` personalizable.** Nuevo getter `requireEmailVerification` (por defecto `false`, manteniendo el comportamiento previo) sobreescribible al extender `AuthService`. → [src/services/auth/index.js](src/services/auth/index.js#L71)
|
|
18
|
+
- [x] 🟡 **Límite en borrado de carpeta.** `delete-folder` rechaza prefijos con más de `maxDeleteBatch` archivos (mismo tope configurable que `delete`). → [src/extensions/interactive-storage/routes/delete-folder.js](src/extensions/interactive-storage/routes/delete-folder.js)
|
|
19
|
+
- [x] 🟠 **Validación de `fileName` en `download`/`info`.** Se rechazan (`400`) claves con segmentos `..`/`.`, backslashes o que empiecen por `/` antes de llegar al cliente S3. → [download.js](src/extensions/interactive-storage/routes/download.js), [info.js](src/extensions/interactive-storage/routes/info.js)
|
|
20
|
+
- [x] 🟡 **Límites de payload configurables (default 10 MB).** Body JSON vía `HTTP_JSON_LIMIT` y socket vía `SOCKET_MAX_HTTP_BUFFER_SIZE` (ambos por defecto 10 MB, incluidos en el `.env` generado). → [Express.js](src/services/express/Express.js#L32), [SocketIO.js](src/services/socketio/SocketIO.js#L32), [EnvManager.js](src/utils/EnvManager.js)
|
|
21
|
+
- [x] 🟡 **Swagger protegible opcionalmente.** `SWAGGER_PASSWORD` activa HTTP Basic auth (usuario configurable con `SWAGGER_USER`, default `admin`); `DISABLE_SWAGGER=true` lo desactiva por completo. Si queda abierto se emite un warning. → [src/services/express/Express.js](src/services/express/Express.js#L44)
|
|
22
|
+
- [x] 🟡 **No se filtra `error.message` al cliente.** Las 4 respuestas `500` (rutas y middlewares, en `boot` y `registerRoutes`) devuelven ahora un genérico `Internal Server Error`; el detalle (incluido el stack) queda solo en los logs del servidor. → [src/services/express/Express.js](src/services/express/Express.js)
|
|
23
|
+
- [x] 🟠 **Cookies dependientes del entorno.** Ya no se fuerza `sameSite:"none"` + `secure` + cross-subdomain siempre. Por defecto `sameSite:"lax"` y `secure` solo en producción (login funciona en `http://localhost`); `AUTH_CROSS_DOMAIN=true` activa `none`+`secure`+cross-subdomain para front/back en dominios distintos (HTTPS). → [src/services/auth/index.js](src/services/auth/index.js#L126-L165)
|
|
24
|
+
- [x] 🟢 **Helpers de autorización en el framework.** Nuevos `RequireAuthMiddleware`, `RequireAdminMiddleware` (rutas) y `AuthGuard` (sockets), exportados desde `zyket`, para proteger rutas/eventos con la sesión de better-auth. → [src/services/express/RequireAuthMiddleware.js](src/services/express/RequireAuthMiddleware.js), [src/services/socketio/AuthGuard.js](src/services/socketio/AuthGuard.js)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## ⏳ Pendiente
|
|
29
|
+
|
|
30
|
+
### 🟠 Alto
|
|
31
|
+
- [ ] **CSRF de OAuth / account linking.** `account.skipStateCookieCheck: true` junto a `accountLinking.enabled: true` desactiva la verificación de `state` → riesgo de account takeover. Quitar `skipStateCookieCheck`. → [src/services/auth/index.js](src/services/auth/index.js#L189-L191)
|
|
32
|
+
- [ ] **Socket.IO CORS `origin:"*"` + guard por defecto que no bloquea.** Restringir origins (reutilizar `TRUSTED_ORIGINS`) y que el guard por defecto deniegue sin sesión válida. → [src/services/socketio/SocketIO.js](src/services/socketio/SocketIO.js#L32), [src/templates/default/src/guards/default.js](src/templates/default/src/guards/default.js)
|
|
33
|
+
|
|
34
|
+
### 🟡 Medio
|
|
35
|
+
- [ ] **Rate limiting** (login, reset password, upload) con `express-rate-limit`. → [src/services/express/Express.js](src/services/express/Express.js)
|
|
36
|
+
- [ ] **Cabeceras de seguridad** con `helmet`. → [src/services/express/Express.js](src/services/express/Express.js)
|
|
37
|
+
|
|
38
|
+
### 🔵 Bajo / Higiene
|
|
39
|
+
- [ ] **Reducir superficie de drivers de BD** (`sqlite3` + `better-sqlite3` + `pg` + `mariadb`).
|
|
40
|
+
- [ ] **Documentar guía de despliegue seguro** (variables obligatorias en producción, HTTPS, secretos).
|
package/bin/cli.js
CHANGED
|
@@ -1,202 +1,282 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const prompts = require('prompts');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { spawn } = require('child_process');
|
|
6
|
-
const TemplateManager = require('../src/services/template-manager');
|
|
7
|
-
const EnvManager = require('../src/utils/EnvManager');
|
|
8
|
-
const templateManager = new TemplateManager();
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const prompts = require('prompts');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const TemplateManager = require('../src/services/template-manager');
|
|
7
|
+
const EnvManager = require('../src/utils/EnvManager');
|
|
8
|
+
const templateManager = new TemplateManager();
|
|
9
|
+
|
|
10
|
+
const ZYKET_PKG = (() => {
|
|
11
|
+
try { return require('../package.json'); } catch { return { version: '1.0.0', dependencies: {} }; }
|
|
12
|
+
})();
|
|
13
|
+
const ZYKET_VERSION = ZYKET_PKG.version || '1.0.0';
|
|
14
|
+
|
|
15
|
+
const TEMPLATE_DESCRIPTIONS = {
|
|
16
|
+
default: 'Full-stack starter (React frontend + auth)',
|
|
17
|
+
'api-rest': 'Backend-only REST API (auth + CRUD)',
|
|
18
|
+
'saas-multitenant': 'Multi-tenant SaaS (orgs, roles) + dashboard',
|
|
19
|
+
'realtime-chat': 'Authenticated real-time chat (Socket.IO) + UI',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// ---- helpers ----------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
function templateChoices() {
|
|
25
|
+
const all = templateManager.getTemplates();
|
|
26
|
+
// Show "default" first, then the rest.
|
|
27
|
+
const ordered = ['default', ...all.filter((t) => t !== 'default')];
|
|
28
|
+
return ordered
|
|
29
|
+
.filter((t) => templateManager.exists(t))
|
|
30
|
+
.map((t) => ({ title: t, value: t, description: TEMPLATE_DESCRIPTIONS[t] || '' }));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Write every file of a template into the project, stripping the template-name
|
|
34
|
+
// prefix. Existing files are left untouched. Returns whether the template ships
|
|
35
|
+
// its own index.js. `exclude(rel)` can skip specific files.
|
|
36
|
+
function writeTemplateFiles(templateName, { exclude = () => false } = {}) {
|
|
37
|
+
const files = templateManager.getTemplate(templateName);
|
|
38
|
+
let hasIndex = false;
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
const rel = file.route.split('/').slice(1).join('/');
|
|
41
|
+
if (!rel || exclude(rel)) continue;
|
|
42
|
+
if (rel === 'index.js') hasIndex = true;
|
|
43
|
+
const dest = path.join(process.cwd(), rel);
|
|
44
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
45
|
+
if (!fs.existsSync(dest)) fs.writeFileSync(dest, file.content);
|
|
46
|
+
}
|
|
47
|
+
return { hasIndex };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Merge KEY=VALUE pairs from a copied .env.example onto the generated .env so a
|
|
51
|
+
// template can override defaults (e.g. DISABLE_VITE=false).
|
|
52
|
+
function applyEnvOverridesFromExample(envPath) {
|
|
53
|
+
const examplePath = path.join(process.cwd(), '.env.example');
|
|
54
|
+
if (!fs.existsSync(examplePath)) return;
|
|
55
|
+
for (const line of fs.readFileSync(examplePath, 'utf-8').split(/\r?\n/)) {
|
|
56
|
+
const trimmed = line.trim();
|
|
57
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
58
|
+
const eq = trimmed.indexOf('=');
|
|
59
|
+
if (eq === -1) continue;
|
|
60
|
+
const key = trimmed.slice(0, eq).trim();
|
|
61
|
+
const value = trimmed.slice(eq + 1).trim();
|
|
62
|
+
if (key) EnvManager.setEnvVariable(envPath, key, value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function writeDefaultIndex() {
|
|
67
|
+
const indexContent = `const { Kernel } = require('zyket');
|
|
68
|
+
|
|
69
|
+
const kernel = new Kernel({
|
|
70
|
+
services: [
|
|
71
|
+
['auth', require('./src/services/auth'), ["@service_container"]],
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
kernel.boot().then(() => {
|
|
76
|
+
console.log('Kernel booted successfully!');
|
|
77
|
+
}).catch((error) => {
|
|
78
|
+
console.error('Error booting kernel:', error);
|
|
79
|
+
});
|
|
80
|
+
`;
|
|
81
|
+
fs.writeFileSync(path.join(process.cwd(), 'index.js'), indexContent);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function templateRoutes(name) {
|
|
85
|
+
return templateManager.getTemplate(name).map((f) => f.route);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function templateHasFrontend(name) {
|
|
89
|
+
return templateRoutes(name).some((r) => r.startsWith(`${name}/frontend/`));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function templateUsesAuth(name) {
|
|
93
|
+
return templateRoutes(name).some((r) => r.startsWith(`${name}/src/services/auth/`));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Build the dependency set a generated project needs. The user's own code
|
|
97
|
+
// (frontend configs, handlers, stores) imports these DIRECTLY, so they must be
|
|
98
|
+
// real project dependencies — not just transitive deps of zyket. Versions are
|
|
99
|
+
// pinned to whatever this zyket release uses.
|
|
100
|
+
function depsForTemplate(template) {
|
|
101
|
+
const deps = { zyket: `^${ZYKET_VERSION}` };
|
|
102
|
+
if (template === 'default') return deps; // default init is backend-only
|
|
103
|
+
|
|
104
|
+
const copy = (names) => names.forEach((n) => {
|
|
105
|
+
const v = ZYKET_PKG.dependencies?.[n];
|
|
106
|
+
if (v) deps[n] = v;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (templateUsesAuth(template)) copy(['better-auth']);
|
|
110
|
+
if (templateHasFrontend(template)) {
|
|
111
|
+
copy([
|
|
112
|
+
'react', 'react-dom', 'react-router-dom', 'zustand', 'prop-types',
|
|
113
|
+
'vite', '@vitejs/plugin-react', '@tailwindcss/vite', 'tailwindcss',
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
if (template === 'realtime-chat') copy(['socket.io-client']);
|
|
117
|
+
|
|
118
|
+
return deps;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function ensurePackageJson(template) {
|
|
122
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
123
|
+
if (fs.existsSync(packageJsonPath)) return;
|
|
124
|
+
const packageJson = {
|
|
125
|
+
name: path.basename(process.cwd()),
|
|
126
|
+
version: '1.0.0',
|
|
127
|
+
description: 'Zyket application',
|
|
128
|
+
main: 'index.js',
|
|
129
|
+
scripts: { dev: 'node index.js' },
|
|
130
|
+
keywords: [],
|
|
131
|
+
author: '',
|
|
132
|
+
license: 'ISC',
|
|
133
|
+
dependencies: depsForTemplate(template),
|
|
134
|
+
};
|
|
135
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function npmInstall() {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const npmInstallProcess = spawn('npm', ['install'], { cwd: process.cwd(), stdio: 'inherit', shell: true });
|
|
141
|
+
npmInstallProcess.on('close', (code) => {
|
|
142
|
+
if (code === 0) resolve();
|
|
143
|
+
else reject(new Error(`npm install exited with code ${code}`));
|
|
144
|
+
});
|
|
145
|
+
npmInstallProcess.on('error', reject);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---- main -------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
(async () => {
|
|
152
|
+
process.stdout.write("[2J[0;0H");
|
|
153
|
+
await templateManager.boot();
|
|
154
|
+
|
|
155
|
+
const args = process.argv.slice(2);
|
|
156
|
+
const directCommand = args[0];
|
|
157
|
+
|
|
158
|
+
let actionToRun = null;
|
|
159
|
+
let templateArg = null;
|
|
160
|
+
|
|
161
|
+
if (directCommand === 'init') {
|
|
162
|
+
actionToRun = 'init-project';
|
|
163
|
+
templateArg = args[1] || null; // e.g. `npx zyket init api-rest`
|
|
164
|
+
} else {
|
|
165
|
+
const response = await prompts({
|
|
166
|
+
type: 'select',
|
|
167
|
+
name: 'value',
|
|
168
|
+
message: '[ZYKET] What do you want to do?',
|
|
169
|
+
choices: [
|
|
170
|
+
{ title: 'Initialize Project', value: 'init-project', description: 'Scaffold a new Zyket project from a template', disabled: false },
|
|
171
|
+
{ title: 'Install Template', value: 'install-template', description: 'Add a template into the current project', disabled: false },
|
|
172
|
+
],
|
|
173
|
+
initial: 0,
|
|
174
|
+
});
|
|
175
|
+
actionToRun = response.value;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!actionToRun) {
|
|
179
|
+
console.log('[ZYKET] No action selected. Exiting.');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const actions = {
|
|
184
|
+
'init-project': async (preselectedTemplate) => {
|
|
185
|
+
// Resolve which template to scaffold.
|
|
186
|
+
let template = preselectedTemplate && templateManager.exists(preselectedTemplate) ? preselectedTemplate : null;
|
|
187
|
+
if (preselectedTemplate && !template) {
|
|
188
|
+
console.log(`[ZYKET] Template '${preselectedTemplate}' not found. Pick one:`);
|
|
189
|
+
}
|
|
190
|
+
if (!template) {
|
|
191
|
+
const response = await prompts({
|
|
192
|
+
type: 'select',
|
|
193
|
+
name: 'value',
|
|
194
|
+
message: '[ZYKET] Choose a template to initialize',
|
|
195
|
+
choices: templateChoices(),
|
|
196
|
+
initial: 0,
|
|
197
|
+
});
|
|
198
|
+
template = response.value;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!template) {
|
|
202
|
+
console.log('[ZYKET] No template selected. Exiting.');
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const indexPath = path.join(process.cwd(), 'index.js');
|
|
207
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
208
|
+
|
|
209
|
+
if (fs.existsSync(indexPath)) {
|
|
210
|
+
const overwrite = await prompts({
|
|
211
|
+
type: 'confirm',
|
|
212
|
+
name: 'value',
|
|
213
|
+
message: '[ZYKET] index.js already exists. Continue scaffolding (existing files are kept)?',
|
|
214
|
+
initial: false,
|
|
215
|
+
});
|
|
216
|
+
if (!overwrite.value) {
|
|
217
|
+
console.log('[ZYKET] Initialization cancelled.');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log(`[ZYKET] Initializing with template '${template}'...`);
|
|
223
|
+
EnvManager.createEnvFile(envPath);
|
|
224
|
+
|
|
225
|
+
if (template === 'default') {
|
|
226
|
+
// Curated default: backend files only (no frontend, no socket guards/handlers).
|
|
227
|
+
writeTemplateFiles('default', {
|
|
228
|
+
exclude: (rel) => !(
|
|
229
|
+
(rel.startsWith('src/') || rel.startsWith('config/')) &&
|
|
230
|
+
!rel.startsWith('src/guards/') &&
|
|
231
|
+
!rel.startsWith('src/handlers/')
|
|
232
|
+
),
|
|
233
|
+
});
|
|
234
|
+
writeDefaultIndex();
|
|
235
|
+
} else {
|
|
236
|
+
// Self-contained template: copy everything (incl. its index.js and frontend).
|
|
237
|
+
const { hasIndex } = writeTemplateFiles(template);
|
|
238
|
+
applyEnvOverridesFromExample(envPath);
|
|
239
|
+
if (!hasIndex) writeDefaultIndex();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
ensurePackageJson(template);
|
|
243
|
+
|
|
244
|
+
console.log('\n[ZYKET] Installing dependencies...');
|
|
245
|
+
await npmInstall();
|
|
246
|
+
|
|
247
|
+
console.log(`\n[ZYKET] ✅ Project initialized with template '${template}'!`);
|
|
248
|
+
|
|
249
|
+
if (template === 'default') {
|
|
250
|
+
// Preserve previous behavior: start immediately.
|
|
251
|
+
console.log('\n[ZYKET] Starting project...\n');
|
|
252
|
+
spawn('node', ['index.js'], { cwd: process.cwd(), stdio: 'inherit', shell: true })
|
|
253
|
+
.on('error', (error) => console.error('[ZYKET] Error starting project:', error));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Templates that ship auth need their tables created first.
|
|
258
|
+
const usesAuth = fs.existsSync(path.join(process.cwd(), 'src', 'services', 'auth'));
|
|
259
|
+
console.log('\nNext steps:');
|
|
260
|
+
if (usesAuth) console.log(' npx @better-auth/cli migrate # create the auth tables');
|
|
261
|
+
console.log(' node index.js\n');
|
|
262
|
+
if (fs.existsSync(path.join(process.cwd(), 'README.md'))) {
|
|
263
|
+
console.log('See README.md for template-specific details.\n');
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
'install-template': async () => {
|
|
268
|
+
const templates = templateManager.getTemplates();
|
|
269
|
+
const response = await prompts({
|
|
270
|
+
type: 'select',
|
|
271
|
+
name: 'templateToInstall',
|
|
272
|
+
message: '[ZYKET] What template would you like to install?',
|
|
273
|
+
choices: templates.map((template) => ({ title: template.toUpperCase(), value: template, description: TEMPLATE_DESCRIPTIONS[template] || '', disabled: false })),
|
|
274
|
+
initial: 0,
|
|
275
|
+
});
|
|
276
|
+
if (!templateManager.exists(response.templateToInstall)) throw new Error(`Template ${response.templateToInstall} not found`);
|
|
277
|
+
templateManager.installTemplate(response.templateToInstall);
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
await actions[actionToRun](templateArg);
|
|
282
|
+
})();
|
package/index.js
CHANGED
|
@@ -2,8 +2,8 @@ const Kernel = require("./src/kernel");
|
|
|
2
2
|
const Service = require("./src/services/Service");
|
|
3
3
|
const EnvManager = require("./src/utils/EnvManager");
|
|
4
4
|
|
|
5
|
-
const {Route, Middleware, Express} = require("./src/services/express");
|
|
6
|
-
const { Handler, Guard } = require("./src/services/socketio");
|
|
5
|
+
const {Route, Middleware, Express, RequireAuthMiddleware, RequireAdminMiddleware} = require("./src/services/express");
|
|
6
|
+
const { Handler, Guard, AuthGuard } = require("./src/services/socketio");
|
|
7
7
|
const Schedule = require("./src/services/scheduler/Schedule");
|
|
8
8
|
const Event = require("./src/services/events/Event");
|
|
9
9
|
const Worker = require("./src/services/bullmq/Worker");
|
|
@@ -20,7 +20,8 @@ module.exports = {
|
|
|
20
20
|
Express,
|
|
21
21
|
Kernel, Service,
|
|
22
22
|
Route, Middleware,
|
|
23
|
-
|
|
23
|
+
RequireAuthMiddleware, RequireAdminMiddleware,
|
|
24
|
+
Handler, Guard, AuthGuard,
|
|
24
25
|
Schedule, Event,
|
|
25
26
|
Worker,
|
|
26
27
|
EnvManager,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zyket",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.19",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"redis": "^5.11.0",
|
|
40
40
|
"sequelize": "^6.37.8",
|
|
41
41
|
"socket.io": "^4.8.3",
|
|
42
|
+
"socket.io-client": "^4.8.3",
|
|
42
43
|
"sqlite3": "^6.0.1",
|
|
43
44
|
"swagger-jsdoc": "^6.2.8",
|
|
44
45
|
"swagger-ui-express": "^5.0.1",
|
|
@@ -50,5 +51,8 @@
|
|
|
50
51
|
"optionalDependencies": {
|
|
51
52
|
"@socket.io/redis-adapter": "^8.3.0",
|
|
52
53
|
"ioredis": "^5.6.1"
|
|
54
|
+
},
|
|
55
|
+
"overrides": {
|
|
56
|
+
"ajv": "^8.17.1"
|
|
53
57
|
}
|
|
54
58
|
}
|
|
@@ -7,14 +7,17 @@ const basicAuth = require('express-basic-auth')
|
|
|
7
7
|
module.exports = class BullBoardExtension extends Extension {
|
|
8
8
|
path;
|
|
9
9
|
|
|
10
|
-
constructor({ path = '/bullboard', basePath = '' } = {}) {
|
|
10
|
+
constructor({ path = '/bullboard', basePath = '', middlewares = [] } = {}) {
|
|
11
11
|
super("BullBoardExtension");
|
|
12
12
|
this.path = path || '/bullboard';
|
|
13
13
|
this.basePath = basePath;
|
|
14
|
+
// Optional Express middlewares (e.g. custom auth) applied before the board.
|
|
15
|
+
this.middlewares = middlewares || [];
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
load(container) {
|
|
17
|
-
|
|
19
|
+
const logger = container.get('logger')
|
|
20
|
+
if (!container.get('bullmq')) return logger.warn('BullBoardExtension: bullmq service not found, skipping BullBoard setup');
|
|
18
21
|
const bull = container.get('bullmq')
|
|
19
22
|
const serverAdapter = new ExpressAdapter()
|
|
20
23
|
serverAdapter.setBasePath(this.basePath + this.path)
|
|
@@ -25,7 +28,7 @@ module.exports = class BullBoardExtension extends Extension {
|
|
|
25
28
|
})
|
|
26
29
|
|
|
27
30
|
const app = container.get('express').app()
|
|
28
|
-
const middlewares = []
|
|
31
|
+
const middlewares = [...this.middlewares]
|
|
29
32
|
|
|
30
33
|
if (process.env.BULLBOARD_ADMIN_PASSWORD) {
|
|
31
34
|
middlewares.push(basicAuth({
|
|
@@ -34,6 +37,14 @@ module.exports = class BullBoardExtension extends Extension {
|
|
|
34
37
|
}))
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
// Fail closed: never expose the queue dashboard (jobs, payloads, retry/delete)
|
|
41
|
+
// without authentication. Require a password or custom auth middlewares.
|
|
42
|
+
if (middlewares.length === 0) {
|
|
43
|
+
return logger.warn(
|
|
44
|
+
`BullBoardExtension: no authentication configured. Set BULLBOARD_ADMIN_PASSWORD or pass "middlewares" to expose the dashboard. Skipping mount of ${this.path}.`
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
app.use(this.path, ...middlewares, serverAdapter.getRouter())
|
|
38
49
|
}
|
|
39
50
|
}
|