configuration-management 0.1.2 → 0.1.3
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 +4 -2
- package/README.md +0 -199
- package/actions/configurations/ext.config.yaml +0 -151
- package/src/abdb-config.js +0 -241
- package/src/abdb-helper.js +0 -476
- package/src/index.js +0 -20
- package/src/oauth1a.js +0 -135
- package/src/system-config-crypto.js +0 -113
- package/src/system-config-shared.js +0 -89
- package/web/styles.css +0 -1
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
-
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
-
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Equivalent of Magento's env.php `crypt.key`: a project-wide secret used to
|
|
9
|
-
// derive an AES-256-GCM key that protects sensitive system_config values at
|
|
10
|
-
// rest in App Builder DB (aio-lib-state).
|
|
11
|
-
//
|
|
12
|
-
// Key material is taken from action params/env (never from request payload):
|
|
13
|
-
// SYSTEM_CONFIG_CRYPT_KEY – preferred, dedicated secret
|
|
14
|
-
// OAUTH_CLIENT_SECRET – fallback, the workspace's client secret
|
|
15
|
-
//
|
|
16
|
-
// Wire format for ciphertext (string):
|
|
17
|
-
// enc:v1:<base64url(salt)>:<base64url(iv)>:<base64url(tag)>:<base64url(ct)>
|
|
18
|
-
// `v1` lets us rotate the algorithm later without breaking previously stored
|
|
19
|
-
// values. `salt` is per-record so the derived key changes even if the same
|
|
20
|
-
// master secret is reused across records.
|
|
21
|
-
|
|
22
|
-
const crypto = require('crypto')
|
|
23
|
-
|
|
24
|
-
const ENC_PREFIX = 'enc:v1:'
|
|
25
|
-
const KEY_BYTES = 32
|
|
26
|
-
const IV_BYTES = 12
|
|
27
|
-
const SALT_BYTES = 16
|
|
28
|
-
const SCRYPT_PARAMS = { N: 16384, r: 8, p: 1 }
|
|
29
|
-
|
|
30
|
-
function b64uEncode (buf) {
|
|
31
|
-
return Buffer.from(buf).toString('base64url')
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function b64uDecode (str) {
|
|
35
|
-
return Buffer.from(str, 'base64url')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function resolveMasterSecret (params = {}) {
|
|
39
|
-
const secret =
|
|
40
|
-
params.SYSTEM_CONFIG_CRYPT_KEY ||
|
|
41
|
-
params.OAUTH_CLIENT_SECRET ||
|
|
42
|
-
process.env.SYSTEM_CONFIG_CRYPT_KEY ||
|
|
43
|
-
process.env.OAUTH_CLIENT_SECRET ||
|
|
44
|
-
''
|
|
45
|
-
if (!secret || typeof secret !== 'string' || secret.length < 8) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
'Encryption key not configured: set SYSTEM_CONFIG_CRYPT_KEY or OAUTH_CLIENT_SECRET'
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
return secret
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function deriveKey (masterSecret, salt) {
|
|
54
|
-
return crypto.scryptSync(masterSecret, salt, KEY_BYTES, SCRYPT_PARAMS)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function isEncrypted (value) {
|
|
58
|
-
return typeof value === 'string' && value.startsWith(ENC_PREFIX)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function encrypt (plaintext, params) {
|
|
62
|
-
if (plaintext == null || plaintext === '') {
|
|
63
|
-
return plaintext
|
|
64
|
-
}
|
|
65
|
-
const text = typeof plaintext === 'string' ? plaintext : String(plaintext)
|
|
66
|
-
const secret = resolveMasterSecret(params)
|
|
67
|
-
const salt = crypto.randomBytes(SALT_BYTES)
|
|
68
|
-
const iv = crypto.randomBytes(IV_BYTES)
|
|
69
|
-
const key = deriveKey(secret, salt)
|
|
70
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)
|
|
71
|
-
const ct = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()])
|
|
72
|
-
const tag = cipher.getAuthTag()
|
|
73
|
-
return [
|
|
74
|
-
ENC_PREFIX,
|
|
75
|
-
b64uEncode(salt),
|
|
76
|
-
':',
|
|
77
|
-
b64uEncode(iv),
|
|
78
|
-
':',
|
|
79
|
-
b64uEncode(tag),
|
|
80
|
-
':',
|
|
81
|
-
b64uEncode(ct)
|
|
82
|
-
].join('')
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function decrypt (encrypted, params) {
|
|
86
|
-
if (!isEncrypted(encrypted)) {
|
|
87
|
-
return encrypted
|
|
88
|
-
}
|
|
89
|
-
const body = encrypted.slice(ENC_PREFIX.length)
|
|
90
|
-
const parts = body.split(':')
|
|
91
|
-
if (parts.length !== 4) {
|
|
92
|
-
throw new Error('Malformed encrypted value')
|
|
93
|
-
}
|
|
94
|
-
const [saltB64, ivB64, tagB64, ctB64] = parts
|
|
95
|
-
const secret = resolveMasterSecret(params)
|
|
96
|
-
const salt = b64uDecode(saltB64)
|
|
97
|
-
const iv = b64uDecode(ivB64)
|
|
98
|
-
const tag = b64uDecode(tagB64)
|
|
99
|
-
const ct = b64uDecode(ctB64)
|
|
100
|
-
const key = deriveKey(secret, salt)
|
|
101
|
-
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)
|
|
102
|
-
decipher.setAuthTag(tag)
|
|
103
|
-
const pt = Buffer.concat([decipher.update(ct), decipher.final()])
|
|
104
|
-
return pt.toString('utf8')
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
module.exports = {
|
|
108
|
-
ENC_PREFIX,
|
|
109
|
-
isEncrypted,
|
|
110
|
-
encrypt,
|
|
111
|
-
decrypt,
|
|
112
|
-
resolveMasterSecret
|
|
113
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
-
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
-
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Mirrors Magento's core_config_data: (scope, scope_id, path, value).
|
|
9
|
-
// aio-lib-state keys allow [a-zA-Z0-9_-] only, so we encode
|
|
10
|
-
// scope=`default` scopeId=0 path=`web/secure/base_url`
|
|
11
|
-
// as the state key
|
|
12
|
-
// sysconfig__default__0__web__secure__base_url
|
|
13
|
-
|
|
14
|
-
const STATE_KEY_PREFIX = 'sysconfig__'
|
|
15
|
-
const SCOPES = ['default', 'websites', 'stores']
|
|
16
|
-
const PATH_SEGMENT = /^[a-zA-Z0-9_-]+$/
|
|
17
|
-
const SCOPE_ID_RE = /^[a-zA-Z0-9_-]+$/
|
|
18
|
-
const SENSITIVE_PLACEHOLDER = '__SENSITIVE_UNCHANGED__'
|
|
19
|
-
const USE_DEFAULT_SENTINEL = '__USE_DEFAULT__'
|
|
20
|
-
|
|
21
|
-
function isValidPath (path) {
|
|
22
|
-
if (typeof path !== 'string') return false
|
|
23
|
-
const parts = path.split('/')
|
|
24
|
-
if (parts.length !== 3) return false
|
|
25
|
-
return parts.every((p) => PATH_SEGMENT.test(p))
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function normalizeScope (scope) {
|
|
29
|
-
if (!scope) return 'default'
|
|
30
|
-
if (!SCOPES.includes(scope)) {
|
|
31
|
-
throw new Error(`Invalid scope "${scope}". Expected one of: ${SCOPES.join(', ')}`)
|
|
32
|
-
}
|
|
33
|
-
return scope
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function normalizeScopeId (scope, scopeId) {
|
|
37
|
-
if (scope === 'default') return '0'
|
|
38
|
-
const id = String(scopeId ?? '').trim()
|
|
39
|
-
if (!id || !SCOPE_ID_RE.test(id)) {
|
|
40
|
-
throw new Error(`Invalid scopeId "${scopeId}" for scope "${scope}"`)
|
|
41
|
-
}
|
|
42
|
-
return id
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function toStateKey (scope, scopeId, path) {
|
|
46
|
-
if (!isValidPath(path)) {
|
|
47
|
-
throw new Error(`Invalid config path: ${path}`)
|
|
48
|
-
}
|
|
49
|
-
const s = normalizeScope(scope)
|
|
50
|
-
const sid = normalizeScopeId(s, scopeId)
|
|
51
|
-
return [STATE_KEY_PREFIX, s, '__', sid, '__', path.split('/').join('__')].join('')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Magento-style fallback chain. When reading at store scope we look up:
|
|
56
|
-
* stores:<storeId> → websites:<websiteId> → default:0
|
|
57
|
-
* `parentWebsiteId` is supplied by the caller (resolved from /rest/V1/store/storeViews).
|
|
58
|
-
*/
|
|
59
|
-
function buildInheritanceChain (scope, scopeId, parentWebsiteId) {
|
|
60
|
-
const s = normalizeScope(scope)
|
|
61
|
-
if (s === 'default') {
|
|
62
|
-
return [{ scope: 'default', scopeId: '0' }]
|
|
63
|
-
}
|
|
64
|
-
if (s === 'websites') {
|
|
65
|
-
return [
|
|
66
|
-
{ scope: 'websites', scopeId: normalizeScopeId('websites', scopeId) },
|
|
67
|
-
{ scope: 'default', scopeId: '0' }
|
|
68
|
-
]
|
|
69
|
-
}
|
|
70
|
-
// stores
|
|
71
|
-
const chain = [{ scope: 'stores', scopeId: normalizeScopeId('stores', scopeId) }]
|
|
72
|
-
if (parentWebsiteId !== undefined && parentWebsiteId !== null && String(parentWebsiteId) !== '') {
|
|
73
|
-
chain.push({ scope: 'websites', scopeId: normalizeScopeId('websites', parentWebsiteId) })
|
|
74
|
-
}
|
|
75
|
-
chain.push({ scope: 'default', scopeId: '0' })
|
|
76
|
-
return chain
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
module.exports = {
|
|
80
|
-
STATE_KEY_PREFIX,
|
|
81
|
-
SCOPES,
|
|
82
|
-
SENSITIVE_PLACEHOLDER,
|
|
83
|
-
USE_DEFAULT_SENTINEL,
|
|
84
|
-
isValidPath,
|
|
85
|
-
normalizeScope,
|
|
86
|
-
normalizeScopeId,
|
|
87
|
-
toStateKey,
|
|
88
|
-
buildInheritanceChain
|
|
89
|
-
}
|
package/web/styles.css
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@import './src/styles/index.css';
|