@open-mercato/cli 0.6.6-develop.5612.1.d382eb2f33 → 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/dist/lib/init-secrets.js +11 -4
- package/dist/lib/init-secrets.js.map +2 -2
- package/dist/lib/testing/integration.js +14 -0
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +6 -1
- package/dist/mercato.js.map +2 -2
- package/package.json +5 -5
- package/src/lib/__tests__/init-secrets.test.ts +37 -6
- package/src/lib/init-secrets.ts +25 -6
- package/src/lib/testing/integration.ts +14 -0
- package/src/mercato.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/cli",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
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.
|
|
63
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
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.
|
|
73
|
+
"@open-mercato/shared": "0.6.6-develop.5617.1.62538c48ca"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
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('
|
|
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('
|
|
28
|
-
const env: NodeJS.ProcessEnv = {
|
|
29
|
-
|
|
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 })
|
package/src/lib/init-secrets.ts
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
|
|
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,
|
|
@@ -2961,6 +2970,11 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
|
|
|
2961
2970
|
JWT_SECRET: process.env.JWT_SECRET ?? 'om-ephemeral-integration-jwt-secret',
|
|
2962
2971
|
OM_SECURITY_MFA_SETUP_SECRET: process.env.OM_SECURITY_MFA_SETUP_SECRET ?? 'om-ephemeral-integration-mfa-setup-secret',
|
|
2963
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',
|
|
2964
2978
|
// Pool sizing for the ephemeral integration runtime. Defaults were once
|
|
2965
2979
|
// very aggressive (max=5, idle=1000) which exposed flaky 'timeout exceeded
|
|
2966
2980
|
// when trying to connect' errors on `progressService.createJob`-backed
|
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')
|