@open-mercato/cli 0.6.6-develop.5598.1.5e7d48d297 → 0.6.6-develop.5617.1.62538c48ca

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": "@open-mercato/cli",
3
- "version": "0.6.6-develop.5598.1.5e7d48d297",
3
+ "version": "0.6.6-develop.5617.1.62538c48ca",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -59,8 +59,8 @@
59
59
  "@mikro-orm/decorators": "^7.1.4",
60
60
  "@mikro-orm/migrations": "^7.1.4",
61
61
  "@mikro-orm/postgresql": "^7.1.4",
62
- "@open-mercato/queue": "0.6.6-develop.5598.1.5e7d48d297",
63
- "@open-mercato/shared": "0.6.6-develop.5598.1.5e7d48d297",
62
+ "@open-mercato/queue": "0.6.6-develop.5617.1.62538c48ca",
63
+ "@open-mercato/shared": "0.6.6-develop.5617.1.62538c48ca",
64
64
  "cross-spawn": "^7.0.6",
65
65
  "pg": "8.21.0",
66
66
  "semver": "^7.8.4",
@@ -70,10 +70,10 @@
70
70
  "typescript": "^6.0.3"
71
71
  },
72
72
  "peerDependencies": {
73
- "@open-mercato/shared": "0.6.6-develop.5598.1.5e7d48d297"
73
+ "@open-mercato/shared": "0.6.6-develop.5617.1.62538c48ca"
74
74
  },
75
75
  "devDependencies": {
76
- "@open-mercato/shared": "0.6.6-develop.5598.1.5e7d48d297",
76
+ "@open-mercato/shared": "0.6.6-develop.5617.1.62538c48ca",
77
77
  "@types/jest": "^30.0.0",
78
78
  "jest": "^30.4.2",
79
79
  "ts-jest": "^29.4.11"
@@ -1,15 +1,33 @@
1
1
  import { resolveInitDerivedSecrets } from '../init-secrets'
2
2
 
3
3
  describe('resolveInitDerivedSecrets', () => {
4
- it('defaults derived passwords to secret', () => {
4
+ it("seeds the well-known 'secret' password in non-production when no env overrides are set", () => {
5
5
  const env: NodeJS.ProcessEnv = {}
6
6
  const result = resolveInitDerivedSecrets({ email: 'piotr@gmail.com', env })
7
7
  expect(result.adminEmail).toBe('admin@gmail.com')
8
8
  expect(result.employeeEmail).toBe('employee@gmail.com')
9
+ // Dev/test default: the documented demo password, so `yarn dev` /
10
+ // `mercato init` workflows stay predictable across runs.
9
11
  expect(result.adminPassword).toBe('secret')
10
12
  expect(result.employeePassword).toBe('secret')
11
13
  })
12
14
 
15
+ it('generates random derived passwords in production when no env overrides are set', () => {
16
+ const env: NodeJS.ProcessEnv = { NODE_ENV: 'production' }
17
+ const result = resolveInitDerivedSecrets({ email: 'piotr@gmail.com', env })
18
+ expect(result.adminEmail).toBe('admin@gmail.com')
19
+ expect(result.employeeEmail).toBe('employee@gmail.com')
20
+ // Production must never seed the hardcoded default — that was the bug.
21
+ expect(result.adminPassword).not.toBe('secret')
22
+ expect(result.employeePassword).not.toBe('secret')
23
+ expect(typeof result.adminPassword).toBe('string')
24
+ expect(typeof result.employeePassword).toBe('string')
25
+ expect(result.adminPassword!.length).toBeGreaterThanOrEqual(16)
26
+ expect(result.employeePassword!.length).toBeGreaterThanOrEqual(16)
27
+ // 96 bits of entropy: two independently generated secrets must not collide.
28
+ expect(result.adminPassword).not.toBe(result.employeePassword)
29
+ })
30
+
13
31
  it('respects explicit derived overrides', () => {
14
32
  const env: NodeJS.ProcessEnv = {
15
33
  OM_INIT_ADMIN_EMAIL: 'admin@acme.com',
@@ -24,11 +42,9 @@ describe('resolveInitDerivedSecrets', () => {
24
42
  expect(result.employeePassword).toBe('EmployeeSecret')
25
43
  })
26
44
 
27
- it('generates random secrets when enabled', () => {
28
- const env: NodeJS.ProcessEnv = {
29
- OM_INIT_GENERATE_RANDOM_PASSWORD: 'true',
30
- }
31
- const randomBuffer = Buffer.from('abcdefghi')
45
+ it('uses the provided randomSource (base64url-encoded 12 bytes) in production', () => {
46
+ const env: NodeJS.ProcessEnv = { NODE_ENV: 'production' }
47
+ const randomBuffer = Buffer.from('abcdefghijkl')
32
48
  const randomSource = () => randomBuffer
33
49
  const expected = randomBuffer.toString('base64url')
34
50
  const result = resolveInitDerivedSecrets({ email: 'boss@acme.com', env, randomSource })
@@ -36,6 +52,21 @@ describe('resolveInitDerivedSecrets', () => {
36
52
  expect(result.employeePassword).toBe(expected)
37
53
  })
38
54
 
55
+ it('ignores the deprecated OM_INIT_GENERATE_RANDOM_PASSWORD toggle in production (randomization is unconditional)', () => {
56
+ // The toggle was a partial mitigation when 'secret' was still the default.
57
+ // Now production randomization is always-on when env vars are unset, so the
58
+ // flag is a no-op — setting it should not change the output shape.
59
+ const warn = jest.spyOn(console, 'warn').mockImplementation(() => undefined)
60
+ try {
61
+ const env: NodeJS.ProcessEnv = { NODE_ENV: 'production', OM_INIT_GENERATE_RANDOM_PASSWORD: 'true' }
62
+ const result = resolveInitDerivedSecrets({ email: 'boss@acme.com', env })
63
+ expect(result.adminPassword).not.toBe('secret')
64
+ expect(typeof result.adminPassword).toBe('string')
65
+ } finally {
66
+ warn.mockRestore()
67
+ }
68
+ })
69
+
39
70
  it('skips derived accounts without a domain', () => {
40
71
  const env: NodeJS.ProcessEnv = {}
41
72
  const result = resolveInitDerivedSecrets({ email: 'local', env })
@@ -3,8 +3,6 @@ import { randomBytes } from 'node:crypto'
3
3
 
4
4
  type EnvReader = (key: string) => string | undefined
5
5
 
6
- const DEFAULT_DERIVED_PASSWORD = 'secret'
7
-
8
6
  function readEnvValue(env: NodeJS.ProcessEnv, key: string): string | undefined {
9
7
  const value = env[key]
10
8
  if (typeof value !== 'string') return undefined
@@ -24,6 +22,14 @@ export type InitDerivedSecrets = {
24
22
  employeePassword: string | null
25
23
  }
26
24
 
25
+ // Well-known dev/demo password baked into local bootstrap flows so developers
26
+ // keep the documented "log in as admin@<domain> with password 'secret'" UX.
27
+ // Only used when NODE_ENV !== 'production' and the OM_INIT_*_PASSWORD env vars
28
+ // are unset — production paths always use a random 96-bit secret.
29
+ const DEMO_DERIVED_PASSWORD = 'secret'
30
+
31
+ let warnedAboutDeprecatedRandomToggle = false
32
+
27
33
  export function resolveInitDerivedSecrets(options: {
28
34
  email: string
29
35
  env?: NodeJS.ProcessEnv
@@ -34,14 +40,28 @@ export function resolveInitDerivedSecrets(options: {
34
40
  const [, domain] = String(options.email ?? '').split('@')
35
41
  const adminEmail = envRead('OM_INIT_ADMIN_EMAIL') ?? resolveEmailFromDomain(domain, 'admin')
36
42
  const employeeEmail = envRead('OM_INIT_EMPLOYEE_EMAIL') ?? resolveEmailFromDomain(domain, 'employee')
37
- const randomEnabled = parseBooleanToken(envRead('OM_INIT_GENERATE_RANDOM_PASSWORD') ?? '') === true
43
+ // OM_INIT_GENERATE_RANDOM_PASSWORD used to be an opt-in toggle for random
44
+ // derived secrets; in production it is now the unconditional behaviour when
45
+ // no override is set. Surface a one-time deprecation warning so existing
46
+ // operators notice their config is no longer required.
47
+ if (parseBooleanToken(envRead('OM_INIT_GENERATE_RANDOM_PASSWORD') ?? '') === true && !warnedAboutDeprecatedRandomToggle) {
48
+ warnedAboutDeprecatedRandomToggle = true
49
+ console.warn(
50
+ '⚠️ OM_INIT_GENERATE_RANDOM_PASSWORD is deprecated and no longer required: derived admin/employee passwords are always randomized in production when overrides are unset.',
51
+ )
52
+ }
53
+ const isProduction = (env.NODE_ENV ?? '').trim().toLowerCase() === 'production'
38
54
  const randomize = options.randomSource ?? randomBytes
39
- const randomSecret = () => randomize(9).toString('base64url')
55
+ const randomSecret = () => randomize(12).toString('base64url')
40
56
  const resolvePassword = (key: string, emailValue: string | null) => {
41
57
  if (!emailValue) return null
42
58
  const envValue = envRead(key)
43
59
  if (envValue) return envValue
44
- return randomEnabled ? randomSecret() : DEFAULT_DERIVED_PASSWORD
60
+ // Non-production (dev/test/staging defaults): seed the documented demo
61
+ // password so `yarn dev` / `mercato init` workflows stay predictable.
62
+ // Production: generate a fresh random secret so credentials are never
63
+ // hardcoded and the operator-facing CLI prints the value once.
64
+ return isProduction ? randomSecret() : DEMO_DERIVED_PASSWORD
45
65
  }
46
66
 
47
67
  return {
@@ -51,4 +71,3 @@ export function resolveInitDerivedSecrets(options: {
51
71
  employeePassword: resolvePassword('OM_INIT_EMPLOYEE_PASSWORD', employeeEmail),
52
72
  }
53
73
  }
54
-
@@ -1660,6 +1660,15 @@ function buildReusableEnvironment(
1660
1660
  NODE_ENV: 'production',
1661
1661
  JWT_SECRET: process.env.JWT_SECRET ?? 'om-ephemeral-integration-jwt-secret',
1662
1662
  OM_SECURITY_MFA_SETUP_SECRET: process.env.OM_SECURITY_MFA_SETUP_SECRET ?? 'om-ephemeral-integration-mfa-setup-secret',
1663
+ // Integration probe + tests expect `admin@acme.com / secret` and
1664
+ // `employee@acme.com / secret`. NODE_ENV=production routes derived-user
1665
+ // password resolution through the random-fallback branch unless these
1666
+ // env vars are explicitly set; without the override every fresh
1667
+ // ephemeral run would mint random passwords and the login probe would
1668
+ // never converge. This is the documented production contract: set
1669
+ // OM_INIT_*_PASSWORD to fix the seeded credential.
1670
+ OM_INIT_ADMIN_PASSWORD: process.env.OM_INIT_ADMIN_PASSWORD ?? 'secret',
1671
+ OM_INIT_EMPLOYEE_PASSWORD: process.env.OM_INIT_EMPLOYEE_PASSWORD ?? 'secret',
1663
1672
  OM_INTEGRATION_TEST: 'true',
1664
1673
  OM_ENABLE_ENTERPRISE_MODULES: enterpriseModulesFlag,
1665
1674
  OM_ENABLE_ENTERPRISE_MODULES_SSO: process.env.OM_ENABLE_ENTERPRISE_MODULES_SSO ?? enterpriseModulesFlag,
@@ -1671,6 +1680,11 @@ function buildReusableEnvironment(
1671
1680
  // not have to call flushPendingCrudAccessLogs() explicitly.
1672
1681
  OM_CRUD_ACCESS_LOG_BLOCKING: process.env.OM_CRUD_ACCESS_LOG_BLOCKING ?? '1',
1673
1682
  OM_WEBHOOKS_ALLOW_PRIVATE_URLS: process.env.OM_WEBHOOKS_ALLOW_PRIVATE_URLS ?? '1',
1683
+ // Keep the bus in the Playwright process (used by in-test queue-drain helpers)
1684
+ // on the same delivery mode as the app server it drives: inline persistent
1685
+ // delivery so event side effects are deterministic for assertions. See the
1686
+ // matching OM_EVENTS_SINGLE_DELIVERY note on the app server environment below.
1687
+ OM_EVENTS_SINGLE_DELIVERY: process.env.OM_EVENTS_SINGLE_DELIVERY ?? 'false',
1674
1688
  ENABLE_CRUD_API_CACHE: 'true',
1675
1689
  MOCK_GATEWAY_WEBHOOK_SECRET: 'open-mercato-mock-dev-webhook-secret',
1676
1690
  MOCK_CARRIER_WEBHOOK_SECRET: 'open-mercato-mock-dev-carrier-webhook-secret',
@@ -2956,6 +2970,11 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
2956
2970
  JWT_SECRET: process.env.JWT_SECRET ?? 'om-ephemeral-integration-jwt-secret',
2957
2971
  OM_SECURITY_MFA_SETUP_SECRET: process.env.OM_SECURITY_MFA_SETUP_SECRET ?? 'om-ephemeral-integration-mfa-setup-secret',
2958
2972
  NODE_ENV: 'production',
2973
+ // See the auth-probe block above: pin derived-user passwords to the
2974
+ // documented 'secret' so the ephemeral login probe converges under
2975
+ // NODE_ENV=production.
2976
+ OM_INIT_ADMIN_PASSWORD: process.env.OM_INIT_ADMIN_PASSWORD ?? 'secret',
2977
+ OM_INIT_EMPLOYEE_PASSWORD: process.env.OM_INIT_EMPLOYEE_PASSWORD ?? 'secret',
2959
2978
  // Pool sizing for the ephemeral integration runtime. Defaults were once
2960
2979
  // very aggressive (max=5, idle=1000) which exposed flaky 'timeout exceeded
2961
2980
  // when trying to connect' errors on `progressService.createJob`-backed
@@ -2987,6 +3006,18 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
2987
3006
  CI: 'true',
2988
3007
  TENANT_DATA_ENCRYPTION_FALLBACK_KEY: process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY ?? 'om-ephemeral-integration-fallback-key',
2989
3008
  AUTO_SPAWN_WORKERS: process.env.AUTO_SPAWN_WORKERS ?? 'true',
3009
+ // Process persistent event subscribers INLINE in the request that emits
3010
+ // the event (legacy dual-dispatch), rather than the production default of
3011
+ // worker-only single delivery. Integration specs assert event side effects
3012
+ // (sync mappings, workflow-trigger instances, notifications) immediately
3013
+ // after the emitting API call and poll for them on short budgets; routing
3014
+ // those subscribers through the async events worker makes the side effect
3015
+ // race the poll under the 15-shard CI load, which surfaced as flaky
3016
+ // timeouts in TC-CRM-028 (inbound sync mapping) and TC-WF-008 (event-
3017
+ // triggered workflow) once single-delivery became default-ON. Inline
3018
+ // delivery makes the side effects deterministic for tests; production keeps
3019
+ // the single-delivery default. Honor an explicit override if the caller set one.
3020
+ OM_EVENTS_SINGLE_DELIVERY: process.env.OM_EVENTS_SINGLE_DELIVERY ?? 'false',
2990
3021
  AUTO_SPAWN_SCHEDULER: 'false',
2991
3022
  // Hide the demo feedback floating action button — it lives at
2992
3023
  // `fixed bottom-6 right-6 z-banner` and consistently intercepts pointer
package/src/mercato.ts CHANGED
@@ -1080,6 +1080,11 @@ export async function run(argv = process.argv) {
1080
1080
  '--email', email,
1081
1081
  '--password', password,
1082
1082
  '--roles', roles,
1083
+ // `mercato init` is the dev/demo bootstrap flow — it explicitly wants
1084
+ // the derived admin@/employee@ demo accounts. Standalone callers of
1085
+ // `mercato auth setup` must opt in themselves; without this flag the
1086
+ // setup command no longer seeds those accounts by default.
1087
+ '--include-demo-users',
1083
1088
  ]
1084
1089
  if (skipPasswordPolicy) {
1085
1090
  setupArgs.push('--skip-password-policy')