@open-mercato/shared 0.6.7-develop.6860.1.f796b22e80 → 0.6.7
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +6 -57
- package/build.mjs +4 -2
- package/dist/lib/auth/server.js +2 -15
- package/dist/lib/auth/server.js.map +2 -2
- package/dist/lib/bootstrap/dynamicLoader.js +43 -117
- package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
- package/dist/lib/commands/command-bus.js +1 -5
- package/dist/lib/commands/command-bus.js.map +2 -2
- package/dist/lib/commands/command-interceptor-runner.js +2 -2
- package/dist/lib/commands/command-interceptor-runner.js.map +2 -2
- package/dist/lib/crud/enricher-runner.js +11 -2
- package/dist/lib/crud/enricher-runner.js.map +2 -2
- package/dist/lib/crud/factory.js +4 -63
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/crud/interceptor-runner.js +2 -2
- package/dist/lib/crud/interceptor-runner.js.map +2 -2
- package/dist/lib/crud/mutation-guard-registry.js +2 -2
- package/dist/lib/crud/mutation-guard-registry.js.map +2 -2
- package/dist/lib/crud/types.js +0 -4
- package/dist/lib/crud/types.js.map +3 -3
- package/dist/lib/data/engine.js +11 -37
- package/dist/lib/data/engine.js.map +2 -2
- package/dist/lib/db/mikro.js +12 -3
- package/dist/lib/db/mikro.js.map +2 -2
- package/dist/lib/db/pg-errors.js +0 -42
- package/dist/lib/db/pg-errors.js.map +2 -2
- package/dist/lib/di/container.js +6 -48
- package/dist/lib/di/container.js.map +2 -2
- package/dist/lib/email/send.js +0 -56
- package/dist/lib/email/send.js.map +2 -2
- package/dist/lib/encryption/subscriber.js +10 -2
- package/dist/lib/encryption/subscriber.js.map +2 -2
- package/dist/lib/encryption/tenantDataEncryptionService.js +17 -52
- package/dist/lib/encryption/tenantDataEncryptionService.js.map +2 -2
- package/dist/lib/i18n/config.js +1 -1
- package/dist/lib/i18n/config.js.map +2 -2
- package/dist/lib/i18n/server.js +4 -4
- package/dist/lib/i18n/server.js.map +3 -3
- package/dist/lib/logger/index.js +3 -47
- package/dist/lib/logger/index.js.map +3 -3
- package/dist/lib/modules/registry.js +1 -5
- package/dist/lib/modules/registry.js.map +2 -2
- package/dist/lib/query/engine.js +40 -54
- package/dist/lib/query/engine.js.map +2 -2
- package/dist/lib/query/types.js.map +1 -1
- package/dist/lib/redis/connection.js +2 -7
- package/dist/lib/redis/connection.js.map +2 -2
- package/dist/lib/search/config.js +2 -61
- package/dist/lib/search/config.js.map +2 -2
- package/dist/lib/search/tokenize.js +11 -23
- package/dist/lib/search/tokenize.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/webhooks/index.js +1 -13
- package/dist/lib/webhooks/index.js.map +2 -2
- package/dist/lib/webhooks/verify.js +7 -12
- package/dist/lib/webhooks/verify.js.map +2 -2
- package/dist/modules/analytics.js.map +2 -2
- package/dist/modules/overrides.js +1 -50
- package/dist/modules/overrides.js.map +2 -2
- package/dist/modules/payment_gateways/types.js +1 -8
- package/dist/modules/payment_gateways/types.js.map +2 -2
- package/dist/modules/registry.js +0 -1
- package/dist/modules/registry.js.map +2 -2
- package/dist/security/enabledModulesRegistry.js +11 -56
- package/dist/security/enabledModulesRegistry.js.map +2 -2
- package/jest.config.cjs +1 -2
- package/package.json +9 -19
- package/src/lib/ai/llm-provider.ts +0 -33
- package/src/lib/auth/__tests__/server.test.ts +0 -23
- package/src/lib/auth/server.ts +2 -23
- package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +8 -63
- package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +1 -21
- package/src/lib/bootstrap/dynamicLoader.ts +65 -213
- package/src/lib/commands/__tests__/command-interceptor-runner.test.ts +0 -28
- package/src/lib/commands/command-bus.ts +1 -5
- package/src/lib/commands/command-interceptor-runner.ts +2 -2
- package/src/lib/crud/__tests__/crud-factory.test.ts +0 -298
- package/src/lib/crud/__tests__/mutation-guard-registry.test.ts +0 -26
- package/src/lib/crud/enricher-runner.ts +11 -2
- package/src/lib/crud/factory.ts +1 -69
- package/src/lib/crud/interceptor-runner.ts +2 -2
- package/src/lib/crud/mutation-guard-registry.ts +2 -2
- package/src/lib/crud/types.ts +0 -3
- package/src/lib/data/__tests__/engine.bulk-suppress.test.ts +3 -18
- package/src/lib/data/engine.ts +16 -50
- package/src/lib/db/__tests__/mikro.test.ts +25 -0
- package/src/lib/db/mikro.ts +38 -13
- package/src/lib/db/pg-errors.ts +0 -58
- package/src/lib/di/container.ts +8 -72
- package/src/lib/email/__tests__/send.test.ts +1 -37
- package/src/lib/email/send.ts +0 -79
- package/src/lib/encryption/__tests__/subscriber.test.ts +2 -54
- package/src/lib/encryption/__tests__/tenantDataEncryptionService.test.ts +0 -58
- package/src/lib/encryption/subscriber.ts +14 -2
- package/src/lib/encryption/tenantDataEncryptionService.ts +18 -64
- package/src/lib/i18n/config.ts +2 -2
- package/src/lib/i18n/server.ts +2 -6
- package/src/lib/logger/__tests__/logger.test.ts +0 -77
- package/src/lib/logger/index.ts +1 -59
- package/src/lib/modules/registry.ts +0 -10
- package/src/lib/query/__tests__/engine.test.ts +13 -65
- package/src/lib/query/engine.ts +58 -75
- package/src/lib/query/types.ts +5 -4
- package/src/lib/redis/__tests__/connection.test.ts +0 -16
- package/src/lib/redis/connection.ts +7 -10
- package/src/lib/search/__tests__/config.test.ts +0 -177
- package/src/lib/search/config.ts +8 -106
- package/src/lib/search/tokenize.ts +11 -33
- package/src/lib/webhooks/__tests__/verify.test.ts +1 -20
- package/src/lib/webhooks/index.ts +1 -19
- package/src/lib/webhooks/verify.ts +9 -17
- package/src/modules/__tests__/registry.test.ts +2 -10
- package/src/modules/analytics.ts +0 -7
- package/src/modules/customer-auth.ts +0 -1
- package/src/modules/encryption.ts +0 -3
- package/src/modules/navigation/backendChrome.ts +0 -15
- package/src/modules/overrides.ts +0 -103
- package/src/modules/payment_gateways/types.ts +0 -41
- package/src/modules/registry.ts +0 -3
- package/src/security/enabledModulesRegistry.ts +12 -64
- package/dist/lib/ai/safety-identifier.js +0 -32
- package/dist/lib/ai/safety-identifier.js.map +0 -7
- package/dist/lib/auth/organizationScope.js +0 -34
- package/dist/lib/auth/organizationScope.js.map +0 -7
- package/dist/lib/bootstrap/clientOnlyModules.js +0 -55
- package/dist/lib/bootstrap/clientOnlyModules.js.map +0 -7
- package/dist/lib/data/consistency.js +0 -19
- package/dist/lib/data/consistency.js.map +0 -7
- package/dist/lib/db/entityMetadata.js +0 -26
- package/dist/lib/db/entityMetadata.js.map +0 -7
- package/dist/lib/logger/extension.js +0 -29
- package/dist/lib/logger/extension.js.map +0 -7
- package/dist/lib/modules/surfaceFingerprint.js +0 -47
- package/dist/lib/modules/surfaceFingerprint.js.map +0 -7
- package/dist/lib/query/ciphertext-search-warning.js +0 -45
- package/dist/lib/query/ciphertext-search-warning.js.map +0 -7
- package/dist/lib/search/auto-indexing.js +0 -14
- package/dist/lib/search/auto-indexing.js.map +0 -7
- package/dist/lib/search/availability.js +0 -107
- package/dist/lib/search/availability.js.map +0 -7
- package/dist/lib/search/tokenLookup.js +0 -46
- package/dist/lib/search/tokenLookup.js.map +0 -7
- package/dist/lib/telemetry/runtime.js +0 -35
- package/dist/lib/telemetry/runtime.js.map +0 -7
- package/dist/lib/webhooks/body.js +0 -57
- package/dist/lib/webhooks/body.js.map +0 -7
- package/dist/lib/webhooks/inbound-types.js +0 -1
- package/dist/lib/webhooks/inbound-types.js.map +0 -7
- package/dist/modules/widgets/extension-points.js +0 -96
- package/dist/modules/widgets/extension-points.js.map +0 -7
- package/dist/security/featurePolicy.js +0 -62
- package/dist/security/featurePolicy.js.map +0 -7
- package/scripts/versionSource.cjs +0 -65
- package/src/lib/__tests__/versionSource.test.ts +0 -46
- package/src/lib/ai/__tests__/llm-provider-contract.test.ts +0 -59
- package/src/lib/ai/__tests__/safety-identifier.test.ts +0 -71
- package/src/lib/ai/safety-identifier.ts +0 -73
- package/src/lib/auth/__tests__/organizationScope.test.ts +0 -107
- package/src/lib/auth/organizationScope.ts +0 -65
- package/src/lib/bootstrap/__tests__/clientOnlyModules.test.ts +0 -189
- package/src/lib/bootstrap/__tests__/dynamicLoader.aliasResolver.test.ts +0 -98
- package/src/lib/bootstrap/__tests__/dynamicLoader.appDi.test.ts +0 -211
- package/src/lib/bootstrap/__tests__/dynamicLoader.generatedCacheRecovery.test.ts +0 -192
- package/src/lib/bootstrap/clientOnlyModules.ts +0 -85
- package/src/lib/crud/__tests__/crud-factory.cache-user-scope.test.ts +0 -171
- package/src/lib/data/__tests__/consistency.test.ts +0 -38
- package/src/lib/data/consistency.ts +0 -17
- package/src/lib/db/__tests__/entityMetadata.test.ts +0 -38
- package/src/lib/db/__tests__/pg-errors.test.ts +0 -48
- package/src/lib/db/entityMetadata.ts +0 -43
- package/src/lib/di/__tests__/container-app-di-absent.test.ts +0 -155
- package/src/lib/di/__tests__/container-app-di.test.ts +0 -173
- package/src/lib/di/__tests__/registrar-error-log.test.ts +0 -154
- package/src/lib/i18n/__tests__/config.test.ts +0 -8
- package/src/lib/i18n/__tests__/server-dictionary-cache.test.ts +0 -30
- package/src/lib/i18n/__tests__/server-unbootstrapped.test.ts +0 -29
- package/src/lib/logger/extension.ts +0 -59
- package/src/lib/modules/__tests__/surfaceFingerprint.test.ts +0 -122
- package/src/lib/modules/surfaceFingerprint.ts +0 -87
- package/src/lib/query/__tests__/ciphertext-search-warning.test.ts +0 -178
- package/src/lib/query/ciphertext-search-warning.ts +0 -95
- package/src/lib/search/__tests__/availability.test.ts +0 -211
- package/src/lib/search/__tests__/tokenLookup.test.ts +0 -206
- package/src/lib/search/__tests__/tokenize.test.ts +0 -49
- package/src/lib/search/auto-indexing.ts +0 -22
- package/src/lib/search/availability.ts +0 -232
- package/src/lib/search/tokenLookup.ts +0 -133
- package/src/lib/telemetry/runtime.ts +0 -72
- package/src/lib/webhooks/__tests__/body.test.ts +0 -95
- package/src/lib/webhooks/body.ts +0 -70
- package/src/lib/webhooks/inbound-types.ts +0 -125
- package/src/modules/__tests__/nav-group-order-override.test.ts +0 -183
- package/src/modules/payment_gateways/__tests__/types.test.ts +0 -37
- package/src/modules/widgets/__tests__/extension-points.test.ts +0 -101
- package/src/modules/widgets/extension-points.ts +0 -524
- package/src/security/__tests__/featurePolicy.test.ts +0 -166
- package/src/security/featurePolicy.ts +0 -89
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import crypto from 'crypto'
|
|
2
|
-
import { resolveSearchConfig,
|
|
2
|
+
import { resolveSearchConfig, type SearchConfig } from './config'
|
|
3
3
|
|
|
4
4
|
export type TokenizationResult = {
|
|
5
5
|
tokens: string[]
|
|
@@ -20,28 +20,13 @@ function splitTokens(text: string, minLength: number): string[] {
|
|
|
20
20
|
.filter((token) => token.length >= minLength)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function
|
|
24
|
-
token
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
limit: number,
|
|
29
|
-
): void {
|
|
30
|
-
const append = (candidate: string): boolean => {
|
|
31
|
-
if (seen.has(candidate)) return tokens.length < limit
|
|
32
|
-
seen.add(candidate)
|
|
33
|
-
tokens.push(candidate)
|
|
34
|
-
return tokens.length < limit
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!config.enablePartials) {
|
|
38
|
-
append(token)
|
|
39
|
-
return
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
for (let length = config.minTokenLength; length <= token.length; length += 1) {
|
|
43
|
-
if (!append(token.slice(0, length))) return
|
|
23
|
+
function expandToken(token: string, config: SearchConfig): string[] {
|
|
24
|
+
if (!config.enablePartials) return [token]
|
|
25
|
+
const results: string[] = []
|
|
26
|
+
for (let i = config.minTokenLength; i <= token.length; i += 1) {
|
|
27
|
+
results.push(token.slice(0, i))
|
|
44
28
|
}
|
|
29
|
+
return results
|
|
45
30
|
}
|
|
46
31
|
|
|
47
32
|
export function hashToken(token: string, config?: SearchConfig): string {
|
|
@@ -51,17 +36,10 @@ export function hashToken(token: string, config?: SearchConfig): string {
|
|
|
51
36
|
|
|
52
37
|
export function tokenizeText(text: string, config?: SearchConfig): TokenizationResult {
|
|
53
38
|
const cfg = config ?? resolveSearchConfig()
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const tokens: string[] = []
|
|
59
|
-
|
|
60
|
-
for (const token of splitTokens(boundedText, cfg.minTokenLength)) {
|
|
61
|
-
if (tokens.length >= tokenLimit) break
|
|
62
|
-
appendExpandedToken(token, cfg, seen, tokens, tokenLimit)
|
|
63
|
-
}
|
|
64
|
-
|
|
39
|
+
const baseTokens = splitTokens(text, cfg.minTokenLength)
|
|
40
|
+
const expanded = baseTokens.flatMap((token) => expandToken(token, cfg))
|
|
41
|
+
const unique = Array.from(new Set(expanded))
|
|
42
|
+
const tokens = unique.filter((token) => token.length >= cfg.minTokenLength)
|
|
65
43
|
const hashes = tokens.map((token) => hashToken(token, cfg))
|
|
66
44
|
return { tokens, hashes }
|
|
67
45
|
}
|
|
@@ -1,26 +1,7 @@
|
|
|
1
1
|
import { signWebhookPayload } from '../sign'
|
|
2
|
-
import {
|
|
2
|
+
import { verifyWebhookSignature } from '../verify'
|
|
3
3
|
import { generateWebhookSecret } from '../secrets'
|
|
4
4
|
|
|
5
|
-
describe('isWebhookTimestampWithinTolerance', () => {
|
|
6
|
-
it('accepts timestamps inside the configured tolerance', () => {
|
|
7
|
-
expect(isWebhookTimestampWithinTolerance('1700000000', 300, 1700000299)).toBe(true)
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it('rejects stale timestamps outside the configured tolerance', () => {
|
|
11
|
-
expect(isWebhookTimestampWithinTolerance('1700000000', 300, 1700000301)).toBe(false)
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('rejects timestamps too far in the future', () => {
|
|
15
|
-
expect(isWebhookTimestampWithinTolerance('1700000601', 300, 1700000000)).toBe(false)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('rejects malformed timestamps', () => {
|
|
19
|
-
expect(isWebhookTimestampWithinTolerance('not-a-timestamp', 300, 1700000000)).toBe(false)
|
|
20
|
-
expect(isWebhookTimestampWithinTolerance('1700000000abc', 300, 1700000000)).toBe(false)
|
|
21
|
-
})
|
|
22
|
-
})
|
|
23
|
-
|
|
24
5
|
describe('verifyWebhookSignature', () => {
|
|
25
6
|
const msgId = 'msg_test123'
|
|
26
7
|
const body = '{"type":"test.event","data":{}}'
|
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
export { signWebhookPayload, buildWebhookHeaders, generateMessageId } from './sign'
|
|
2
|
-
export {
|
|
2
|
+
export { verifyWebhookSignature } from './verify'
|
|
3
3
|
export { generateWebhookSecret, parseWebhookSecret, isValidWebhookSecret } from './secrets'
|
|
4
|
-
export {
|
|
5
|
-
DEFAULT_WEBHOOK_BODY_LIMIT_BYTES,
|
|
6
|
-
readBoundedRequestBody,
|
|
7
|
-
resolveWebhookBodyLimitBytes,
|
|
8
|
-
WebhookBodyTooLargeError,
|
|
9
|
-
} from './body'
|
|
10
4
|
export type { StandardWebhookHeaders, WebhookSigningKey, WebhookVerificationResult, StandardWebhookPayload } from './types'
|
|
11
|
-
export type {
|
|
12
|
-
InboundWebhookRequest,
|
|
13
|
-
WebhookSourceCredentialField,
|
|
14
|
-
WebhookSourceConfig,
|
|
15
|
-
WebhookHandlerMeta,
|
|
16
|
-
WebhookHandlerPayload,
|
|
17
|
-
WebhookHandlerContext,
|
|
18
|
-
WebhookHandler,
|
|
19
|
-
WebhookHandlerRegistryEntry,
|
|
20
|
-
WebhookHandlerResult,
|
|
21
|
-
WebhookIngestionStatus,
|
|
22
|
-
} from './inbound-types'
|
|
@@ -2,21 +2,7 @@ import { timingSafeEqual, createHmac } from 'node:crypto'
|
|
|
2
2
|
import { parseWebhookSecret } from './secrets'
|
|
3
3
|
import type { WebhookVerificationResult } from './types'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export function isWebhookTimestampWithinTolerance(
|
|
8
|
-
timestamp: string,
|
|
9
|
-
toleranceSeconds: number = WEBHOOK_SIGNATURE_TOLERANCE_SECONDS,
|
|
10
|
-
nowSeconds: number = Math.floor(Date.now() / 1000),
|
|
11
|
-
): boolean {
|
|
12
|
-
const normalizedTimestamp = timestamp.trim()
|
|
13
|
-
if (!/^\d+$/.test(normalizedTimestamp)) {
|
|
14
|
-
return false
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const timestampNum = Number.parseInt(normalizedTimestamp, 10)
|
|
18
|
-
return Math.abs(nowSeconds - timestampNum) <= toleranceSeconds
|
|
19
|
-
}
|
|
5
|
+
const TOLERANCE_SECONDS = 5 * 60 // 5 minutes
|
|
20
6
|
|
|
21
7
|
/**
|
|
22
8
|
* Verify a Standard Webhooks signature against one or more secrets.
|
|
@@ -28,9 +14,15 @@ export function verifyWebhookSignature(
|
|
|
28
14
|
body: string,
|
|
29
15
|
signatureHeader: string,
|
|
30
16
|
secrets: string[],
|
|
31
|
-
toleranceSeconds: number =
|
|
17
|
+
toleranceSeconds: number = TOLERANCE_SECONDS,
|
|
32
18
|
): WebhookVerificationResult {
|
|
33
|
-
|
|
19
|
+
const timestampNum = parseInt(timestamp, 10)
|
|
20
|
+
if (isNaN(timestampNum)) {
|
|
21
|
+
return { valid: false }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const now = Math.floor(Date.now() / 1000)
|
|
25
|
+
if (Math.abs(now - timestampNum) > toleranceSeconds) {
|
|
34
26
|
return { valid: false }
|
|
35
27
|
}
|
|
36
28
|
|
|
@@ -94,11 +94,7 @@ describe('CLI Modules Registry', () => {
|
|
|
94
94
|
{
|
|
95
95
|
id: 'auth',
|
|
96
96
|
defaultEncryptionMaps: [
|
|
97
|
-
{
|
|
98
|
-
entityId: 'auth:user',
|
|
99
|
-
keyScope: 'system',
|
|
100
|
-
fields: [{ field: 'email', hashField: 'email_hash' }],
|
|
101
|
-
},
|
|
97
|
+
{ entityId: 'auth:user', fields: [{ field: 'email', hashField: 'email_hash' }] },
|
|
102
98
|
],
|
|
103
99
|
},
|
|
104
100
|
{
|
|
@@ -110,11 +106,7 @@ describe('CLI Modules Registry', () => {
|
|
|
110
106
|
])
|
|
111
107
|
|
|
112
108
|
expect(maps).toEqual([
|
|
113
|
-
{
|
|
114
|
-
entityId: 'auth:user',
|
|
115
|
-
keyScope: 'system',
|
|
116
|
-
fields: [{ field: 'email', hashField: 'email_hash' }],
|
|
117
|
-
},
|
|
109
|
+
{ entityId: 'auth:user', fields: [{ field: 'email', hashField: 'email_hash' }] },
|
|
118
110
|
{ entityId: 'customers:customer_comment', fields: [{ field: 'body', hashField: null }] },
|
|
119
111
|
])
|
|
120
112
|
})
|
package/src/modules/analytics.ts
CHANGED
|
@@ -13,13 +13,6 @@ export type AnalyticsEntityTypeConfig = {
|
|
|
13
13
|
schema?: string
|
|
14
14
|
dateField: string
|
|
15
15
|
defaultScopeFields: string[]
|
|
16
|
-
/**
|
|
17
|
-
* Field name (a key of `fieldMappings`) holding the per-row currency of monetary
|
|
18
|
-
* columns. Declaring it lets analytics consumers verify that the rows they aggregate
|
|
19
|
-
* all carry one currency before labelling the result with it (#4676). Omit it for
|
|
20
|
-
* entities whose amounts are not per-row denominated.
|
|
21
|
-
*/
|
|
22
|
-
currencyField?: string
|
|
23
16
|
}
|
|
24
17
|
|
|
25
18
|
export type AnalyticsFieldMapping = {
|
|
@@ -3,10 +3,7 @@ export type ModuleEncryptionFieldRule = {
|
|
|
3
3
|
hashField?: string | null
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export type EncryptionKeyScope = 'tenant' | 'system'
|
|
7
|
-
|
|
8
6
|
export type ModuleEncryptionMap = {
|
|
9
7
|
entityId: string
|
|
10
|
-
keyScope?: EncryptionKeyScope
|
|
11
8
|
fields: ModuleEncryptionFieldRule[]
|
|
12
9
|
}
|
|
@@ -44,23 +44,9 @@ export type BackendChromeBrand = {
|
|
|
44
44
|
logo?: {
|
|
45
45
|
src: string
|
|
46
46
|
alt?: string
|
|
47
|
-
preserveAspectRatio?: boolean
|
|
48
47
|
} | null
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
/**
|
|
52
|
-
* The organization the current request is scoped to, resolved server-side.
|
|
53
|
-
*
|
|
54
|
-
* Distinct from `brand`, which is a *branding* channel and only populates when the organization has a
|
|
55
|
-
* logo configured. This is always present when a single organization is in scope, so UI can label
|
|
56
|
-
* "you are viewing: <name>" without a second round trip. `null` under an all-organizations selection,
|
|
57
|
-
* when no organization is in scope, or when the lookup fails.
|
|
58
|
-
*/
|
|
59
|
-
export type BackendChromeCurrentOrganization = {
|
|
60
|
-
id: string
|
|
61
|
-
name: string
|
|
62
|
-
}
|
|
63
|
-
|
|
64
50
|
export type BackendChromePayload = {
|
|
65
51
|
groups: BackendChromeNavGroup[]
|
|
66
52
|
settingsSections: BackendChromeSectionGroup[]
|
|
@@ -70,5 +56,4 @@ export type BackendChromePayload = {
|
|
|
70
56
|
grantedFeatures: string[]
|
|
71
57
|
roles: string[]
|
|
72
58
|
brand?: BackendChromeBrand | null
|
|
73
|
-
currentOrganization?: BackendChromeCurrentOrganization | null
|
|
74
59
|
}
|
package/src/modules/overrides.ts
CHANGED
|
@@ -116,21 +116,6 @@ export interface EncryptionOverridesShape {
|
|
|
116
116
|
maps?: EncryptionMapOverridesMap | LooseOverrideMap
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
/**
|
|
120
|
-
* Backend navigation ordering.
|
|
121
|
-
*
|
|
122
|
-
* `groupOrder` **prepends** sidebar nav group ids: the ids listed here rank ahead of every other
|
|
123
|
-
* group, in the order given, and any group not named keeps the ordering it has today. Prepending
|
|
124
|
-
* rather than replacing means an app that only cares about its own group lists that one id, instead of
|
|
125
|
-
* having to enumerate every shipped group and accidentally demoting the ones it forgot.
|
|
126
|
-
*
|
|
127
|
-
* This is a *default*, applied beneath both role and user sidebar preferences — an operator's own
|
|
128
|
-
* arrangement still wins.
|
|
129
|
-
*/
|
|
130
|
-
export interface NavOverridesShape {
|
|
131
|
-
groupOrder?: string[]
|
|
132
|
-
}
|
|
133
|
-
|
|
134
119
|
/**
|
|
135
120
|
* Umbrella shape for `entry.overrides`. Every key is optional; a
|
|
136
121
|
* downstream app sets only the domains it cares about.
|
|
@@ -151,7 +136,6 @@ export interface ModuleOverrides {
|
|
|
151
136
|
acl?: AclOverridesShape
|
|
152
137
|
di?: DiOverridesMap | LooseOverrideMap
|
|
153
138
|
encryption?: EncryptionOverridesShape
|
|
154
|
-
nav?: NavOverridesShape
|
|
155
139
|
}
|
|
156
140
|
|
|
157
141
|
/**
|
|
@@ -191,7 +175,6 @@ export type ModuleOverrideDomain =
|
|
|
191
175
|
| 'acl'
|
|
192
176
|
| 'di'
|
|
193
177
|
| 'encryption'
|
|
194
|
-
| 'nav'
|
|
195
178
|
|
|
196
179
|
export interface ModuleOverrideEntry<TShape> {
|
|
197
180
|
moduleId: string
|
|
@@ -244,7 +227,6 @@ const DOMAIN_KEYS: ModuleOverrideDomain[] = [
|
|
|
244
227
|
'acl',
|
|
245
228
|
'di',
|
|
246
229
|
'encryption',
|
|
247
|
-
'nav',
|
|
248
230
|
]
|
|
249
231
|
|
|
250
232
|
const TRACKING_ISSUE_HINT =
|
|
@@ -459,71 +441,6 @@ const encryptionMapOverrideStore: OverrideStore<ModuleEncryptionMap> = { modules
|
|
|
459
441
|
const diOverrideStore: OverrideStore<Exclude<DiBindingOverride, null>> = { modules: {}, programmatic: {} }
|
|
460
442
|
const setupOverridesByModule: Record<string, SetupOverridesShape> = {}
|
|
461
443
|
|
|
462
|
-
/**
|
|
463
|
-
* Sidebar nav ordering state.
|
|
464
|
-
*
|
|
465
|
-
* Persisted on `globalThis` rather than in a module-local variable. This is the one override domain
|
|
466
|
-
* whose consumer lives in a *different package* (`@open-mercato/core`'s backend chrome reads what the
|
|
467
|
-
* app's bootstrap wrote), and standalone builds can evaluate `@open-mercato/shared` through more than
|
|
468
|
-
* one server chunk — bootstrap would store the value in one instance while the reader saw `null` from
|
|
469
|
-
* another. See `.ai/lessons.md`, "Global registries in publishable packages must use `globalThis`".
|
|
470
|
-
*
|
|
471
|
-
* Two tiers, matching every other override domain and the documented resolution order: programmatic
|
|
472
|
-
* calls win over `modules.ts` inline declarations.
|
|
473
|
-
*/
|
|
474
|
-
const GLOBAL_NAV_OVERRIDE_STATE_KEY = '__openMercatoNavOverrideState__'
|
|
475
|
-
|
|
476
|
-
type NavOverrideState = {
|
|
477
|
-
/** From `modules.ts` inline `overrides.nav`, with the module entry that supplied it. */
|
|
478
|
-
modules: { moduleId: string; groupOrder: string[] } | null
|
|
479
|
-
/** From `applyNavGroupOrderOverrides`. Takes precedence over `modules`. */
|
|
480
|
-
programmatic: string[] | null
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function getNavOverrideState(): NavOverrideState {
|
|
484
|
-
const existing = (globalThis as Record<string, unknown>)[GLOBAL_NAV_OVERRIDE_STATE_KEY]
|
|
485
|
-
if (existing && typeof existing === 'object') {
|
|
486
|
-
const typed = existing as NavOverrideState
|
|
487
|
-
if ('modules' in typed && 'programmatic' in typed) return typed
|
|
488
|
-
}
|
|
489
|
-
const initial: NavOverrideState = { modules: null, programmatic: null }
|
|
490
|
-
;(globalThis as Record<string, unknown>)[GLOBAL_NAV_OVERRIDE_STATE_KEY] = initial
|
|
491
|
-
return initial
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/** Drops blank/duplicate ids and returns `null` when nothing usable remains. */
|
|
495
|
-
function normalizeNavGroupOrder(value: unknown): string[] | null {
|
|
496
|
-
if (!Array.isArray(value)) return null
|
|
497
|
-
const ids = Array.from(
|
|
498
|
-
new Set(
|
|
499
|
-
value
|
|
500
|
-
.filter((id): id is string => typeof id === 'string' && id.trim().length > 0)
|
|
501
|
-
.map((id) => id.trim()),
|
|
502
|
-
),
|
|
503
|
-
)
|
|
504
|
-
return ids.length > 0 ? ids : null
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Programmatic nav ordering, for env-driven boot decisions and tests. Takes precedence over
|
|
509
|
-
* `modules.ts` inline `overrides.nav`, consistent with the other domains' programmatic tier. Pass
|
|
510
|
-
* `null` to clear it and fall back to the inline declaration.
|
|
511
|
-
*/
|
|
512
|
-
export function applyNavGroupOrderOverrides(groupOrder: string[] | null): void {
|
|
513
|
-
getNavOverrideState().programmatic = groupOrder === null ? null : normalizeNavGroupOrder(groupOrder)
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Nav group ids an app wants ranked ahead of the built-in order, or `null` when none is configured.
|
|
518
|
-
*
|
|
519
|
-
* Consumers MUST treat `null` as "use the shipped ordering unchanged" — this is a default, applied
|
|
520
|
-
* beneath role and user sidebar preferences.
|
|
521
|
-
*/
|
|
522
|
-
export function getNavGroupOrderOverride(): readonly string[] | null {
|
|
523
|
-
const state = getNavOverrideState()
|
|
524
|
-
return state.programmatic ?? state.modules?.groupOrder ?? null
|
|
525
|
-
}
|
|
526
|
-
|
|
527
444
|
function normalizeIdOverrideKey(key: string, label: string): string | null {
|
|
528
445
|
if (typeof key !== 'string') return null
|
|
529
446
|
const trimmed = key.trim()
|
|
@@ -804,9 +721,6 @@ export function resetModuleContractOverridesForTests(): void {
|
|
|
804
721
|
clearStore(encryptionMapOverrideStore)
|
|
805
722
|
clearStore(diOverrideStore)
|
|
806
723
|
for (const key of Object.keys(setupOverridesByModule)) delete setupOverridesByModule[key]
|
|
807
|
-
const navState = getNavOverrideState()
|
|
808
|
-
navState.modules = null
|
|
809
|
-
navState.programmatic = null
|
|
810
724
|
}
|
|
811
725
|
|
|
812
726
|
/**
|
|
@@ -1586,24 +1500,7 @@ function encryptionOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<E
|
|
|
1586
1500
|
}
|
|
1587
1501
|
}
|
|
1588
1502
|
|
|
1589
|
-
function navOverridesApplier(entries: ReadonlyArray<ModuleOverrideEntry<NavOverridesShape>>): void {
|
|
1590
|
-
const state = getNavOverrideState()
|
|
1591
|
-
for (const entry of entries) {
|
|
1592
|
-
const groupOrder = normalizeNavGroupOrder(entry.overrides?.groupOrder)
|
|
1593
|
-
if (!groupOrder) continue
|
|
1594
|
-
if (state.modules && state.modules.moduleId !== entry.moduleId) {
|
|
1595
|
-
logger.warn('nav.groupOrder declared by more than one module — the later one wins', {
|
|
1596
|
-
previousModuleId: state.modules.moduleId,
|
|
1597
|
-
moduleId: entry.moduleId,
|
|
1598
|
-
hint: 'Sidebar group ordering is a single app-wide decision; declare it on one module entry.',
|
|
1599
|
-
})
|
|
1600
|
-
}
|
|
1601
|
-
state.modules = { moduleId: entry.moduleId, groupOrder }
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
1503
|
function registerBuiltInModuleOverrideAppliers(): void {
|
|
1606
|
-
registerModuleOverrideApplier<NavOverridesShape>('nav', navOverridesApplier)
|
|
1607
1504
|
registerModuleOverrideApplier<RoutesOverridesShape>('routes', routesOverridesApplier)
|
|
1608
1505
|
registerModuleOverrideApplier<EventsOverridesShape>('events', eventsOverridesApplier)
|
|
1609
1506
|
registerModuleOverrideApplier<WorkerOverridesMap>('workers', workersOverridesApplier)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { DEFAULT_WEBHOOK_BODY_LIMIT_BYTES } from '../../lib/webhooks/body'
|
|
2
|
-
|
|
3
1
|
// ── Unified Payment Status ──────────────────────────────────────────────────
|
|
4
2
|
|
|
5
3
|
export type UnifiedPaymentStatus =
|
|
@@ -114,32 +112,6 @@ export type PaymentGatewayClientSession =
|
|
|
114
112
|
| EmbeddedPaymentGatewayClientSession
|
|
115
113
|
| RedirectPaymentGatewayClientSession
|
|
116
114
|
|
|
117
|
-
// ── Order Amount Reconciliation ─────────────────────────────────────────────
|
|
118
|
-
|
|
119
|
-
export interface PaymentGatewayScope {
|
|
120
|
-
organizationId: string
|
|
121
|
-
tenantId: string
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export interface PaymentOrderTotal {
|
|
125
|
-
orderId: string
|
|
126
|
-
currencyCode: string
|
|
127
|
-
amountDue: number
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Contract a module that owns orders implements so `payment_gateways` can
|
|
132
|
-
* reconcile a caller-supplied session amount against the authoritative amount
|
|
133
|
-
* due, without depending on that module. Implementations MUST scope the lookup
|
|
134
|
-
* by tenant and organization and MUST return `null` for anything outside the
|
|
135
|
-
* caller's scope, so an out-of-scope order is indistinguishable from a missing
|
|
136
|
-
* one. The `sales` module registers the default implementation under the
|
|
137
|
-
* `paymentOrderTotalResolver` DI name.
|
|
138
|
-
*/
|
|
139
|
-
export interface PaymentOrderTotalResolver {
|
|
140
|
-
resolveOrderTotal(orderId: string, scope: PaymentGatewayScope): Promise<PaymentOrderTotal | null>
|
|
141
|
-
}
|
|
142
|
-
|
|
143
115
|
// ── Input / Output Types ────────────────────────────────────────────────────
|
|
144
116
|
|
|
145
117
|
export interface CreateSessionInput {
|
|
@@ -253,7 +225,6 @@ export interface WebhookHandlerRegistration {
|
|
|
253
225
|
handler: (input: VerifyWebhookInput) => Promise<WebhookEvent>
|
|
254
226
|
queue?: string
|
|
255
227
|
readSessionIdHint?: (payload: Record<string, unknown> | null) => string | null
|
|
256
|
-
maxBodyBytes?: number
|
|
257
228
|
}
|
|
258
229
|
|
|
259
230
|
// ── Adapter Registry Options ────────────────────────────────────────────────
|
|
@@ -348,25 +319,13 @@ export function registerWebhookHandler(
|
|
|
348
319
|
options?: {
|
|
349
320
|
queue?: string
|
|
350
321
|
readSessionIdHint?: (payload: Record<string, unknown> | null) => string | null
|
|
351
|
-
maxBodyBytes?: number
|
|
352
322
|
},
|
|
353
323
|
): () => void {
|
|
354
|
-
if (
|
|
355
|
-
options?.maxBodyBytes !== undefined
|
|
356
|
-
&& (!Number.isSafeInteger(options.maxBodyBytes)
|
|
357
|
-
|| options.maxBodyBytes <= 0
|
|
358
|
-
|| options.maxBodyBytes > DEFAULT_WEBHOOK_BODY_LIMIT_BYTES)
|
|
359
|
-
) {
|
|
360
|
-
throw new Error(
|
|
361
|
-
`[internal] Payment gateway webhook maxBodyBytes must be a positive safe integer no greater than ${DEFAULT_WEBHOOK_BODY_LIMIT_BYTES}`,
|
|
362
|
-
)
|
|
363
|
-
}
|
|
364
324
|
const webhookHandlerRegistry = getWebhookHandlerRegistry()
|
|
365
325
|
webhookHandlerRegistry.set(providerKey, {
|
|
366
326
|
handler,
|
|
367
327
|
queue: options?.queue,
|
|
368
328
|
readSessionIdHint: options?.readSessionIdHint,
|
|
369
|
-
maxBodyBytes: options?.maxBodyBytes,
|
|
370
329
|
})
|
|
371
330
|
return () => {
|
|
372
331
|
webhookHandlerRegistry.delete(providerKey)
|
package/src/modules/registry.ts
CHANGED
|
@@ -194,8 +194,6 @@ export type ModuleWorker = {
|
|
|
194
194
|
moduleId?: string
|
|
195
195
|
queue: string
|
|
196
196
|
concurrency: number
|
|
197
|
-
lockDuration?: number
|
|
198
|
-
maxStalledCount?: number
|
|
199
197
|
handler: ModuleWorkerHandler
|
|
200
198
|
}
|
|
201
199
|
|
|
@@ -515,7 +513,6 @@ export function getDefaultEncryptionMaps(modules: Module[]): import('./encryptio
|
|
|
515
513
|
moduleId: mod.id,
|
|
516
514
|
map: {
|
|
517
515
|
entityId: entry.entityId,
|
|
518
|
-
...(entry.keyScope ? { keyScope: entry.keyScope } : {}),
|
|
519
516
|
fields: entry.fields.map((field) => ({
|
|
520
517
|
field: field.field,
|
|
521
518
|
hashField: field.hashField ?? null,
|
|
@@ -31,7 +31,6 @@ type FeatureRegistry = {
|
|
|
31
31
|
enabledModuleSet: Set<string>
|
|
32
32
|
featureToModule: Map<string, string>
|
|
33
33
|
prefixToModule: Map<string, string>
|
|
34
|
-
concreteFeatureIds: string[]
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
let cachedRegistry: FeatureRegistry | null = null
|
|
@@ -42,63 +41,23 @@ function buildRegistry(modules: readonly Module[]): FeatureRegistry {
|
|
|
42
41
|
const enabledModuleSet = new Set(enabledModuleIds)
|
|
43
42
|
const featureToModule = new Map<string, string>()
|
|
44
43
|
const prefixToModule = new Map<string, string>()
|
|
45
|
-
const concreteFeatureIds: string[] = []
|
|
46
|
-
const concreteFeatureSet = new Set<string>()
|
|
47
|
-
|
|
48
|
-
const addConcreteFeature = (featureId: string, owningModule: string, authoritative: boolean) => {
|
|
49
|
-
if (!featureId || featureId === '*' || featureId.endsWith('.*')) return
|
|
50
|
-
if (!concreteFeatureSet.has(featureId)) {
|
|
51
|
-
concreteFeatureSet.add(featureId)
|
|
52
|
-
concreteFeatureIds.push(featureId)
|
|
53
|
-
}
|
|
54
|
-
if (authoritative || !featureToModule.has(featureId)) {
|
|
55
|
-
featureToModule.set(featureId, owningModule)
|
|
56
|
-
}
|
|
57
|
-
const dot = featureId.indexOf('.')
|
|
58
|
-
if (dot > 0) {
|
|
59
|
-
const prefix = featureId.slice(0, dot)
|
|
60
|
-
if (!prefixToModule.has(prefix)) prefixToModule.set(prefix, owningModule)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
44
|
for (const mod of modules) {
|
|
65
45
|
const features = mod.features
|
|
66
|
-
if (Array.isArray(features))
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (customerDefaults) {
|
|
78
|
-
for (const roleFeatures of Object.values(customerDefaults)) {
|
|
79
|
-
if (!Array.isArray(roleFeatures)) continue
|
|
80
|
-
for (const featureId of roleFeatures) {
|
|
81
|
-
if (typeof featureId === 'string') addConcreteFeature(featureId, mod.id, false)
|
|
82
|
-
}
|
|
46
|
+
if (!Array.isArray(features)) continue
|
|
47
|
+
for (const feature of features) {
|
|
48
|
+
if (!feature || typeof feature.id !== 'string' || !feature.id) continue
|
|
49
|
+
const declared = typeof feature.module === 'string' && feature.module.length > 0
|
|
50
|
+
? feature.module
|
|
51
|
+
: mod.id
|
|
52
|
+
featureToModule.set(feature.id, declared)
|
|
53
|
+
const dot = feature.id.indexOf('.')
|
|
54
|
+
if (dot > 0) {
|
|
55
|
+
const prefix = feature.id.slice(0, dot)
|
|
56
|
+
if (!prefixToModule.has(prefix)) prefixToModule.set(prefix, declared)
|
|
83
57
|
}
|
|
84
58
|
}
|
|
85
|
-
|
|
86
|
-
if (Array.isArray(mod.frontendRoutes)) {
|
|
87
|
-
for (const route of mod.frontendRoutes) {
|
|
88
|
-
if (!Array.isArray(route.requireCustomerFeatures)) continue
|
|
89
|
-
for (const featureId of route.requireCustomerFeatures) {
|
|
90
|
-
if (typeof featureId === 'string') addConcreteFeature(featureId, mod.id, false)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
enabledModuleIds,
|
|
97
|
-
enabledModuleSet,
|
|
98
|
-
featureToModule,
|
|
99
|
-
prefixToModule,
|
|
100
|
-
concreteFeatureIds,
|
|
101
59
|
}
|
|
60
|
+
return { enabledModuleIds, enabledModuleSet, featureToModule, prefixToModule }
|
|
102
61
|
}
|
|
103
62
|
|
|
104
63
|
function getRegistry(): FeatureRegistry | null {
|
|
@@ -134,17 +93,6 @@ export function getEnabledModuleIds(): string[] {
|
|
|
134
93
|
return registry ? [...registry.enabledModuleIds] : []
|
|
135
94
|
}
|
|
136
95
|
|
|
137
|
-
/** @internal Infrastructure input for concrete feature-policy projection. */
|
|
138
|
-
export function getConcreteFeatureIds(): string[] {
|
|
139
|
-
const registry = getRegistry()
|
|
140
|
-
return registry ? [...registry.concreteFeatureIds] : []
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** @internal Distinguishes an empty registry from an unavailable bootstrap registry. */
|
|
144
|
-
export function hasEnabledModulesRegistry(): boolean {
|
|
145
|
-
return getRegistry() !== null
|
|
146
|
-
}
|
|
147
|
-
|
|
148
96
|
/**
|
|
149
97
|
* Filters a raw granted-features list down to the grants whose owning
|
|
150
98
|
* module is currently enabled. Expands `*` (superadmin) into one wildcard
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
const SAFETY_IDENTIFIER_SECRET_LABEL = "open-mercato:ai-safety-identifier:v1";
|
|
3
|
-
const derivedSecretCache = /* @__PURE__ */ new Map();
|
|
4
|
-
function readBaseSecret(explicit) {
|
|
5
|
-
const secret = explicit ?? process.env.JWT_SECRET;
|
|
6
|
-
if (!secret) {
|
|
7
|
-
throw new Error("[internal] JWT_SECRET is not set; cannot derive AI safety-identifier secret");
|
|
8
|
-
}
|
|
9
|
-
return secret;
|
|
10
|
-
}
|
|
11
|
-
function deriveAiSafetyIdentifierSecret(baseSecret) {
|
|
12
|
-
const base = readBaseSecret(baseSecret);
|
|
13
|
-
const cached = derivedSecretCache.get(base);
|
|
14
|
-
if (cached !== void 0) return cached;
|
|
15
|
-
const derived = crypto.createHmac("sha256", base).update(SAFETY_IDENTIFIER_SECRET_LABEL).digest("hex");
|
|
16
|
-
derivedSecretCache.set(base, derived);
|
|
17
|
-
return derived;
|
|
18
|
-
}
|
|
19
|
-
function computeEndUserIdentifier(tenantId, userId, options) {
|
|
20
|
-
const normalizedUser = (userId ?? "").trim();
|
|
21
|
-
if (!normalizedUser) {
|
|
22
|
-
throw new Error("[internal] computeEndUserIdentifier requires a non-empty userId");
|
|
23
|
-
}
|
|
24
|
-
const key = deriveAiSafetyIdentifierSecret(options?.baseSecret);
|
|
25
|
-
const salt = (tenantId ?? "").trim();
|
|
26
|
-
return crypto.createHmac("sha256", key).update(`${salt}:${normalizedUser}`).digest("hex");
|
|
27
|
-
}
|
|
28
|
-
export {
|
|
29
|
-
computeEndUserIdentifier,
|
|
30
|
-
deriveAiSafetyIdentifierSecret
|
|
31
|
-
};
|
|
32
|
-
//# sourceMappingURL=safety-identifier.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/lib/ai/safety-identifier.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * End-user safety identifiers for AI provider calls.\n *\n * Providers such as OpenAI let developers attach an opaque per-end-user\n * identifier to each request so abuse enforcement can target one user instead\n * of suspending the whole API-key organization. We never send a reversible id:\n * the value is a tenant-salted HMAC computed from the platform's existing auth\n * secret, so no PII or internal id leaves the platform and the same end user in\n * two tenants produces two unrelated hashes.\n *\n * The per-process secret derivation mirrors `deriveJwtAudienceSecret`\n * (`@open-mercato/shared/lib/auth/jwt`): one HMAC from the base `JWT_SECRET`\n * under a versioned purpose label, memoized for the process lifetime. No new\n * secret to provision.\n *\n * @see .ai/specs/2026-06-04-ai-input-moderation-and-safety-identifiers.md\n */\n\nimport crypto from 'node:crypto'\n\nconst SAFETY_IDENTIFIER_SECRET_LABEL = 'open-mercato:ai-safety-identifier:v1'\n\nconst derivedSecretCache = new Map<string, string>()\n\nfunction readBaseSecret(explicit?: string): string {\n const secret = explicit ?? process.env.JWT_SECRET\n if (!secret) {\n throw new Error('[internal] JWT_SECRET is not set; cannot derive AI safety-identifier secret')\n }\n return secret\n}\n\n/**\n * Derive the per-process safety-identifier HMAC key from the base auth secret.\n *\n * Deterministic HMAC-SHA256 of a versioned purpose label keyed by the base\n * secret, memoized per base secret. Rotating the base secret rotates every\n * derived identifier \u2014 documented as accepted (identifiers are advisory\n * provider-side metadata, not an in-platform security control).\n */\nexport function deriveAiSafetyIdentifierSecret(baseSecret?: string): string {\n const base = readBaseSecret(baseSecret)\n const cached = derivedSecretCache.get(base)\n if (cached !== undefined) return cached\n const derived = crypto\n .createHmac('sha256', base)\n .update(SAFETY_IDENTIFIER_SECRET_LABEL)\n .digest('hex')\n derivedSecretCache.set(base, derived)\n return derived\n}\n\n/**\n * Compute the opaque end-user safety identifier for a (tenant, user) pair.\n *\n * Returns a 64-char lowercase hex HMAC-SHA256 of `${tenantId}:${userId}` keyed\n * by the derived secret. Throws (with an `[internal]` message) when the base\n * secret is missing or `userId` is empty \u2014 callers in the runtime wrap this in\n * a best-effort try/catch so identifier-derivation failures never break chat.\n */\nexport function computeEndUserIdentifier(\n tenantId: string | null | undefined,\n userId: string,\n options?: { baseSecret?: string },\n): string {\n const normalizedUser = (userId ?? '').trim()\n if (!normalizedUser) {\n throw new Error('[internal] computeEndUserIdentifier requires a non-empty userId')\n }\n const key = deriveAiSafetyIdentifierSecret(options?.baseSecret)\n const salt = (tenantId ?? '').trim()\n return crypto.createHmac('sha256', key).update(`${salt}:${normalizedUser}`).digest('hex')\n}\n"],
|
|
5
|
-
"mappings": "AAkBA,OAAO,YAAY;AAEnB,MAAM,iCAAiC;AAEvC,MAAM,qBAAqB,oBAAI,IAAoB;AAEnD,SAAS,eAAe,UAA2B;AACjD,QAAM,SAAS,YAAY,QAAQ,IAAI;AACvC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,6EAA6E;AAAA,EAC/F;AACA,SAAO;AACT;AAUO,SAAS,+BAA+B,YAA6B;AAC1E,QAAM,OAAO,eAAe,UAAU;AACtC,QAAM,SAAS,mBAAmB,IAAI,IAAI;AAC1C,MAAI,WAAW,OAAW,QAAO;AACjC,QAAM,UAAU,OACb,WAAW,UAAU,IAAI,EACzB,OAAO,8BAA8B,EACrC,OAAO,KAAK;AACf,qBAAmB,IAAI,MAAM,OAAO;AACpC,SAAO;AACT;AAUO,SAAS,yBACd,UACA,QACA,SACQ;AACR,QAAM,kBAAkB,UAAU,IAAI,KAAK;AAC3C,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,MAAM,iEAAiE;AAAA,EACnF;AACA,QAAM,MAAM,+BAA+B,SAAS,UAAU;AAC9D,QAAM,QAAQ,YAAY,IAAI,KAAK;AACnC,SAAO,OAAO,WAAW,UAAU,GAAG,EAAE,OAAO,GAAG,IAAI,IAAI,cAAc,EAAE,EAAE,OAAO,KAAK;AAC1F;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|