@open-mercato/shared 0.4.8-canary-8502c817af → 0.4.8-develop-4e71d95aba
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/dist/lib/auth/server.js +1 -1
- package/dist/lib/auth/server.js.map +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/widgets/component-registry.js +1 -12
- package/dist/modules/widgets/component-registry.js.map +2 -2
- package/package.json +1 -1
- package/src/lib/auth/server.ts +1 -1
- package/src/modules/widgets/component-registry.ts +1 -15
- package/dist/lib/crud/custom-route-interceptor.js +0 -27
- package/dist/lib/crud/custom-route-interceptor.js.map +0 -7
- package/dist/lib/middleware/page-executor.js +0 -42
- package/dist/lib/middleware/page-executor.js.map +0 -7
- package/dist/modules/middleware/page.js +0 -15
- package/dist/modules/middleware/page.js.map +0 -7
- package/src/lib/crud/__tests__/custom-route-interceptor.test.ts +0 -180
- package/src/lib/crud/custom-route-interceptor.ts +0 -47
- package/src/lib/middleware/__tests__/page-executor.test.ts +0 -161
- package/src/lib/middleware/page-executor.ts +0 -63
- package/src/modules/middleware/page.ts +0 -52
package/dist/lib/auth/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/auth/server.ts"],
|
|
4
|
-
"sourcesContent": ["import { cookies } from 'next/headers
|
|
4
|
+
"sourcesContent": ["import { cookies } from 'next/headers'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { verifyJwt } from './jwt'\n\nconst TENANT_COOKIE_NAME = 'om_selected_tenant'\nconst ORGANIZATION_COOKIE_NAME = 'om_selected_org'\nconst ALL_ORGANIZATIONS_COOKIE_VALUE = '__all__'\nconst SUPERADMIN_ROLE = 'superadmin'\n\nexport type AuthContext = {\n sub: string\n tenantId: string | null\n orgId: string | null\n email?: string\n roles?: string[]\n isApiKey?: boolean\n userId?: string\n keyId?: string\n keyName?: string\n [k: string]: unknown\n} | null\n\ntype CookieOverride = { applied: boolean; value: string | null }\n\nfunction decodeCookieValue(raw: string | undefined): string | null {\n if (raw === undefined) return null\n try {\n const decoded = decodeURIComponent(raw)\n return decoded ?? null\n } catch {\n return raw ?? null\n }\n}\n\nfunction readCookieFromHeader(header: string | null | undefined, name: string): string | undefined {\n if (!header) return undefined\n const parts = header.split(';')\n for (const part of parts) {\n const trimmed = part.trim()\n if (trimmed.startsWith(`${name}=`)) {\n return trimmed.slice(name.length + 1)\n }\n }\n return undefined\n}\n\nfunction resolveTenantOverride(raw: string | undefined): CookieOverride {\n if (raw === undefined) return { applied: false, value: null }\n const decoded = decodeCookieValue(raw)\n if (!decoded) return { applied: true, value: null }\n const trimmed = decoded.trim()\n if (!trimmed) return { applied: true, value: null }\n return { applied: true, value: trimmed }\n}\n\nfunction resolveOrganizationOverride(raw: string | undefined): CookieOverride {\n if (raw === undefined) return { applied: false, value: null }\n const decoded = decodeCookieValue(raw)\n if (!decoded || decoded === ALL_ORGANIZATIONS_COOKIE_VALUE) {\n return { applied: true, value: null }\n }\n const trimmed = decoded.trim()\n if (!trimmed || trimmed === ALL_ORGANIZATIONS_COOKIE_VALUE) {\n return { applied: true, value: null }\n }\n return { applied: true, value: trimmed }\n}\n\nfunction isSuperAdminAuth(auth: AuthContext | null | undefined): boolean {\n if (!auth) return false\n if ((auth as Record<string, unknown>).isSuperAdmin === true) return true\n const roles = Array.isArray(auth?.roles) ? auth.roles : []\n return roles.some((role) => typeof role === 'string' && role.trim().toLowerCase() === SUPERADMIN_ROLE)\n}\n\nfunction applySuperAdminScope(\n auth: AuthContext,\n tenantCookie: string | undefined,\n orgCookie: string | undefined\n): AuthContext {\n if (!auth || !isSuperAdminAuth(auth)) return auth\n\n const tenantOverride = resolveTenantOverride(tenantCookie)\n const orgOverride = resolveOrganizationOverride(orgCookie)\n if (!tenantOverride.applied && !orgOverride.applied) return auth\n\n type MutableAuthContext = Exclude<AuthContext, null> & {\n actorTenantId?: string | null\n actorOrgId?: string | null\n }\n const baseAuth = auth as Exclude<AuthContext, null>\n const next: MutableAuthContext = { ...baseAuth }\n if (tenantOverride.applied) {\n if (!('actorTenantId' in next)) next.actorTenantId = auth?.tenantId ?? null\n next.tenantId = tenantOverride.value\n }\n if (orgOverride.applied) {\n if (!('actorOrgId' in next)) next.actorOrgId = auth?.orgId ?? null\n next.orgId = orgOverride.value\n }\n next.isSuperAdmin = true\n const existingRoles = Array.isArray(next.roles) ? next.roles : []\n if (!existingRoles.some((role) => typeof role === 'string' && role.trim().toLowerCase() === SUPERADMIN_ROLE)) {\n next.roles = [...existingRoles, 'superadmin']\n }\n return next\n}\n\nasync function resolveApiKeyAuth(secret: string): Promise<AuthContext> {\n if (!secret) return null\n try {\n const { createRequestContainer } = await import('@open-mercato/shared/lib/di/container')\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n const { findApiKeyBySecret } = await import('@open-mercato/core/modules/api_keys/services/apiKeyService')\n const { Role } = await import('@open-mercato/core/modules/auth/data/entities')\n\n const record = await findApiKeyBySecret(em, secret)\n if (!record) return null\n\n const roleIds = Array.isArray(record.rolesJson)\n ? record.rolesJson.filter((value): value is string => typeof value === 'string' && value.length > 0)\n : []\n const roles = roleIds.length\n ? await em.find(Role, { id: { $in: roleIds } })\n : []\n const roleNames = roles.map((role) => role.name).filter((name): name is string => typeof name === 'string' && name.length > 0)\n\n try {\n record.lastUsedAt = new Date()\n await em.persistAndFlush(record)\n } catch {\n // best-effort update; ignore write failures\n }\n\n // For session keys, use sessionUserId; for regular keys, use createdBy\n const actualUserId = record.sessionUserId ?? record.createdBy ?? null\n\n return {\n sub: `api_key:${record.id}`,\n tenantId: record.tenantId ?? null,\n orgId: record.organizationId ?? null,\n roles: roleNames,\n isApiKey: true,\n keyId: record.id,\n keyName: record.name,\n ...(actualUserId ? { userId: actualUserId } : {}),\n }\n } catch {\n return null\n }\n}\n\nfunction extractApiKey(req: Request): string | null {\n const header = (req.headers.get('x-api-key') || '').trim()\n if (header) return header\n const authHeader = (req.headers.get('authorization') || '').trim()\n if (authHeader.toLowerCase().startsWith('apikey ')) {\n return authHeader.slice(7).trim()\n }\n return null\n}\n\nexport async function getAuthFromCookies(): Promise<AuthContext> {\n const cookieStore = await cookies()\n const token = cookieStore.get('auth_token')?.value\n if (!token) return null\n try {\n const payload = verifyJwt(token) as AuthContext\n if (!payload) return null\n const tenantCookie = cookieStore.get(TENANT_COOKIE_NAME)?.value\n const orgCookie = cookieStore.get(ORGANIZATION_COOKIE_NAME)?.value\n return applySuperAdminScope(payload, tenantCookie, orgCookie)\n } catch {\n return null\n }\n}\n\nexport async function getAuthFromRequest(req: Request): Promise<AuthContext> {\n const cookieHeader = req.headers.get('cookie') || ''\n const tenantCookie = readCookieFromHeader(cookieHeader, TENANT_COOKIE_NAME)\n const orgCookie = readCookieFromHeader(cookieHeader, ORGANIZATION_COOKIE_NAME)\n const authHeader = (req.headers.get('authorization') || '').trim()\n let token: string | undefined\n if (authHeader.toLowerCase().startsWith('bearer ')) token = authHeader.slice(7).trim()\n if (!token) {\n const match = cookieHeader.match(/(?:^|;\\s*)auth_token=([^;]+)/)\n if (match) token = decodeURIComponent(match[1])\n }\n if (token) {\n try {\n const payload = verifyJwt(token) as AuthContext\n if (payload) return applySuperAdminScope(payload, tenantCookie, orgCookie)\n } catch {\n // fall back to API key detection\n }\n }\n\n const apiKey = extractApiKey(req)\n if (!apiKey) return null\n const apiAuth = await resolveApiKeyAuth(apiKey)\n if (!apiAuth) return null\n return applySuperAdminScope(apiAuth, tenantCookie, orgCookie)\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.4.8-
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.4.8-develop-4e71d95aba'\nexport const appVersion = APP_VERSION\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { hasAllFeatures } from "../../security/features.js";
|
|
3
3
|
const GLOBAL_COMPONENT_REGISTRY_KEY = "__openMercatoComponentRegistry__";
|
|
4
|
-
function isComponentOverride(value) {
|
|
5
|
-
if (!value || typeof value !== "object") {
|
|
6
|
-
return false;
|
|
7
|
-
}
|
|
8
|
-
const target = value.target;
|
|
9
|
-
if (!target || typeof target !== "object") {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
return typeof target.componentId === "string" && target.componentId.length > 0;
|
|
13
|
-
}
|
|
14
4
|
function getState() {
|
|
15
5
|
const globalValue = globalThis[GLOBAL_COMPONENT_REGISTRY_KEY];
|
|
16
6
|
if (globalValue && typeof globalValue === "object") {
|
|
@@ -32,7 +22,7 @@ function registerComponent(entry) {
|
|
|
32
22
|
}
|
|
33
23
|
function registerComponentOverrides(overrides) {
|
|
34
24
|
const state = getState();
|
|
35
|
-
state.overrides = overrides
|
|
25
|
+
state.overrides = [...overrides];
|
|
36
26
|
}
|
|
37
27
|
function getComponentEntry(componentId) {
|
|
38
28
|
const state = getState();
|
|
@@ -41,7 +31,6 @@ function getComponentEntry(componentId) {
|
|
|
41
31
|
function getComponentOverrides(componentId, userFeatures) {
|
|
42
32
|
const state = getState();
|
|
43
33
|
const relevant = state.overrides.filter((override) => {
|
|
44
|
-
if (!isComponentOverride(override)) return false;
|
|
45
34
|
if (override.target.componentId !== componentId) return false;
|
|
46
35
|
if (override.features && override.features.length > 0) {
|
|
47
36
|
if (!hasAllFeatures(userFeatures, override.features)) return false;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/widgets/component-registry.ts"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react'\nimport type { ComponentType, LazyExoticComponent } from 'react'\nimport type { ZodType } from 'zod'\nimport { hasAllFeatures } from '../../security/features'\n\nexport type ComponentRegistryEntry<TProps = unknown> = {\n id: string\n component: ComponentType<TProps>\n metadata: {\n module: string\n description?: string\n propsSchema?: ZodType<TProps>\n }\n}\n\nexport type ComponentOverride<TProps = unknown> = {\n target: { componentId: string }\n priority: number\n features?: string[]\n metadata?: {\n module?: string\n }\n} & (\n | {\n replacement: LazyExoticComponent<ComponentType<TProps>> | ComponentType<TProps>\n propsSchema: ZodType<TProps>\n }\n | {\n wrapper: (Original: ComponentType<TProps>) => ComponentType<TProps>\n }\n | {\n propsTransform: (props: TProps) => TProps\n }\n)\n\ntype RuntimeState = {\n components: Map<string, ComponentRegistryEntry>\n overrides: ComponentOverride[]\n}\n\nconst GLOBAL_COMPONENT_REGISTRY_KEY = '__openMercatoComponentRegistry__'\n\nfunction
|
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;AAGvB,SAAS,sBAAsB;AAqC/B,MAAM,gCAAgC;AAEtC,SAAS,
|
|
4
|
+
"sourcesContent": ["import * as React from 'react'\nimport type { ComponentType, LazyExoticComponent } from 'react'\nimport type { ZodType } from 'zod'\nimport { hasAllFeatures } from '../../security/features'\n\nexport type ComponentRegistryEntry<TProps = unknown> = {\n id: string\n component: ComponentType<TProps>\n metadata: {\n module: string\n description?: string\n propsSchema?: ZodType<TProps>\n }\n}\n\nexport type ComponentOverride<TProps = unknown> = {\n target: { componentId: string }\n priority: number\n features?: string[]\n metadata?: {\n module?: string\n }\n} & (\n | {\n replacement: LazyExoticComponent<ComponentType<TProps>> | ComponentType<TProps>\n propsSchema: ZodType<TProps>\n }\n | {\n wrapper: (Original: ComponentType<TProps>) => ComponentType<TProps>\n }\n | {\n propsTransform: (props: TProps) => TProps\n }\n)\n\ntype RuntimeState = {\n components: Map<string, ComponentRegistryEntry>\n overrides: ComponentOverride[]\n}\n\nconst GLOBAL_COMPONENT_REGISTRY_KEY = '__openMercatoComponentRegistry__'\n\nfunction getState(): RuntimeState {\n const globalValue = (globalThis as Record<string, unknown>)[GLOBAL_COMPONENT_REGISTRY_KEY]\n if (globalValue && typeof globalValue === 'object') {\n const typed = globalValue as RuntimeState\n if (typed.components instanceof Map && Array.isArray(typed.overrides)) {\n return typed\n }\n }\n const initial: RuntimeState = {\n components: new Map<string, ComponentRegistryEntry>(),\n overrides: [],\n }\n ;(globalThis as Record<string, unknown>)[GLOBAL_COMPONENT_REGISTRY_KEY] = initial\n return initial\n}\n\nexport function registerComponent<TProps = unknown>(entry: ComponentRegistryEntry<TProps>) {\n const state = getState()\n state.components.set(entry.id, entry as ComponentRegistryEntry)\n}\n\nexport function registerComponentOverrides(overrides: ComponentOverride[]) {\n const state = getState()\n state.overrides = [...overrides]\n}\n\nexport function getComponentEntry(componentId: string): ComponentRegistryEntry | null {\n const state = getState()\n return state.components.get(componentId) ?? null\n}\n\nexport function getComponentOverrides(componentId: string, userFeatures?: readonly string[]): ComponentOverride[] {\n const state = getState()\n const relevant = state.overrides.filter((override) => {\n if (override.target.componentId !== componentId) return false\n if (override.features && override.features.length > 0) {\n if (!hasAllFeatures(userFeatures, override.features)) return false\n }\n return true\n })\n return relevant.sort((a, b) => a.priority - b.priority)\n}\n\nexport function resolveRegisteredComponent<TProps>(\n componentId: string,\n fallback: ComponentType<TProps>,\n userFeatures?: readonly string[],\n): ComponentType<TProps> {\n const overrides = getComponentOverrides(componentId, userFeatures)\n let resolved: ComponentType<TProps> = fallback\n for (const override of overrides) {\n if ('replacement' in override) {\n resolved = override.replacement as ComponentType<TProps>\n continue\n }\n if ('wrapper' in override) {\n resolved = override.wrapper(resolved as ComponentType<unknown>) as ComponentType<TProps>\n continue\n }\n if ('propsTransform' in override) {\n const transform = override.propsTransform as (props: TProps) => TProps\n const Current = resolved\n resolved = ((props: TProps) => {\n const transformed = transform(props)\n return React.createElement(Current as ComponentType<Record<string, unknown>>, transformed as Record<string, unknown>)\n }) as ComponentType<TProps>\n }\n }\n return resolved\n}\n\nexport const ComponentReplacementHandles = {\n page: (path: string) => `page:${path}`,\n dataTable: (tableId: string) => `data-table:${tableId}`,\n crudForm: (entityId: string) => `crud-form:${entityId}`,\n section: (scope: string, sectionId: string) => `section:${scope}.${sectionId}`,\n} as const\n"],
|
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;AAGvB,SAAS,sBAAsB;AAqC/B,MAAM,gCAAgC;AAEtC,SAAS,WAAyB;AAChC,QAAM,cAAe,WAAuC,6BAA6B;AACzF,MAAI,eAAe,OAAO,gBAAgB,UAAU;AAClD,UAAM,QAAQ;AACd,QAAI,MAAM,sBAAsB,OAAO,MAAM,QAAQ,MAAM,SAAS,GAAG;AACrE,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,UAAwB;AAAA,IAC5B,YAAY,oBAAI,IAAoC;AAAA,IACpD,WAAW,CAAC;AAAA,EACd;AACC,EAAC,WAAuC,6BAA6B,IAAI;AAC1E,SAAO;AACT;AAEO,SAAS,kBAAoC,OAAuC;AACzF,QAAM,QAAQ,SAAS;AACvB,QAAM,WAAW,IAAI,MAAM,IAAI,KAA+B;AAChE;AAEO,SAAS,2BAA2B,WAAgC;AACzE,QAAM,QAAQ,SAAS;AACvB,QAAM,YAAY,CAAC,GAAG,SAAS;AACjC;AAEO,SAAS,kBAAkB,aAAoD;AACpF,QAAM,QAAQ,SAAS;AACvB,SAAO,MAAM,WAAW,IAAI,WAAW,KAAK;AAC9C;AAEO,SAAS,sBAAsB,aAAqB,cAAuD;AAChH,QAAM,QAAQ,SAAS;AACvB,QAAM,WAAW,MAAM,UAAU,OAAO,CAAC,aAAa;AACpD,QAAI,SAAS,OAAO,gBAAgB,YAAa,QAAO;AACxD,QAAI,SAAS,YAAY,SAAS,SAAS,SAAS,GAAG;AACrD,UAAI,CAAC,eAAe,cAAc,SAAS,QAAQ,EAAG,QAAO;AAAA,IAC/D;AACA,WAAO;AAAA,EACT,CAAC;AACD,SAAO,SAAS,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AACxD;AAEO,SAAS,2BACd,aACA,UACA,cACuB;AACvB,QAAM,YAAY,sBAAsB,aAAa,YAAY;AACjE,MAAI,WAAkC;AACtC,aAAW,YAAY,WAAW;AAChC,QAAI,iBAAiB,UAAU;AAC7B,iBAAW,SAAS;AACpB;AAAA,IACF;AACA,QAAI,aAAa,UAAU;AACzB,iBAAW,SAAS,QAAQ,QAAkC;AAC9D;AAAA,IACF;AACA,QAAI,oBAAoB,UAAU;AAChC,YAAM,YAAY,SAAS;AAC3B,YAAM,UAAU;AAChB,kBAAY,CAAC,UAAkB;AAC7B,cAAM,cAAc,UAAU,KAAK;AACnC,eAAO,MAAM,cAAc,SAAmD,WAAsC;AAAA,MACtH;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,MAAM,8BAA8B;AAAA,EACzC,MAAM,CAAC,SAAiB,QAAQ,IAAI;AAAA,EACpC,WAAW,CAAC,YAAoB,cAAc,OAAO;AAAA,EACrD,UAAU,CAAC,aAAqB,aAAa,QAAQ;AAAA,EACrD,SAAS,CAAC,OAAe,cAAsB,WAAW,KAAK,IAAI,SAAS;AAC9E;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
package/src/lib/auth/server.ts
CHANGED
|
@@ -40,19 +40,6 @@ type RuntimeState = {
|
|
|
40
40
|
|
|
41
41
|
const GLOBAL_COMPONENT_REGISTRY_KEY = '__openMercatoComponentRegistry__'
|
|
42
42
|
|
|
43
|
-
function isComponentOverride(value: unknown): value is ComponentOverride {
|
|
44
|
-
if (!value || typeof value !== 'object') {
|
|
45
|
-
return false
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const target = (value as { target?: { componentId?: unknown } }).target
|
|
49
|
-
if (!target || typeof target !== 'object') {
|
|
50
|
-
return false
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return typeof target.componentId === 'string' && target.componentId.length > 0
|
|
54
|
-
}
|
|
55
|
-
|
|
56
43
|
function getState(): RuntimeState {
|
|
57
44
|
const globalValue = (globalThis as Record<string, unknown>)[GLOBAL_COMPONENT_REGISTRY_KEY]
|
|
58
45
|
if (globalValue && typeof globalValue === 'object') {
|
|
@@ -76,7 +63,7 @@ export function registerComponent<TProps = unknown>(entry: ComponentRegistryEntr
|
|
|
76
63
|
|
|
77
64
|
export function registerComponentOverrides(overrides: ComponentOverride[]) {
|
|
78
65
|
const state = getState()
|
|
79
|
-
state.overrides = overrides
|
|
66
|
+
state.overrides = [...overrides]
|
|
80
67
|
}
|
|
81
68
|
|
|
82
69
|
export function getComponentEntry(componentId: string): ComponentRegistryEntry | null {
|
|
@@ -87,7 +74,6 @@ export function getComponentEntry(componentId: string): ComponentRegistryEntry |
|
|
|
87
74
|
export function getComponentOverrides(componentId: string, userFeatures?: readonly string[]): ComponentOverride[] {
|
|
88
75
|
const state = getState()
|
|
89
76
|
const relevant = state.overrides.filter((override) => {
|
|
90
|
-
if (!isComponentOverride(override)) return false
|
|
91
77
|
if (override.target.componentId !== componentId) return false
|
|
92
78
|
if (override.features && override.features.length > 0) {
|
|
93
79
|
if (!hasAllFeatures(userFeatures, override.features)) return false
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { runApiInterceptorsAfter } from "./interceptor-runner.js";
|
|
2
|
-
function normalizeIdentity(value) {
|
|
3
|
-
if (!value) return "";
|
|
4
|
-
return value;
|
|
5
|
-
}
|
|
6
|
-
async function runCustomRouteAfterInterceptors(args) {
|
|
7
|
-
return runApiInterceptorsAfter({
|
|
8
|
-
routePath: args.routePath,
|
|
9
|
-
method: args.method,
|
|
10
|
-
request: args.request,
|
|
11
|
-
response: args.response,
|
|
12
|
-
context: {
|
|
13
|
-
em: args.context.em,
|
|
14
|
-
container: args.context.container,
|
|
15
|
-
userId: normalizeIdentity(args.context.userId),
|
|
16
|
-
organizationId: normalizeIdentity(args.context.organizationId),
|
|
17
|
-
tenantId: normalizeIdentity(args.context.tenantId),
|
|
18
|
-
userFeatures: args.context.userFeatures ?? [],
|
|
19
|
-
extensionHeaders: args.context.extensionHeaders
|
|
20
|
-
},
|
|
21
|
-
metadataByInterceptor: args.metadataByInterceptor
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
export {
|
|
25
|
-
runCustomRouteAfterInterceptors
|
|
26
|
-
};
|
|
27
|
-
//# sourceMappingURL=custom-route-interceptor.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/lib/crud/custom-route-interceptor.ts"],
|
|
4
|
-
"sourcesContent": ["import type { InterceptorContext, InterceptorRequest, InterceptorResponse, ApiInterceptorMethod } from './api-interceptor'\nimport { runApiInterceptorsAfter, type RunInterceptorsAfterResult } from './interceptor-runner'\n\nexport type CustomRouteAfterInterceptorContext = {\n em: InterceptorContext['em']\n container: InterceptorContext['container']\n userId?: string | null\n organizationId?: string | null\n tenantId?: string | null\n userFeatures?: string[]\n extensionHeaders?: InterceptorContext['extensionHeaders']\n}\n\ntype RunCustomRouteAfterInterceptorsArgs = {\n routePath: string\n method: ApiInterceptorMethod\n request: InterceptorRequest\n response: InterceptorResponse\n context: CustomRouteAfterInterceptorContext\n metadataByInterceptor?: Record<string, Record<string, unknown> | undefined>\n}\n\nfunction normalizeIdentity(value: string | null | undefined): string {\n if (!value) return ''\n return value\n}\n\nexport async function runCustomRouteAfterInterceptors(\n args: RunCustomRouteAfterInterceptorsArgs,\n): Promise<RunInterceptorsAfterResult> {\n return runApiInterceptorsAfter({\n routePath: args.routePath,\n method: args.method,\n request: args.request,\n response: args.response,\n context: {\n em: args.context.em,\n container: args.context.container,\n userId: normalizeIdentity(args.context.userId),\n organizationId: normalizeIdentity(args.context.organizationId),\n tenantId: normalizeIdentity(args.context.tenantId),\n userFeatures: args.context.userFeatures ?? [],\n extensionHeaders: args.context.extensionHeaders,\n },\n metadataByInterceptor: args.metadataByInterceptor,\n })\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,+BAAgE;AAqBzE,SAAS,kBAAkB,OAA0C;AACnE,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO;AACT;AAEA,eAAsB,gCACpB,MACqC;AACrC,SAAO,wBAAwB;AAAA,IAC7B,WAAW,KAAK;AAAA,IAChB,QAAQ,KAAK;AAAA,IACb,SAAS,KAAK;AAAA,IACd,UAAU,KAAK;AAAA,IACf,SAAS;AAAA,MACP,IAAI,KAAK,QAAQ;AAAA,MACjB,WAAW,KAAK,QAAQ;AAAA,MACxB,QAAQ,kBAAkB,KAAK,QAAQ,MAAM;AAAA,MAC7C,gBAAgB,kBAAkB,KAAK,QAAQ,cAAc;AAAA,MAC7D,UAAU,kBAAkB,KAAK,QAAQ,QAAQ;AAAA,MACjD,cAAc,KAAK,QAAQ,gBAAgB,CAAC;AAAA,MAC5C,kBAAkB,KAAK,QAAQ;AAAA,IACjC;AAAA,IACA,uBAAuB,KAAK;AAAA,EAC9B,CAAC;AACH;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { CONTINUE_PAGE_MIDDLEWARE, matchPageMiddlewareTarget } from "@open-mercato/shared/modules/middleware/page";
|
|
2
|
-
const DEFAULT_PRIORITY = 100;
|
|
3
|
-
function shouldRunMiddleware(middleware, mode, pathname) {
|
|
4
|
-
if (middleware.mode && middleware.mode !== mode) return false;
|
|
5
|
-
return matchPageMiddlewareTarget(pathname, middleware.target);
|
|
6
|
-
}
|
|
7
|
-
function compareMiddleware(a, b) {
|
|
8
|
-
const priorityDiff = (a.priority ?? DEFAULT_PRIORITY) - (b.priority ?? DEFAULT_PRIORITY);
|
|
9
|
-
if (priorityDiff !== 0) return priorityDiff;
|
|
10
|
-
return a.id.localeCompare(b.id);
|
|
11
|
-
}
|
|
12
|
-
function flattenAndSortMiddleware(entries, mode, pathname) {
|
|
13
|
-
return entries.flatMap((entry) => entry.middleware).filter((middleware) => shouldRunMiddleware(middleware, mode, pathname)).sort(compareMiddleware);
|
|
14
|
-
}
|
|
15
|
-
async function executePageMiddleware(args) {
|
|
16
|
-
const { entries, context, onError } = args;
|
|
17
|
-
const matchedMiddleware = flattenAndSortMiddleware(entries, context.mode, context.pathname);
|
|
18
|
-
for (const middleware of matchedMiddleware) {
|
|
19
|
-
try {
|
|
20
|
-
const result = await middleware.run(context);
|
|
21
|
-
if (result.action === "redirect") return result;
|
|
22
|
-
} catch (error) {
|
|
23
|
-
if (onError) {
|
|
24
|
-
onError(error, { id: middleware.id, priority: middleware.priority });
|
|
25
|
-
} else {
|
|
26
|
-
console.error("[middleware:page] execution failed", { id: middleware.id, error });
|
|
27
|
-
}
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return CONTINUE_PAGE_MIDDLEWARE;
|
|
32
|
-
}
|
|
33
|
-
async function resolvePageMiddlewareRedirect(args) {
|
|
34
|
-
const result = await executePageMiddleware(args);
|
|
35
|
-
if (result.action !== "redirect") return null;
|
|
36
|
-
return result.location;
|
|
37
|
-
}
|
|
38
|
-
export {
|
|
39
|
-
executePageMiddleware,
|
|
40
|
-
resolvePageMiddlewareRedirect
|
|
41
|
-
};
|
|
42
|
-
//# sourceMappingURL=page-executor.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/lib/middleware/page-executor.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n PageMiddlewareContext,\n PageMiddlewareMode,\n PageMiddlewareRegistryEntry,\n PageMiddlewareResult,\n PageRouteMiddleware,\n} from '@open-mercato/shared/modules/middleware/page'\nimport { CONTINUE_PAGE_MIDDLEWARE, matchPageMiddlewareTarget } from '@open-mercato/shared/modules/middleware/page'\n\ntype ExecutePageMiddlewareArgs = {\n entries: PageMiddlewareRegistryEntry[]\n context: PageMiddlewareContext\n onError?: (error: unknown, middleware: Pick<PageRouteMiddleware, 'id' | 'priority'>) => void\n}\n\nconst DEFAULT_PRIORITY = 100\n\nfunction shouldRunMiddleware(middleware: PageRouteMiddleware, mode: PageMiddlewareMode, pathname: string): boolean {\n if (middleware.mode && middleware.mode !== mode) return false\n return matchPageMiddlewareTarget(pathname, middleware.target)\n}\n\nfunction compareMiddleware(a: PageRouteMiddleware, b: PageRouteMiddleware): number {\n const priorityDiff = (a.priority ?? DEFAULT_PRIORITY) - (b.priority ?? DEFAULT_PRIORITY)\n if (priorityDiff !== 0) return priorityDiff\n return a.id.localeCompare(b.id)\n}\n\nfunction flattenAndSortMiddleware(\n entries: PageMiddlewareRegistryEntry[],\n mode: PageMiddlewareMode,\n pathname: string\n): PageRouteMiddleware[] {\n return entries\n .flatMap((entry) => entry.middleware)\n .filter((middleware) => shouldRunMiddleware(middleware, mode, pathname))\n .sort(compareMiddleware)\n}\n\nexport async function executePageMiddleware(args: ExecutePageMiddlewareArgs): Promise<PageMiddlewareResult> {\n const { entries, context, onError } = args\n const matchedMiddleware = flattenAndSortMiddleware(entries, context.mode, context.pathname)\n for (const middleware of matchedMiddleware) {\n try {\n const result = await middleware.run(context)\n if (result.action === 'redirect') return result\n } catch (error) {\n if (onError) {\n onError(error, { id: middleware.id, priority: middleware.priority })\n } else {\n console.error('[middleware:page] execution failed', { id: middleware.id, error })\n }\n throw error\n }\n }\n return CONTINUE_PAGE_MIDDLEWARE\n}\n\nexport async function resolvePageMiddlewareRedirect(args: ExecutePageMiddlewareArgs): Promise<string | null> {\n const result = await executePageMiddleware(args)\n if (result.action !== 'redirect') return null\n return result.location\n}\n"],
|
|
5
|
-
"mappings": "AAOA,SAAS,0BAA0B,iCAAiC;AAQpE,MAAM,mBAAmB;AAEzB,SAAS,oBAAoB,YAAiC,MAA0B,UAA2B;AACjH,MAAI,WAAW,QAAQ,WAAW,SAAS,KAAM,QAAO;AACxD,SAAO,0BAA0B,UAAU,WAAW,MAAM;AAC9D;AAEA,SAAS,kBAAkB,GAAwB,GAAgC;AACjF,QAAM,gBAAgB,EAAE,YAAY,qBAAqB,EAAE,YAAY;AACvE,MAAI,iBAAiB,EAAG,QAAO;AAC/B,SAAO,EAAE,GAAG,cAAc,EAAE,EAAE;AAChC;AAEA,SAAS,yBACP,SACA,MACA,UACuB;AACvB,SAAO,QACJ,QAAQ,CAAC,UAAU,MAAM,UAAU,EACnC,OAAO,CAAC,eAAe,oBAAoB,YAAY,MAAM,QAAQ,CAAC,EACtE,KAAK,iBAAiB;AAC3B;AAEA,eAAsB,sBAAsB,MAAgE;AAC1G,QAAM,EAAE,SAAS,SAAS,QAAQ,IAAI;AACtC,QAAM,oBAAoB,yBAAyB,SAAS,QAAQ,MAAM,QAAQ,QAAQ;AAC1F,aAAW,cAAc,mBAAmB;AAC1C,QAAI;AACF,YAAM,SAAS,MAAM,WAAW,IAAI,OAAO;AAC3C,UAAI,OAAO,WAAW,WAAY,QAAO;AAAA,IAC3C,SAAS,OAAO;AACd,UAAI,SAAS;AACX,gBAAQ,OAAO,EAAE,IAAI,WAAW,IAAI,UAAU,WAAW,SAAS,CAAC;AAAA,MACrE,OAAO;AACL,gBAAQ,MAAM,sCAAsC,EAAE,IAAI,WAAW,IAAI,MAAM,CAAC;AAAA,MAClF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,8BAA8B,MAAyD;AAC3G,QAAM,SAAS,MAAM,sBAAsB,IAAI;AAC/C,MAAI,OAAO,WAAW,WAAY,QAAO;AACzC,SAAO,OAAO;AAChB;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
function matchPageMiddlewareTarget(pathname, target) {
|
|
2
|
-
if (target instanceof RegExp) {
|
|
3
|
-
return target.test(pathname);
|
|
4
|
-
}
|
|
5
|
-
if (target.endsWith("*")) {
|
|
6
|
-
return pathname.startsWith(target.slice(0, -1));
|
|
7
|
-
}
|
|
8
|
-
return pathname === target;
|
|
9
|
-
}
|
|
10
|
-
const CONTINUE_PAGE_MIDDLEWARE = { action: "continue" };
|
|
11
|
-
export {
|
|
12
|
-
CONTINUE_PAGE_MIDDLEWARE,
|
|
13
|
-
matchPageMiddlewareTarget
|
|
14
|
-
};
|
|
15
|
-
//# sourceMappingURL=page.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/modules/middleware/page.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AuthContext } from '@open-mercato/shared/lib/auth/server'\n\nexport type PageMiddlewareMode = 'frontend' | 'backend'\n\nexport type PageRouteMeta = {\n requireAuth?: boolean\n requireRoles?: string[]\n requireFeatures?: string[]\n}\n\nexport type PageMiddlewareContainer = {\n resolve: (name: string) => unknown\n}\n\nexport type PageMiddlewareContext = {\n pathname: string\n mode: PageMiddlewareMode\n routeMeta: PageRouteMeta\n auth: AuthContext\n ensureContainer: () => Promise<PageMiddlewareContainer>\n}\n\nexport type PageMiddlewareResult =\n | { action: 'continue' }\n | { action: 'redirect'; location: string }\n\nexport type PageMiddlewareTarget = string | RegExp\n\nexport type PageRouteMiddleware = {\n id: string\n mode?: PageMiddlewareMode\n target: PageMiddlewareTarget\n priority?: number\n run: (ctx: PageMiddlewareContext) => Promise<PageMiddlewareResult> | PageMiddlewareResult\n}\n\nexport type PageMiddlewareRegistryEntry = {\n moduleId: string\n middleware: PageRouteMiddleware[]\n}\n\nexport function matchPageMiddlewareTarget(pathname: string, target: PageMiddlewareTarget): boolean {\n if (target instanceof RegExp) {\n return target.test(pathname)\n }\n if (target.endsWith('*')) {\n return pathname.startsWith(target.slice(0, -1))\n }\n return pathname === target\n}\n\nexport const CONTINUE_PAGE_MIDDLEWARE: PageMiddlewareResult = { action: 'continue' }\n"],
|
|
5
|
-
"mappings": "AAyCO,SAAS,0BAA0B,UAAkB,QAAuC;AACjG,MAAI,kBAAkB,QAAQ;AAC5B,WAAO,OAAO,KAAK,QAAQ;AAAA,EAC7B;AACA,MAAI,OAAO,SAAS,GAAG,GAAG;AACxB,WAAO,SAAS,WAAW,OAAO,MAAM,GAAG,EAAE,CAAC;AAAA,EAChD;AACA,SAAO,aAAa;AACtB;AAEO,MAAM,2BAAiD,EAAE,QAAQ,WAAW;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { registerApiInterceptors } from '@open-mercato/shared/lib/crud/interceptor-registry'
|
|
2
|
-
import { runCustomRouteAfterInterceptors } from '@open-mercato/shared/lib/crud/custom-route-interceptor'
|
|
3
|
-
import type { InterceptorContext } from '@open-mercato/shared/lib/crud/api-interceptor'
|
|
4
|
-
|
|
5
|
-
function buildArgs() {
|
|
6
|
-
return {
|
|
7
|
-
routePath: 'auth/login',
|
|
8
|
-
method: 'POST' as const,
|
|
9
|
-
request: {
|
|
10
|
-
method: 'POST' as const,
|
|
11
|
-
url: 'http://localhost/api/auth/login',
|
|
12
|
-
headers: {},
|
|
13
|
-
body: { email: 'user@example.com' },
|
|
14
|
-
},
|
|
15
|
-
response: {
|
|
16
|
-
statusCode: 200,
|
|
17
|
-
body: { ok: true, token: 'token-1', redirect: '/backend' },
|
|
18
|
-
headers: { 'x-test': '1' },
|
|
19
|
-
},
|
|
20
|
-
context: {
|
|
21
|
-
em: {} as InterceptorContext['em'],
|
|
22
|
-
container: { resolve: jest.fn() } as unknown as InterceptorContext['container'],
|
|
23
|
-
},
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
describe('runCustomRouteAfterInterceptors', () => {
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
registerApiInterceptors([])
|
|
30
|
-
jest.clearAllMocks()
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test('returns unchanged response when no interceptor matches', async () => {
|
|
34
|
-
const result = await runCustomRouteAfterInterceptors(buildArgs())
|
|
35
|
-
|
|
36
|
-
expect(result).toEqual({
|
|
37
|
-
ok: true,
|
|
38
|
-
statusCode: 200,
|
|
39
|
-
body: { ok: true, token: 'token-1', redirect: '/backend' },
|
|
40
|
-
headers: { 'x-test': '1' },
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
test('supports merge result from matching after interceptor', async () => {
|
|
45
|
-
registerApiInterceptors([
|
|
46
|
-
{
|
|
47
|
-
moduleId: 'example',
|
|
48
|
-
interceptors: [
|
|
49
|
-
{
|
|
50
|
-
id: 'example.auth.login.merge',
|
|
51
|
-
targetRoute: 'auth/login',
|
|
52
|
-
methods: ['POST'],
|
|
53
|
-
async after() {
|
|
54
|
-
return { merge: { mfa_required: true } }
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
])
|
|
60
|
-
|
|
61
|
-
const result = await runCustomRouteAfterInterceptors(buildArgs())
|
|
62
|
-
expect(result.ok).toBe(true)
|
|
63
|
-
expect(result.body).toEqual({
|
|
64
|
-
ok: true,
|
|
65
|
-
token: 'token-1',
|
|
66
|
-
redirect: '/backend',
|
|
67
|
-
mfa_required: true,
|
|
68
|
-
})
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('supports replace result from matching after interceptor', async () => {
|
|
72
|
-
registerApiInterceptors([
|
|
73
|
-
{
|
|
74
|
-
moduleId: 'example',
|
|
75
|
-
interceptors: [
|
|
76
|
-
{
|
|
77
|
-
id: 'example.auth.login.replace',
|
|
78
|
-
targetRoute: 'auth/login',
|
|
79
|
-
methods: ['POST'],
|
|
80
|
-
async after() {
|
|
81
|
-
return { replace: { ok: true, mfa_required: true, challenge_id: 'c-1', token: 'pending' } }
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
])
|
|
87
|
-
|
|
88
|
-
const result = await runCustomRouteAfterInterceptors(buildArgs())
|
|
89
|
-
expect(result.ok).toBe(true)
|
|
90
|
-
expect(result.body).toEqual({
|
|
91
|
-
ok: true,
|
|
92
|
-
mfa_required: true,
|
|
93
|
-
challenge_id: 'c-1',
|
|
94
|
-
token: 'pending',
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
test('propagates timeout failures from interceptor runner', async () => {
|
|
99
|
-
registerApiInterceptors([
|
|
100
|
-
{
|
|
101
|
-
moduleId: 'example',
|
|
102
|
-
interceptors: [
|
|
103
|
-
{
|
|
104
|
-
id: 'example.auth.login.timeout',
|
|
105
|
-
targetRoute: 'auth/login',
|
|
106
|
-
methods: ['POST'],
|
|
107
|
-
timeoutMs: 5,
|
|
108
|
-
async after() {
|
|
109
|
-
await new Promise((resolve) => setTimeout(resolve, 20))
|
|
110
|
-
return {}
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
],
|
|
114
|
-
},
|
|
115
|
-
])
|
|
116
|
-
|
|
117
|
-
const result = await runCustomRouteAfterInterceptors(buildArgs())
|
|
118
|
-
expect(result.ok).toBe(false)
|
|
119
|
-
expect(result.statusCode).toBe(504)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
test('supports unauthenticated execution context defaults', async () => {
|
|
123
|
-
const capturedContexts: Array<{ userId: string; organizationId: string; tenantId: string; userFeatures?: string[] }> = []
|
|
124
|
-
registerApiInterceptors([
|
|
125
|
-
{
|
|
126
|
-
moduleId: 'example',
|
|
127
|
-
interceptors: [
|
|
128
|
-
{
|
|
129
|
-
id: 'example.auth.login.capture-context',
|
|
130
|
-
targetRoute: 'auth/login',
|
|
131
|
-
methods: ['POST'],
|
|
132
|
-
async after(_request, _response, context) {
|
|
133
|
-
capturedContexts.push({
|
|
134
|
-
userId: context.userId,
|
|
135
|
-
organizationId: context.organizationId,
|
|
136
|
-
tenantId: context.tenantId,
|
|
137
|
-
userFeatures: context.userFeatures,
|
|
138
|
-
})
|
|
139
|
-
return {}
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
},
|
|
144
|
-
])
|
|
145
|
-
|
|
146
|
-
const result = await runCustomRouteAfterInterceptors(buildArgs())
|
|
147
|
-
expect(result.ok).toBe(true)
|
|
148
|
-
expect(capturedContexts).toEqual([
|
|
149
|
-
{
|
|
150
|
-
userId: '',
|
|
151
|
-
organizationId: '',
|
|
152
|
-
tenantId: '',
|
|
153
|
-
userFeatures: [],
|
|
154
|
-
},
|
|
155
|
-
])
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
test('propagates interceptor exceptions as failed responses', async () => {
|
|
159
|
-
registerApiInterceptors([
|
|
160
|
-
{
|
|
161
|
-
moduleId: 'example',
|
|
162
|
-
interceptors: [
|
|
163
|
-
{
|
|
164
|
-
id: 'example.auth.login.throw',
|
|
165
|
-
targetRoute: 'auth/login',
|
|
166
|
-
methods: ['POST'],
|
|
167
|
-
async after() {
|
|
168
|
-
throw new Error('boom')
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
},
|
|
173
|
-
])
|
|
174
|
-
|
|
175
|
-
const result = await runCustomRouteAfterInterceptors(buildArgs())
|
|
176
|
-
expect(result.ok).toBe(false)
|
|
177
|
-
expect(result.statusCode).toBe(500)
|
|
178
|
-
expect(result.body.error).toBe('Internal interceptor error')
|
|
179
|
-
})
|
|
180
|
-
})
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import type { InterceptorContext, InterceptorRequest, InterceptorResponse, ApiInterceptorMethod } from './api-interceptor'
|
|
2
|
-
import { runApiInterceptorsAfter, type RunInterceptorsAfterResult } from './interceptor-runner'
|
|
3
|
-
|
|
4
|
-
export type CustomRouteAfterInterceptorContext = {
|
|
5
|
-
em: InterceptorContext['em']
|
|
6
|
-
container: InterceptorContext['container']
|
|
7
|
-
userId?: string | null
|
|
8
|
-
organizationId?: string | null
|
|
9
|
-
tenantId?: string | null
|
|
10
|
-
userFeatures?: string[]
|
|
11
|
-
extensionHeaders?: InterceptorContext['extensionHeaders']
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type RunCustomRouteAfterInterceptorsArgs = {
|
|
15
|
-
routePath: string
|
|
16
|
-
method: ApiInterceptorMethod
|
|
17
|
-
request: InterceptorRequest
|
|
18
|
-
response: InterceptorResponse
|
|
19
|
-
context: CustomRouteAfterInterceptorContext
|
|
20
|
-
metadataByInterceptor?: Record<string, Record<string, unknown> | undefined>
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function normalizeIdentity(value: string | null | undefined): string {
|
|
24
|
-
if (!value) return ''
|
|
25
|
-
return value
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function runCustomRouteAfterInterceptors(
|
|
29
|
-
args: RunCustomRouteAfterInterceptorsArgs,
|
|
30
|
-
): Promise<RunInterceptorsAfterResult> {
|
|
31
|
-
return runApiInterceptorsAfter({
|
|
32
|
-
routePath: args.routePath,
|
|
33
|
-
method: args.method,
|
|
34
|
-
request: args.request,
|
|
35
|
-
response: args.response,
|
|
36
|
-
context: {
|
|
37
|
-
em: args.context.em,
|
|
38
|
-
container: args.context.container,
|
|
39
|
-
userId: normalizeIdentity(args.context.userId),
|
|
40
|
-
organizationId: normalizeIdentity(args.context.organizationId),
|
|
41
|
-
tenantId: normalizeIdentity(args.context.tenantId),
|
|
42
|
-
userFeatures: args.context.userFeatures ?? [],
|
|
43
|
-
extensionHeaders: args.context.extensionHeaders,
|
|
44
|
-
},
|
|
45
|
-
metadataByInterceptor: args.metadataByInterceptor,
|
|
46
|
-
})
|
|
47
|
-
}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
executePageMiddleware,
|
|
3
|
-
resolvePageMiddlewareRedirect,
|
|
4
|
-
} from '@open-mercato/shared/lib/middleware/page-executor'
|
|
5
|
-
import {
|
|
6
|
-
CONTINUE_PAGE_MIDDLEWARE,
|
|
7
|
-
matchPageMiddlewareTarget,
|
|
8
|
-
type PageMiddlewareContext,
|
|
9
|
-
type PageMiddlewareRegistryEntry,
|
|
10
|
-
} from '@open-mercato/shared/modules/middleware/page'
|
|
11
|
-
|
|
12
|
-
function buildContext(overrides?: Partial<PageMiddlewareContext>): PageMiddlewareContext {
|
|
13
|
-
return {
|
|
14
|
-
pathname: '/backend/customers/people',
|
|
15
|
-
mode: 'backend',
|
|
16
|
-
routeMeta: { requireAuth: true },
|
|
17
|
-
auth: {
|
|
18
|
-
sub: 'user-1',
|
|
19
|
-
tenantId: 'tenant-1',
|
|
20
|
-
orgId: 'org-1',
|
|
21
|
-
roles: ['admin'],
|
|
22
|
-
},
|
|
23
|
-
ensureContainer: async () => ({ resolve: () => null }),
|
|
24
|
-
...overrides,
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe('matchPageMiddlewareTarget', () => {
|
|
29
|
-
it('matches exact string targets', () => {
|
|
30
|
-
expect(matchPageMiddlewareTarget('/backend', '/backend')).toBe(true)
|
|
31
|
-
expect(matchPageMiddlewareTarget('/backend/customers', '/backend')).toBe(false)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('matches wildcard prefix targets', () => {
|
|
35
|
-
expect(matchPageMiddlewareTarget('/backend/customers', '/backend/*')).toBe(true)
|
|
36
|
-
expect(matchPageMiddlewareTarget('/frontend/home', '/backend/*')).toBe(false)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('matches regexp targets', () => {
|
|
40
|
-
expect(matchPageMiddlewareTarget('/backend/customers', /^\/backend\/.+$/)).toBe(true)
|
|
41
|
-
expect(matchPageMiddlewareTarget('/backend', /^\/backend\/.+$/)).toBe(false)
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
describe('executePageMiddleware', () => {
|
|
46
|
-
it('returns continue when no middleware matches', async () => {
|
|
47
|
-
const entries: PageMiddlewareRegistryEntry[] = [
|
|
48
|
-
{
|
|
49
|
-
moduleId: 'security',
|
|
50
|
-
middleware: [
|
|
51
|
-
{
|
|
52
|
-
id: 'security.frontend',
|
|
53
|
-
mode: 'frontend',
|
|
54
|
-
target: '/frontend/*',
|
|
55
|
-
run: async () => CONTINUE_PAGE_MIDDLEWARE,
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
]
|
|
60
|
-
|
|
61
|
-
await expect(executePageMiddleware({ entries, context: buildContext() })).resolves.toEqual(
|
|
62
|
-
CONTINUE_PAGE_MIDDLEWARE,
|
|
63
|
-
)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('applies deterministic priority ordering and short-circuits on redirect', async () => {
|
|
67
|
-
const calls: string[] = []
|
|
68
|
-
const entries: PageMiddlewareRegistryEntry[] = [
|
|
69
|
-
{
|
|
70
|
-
moduleId: 'mod-a',
|
|
71
|
-
middleware: [
|
|
72
|
-
{
|
|
73
|
-
id: 'mod-a.first',
|
|
74
|
-
mode: 'backend',
|
|
75
|
-
target: '/backend/*',
|
|
76
|
-
priority: 20,
|
|
77
|
-
run: async () => {
|
|
78
|
-
calls.push('mod-a.first')
|
|
79
|
-
return CONTINUE_PAGE_MIDDLEWARE
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
id: 'mod-a.third',
|
|
84
|
-
mode: 'backend',
|
|
85
|
-
target: '/backend/*',
|
|
86
|
-
priority: 30,
|
|
87
|
-
run: async () => {
|
|
88
|
-
calls.push('mod-a.third')
|
|
89
|
-
return CONTINUE_PAGE_MIDDLEWARE
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
moduleId: 'mod-b',
|
|
96
|
-
middleware: [
|
|
97
|
-
{
|
|
98
|
-
id: 'mod-b.second',
|
|
99
|
-
mode: 'backend',
|
|
100
|
-
target: '/backend/*',
|
|
101
|
-
priority: 25,
|
|
102
|
-
run: async () => {
|
|
103
|
-
calls.push('mod-b.second')
|
|
104
|
-
return { action: 'redirect', location: '/backend/profile/security/mfa' } as const
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
},
|
|
109
|
-
]
|
|
110
|
-
|
|
111
|
-
await expect(
|
|
112
|
-
executePageMiddleware({ entries, context: buildContext() }),
|
|
113
|
-
).resolves.toEqual({ action: 'redirect', location: '/backend/profile/security/mfa' })
|
|
114
|
-
expect(calls).toEqual(['mod-a.first', 'mod-b.second'])
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('throws and emits error callback on middleware failure', async () => {
|
|
118
|
-
const onError = jest.fn()
|
|
119
|
-
const entries: PageMiddlewareRegistryEntry[] = [
|
|
120
|
-
{
|
|
121
|
-
moduleId: 'mod-a',
|
|
122
|
-
middleware: [
|
|
123
|
-
{
|
|
124
|
-
id: 'mod-a.fail',
|
|
125
|
-
mode: 'backend',
|
|
126
|
-
target: '/backend/*',
|
|
127
|
-
run: async () => {
|
|
128
|
-
throw new Error('boom')
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
},
|
|
133
|
-
]
|
|
134
|
-
|
|
135
|
-
await expect(
|
|
136
|
-
executePageMiddleware({ entries, context: buildContext(), onError }),
|
|
137
|
-
).rejects.toThrow('boom')
|
|
138
|
-
expect(onError).toHaveBeenCalledWith(expect.any(Error), { id: 'mod-a.fail', priority: undefined })
|
|
139
|
-
})
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
describe('resolvePageMiddlewareRedirect', () => {
|
|
143
|
-
it('returns redirect location for first terminal middleware', async () => {
|
|
144
|
-
const entries: PageMiddlewareRegistryEntry[] = [
|
|
145
|
-
{
|
|
146
|
-
moduleId: 'mod-a',
|
|
147
|
-
middleware: [
|
|
148
|
-
{
|
|
149
|
-
id: 'mod-a.redirect',
|
|
150
|
-
target: '/backend/*',
|
|
151
|
-
run: async () => ({ action: 'redirect', location: '/backend/profile/security/mfa' }),
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
},
|
|
155
|
-
]
|
|
156
|
-
|
|
157
|
-
await expect(resolvePageMiddlewareRedirect({ entries, context: buildContext() })).resolves.toBe(
|
|
158
|
-
'/backend/profile/security/mfa',
|
|
159
|
-
)
|
|
160
|
-
})
|
|
161
|
-
})
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
PageMiddlewareContext,
|
|
3
|
-
PageMiddlewareMode,
|
|
4
|
-
PageMiddlewareRegistryEntry,
|
|
5
|
-
PageMiddlewareResult,
|
|
6
|
-
PageRouteMiddleware,
|
|
7
|
-
} from '@open-mercato/shared/modules/middleware/page'
|
|
8
|
-
import { CONTINUE_PAGE_MIDDLEWARE, matchPageMiddlewareTarget } from '@open-mercato/shared/modules/middleware/page'
|
|
9
|
-
|
|
10
|
-
type ExecutePageMiddlewareArgs = {
|
|
11
|
-
entries: PageMiddlewareRegistryEntry[]
|
|
12
|
-
context: PageMiddlewareContext
|
|
13
|
-
onError?: (error: unknown, middleware: Pick<PageRouteMiddleware, 'id' | 'priority'>) => void
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const DEFAULT_PRIORITY = 100
|
|
17
|
-
|
|
18
|
-
function shouldRunMiddleware(middleware: PageRouteMiddleware, mode: PageMiddlewareMode, pathname: string): boolean {
|
|
19
|
-
if (middleware.mode && middleware.mode !== mode) return false
|
|
20
|
-
return matchPageMiddlewareTarget(pathname, middleware.target)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function compareMiddleware(a: PageRouteMiddleware, b: PageRouteMiddleware): number {
|
|
24
|
-
const priorityDiff = (a.priority ?? DEFAULT_PRIORITY) - (b.priority ?? DEFAULT_PRIORITY)
|
|
25
|
-
if (priorityDiff !== 0) return priorityDiff
|
|
26
|
-
return a.id.localeCompare(b.id)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function flattenAndSortMiddleware(
|
|
30
|
-
entries: PageMiddlewareRegistryEntry[],
|
|
31
|
-
mode: PageMiddlewareMode,
|
|
32
|
-
pathname: string
|
|
33
|
-
): PageRouteMiddleware[] {
|
|
34
|
-
return entries
|
|
35
|
-
.flatMap((entry) => entry.middleware)
|
|
36
|
-
.filter((middleware) => shouldRunMiddleware(middleware, mode, pathname))
|
|
37
|
-
.sort(compareMiddleware)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function executePageMiddleware(args: ExecutePageMiddlewareArgs): Promise<PageMiddlewareResult> {
|
|
41
|
-
const { entries, context, onError } = args
|
|
42
|
-
const matchedMiddleware = flattenAndSortMiddleware(entries, context.mode, context.pathname)
|
|
43
|
-
for (const middleware of matchedMiddleware) {
|
|
44
|
-
try {
|
|
45
|
-
const result = await middleware.run(context)
|
|
46
|
-
if (result.action === 'redirect') return result
|
|
47
|
-
} catch (error) {
|
|
48
|
-
if (onError) {
|
|
49
|
-
onError(error, { id: middleware.id, priority: middleware.priority })
|
|
50
|
-
} else {
|
|
51
|
-
console.error('[middleware:page] execution failed', { id: middleware.id, error })
|
|
52
|
-
}
|
|
53
|
-
throw error
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return CONTINUE_PAGE_MIDDLEWARE
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function resolvePageMiddlewareRedirect(args: ExecutePageMiddlewareArgs): Promise<string | null> {
|
|
60
|
-
const result = await executePageMiddleware(args)
|
|
61
|
-
if (result.action !== 'redirect') return null
|
|
62
|
-
return result.location
|
|
63
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
|
|
2
|
-
|
|
3
|
-
export type PageMiddlewareMode = 'frontend' | 'backend'
|
|
4
|
-
|
|
5
|
-
export type PageRouteMeta = {
|
|
6
|
-
requireAuth?: boolean
|
|
7
|
-
requireRoles?: string[]
|
|
8
|
-
requireFeatures?: string[]
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type PageMiddlewareContainer = {
|
|
12
|
-
resolve: (name: string) => unknown
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type PageMiddlewareContext = {
|
|
16
|
-
pathname: string
|
|
17
|
-
mode: PageMiddlewareMode
|
|
18
|
-
routeMeta: PageRouteMeta
|
|
19
|
-
auth: AuthContext
|
|
20
|
-
ensureContainer: () => Promise<PageMiddlewareContainer>
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export type PageMiddlewareResult =
|
|
24
|
-
| { action: 'continue' }
|
|
25
|
-
| { action: 'redirect'; location: string }
|
|
26
|
-
|
|
27
|
-
export type PageMiddlewareTarget = string | RegExp
|
|
28
|
-
|
|
29
|
-
export type PageRouteMiddleware = {
|
|
30
|
-
id: string
|
|
31
|
-
mode?: PageMiddlewareMode
|
|
32
|
-
target: PageMiddlewareTarget
|
|
33
|
-
priority?: number
|
|
34
|
-
run: (ctx: PageMiddlewareContext) => Promise<PageMiddlewareResult> | PageMiddlewareResult
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type PageMiddlewareRegistryEntry = {
|
|
38
|
-
moduleId: string
|
|
39
|
-
middleware: PageRouteMiddleware[]
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function matchPageMiddlewareTarget(pathname: string, target: PageMiddlewareTarget): boolean {
|
|
43
|
-
if (target instanceof RegExp) {
|
|
44
|
-
return target.test(pathname)
|
|
45
|
-
}
|
|
46
|
-
if (target.endsWith('*')) {
|
|
47
|
-
return pathname.startsWith(target.slice(0, -1))
|
|
48
|
-
}
|
|
49
|
-
return pathname === target
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const CONTINUE_PAGE_MIDDLEWARE: PageMiddlewareResult = { action: 'continue' }
|