@synchjs/ewb 1.0.1 → 1.0.3
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/README.md +76 -196
- package/dist/Components/ServeMemoryStore.d.ts +6 -2
- package/dist/Components/ServeMemoryStore.d.ts.map +1 -1
- package/dist/Components/ServeMemoryStore.js +90 -26
- package/dist/Components/Server.d.ts +10 -3
- package/dist/Components/Server.d.ts.map +1 -1
- package/dist/Components/Server.js +185 -66
- package/dist/Components/UserHandler.d.ts +21 -0
- package/dist/Components/UserHandler.d.ts.map +1 -0
- package/dist/Components/UserHandler.js +9 -0
- package/dist/Decorations/Authorized.d.ts +2 -5
- package/dist/Decorations/Authorized.d.ts.map +1 -1
- package/dist/Decorations/Authorized.js +6 -6
- package/dist/Decorations/Metadata.d.ts +1 -1
- package/dist/Decorations/Metadata.d.ts.map +1 -1
- package/dist/Decorations/Methods.d.ts +5 -5
- package/dist/Decorations/Methods.d.ts.map +1 -1
- package/dist/Decorations/Security.d.ts +0 -1
- package/dist/Decorations/Security.d.ts.map +1 -1
- package/dist/Decorations/Security.js +0 -3
- package/dist/Decorations/Serve.d.ts.map +1 -1
- package/dist/Decorations/Serve.js +17 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +12 -9
|
@@ -9,8 +9,9 @@ import rateLimit from "express-rate-limit";
|
|
|
9
9
|
import Ajv from "ajv";
|
|
10
10
|
import addFormats from "ajv-formats";
|
|
11
11
|
import { MetadataStorage } from "../Decorations";
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
12
|
+
import { createServer } from "http";
|
|
13
|
+
import { Server as SocketServer } from "socket.io";
|
|
14
|
+
import { PUBLIC_METADATA_KEY, ROLES_METADATA_KEY, } from "../Decorations/Authorized";
|
|
14
15
|
import { SECURITY_METADATA_KEY, } from "../Decorations/Security";
|
|
15
16
|
import { TAILWIND_METADATA_KEY, } from "../Decorations/Tailwind";
|
|
16
17
|
import { SERVE_HTML_METADATA_KEY } from "../Decorations/Serve";
|
|
@@ -21,6 +22,7 @@ export class Server {
|
|
|
21
22
|
_app;
|
|
22
23
|
_port;
|
|
23
24
|
_controllersDir;
|
|
25
|
+
_viewsDir;
|
|
24
26
|
_id;
|
|
25
27
|
_enableSwagger;
|
|
26
28
|
_swaggerPath;
|
|
@@ -29,13 +31,15 @@ export class Server {
|
|
|
29
31
|
_ajv;
|
|
30
32
|
_securityHandlers;
|
|
31
33
|
_container;
|
|
34
|
+
_roleHandler;
|
|
35
|
+
_userHandler;
|
|
32
36
|
_serverInstance;
|
|
37
|
+
_io;
|
|
33
38
|
// Stats
|
|
34
39
|
_controllerCount = 0;
|
|
35
40
|
_routeCount = 0;
|
|
36
41
|
_tailwindEnabled = false;
|
|
37
42
|
_devMode = false;
|
|
38
|
-
_sseClients = [];
|
|
39
43
|
constructor(options) {
|
|
40
44
|
this._port = options.port;
|
|
41
45
|
this._app = express();
|
|
@@ -45,21 +49,44 @@ export class Server {
|
|
|
45
49
|
this._enableLogging =
|
|
46
50
|
options.logging === undefined ? true : options.logging;
|
|
47
51
|
this._controllersDir = options.controllersDir || "controllers";
|
|
52
|
+
this._viewsDir = options.viewsDir;
|
|
48
53
|
this._securityHandlers = options.securityHandlers || {};
|
|
49
54
|
this._securitySchemes = options.securitySchemes;
|
|
50
55
|
this._container = options.container;
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
this._roleHandler = options.roleHandler;
|
|
57
|
+
// Initialize AJV with strict mode for better security
|
|
58
|
+
this._ajv = new Ajv({
|
|
59
|
+
allErrors: true,
|
|
60
|
+
strict: true,
|
|
61
|
+
removeAdditional: true, // Automatically remove properties not in schema
|
|
62
|
+
});
|
|
53
63
|
addFormats(this._ajv);
|
|
54
64
|
// Security Middleware
|
|
55
|
-
|
|
65
|
+
const helmetOptions = options.helmetOptions || {};
|
|
66
|
+
if (process.env.NODE_ENV !== "production") {
|
|
67
|
+
// Relax CSP for HMR in development
|
|
68
|
+
helmetOptions.contentSecurityPolicy = {
|
|
69
|
+
directives: {
|
|
70
|
+
defaultSrc: ["'self'"],
|
|
71
|
+
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'", "blob:"],
|
|
72
|
+
connectSrc: ["'self'", "ws:", "wss:", "http:", "https:"],
|
|
73
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
74
|
+
imgSrc: ["'self'", "data:", "blob:"],
|
|
75
|
+
frameSrc: ["'self'"],
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
this._app.use(helmet(helmetOptions));
|
|
56
80
|
this._app.use(cors(options.corsOptions));
|
|
57
81
|
this._app.use(rateLimit(options.rateLimitOptions || {
|
|
58
82
|
windowMs: 15 * 60 * 1000,
|
|
59
83
|
max: 100,
|
|
84
|
+
skip: (req) => ServeMemoryStore.instance.getAsset(req.path) !== undefined, // Don't rate limit static assets
|
|
60
85
|
}));
|
|
61
|
-
this._app.use(express.json());
|
|
62
|
-
|
|
86
|
+
this._app.use(express.json({ limit: "1mb" })); // Protection against large payloads
|
|
87
|
+
// Clear cache on startup
|
|
88
|
+
ServeMemoryStore.instance.clearCache();
|
|
89
|
+
this._devMode = process.env.NODE_ENV !== "production";
|
|
63
90
|
if (this._devMode) {
|
|
64
91
|
this.setupHmr();
|
|
65
92
|
}
|
|
@@ -67,22 +94,16 @@ export class Server {
|
|
|
67
94
|
}
|
|
68
95
|
setupHmr() {
|
|
69
96
|
ServeMemoryStore.instance.setDevMode(true);
|
|
70
|
-
ServeMemoryStore.instance.onRebuild(() => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
res.setHeader("Content-Type", "text/event-stream");
|
|
78
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
79
|
-
res.setHeader("Connection", "keep-alive");
|
|
80
|
-
res.flushHeaders();
|
|
81
|
-
this._sseClients.push(res);
|
|
82
|
-
req.on("close", () => {
|
|
83
|
-
this._sseClients = this._sseClients.filter((c) => c !== res);
|
|
84
|
-
});
|
|
97
|
+
ServeMemoryStore.instance.onRebuild((data) => {
|
|
98
|
+
if (data && data.html) {
|
|
99
|
+
this._io?.emit("rebuild", { html: data.html });
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this._io?.emit("reload");
|
|
103
|
+
}
|
|
85
104
|
});
|
|
105
|
+
// No redundant global watcher here, we rely on ServeMemoryStore's specific watchers
|
|
106
|
+
// which rebuild before notifying.
|
|
86
107
|
}
|
|
87
108
|
log(message) {
|
|
88
109
|
if (this._enableLogging) {
|
|
@@ -90,6 +111,9 @@ export class Server {
|
|
|
90
111
|
}
|
|
91
112
|
}
|
|
92
113
|
async init() {
|
|
114
|
+
if (this._userHandler) {
|
|
115
|
+
this.setupAuthRoutes();
|
|
116
|
+
}
|
|
93
117
|
await this.loadControllers();
|
|
94
118
|
if (this._enableSwagger) {
|
|
95
119
|
this.setupSwagger();
|
|
@@ -100,6 +124,11 @@ export class Server {
|
|
|
100
124
|
return next();
|
|
101
125
|
const asset = ServeMemoryStore.instance.getAsset(req.path);
|
|
102
126
|
if (asset) {
|
|
127
|
+
if (this._devMode) {
|
|
128
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
|
|
129
|
+
res.setHeader("Pragma", "no-cache");
|
|
130
|
+
res.setHeader("Expires", "0");
|
|
131
|
+
}
|
|
103
132
|
res.type(asset.type).send(Buffer.from(asset.content));
|
|
104
133
|
return;
|
|
105
134
|
}
|
|
@@ -107,13 +136,31 @@ export class Server {
|
|
|
107
136
|
});
|
|
108
137
|
// Global Error Handler
|
|
109
138
|
this._app.use((err, req, res, next) => {
|
|
110
|
-
|
|
139
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
140
|
+
if (!isProd) {
|
|
141
|
+
console.error(`[${this._id}] Error:`, err);
|
|
142
|
+
}
|
|
111
143
|
res.status(err.status || 500).json({
|
|
112
144
|
error: "Internal Server Error",
|
|
113
|
-
message:
|
|
145
|
+
message: isProd
|
|
146
|
+
? "An unexpected error occurred"
|
|
147
|
+
: err.message || "An unexpected error occurred",
|
|
148
|
+
// Mask stack trace in production
|
|
149
|
+
stack: isProd ? undefined : err.stack,
|
|
114
150
|
});
|
|
115
151
|
});
|
|
116
|
-
this._serverInstance = this._app
|
|
152
|
+
this._serverInstance = createServer(this._app);
|
|
153
|
+
if (this._devMode) {
|
|
154
|
+
this._io = new SocketServer(this._serverInstance, {
|
|
155
|
+
cors: {
|
|
156
|
+
origin: [/localhost/, /127\.0\.0\.1/], // Restrict HMR to local development origin
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
this._io.on("connection", (socket) => {
|
|
160
|
+
// Disconnected client log
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
this._serverInstance.listen(this._port, () => {
|
|
117
164
|
if (this._enableLogging) {
|
|
118
165
|
this.printStartupMessage();
|
|
119
166
|
}
|
|
@@ -131,6 +178,9 @@ export class Server {
|
|
|
131
178
|
`${pc.bold(pad("Tailwind:"))}${this._tailwindEnabled ? pc.blue("Enabled") : pc.dim("Disabled")}`,
|
|
132
179
|
`${pc.bold(pad("HMR:"))}${this._devMode ? pc.cyan("Active") : pc.dim("Inactive")}`,
|
|
133
180
|
];
|
|
181
|
+
if (this._viewsDir) {
|
|
182
|
+
lines.push(`${pc.bold(pad("Views:"))}${this._viewsDir}`);
|
|
183
|
+
}
|
|
134
184
|
if (this._enableSwagger) {
|
|
135
185
|
lines.push(`${pc.bold(pad("Swagger:"))}http://localhost:${this._port}${this._swaggerPath}`);
|
|
136
186
|
}
|
|
@@ -151,6 +201,40 @@ export class Server {
|
|
|
151
201
|
}
|
|
152
202
|
global.servers.delete(this._id);
|
|
153
203
|
}
|
|
204
|
+
setUserHandler(handler) {
|
|
205
|
+
this._userHandler = handler;
|
|
206
|
+
}
|
|
207
|
+
setupAuthRoutes() {
|
|
208
|
+
if (!this._userHandler)
|
|
209
|
+
return;
|
|
210
|
+
this._app.post("/auth/signin", async (req, res, next) => {
|
|
211
|
+
try {
|
|
212
|
+
const result = await this._userHandler.signin(req, res);
|
|
213
|
+
res.json(result);
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
next(err);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
this._app.post("/auth/signup", async (req, res, next) => {
|
|
220
|
+
try {
|
|
221
|
+
const result = await this._userHandler.signup(req, res);
|
|
222
|
+
res.json(result);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
next(err);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
this._app.post("/auth/logout", async (req, res, next) => {
|
|
229
|
+
try {
|
|
230
|
+
const result = await this._userHandler.logout(req, res);
|
|
231
|
+
res.json(result);
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
next(err);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
154
238
|
async loadControllers() {
|
|
155
239
|
const absoluteControllersPath = path.resolve(process.cwd(), this._controllersDir);
|
|
156
240
|
if (!fs.existsSync(absoluteControllersPath)) {
|
|
@@ -178,28 +262,24 @@ export class Server {
|
|
|
178
262
|
const instance = this._container?.get
|
|
179
263
|
? this._container.get(controller.target)
|
|
180
264
|
: new controller.target();
|
|
181
|
-
|
|
182
|
-
|
|
265
|
+
const router = express.Router({ mergeParams: true });
|
|
266
|
+
if (controller.middlewares) {
|
|
267
|
+
router.use(...controller.middlewares);
|
|
268
|
+
}
|
|
183
269
|
for (const route of controller.routes) {
|
|
184
|
-
|
|
270
|
+
// Used only for Swagger and internal path logic if needed,
|
|
271
|
+
// but actual routing uses route.path directly which supports RegExp
|
|
272
|
+
const routePathString = route.path instanceof RegExp ? route.path.source : route.path;
|
|
185
273
|
const middlewares = [];
|
|
186
|
-
// Check
|
|
187
|
-
const
|
|
274
|
+
// Check ROLES metadata
|
|
275
|
+
const classRoles = Reflect.getMetadata(ROLES_METADATA_KEY, controller.target);
|
|
276
|
+
const methodRoles = Reflect.getMetadata(ROLES_METADATA_KEY, controller.target.prototype, route.handlerName);
|
|
277
|
+
const requiredRoles = methodRoles || classRoles;
|
|
188
278
|
const isPublic = Reflect.getMetadata(PUBLIC_METADATA_KEY, controller.target.prototype, route.handlerName);
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
else if (methodAuthInfo) {
|
|
195
|
-
authSecret = methodAuthInfo.secret || process.env.JWT_SECRET;
|
|
196
|
-
}
|
|
197
|
-
else if (classAuthInfo) {
|
|
198
|
-
authSecret = classAuthInfo.secret || process.env.JWT_SECRET;
|
|
199
|
-
}
|
|
200
|
-
// 1. Auth Middleware (New Decorator Logic)
|
|
201
|
-
if (authSecret) {
|
|
202
|
-
middlewares.push(this.createJwtMiddleware(authSecret));
|
|
279
|
+
// 1. Auth & Role Middlewares
|
|
280
|
+
if (!isPublic) {
|
|
281
|
+
// If not public, we always run the role check (which might include authentication via UserHandler)
|
|
282
|
+
middlewares.push(this.createRoleMiddleware(requiredRoles || []));
|
|
203
283
|
// Inject Swagger security definition automatically
|
|
204
284
|
if (!route.swagger) {
|
|
205
285
|
route.swagger = {
|
|
@@ -219,7 +299,8 @@ export class Server {
|
|
|
219
299
|
route.swagger.security.push({ bearerAuth: [] });
|
|
220
300
|
}
|
|
221
301
|
}
|
|
222
|
-
//
|
|
302
|
+
// 1.1 Role Middlewares - now handled above for non-public routes
|
|
303
|
+
// 2. Generic Security Middleware (@Security, @ApiKey, etc)
|
|
223
304
|
const classGenericSecurity = Reflect.getMetadata(SECURITY_METADATA_KEY, controller.target) || [];
|
|
224
305
|
const methodGenericSecurity = Reflect.getMetadata(SECURITY_METADATA_KEY, controller.target.prototype, route.handlerName) || [];
|
|
225
306
|
let genericRequirements = [];
|
|
@@ -293,17 +374,26 @@ export class Server {
|
|
|
293
374
|
}
|
|
294
375
|
// 3. Custom Middlewares
|
|
295
376
|
// Controller Level
|
|
296
|
-
if (controller.middlewares) {
|
|
297
|
-
|
|
298
|
-
}
|
|
377
|
+
// if (controller.middlewares) {
|
|
378
|
+
// middlewares.push(...controller.middlewares);
|
|
379
|
+
// }
|
|
380
|
+
// Controller middlewares are now mounted on the router level.
|
|
299
381
|
// Route Level
|
|
300
382
|
if (route.middlewares) {
|
|
301
383
|
middlewares.push(...route.middlewares);
|
|
302
384
|
}
|
|
303
385
|
// 3. Route Handler
|
|
304
|
-
const handler = (req, res, next) => {
|
|
386
|
+
const handler = async (req, res, next) => {
|
|
305
387
|
try {
|
|
306
|
-
const
|
|
388
|
+
const user = this._userHandler
|
|
389
|
+
? await this._userHandler.authenticate(req, res)
|
|
390
|
+
: req.user;
|
|
391
|
+
const result = instance[route.handlerName]({
|
|
392
|
+
req,
|
|
393
|
+
res,
|
|
394
|
+
user,
|
|
395
|
+
next,
|
|
396
|
+
});
|
|
307
397
|
const handleResult = (val) => {
|
|
308
398
|
if (val !== undefined && !res.headersSent) {
|
|
309
399
|
if (typeof val === "string") {
|
|
@@ -325,33 +415,61 @@ export class Server {
|
|
|
325
415
|
next(error);
|
|
326
416
|
}
|
|
327
417
|
};
|
|
328
|
-
|
|
418
|
+
// Register route on the controller router
|
|
419
|
+
router[route.method](route.path, ...middlewares, handler);
|
|
329
420
|
this._routeCount++;
|
|
330
421
|
// Pre-build if @Serve is used
|
|
331
422
|
const htmlPath = Reflect.getMetadata(SERVE_HTML_METADATA_KEY, controller.target.prototype, route.handlerName);
|
|
332
423
|
if (htmlPath) {
|
|
333
|
-
|
|
424
|
+
// Silent pre-build
|
|
334
425
|
await ServeMemoryStore.instance.buildAndCache(htmlPath, tailwindOptions);
|
|
335
426
|
}
|
|
336
427
|
}
|
|
428
|
+
this._app.use(controller.path, router);
|
|
337
429
|
}
|
|
338
430
|
}
|
|
339
|
-
|
|
340
|
-
return (req, res, next) => {
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
431
|
+
createRoleMiddleware(roles) {
|
|
432
|
+
return async (req, res, next) => {
|
|
433
|
+
const user = this._userHandler
|
|
434
|
+
? await this._userHandler.authenticate(req, res)
|
|
435
|
+
: req.user;
|
|
436
|
+
if (!user) {
|
|
437
|
+
return res.status(401).json({ error: "Unauthorized: No user found" });
|
|
438
|
+
}
|
|
439
|
+
// If no specific roles required, but it's not public, just authentication is enough
|
|
440
|
+
if (roles.length === 0) {
|
|
441
|
+
return next();
|
|
442
|
+
}
|
|
443
|
+
if (!this._roleHandler) {
|
|
444
|
+
// Default role check behavior
|
|
445
|
+
const userRoles = Array.isArray(user.roles)
|
|
446
|
+
? user.roles
|
|
447
|
+
: user.role
|
|
448
|
+
? [user.role]
|
|
449
|
+
: [];
|
|
450
|
+
const hasRole = roles.some((role) => userRoles.includes(role));
|
|
451
|
+
if (!hasRole) {
|
|
452
|
+
return res.status(403).json({
|
|
453
|
+
error: "Forbidden: You do not have the required permissions",
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return next();
|
|
346
457
|
}
|
|
347
|
-
const token = authHeader.split(" ")[1];
|
|
348
458
|
try {
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
459
|
+
const result = await this._roleHandler(req, roles);
|
|
460
|
+
if (result) {
|
|
461
|
+
// Assuming 'result' is a boolean indicating success
|
|
462
|
+
next();
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
// Applying the original logic for role failure.
|
|
466
|
+
res.status(403).json({
|
|
467
|
+
error: "Forbidden: You do not have the required permissions",
|
|
468
|
+
});
|
|
469
|
+
}
|
|
352
470
|
}
|
|
353
471
|
catch (err) {
|
|
354
|
-
|
|
472
|
+
next(err);
|
|
355
473
|
}
|
|
356
474
|
};
|
|
357
475
|
}
|
|
@@ -420,7 +538,8 @@ export class Server {
|
|
|
420
538
|
const paths = {};
|
|
421
539
|
for (const controller of controllers) {
|
|
422
540
|
for (const route of controller.routes) {
|
|
423
|
-
const
|
|
541
|
+
const routePathString = route.path instanceof RegExp ? route.path.source : route.path;
|
|
542
|
+
const fullPath = (controller.path + "/" + routePathString)
|
|
424
543
|
.replace(/\/+/g, "/")
|
|
425
544
|
.replace(/:([^/]+)/g, "{$1}");
|
|
426
545
|
if (!paths[fullPath]) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Request, Response } from "express";
|
|
2
|
+
export declare abstract class UserHandler {
|
|
3
|
+
/**
|
|
4
|
+
* Optional: Logic to authenticate the request and return the user object.
|
|
5
|
+
* If this is not implemented, 'user' will be undefined in route parameters.
|
|
6
|
+
*/
|
|
7
|
+
authenticate(req: Request, res: Response): Promise<any | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Sign in logic. Usually returns a token or user info.
|
|
10
|
+
*/
|
|
11
|
+
abstract signin(req: Request, res: Response): Promise<any> | any;
|
|
12
|
+
/**
|
|
13
|
+
* Sign up logic.
|
|
14
|
+
*/
|
|
15
|
+
abstract signup(req: Request, res: Response): Promise<any> | any;
|
|
16
|
+
/**
|
|
17
|
+
* Logout logic.
|
|
18
|
+
*/
|
|
19
|
+
abstract logout(req: Request, res: Response): Promise<any> | any;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=UserHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserHandler.d.ts","sourceRoot":"","sources":["../../src/Components/UserHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAgB,MAAM,SAAS,CAAC;AAE1D,8BAAsB,WAAW;IAC/B;;;OAGG;IACU,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAI3E;;OAEG;aACa,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG;IAEvE;;OAEG;aACa,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG;IAEvE;;OAEG;aACa,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG;CACxE"}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
export declare const AUTH_METADATA_KEY = "auth:info";
|
|
2
1
|
export declare const PUBLIC_METADATA_KEY = "auth:public";
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
export declare function BearerAuth(secret?: string): MethodDecorator & ClassDecorator;
|
|
2
|
+
export declare const ROLES_METADATA_KEY = "auth:roles";
|
|
3
|
+
export declare function Authorized(roles?: string[]): MethodDecorator & ClassDecorator;
|
|
7
4
|
export declare function Public(): MethodDecorator;
|
|
8
5
|
//# sourceMappingURL=Authorized.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Authorized.d.ts","sourceRoot":"","sources":["../../src/Decorations/Authorized.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"Authorized.d.ts","sourceRoot":"","sources":["../../src/Decorations/Authorized.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AACjD,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAE/C,wBAAgB,UAAU,CACxB,KAAK,GAAE,MAAM,EAAO,GACnB,eAAe,GAAG,cAAc,CAclC;AAED,wBAAgB,MAAM,IAAI,eAAe,CAQxC"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// Key to store auth metadata
|
|
2
|
-
export const AUTH_METADATA_KEY = "auth:info";
|
|
3
2
|
export const PUBLIC_METADATA_KEY = "auth:public";
|
|
4
|
-
export
|
|
3
|
+
export const ROLES_METADATA_KEY = "auth:roles";
|
|
4
|
+
export function Authorized(roles = []) {
|
|
5
5
|
return function (target, propertyKey, descriptor) {
|
|
6
|
-
// If used on a method
|
|
7
6
|
if (propertyKey) {
|
|
8
|
-
Reflect.defineMetadata(
|
|
7
|
+
Reflect.defineMetadata(ROLES_METADATA_KEY, roles, target, propertyKey);
|
|
8
|
+
// Explicitly mark as not public to prevent bypass if @Public() is also used
|
|
9
|
+
Reflect.defineMetadata(PUBLIC_METADATA_KEY, false, target, propertyKey);
|
|
9
10
|
}
|
|
10
11
|
else {
|
|
11
|
-
|
|
12
|
-
Reflect.defineMetadata(AUTH_METADATA_KEY, { secret }, target);
|
|
12
|
+
Reflect.defineMetadata(ROLES_METADATA_KEY, roles, target);
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
15
|
}
|
|
@@ -3,7 +3,7 @@ import type { RequestHandler } from "express";
|
|
|
3
3
|
import { OpenAPIV3 } from "openapi-types";
|
|
4
4
|
export interface RouteDefinition {
|
|
5
5
|
method: "get" | "post" | "put" | "delete" | "patch";
|
|
6
|
-
path: string;
|
|
6
|
+
path: string | RegExp;
|
|
7
7
|
handlerName: string;
|
|
8
8
|
swagger?: OpenAPIV3.OperationObject;
|
|
9
9
|
middlewares?: RequestHandler[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Metadata.d.ts","sourceRoot":"","sources":["../../src/Decorations/Metadata.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpD,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"Metadata.d.ts","sourceRoot":"","sources":["../../src/Decorations/Metadata.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpD,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,CAAC;IACpC,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,GAAG,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC,yBAAyB,EAAE,CAAC;IACjD,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CAChC;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAkB;IAC1C,OAAO,CAAC,YAAY,CAA8B;IAElD,OAAO;IAEP,WAAkB,QAAQ,IAAI,eAAe,CAK5C;IAEM,aAAa,CAAC,UAAU,EAAE,oBAAoB;IAI9C,cAAc,IAAI,oBAAoB,EAAE;IAIxC,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,eAAe;IAc5C,SAAS,CAAC,MAAM,EAAE,GAAG,GAAG,eAAe,EAAE;CAGjD"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { OpenAPIV3 } from "openapi-types";
|
|
3
|
-
export declare function Get(path: string, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
4
|
-
export declare function Post(path: string, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
5
|
-
export declare function Put(path: string, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
6
|
-
export declare function Delete(path: string, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
7
|
-
export declare function Patch(path: string, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
3
|
+
export declare function Get(path: string | RegExp, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
4
|
+
export declare function Post(path: string | RegExp, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
5
|
+
export declare function Put(path: string | RegExp, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
6
|
+
export declare function Delete(path: string | RegExp, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
7
|
+
export declare function Patch(path: string | RegExp, swagger?: OpenAPIV3.OperationObject): MethodDecorator;
|
|
8
8
|
//# sourceMappingURL=Methods.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Methods.d.ts","sourceRoot":"","sources":["../../src/Decorations/Methods.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,wBAAgB,GAAG,CACjB,IAAI,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"Methods.d.ts","sourceRoot":"","sources":["../../src/Decorations/Methods.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,wBAAgB,GAAG,CACjB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,GAClC,eAAe,CAajB;AAED,wBAAgB,IAAI,CAClB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,GAClC,eAAe,CAajB;AAED,wBAAgB,GAAG,CACjB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,GAClC,eAAe,CAajB;AAED,wBAAgB,MAAM,CACpB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,GAClC,eAAe,CAajB;AAED,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,CAAC,EAAE,SAAS,CAAC,eAAe,GAClC,eAAe,CAajB"}
|
|
@@ -4,6 +4,5 @@ export interface SecurityRequirement {
|
|
|
4
4
|
[name: string]: string[];
|
|
5
5
|
}
|
|
6
6
|
export declare function Security(name: string, scopes?: string[]): MethodDecorator & ClassDecorator;
|
|
7
|
-
export declare function OAuth(scopes?: string[]): MethodDecorator & ClassDecorator;
|
|
8
7
|
export declare function ApiKey(name: string): MethodDecorator & ClassDecorator;
|
|
9
8
|
//# sourceMappingURL=Security.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Security.d.ts","sourceRoot":"","sources":["../../src/Decorations/Security.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,eAAO,MAAM,qBAAqB,eAAqB,CAAC;AAExD,MAAM,WAAW,mBAAmB;IAClC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAAM,EAAO,GACpB,eAAe,GAAG,cAAc,CAyBlC;AAED,wBAAgB,
|
|
1
|
+
{"version":3,"file":"Security.d.ts","sourceRoot":"","sources":["../../src/Decorations/Security.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,eAAO,MAAM,qBAAqB,eAAqB,CAAC;AAExD,MAAM,WAAW,mBAAmB;IAClC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAAM,EAAO,GACpB,eAAe,GAAG,cAAc,CAyBlC;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,cAAc,CAErE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Serve.d.ts","sourceRoot":"","sources":["../../src/Decorations/Serve.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,eAAe,CAAC;AAEpD,wBAAgB,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"Serve.d.ts","sourceRoot":"","sources":["../../src/Decorations/Serve.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,eAAe,CAAC;AAEpD,wBAAgB,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAwEvD"}
|
|
@@ -6,10 +6,12 @@ export function Serve(htmlPath) {
|
|
|
6
6
|
// Store the HTML path in metadata for pre-building
|
|
7
7
|
Reflect.defineMetadata(SERVE_HTML_METADATA_KEY, htmlPath, target, propertyKey);
|
|
8
8
|
const originalMethod = descriptor.value;
|
|
9
|
-
descriptor.value = async function (req, res, next) {
|
|
9
|
+
descriptor.value = async function ({ req, res, user, next, }) {
|
|
10
10
|
try {
|
|
11
11
|
// Execute the original method
|
|
12
|
-
const result = await originalMethod.apply(this, [
|
|
12
|
+
const result = await originalMethod.apply(this, [
|
|
13
|
+
{ req, res, user, next },
|
|
14
|
+
]);
|
|
13
15
|
// Check if response has been sent or if result is not undefined/void
|
|
14
16
|
if (res.headersSent || result !== undefined) {
|
|
15
17
|
return result;
|
|
@@ -19,14 +21,25 @@ export function Serve(htmlPath) {
|
|
|
19
21
|
// If no response, build and serve the HTML
|
|
20
22
|
const html = await ServeMemoryStore.instance.buildAndCache(htmlPath, tailwindOptions);
|
|
21
23
|
if (html) {
|
|
24
|
+
const devMode = process.env.NODE_ENV === "development";
|
|
25
|
+
if (devMode) {
|
|
26
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
|
|
27
|
+
res.setHeader("Pragma", "no-cache");
|
|
28
|
+
res.setHeader("Expires", "0");
|
|
29
|
+
}
|
|
22
30
|
res.type("html").send(html);
|
|
23
31
|
}
|
|
24
|
-
else {
|
|
32
|
+
else if (next) {
|
|
25
33
|
next(); // No HTML found?
|
|
26
34
|
}
|
|
27
35
|
}
|
|
28
36
|
catch (error) {
|
|
29
|
-
next
|
|
37
|
+
if (next) {
|
|
38
|
+
next(error);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
30
43
|
}
|
|
31
44
|
};
|
|
32
45
|
return descriptor;
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,WAAW,CAAC;AACnB,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,WAAW,CAAC;AACnB,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,eAAe,CAAC"}
|