create-mercato-app 0.4.9-develop-7afbe1e834 → 0.4.9-develop-94fb251ed3

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,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mercato-app",
3
- "version": "0.4.9-develop-7afbe1e834",
3
+ "version": "0.4.9-develop-94fb251ed3",
4
4
  "type": "module",
5
5
  "description": "Create a new Open Mercato application",
6
6
  "main": "./dist/index.js",
@@ -37,6 +37,27 @@ OM_ENABLE_ENTERPRISE_MODULES=false
37
37
  # Requires OM_ENABLE_ENTERPRISE_MODULES=true
38
38
  OM_ENABLE_ENTERPRISE_MODULES_SSO=false
39
39
 
40
+ # Enterprise security module (MFA, passkeys, sudo)
41
+ # Active when enterprise modules are enabled and the security module is installed.
42
+ # Optional dedicated signing secret for MFA setup tokens.
43
+ # Falls back to AUTH_JWT_SECRET, AUTH_SECRET, or JWT_SECRET when left blank.
44
+ # Enable enterprise security module (default: false)
45
+ # Requires OM_ENABLE_ENTERPRISE_MODULES=true
46
+ OM_ENABLE_ENTERPRISE_MODULES_SECURITY=false
47
+
48
+ # OM_SECURITY_MFA_SETUP_SECRET=change-me-mfa-setup-secret
49
+ OM_SECURITY_TOTP_ISSUER=Open Mercato
50
+ OM_SECURITY_TOTP_WINDOW=1
51
+ OM_SECURITY_OTP_EXPIRY_SECONDS=600
52
+ OM_SECURITY_OTP_MAX_ATTEMPTS=5
53
+ OM_SECURITY_SUDO_DEFAULT_TTL=300
54
+ OM_SECURITY_SUDO_MAX_TTL=1800
55
+ OM_SECURITY_WEBAUTHN_RP_NAME=Open Mercato
56
+ # Defaults to the hostname from APP_URL / NEXT_PUBLIC_APP_URL when left blank.
57
+ OM_SECURITY_WEBAUTHN_RP_ID=
58
+ OM_SECURITY_RECOVERY_CODE_COUNT=10
59
+ OM_SECURITY_MFA_EMERGENCY_BYPASS=false
60
+
40
61
  # Admin email to notify about onboarding submissions
41
62
  ADMIN_EMAIL=ops@your-domain.com
42
63
 
@@ -4,6 +4,7 @@ import Link from 'next/link'
4
4
  import { findBackendMatch } from '@open-mercato/shared/modules/registry'
5
5
  import { modules } from '@/.mercato/generated/modules.generated'
6
6
  import { getAuthFromCookies } from '@open-mercato/shared/lib/auth/server'
7
+ import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
7
8
  import { ApplyBreadcrumb } from '@open-mercato/ui/backend/AppShell'
8
9
  import { AccessDeniedMessage } from '@open-mercato/ui/backend/detail'
9
10
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
@@ -13,6 +14,8 @@ import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacS
13
14
  import { ComponentReplacementHandles, resolveRegisteredComponent } from '@open-mercato/shared/modules/widgets/component-registry'
14
15
  import type { Metadata } from 'next'
15
16
  import { resolveLocalizedTitleMetadata } from '@/lib/metadata'
17
+ import { resolvePageMiddlewareRedirect } from '@open-mercato/shared/lib/middleware/page-executor'
18
+ import { backendMiddlewareEntries } from '@/.mercato/generated/backend-middleware.generated'
16
19
 
17
20
  type Awaitable<T> = T | Promise<T>
18
21
 
@@ -52,8 +55,16 @@ export default async function BackendCatchAll(props: BackendParams) {
52
55
  const pathname = '/backend/' + (params.slug?.join('/') ?? '')
53
56
  const match = findBackendMatch(modules, pathname)
54
57
  if (!match) return notFound()
58
+ let auth: AuthContext = null
59
+ let container: Awaited<ReturnType<typeof createRequestContainer>> | null = null
60
+ const ensureContainer = async () => {
61
+ if (!container) {
62
+ container = await createRequestContainer()
63
+ }
64
+ return container
65
+ }
55
66
  if (match.route.requireAuth) {
56
- const auth = await getAuthFromCookies()
67
+ auth = await getAuthFromCookies()
57
68
  if (!auth) redirect('/api/auth/session/refresh?redirect=' + encodeURIComponent(pathname))
58
69
  const required = match.route.requireRoles || []
59
70
  if (required.length) {
@@ -84,6 +95,21 @@ export default async function BackendCatchAll(props: BackendParams) {
84
95
  if (!ok) return renderAccessDenied()
85
96
  }
86
97
  }
98
+ const middlewareRedirect = await resolvePageMiddlewareRedirect({
99
+ entries: backendMiddlewareEntries,
100
+ context: {
101
+ pathname,
102
+ mode: 'backend',
103
+ routeMeta: {
104
+ requireAuth: match.route.requireAuth,
105
+ requireRoles: match.route.requireRoles,
106
+ requireFeatures: match.route.requireFeatures,
107
+ },
108
+ auth,
109
+ ensureContainer,
110
+ },
111
+ })
112
+ if (middlewareRedirect) redirect(middlewareRedirect)
87
113
  const pageHandle = ComponentReplacementHandles.page(pathname)
88
114
  const Component = resolveRegisteredComponent(pageHandle, match.route.Component)
89
115
 
@@ -1,10 +1,31 @@
1
1
  import { getAuthFromCookies } from '@open-mercato/shared/lib/auth/server'
2
2
  import { redirect } from 'next/navigation'
3
3
  import { DashboardScreen } from '@open-mercato/ui/backend/dashboard'
4
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
+ import { resolvePageMiddlewareRedirect } from '@open-mercato/shared/lib/middleware/page-executor'
6
+ import { backendMiddlewareEntries } from '@/.mercato/generated/backend-middleware.generated'
4
7
 
5
8
  export default async function BackendIndex() {
6
9
  const auth = await getAuthFromCookies()
7
10
  if (!auth) redirect('/api/auth/session/refresh?redirect=/backend')
11
+ let container: Awaited<ReturnType<typeof createRequestContainer>> | null = null
12
+ const ensureContainer = async () => {
13
+ if (!container) {
14
+ container = await createRequestContainer()
15
+ }
16
+ return container
17
+ }
18
+ const middlewareRedirect = await resolvePageMiddlewareRedirect({
19
+ entries: backendMiddlewareEntries,
20
+ context: {
21
+ pathname: '/backend',
22
+ mode: 'backend',
23
+ routeMeta: { requireAuth: true },
24
+ auth,
25
+ ensureContainer,
26
+ },
27
+ })
28
+ if (middlewareRedirect) redirect(middlewareRedirect)
8
29
  return (
9
30
  <div className="p-6 space-y-6">
10
31
  <DashboardScreen />
@@ -4,12 +4,15 @@ import { findFrontendMatch } from '@open-mercato/shared/modules/registry'
4
4
  import { modules } from '@/.mercato/generated/modules.generated'
5
5
  import { getAuthFromCookies } from '@open-mercato/shared/lib/auth/server'
6
6
  import { AccessDeniedMessage } from '@open-mercato/ui/backend/detail'
7
+ import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
7
8
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
8
9
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
9
10
  import { hasAllFeatures } from '@open-mercato/shared/lib/auth/featureMatch'
10
11
  import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
11
12
  import type { Metadata } from 'next'
12
13
  import { resolveLocalizedTitleMetadata } from '@/lib/metadata'
14
+ import { resolvePageMiddlewareRedirect } from '@open-mercato/shared/lib/middleware/page-executor'
15
+ import { frontendMiddlewareEntries } from '@/.mercato/generated/frontend-middleware.generated'
13
16
 
14
17
  type FrontendParams = { params: Promise<{ slug: string[] }> }
15
18
 
@@ -68,8 +71,16 @@ export default async function SiteCatchAll({ params }: FrontendParams) {
68
71
  }
69
72
 
70
73
  // Staff auth gate
74
+ let auth: AuthContext = null
75
+ let container: Awaited<ReturnType<typeof createRequestContainer>> | null = null
76
+ const ensureContainer = async () => {
77
+ if (!container) {
78
+ container = await createRequestContainer()
79
+ }
80
+ return container
81
+ }
71
82
  if (match.route.requireAuth) {
72
- const auth = await getAuthFromCookies()
83
+ auth = await getAuthFromCookies()
73
84
  if (!auth) redirect('/api/auth/session/refresh?redirect=' + encodeURIComponent(pathname))
74
85
  const required = match.route.requireRoles || []
75
86
  if (required.length) {
@@ -85,6 +96,21 @@ export default async function SiteCatchAll({ params }: FrontendParams) {
85
96
  if (!ok) return renderAccessDenied()
86
97
  }
87
98
  }
99
+ const middlewareRedirect = await resolvePageMiddlewareRedirect({
100
+ entries: frontendMiddlewareEntries,
101
+ context: {
102
+ pathname,
103
+ mode: 'frontend',
104
+ routeMeta: {
105
+ requireAuth: match.route.requireAuth,
106
+ requireRoles: match.route.requireRoles,
107
+ requireFeatures: match.route.requireFeatures,
108
+ },
109
+ auth,
110
+ ensureContainer,
111
+ },
112
+ })
113
+ if (middlewareRedirect) redirect(middlewareRedirect)
88
114
  const Component = match.route.Component
89
115
  return <Component params={match.params} />
90
116
  }
@@ -1,6 +1,6 @@
1
1
  import { NextResponse, type NextRequest } from 'next/server'
2
2
  import { findApi, type HttpMethod } from '@open-mercato/shared/modules/registry'
3
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
3
+ import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
4
4
  import { modules } from '@/.mercato/generated/modules.generated'
5
5
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
6
6
  import { bootstrap } from '@/bootstrap'
@@ -135,7 +135,7 @@ async function checkAuthorization(
135
135
  const guardContainer = await ensureContainer()
136
136
  await enforceTenantSelection({ auth, container: guardContainer }, tenantCandidate)
137
137
  } catch (error) {
138
- if (error instanceof CrudHttpError) {
138
+ if (isCrudHttpError(error)) {
139
139
  return NextResponse.json(error.body ?? { error: t('api.errors.forbidden', 'Forbidden') }, { status: error.status })
140
140
  }
141
141
  throw error
@@ -53,11 +53,13 @@ import { messageTypes } from '@/.mercato/generated/message-types.generated'
53
53
  import { messageObjectTypes } from '@/.mercato/generated/message-objects.generated'
54
54
  import { registerMessageTypes } from '@open-mercato/core/modules/messages/lib/message-types-registry'
55
55
  import { registerMessageObjectTypes } from '@open-mercato/core/modules/messages/lib/message-objects-registry'
56
+ import { runBootstrapRegistrations } from '@/.mercato/generated/bootstrap-registrations.generated'
56
57
 
57
58
  // Register event configs globally (similar to search)
58
59
  registerEventModuleConfigs(eventModuleConfigs)
59
60
  registerMessageTypes(messageTypes, { replace: true })
60
61
  registerMessageObjectTypes(messageObjectTypes, { replace: true })
62
+ runBootstrapRegistrations()
61
63
 
62
64
  // Bootstrap factory from shared package
63
65
  import { createBootstrap, isBootstrapped } from '@open-mercato/shared/lib/bootstrap'
@@ -615,8 +615,9 @@ test.describe('TC-UMES-003: Events & DOM Bridge', () => {
615
615
  await page.getByTestId('phase-d-run-probe').click()
616
616
 
617
617
  await expect(page.getByTestId('phase-d-status')).toContainText('ok')
618
- await expect(page.getByTestId('phase-d-result')).toContainText(personId)
619
618
  await expect(page.getByTestId('phase-d-result')).toContainText('_example')
619
+ await expect(page.getByTestId('phase-d-result')).toContainText('selectedRecord')
620
+ await expect(page.getByTestId('phase-d-result')).toContainText('inspectedCount')
620
621
  await expect(page.getByTestId('phase-d-result')).toContainText('example.customer-todo-count')
621
622
  } finally {
622
623
  await deleteEntityIfExists(request, adminToken, '/api/customers/people', personId)
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod'
2
2
  import { resolveNotificationContext } from '@open-mercato/core/modules/notifications/lib/routeHelpers'
3
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
3
4
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
4
5
 
5
6
  const emitNotificationSchema = z.object({
@@ -65,27 +66,23 @@ export async function POST(request: Request) {
65
66
  return Response.json({ id: notification.id }, { status: 201 })
66
67
  }
67
68
 
68
- export const openApi = {
69
- POST: {
70
- summary: 'Emit example actionable notification',
71
- tags: ['Example'],
72
- requestBody: {
73
- required: false,
74
- content: {
75
- 'application/json': {
76
- schema: emitNotificationSchema,
77
- },
69
+ export const openApi: OpenApiRouteDoc = {
70
+ tag: 'Example',
71
+ methods: {
72
+ POST: {
73
+ summary: 'Emit example actionable notification',
74
+ tags: ['Example'],
75
+ requestBody: {
76
+ contentType: 'application/json',
77
+ schema: emitNotificationSchema.optional(),
78
78
  },
79
- },
80
- responses: {
81
- 201: {
82
- description: 'Notification emitted',
83
- content: {
84
- 'application/json': {
85
- schema: z.object({ id: z.string().uuid() }),
86
- },
79
+ responses: [
80
+ {
81
+ status: 201,
82
+ description: 'Notification emitted',
83
+ schema: z.object({ id: z.string().uuid() }),
87
84
  },
88
- },
85
+ ],
89
86
  },
90
87
  },
91
88
  }