@mantiq/core 0.5.7 → 0.5.8
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,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
2
|
-
import { dirname, join, relative } from 'node:path'
|
|
2
|
+
import { basename, dirname, join, relative } from 'node:path'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Auto-discovers application classes by scanning conventional directories.
|
|
@@ -126,17 +126,39 @@ export class Discoverer {
|
|
|
126
126
|
/**
|
|
127
127
|
* Load and register all discovered route files.
|
|
128
128
|
* Each route file should export a default function: (router) => void
|
|
129
|
+
*
|
|
130
|
+
* Routes are auto-wrapped in middleware groups by filename convention:
|
|
131
|
+
* - web.ts → router.group({ middleware: ['web'] })
|
|
132
|
+
* - api.ts → router.group({ middleware: ['api'], prefix: '/api' })
|
|
133
|
+
* - Other files (console.ts, channels.ts) → loaded as-is
|
|
129
134
|
*/
|
|
130
135
|
async loadRoutes(manifest: DiscoveryManifest, router: any): Promise<void> {
|
|
136
|
+
const groupMap: Record<string, { prefix: string }> = {
|
|
137
|
+
web: { prefix: '' },
|
|
138
|
+
api: { prefix: '/api' },
|
|
139
|
+
}
|
|
140
|
+
|
|
131
141
|
for (const file of manifest.routes) {
|
|
132
142
|
const fullPath = join(this.basePath, file)
|
|
133
143
|
try {
|
|
134
144
|
const mod = await import(fullPath)
|
|
135
|
-
if (typeof mod.default
|
|
145
|
+
if (typeof mod.default !== 'function') continue
|
|
146
|
+
|
|
147
|
+
const stem = basename(file, '.ts')
|
|
148
|
+
const group = groupMap[stem]
|
|
149
|
+
|
|
150
|
+
if (group) {
|
|
151
|
+
router.group(
|
|
152
|
+
{ middleware: [stem], prefix: group.prefix },
|
|
153
|
+
(r: any) => mod.default(r),
|
|
154
|
+
)
|
|
155
|
+
} else {
|
|
136
156
|
mod.default(router)
|
|
137
157
|
}
|
|
138
|
-
} catch {
|
|
139
|
-
|
|
158
|
+
} catch (e) {
|
|
159
|
+
if (process.env.APP_DEBUG === 'true') {
|
|
160
|
+
console.warn(`[Mantiq] Failed to load route file ${file}:`, (e as Error)?.message ?? e)
|
|
161
|
+
}
|
|
140
162
|
}
|
|
141
163
|
}
|
|
142
164
|
}
|
package/src/middleware/Cors.ts
CHANGED
|
@@ -19,13 +19,18 @@ export class CorsMiddleware implements Middleware {
|
|
|
19
19
|
private config: CorsConfig
|
|
20
20
|
|
|
21
21
|
constructor(configRepo?: ConfigRepository) {
|
|
22
|
+
// Smart default: use APP_URL as origin with credentials when available
|
|
23
|
+
const appUrl = configRepo?.get('app.url', '') as string
|
|
24
|
+
const defaultOrigin = appUrl || '*'
|
|
25
|
+
const defaultCredentials = !!appUrl
|
|
26
|
+
|
|
22
27
|
this.config = {
|
|
23
|
-
origin: configRepo?.get('cors.origin',
|
|
28
|
+
origin: configRepo?.get('cors.origin', defaultOrigin) ?? defaultOrigin,
|
|
24
29
|
methods: configRepo?.get('cors.methods', ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']) ?? ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
25
|
-
allowedHeaders: configRepo?.get('cors.allowedHeaders', ['Content-Type', 'Authorization', 'X-Requested-With']) ?? ['Content-Type', 'Authorization', 'X-Requested-With'],
|
|
26
|
-
exposedHeaders: configRepo?.get('cors.exposedHeaders', []) ?? [],
|
|
27
|
-
credentials: configRepo?.get('cors.credentials',
|
|
28
|
-
maxAge: configRepo?.get('cors.maxAge',
|
|
30
|
+
allowedHeaders: configRepo?.get('cors.allowedHeaders', ['Content-Type', 'Authorization', 'X-Requested-With', 'X-CSRF-TOKEN', 'X-XSRF-TOKEN', 'X-Mantiq']) ?? ['Content-Type', 'Authorization', 'X-Requested-With', 'X-CSRF-TOKEN', 'X-XSRF-TOKEN', 'X-Mantiq'],
|
|
31
|
+
exposedHeaders: configRepo?.get('cors.exposedHeaders', ['X-Heartbeat']) ?? ['X-Heartbeat'],
|
|
32
|
+
credentials: configRepo?.get('cors.credentials', defaultCredentials) ?? defaultCredentials,
|
|
33
|
+
maxAge: configRepo?.get('cors.maxAge', 7200) ?? 7200,
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
|
|
@@ -11,7 +11,7 @@ import { parseCookies, serializeCookie } from '../http/Cookie.ts'
|
|
|
11
11
|
*/
|
|
12
12
|
export class EncryptCookies implements Middleware {
|
|
13
13
|
/** Cookie names that should NOT be encrypted/decrypted. */
|
|
14
|
-
protected except: string[] = []
|
|
14
|
+
protected except: string[] = ['XSRF-TOKEN']
|
|
15
15
|
|
|
16
16
|
constructor(private readonly encrypter: AesEncrypter) {}
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ import { serializeCookie } from '../http/Cookie.ts'
|
|
|
19
19
|
*/
|
|
20
20
|
export class VerifyCsrfToken implements Middleware {
|
|
21
21
|
/** URIs that should be excluded from CSRF verification. */
|
|
22
|
-
protected except: string[] = [
|
|
22
|
+
protected except: string[] = []
|
|
23
23
|
|
|
24
24
|
constructor(private readonly encrypter: AesEncrypter) {}
|
|
25
25
|
|
|
@@ -41,9 +41,6 @@ export class VerifyCsrfToken implements Middleware {
|
|
|
41
41
|
|
|
42
42
|
const path = request.path()
|
|
43
43
|
|
|
44
|
-
// API routes are always excluded — token auth is inherently CSRF-safe
|
|
45
|
-
if (path.startsWith('/api/') || path === '/api') return false
|
|
46
|
-
|
|
47
44
|
// Check user-defined exclusions
|
|
48
45
|
return !this.except.some((pattern) => {
|
|
49
46
|
if (pattern.endsWith('*')) {
|
|
@@ -107,11 +107,21 @@ export class CoreServiceProvider extends ServiceProvider {
|
|
|
107
107
|
kernel.registerMiddleware('session', StartSession)
|
|
108
108
|
kernel.registerMiddleware('csrf', VerifyCsrfToken)
|
|
109
109
|
|
|
110
|
-
//
|
|
110
|
+
// Register middleware groups from config
|
|
111
111
|
const configRepo = this.app.make(ConfigRepository)
|
|
112
|
-
const
|
|
113
|
-
'cors', 'encrypt.cookies', 'session', 'csrf',
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
const middlewareGroups = configRepo.get('app.middlewareGroups', {
|
|
113
|
+
web: ['cors', 'encrypt.cookies', 'session', 'csrf'],
|
|
114
|
+
api: ['cors', 'throttle'],
|
|
115
|
+
}) as Record<string, string[]>
|
|
116
|
+
|
|
117
|
+
for (const [name, middleware] of Object.entries(middlewareGroups)) {
|
|
118
|
+
kernel.registerMiddlewareGroup(name, middleware)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Legacy: if app.middleware is set, apply as global middleware (backward compat)
|
|
122
|
+
const globalMiddleware = configRepo.get('app.middleware', []) as string[]
|
|
123
|
+
if (globalMiddleware.length > 0) {
|
|
124
|
+
kernel.setGlobalMiddleware(globalMiddleware)
|
|
125
|
+
}
|
|
116
126
|
}
|
|
117
127
|
}
|