@memberjunction/global 6.2.0-edge.5 → 6.2.0-edge.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/README.md +8 -4
- package/dist/ClassFactory.d.ts +30 -0
- package/dist/ClassFactory.d.ts.map +1 -1
- package/dist/ClassFactory.js +69 -5
- package/dist/ClassFactory.js.map +1 -1
- package/dist/KeyedSerialTaskQueue.d.ts +18 -1
- package/dist/KeyedSerialTaskQueue.d.ts.map +1 -1
- package/dist/KeyedSerialTaskQueue.js +24 -3
- package/dist/KeyedSerialTaskQueue.js.map +1 -1
- package/dist/SQLExpressionValidator.d.ts +34 -9
- package/dist/SQLExpressionValidator.d.ts.map +1 -1
- package/dist/SQLExpressionValidator.js +58 -17
- package/dist/SQLExpressionValidator.js.map +1 -1
- package/dist/SQLScreeningScanner.d.ts +28 -2
- package/dist/SQLScreeningScanner.d.ts.map +1 -1
- package/dist/SQLScreeningScanner.js +166 -23
- package/dist/SQLScreeningScanner.js.map +1 -1
- package/dist/SafeExpressionEvaluator.d.ts +93 -109
- package/dist/SafeExpressionEvaluator.d.ts.map +1 -1
- package/dist/SafeExpressionEvaluator.js +494 -346
- package/dist/SafeExpressionEvaluator.js.map +1 -1
- package/dist/__tests__/ClassFactory.collision.test.js +110 -0
- package/dist/__tests__/ClassFactory.collision.test.js.map +1 -1
- package/dist/__tests__/ClassFactory.test.js +21 -0
- package/dist/__tests__/ClassFactory.test.js.map +1 -1
- package/dist/__tests__/FieldSecurityEgressCompliance.test.d.ts +2 -0
- package/dist/__tests__/FieldSecurityEgressCompliance.test.d.ts.map +1 -0
- package/dist/__tests__/FieldSecurityEgressCompliance.test.js +228 -0
- package/dist/__tests__/FieldSecurityEgressCompliance.test.js.map +1 -0
- package/dist/__tests__/FieldTransformEngine.screen.test.d.ts +2 -0
- package/dist/__tests__/FieldTransformEngine.screen.test.d.ts.map +1 -0
- package/dist/__tests__/FieldTransformEngine.screen.test.js +41 -0
- package/dist/__tests__/FieldTransformEngine.screen.test.js.map +1 -0
- package/dist/__tests__/FieldTransformEngine.security.test.d.ts +2 -0
- package/dist/__tests__/FieldTransformEngine.security.test.d.ts.map +1 -0
- package/dist/__tests__/FieldTransformEngine.security.test.js +74 -0
- package/dist/__tests__/FieldTransformEngine.security.test.js.map +1 -0
- package/dist/__tests__/KeyedSerialTaskQueue.test.js +92 -0
- package/dist/__tests__/KeyedSerialTaskQueue.test.js.map +1 -1
- package/dist/__tests__/PlatformStringCompliance.baseline.d.ts +10 -0
- package/dist/__tests__/PlatformStringCompliance.baseline.d.ts.map +1 -0
- package/dist/__tests__/PlatformStringCompliance.baseline.js +12 -0
- package/dist/__tests__/PlatformStringCompliance.baseline.js.map +1 -0
- package/dist/__tests__/PlatformStringCompliance.test.d.ts +8 -0
- package/dist/__tests__/PlatformStringCompliance.test.d.ts.map +1 -0
- package/dist/__tests__/PlatformStringCompliance.test.js +152 -0
- package/dist/__tests__/PlatformStringCompliance.test.js.map +1 -0
- package/dist/__tests__/ProofOfPossession.test.d.ts +2 -0
- package/dist/__tests__/ProofOfPossession.test.d.ts.map +1 -0
- package/dist/__tests__/ProofOfPossession.test.js +123 -0
- package/dist/__tests__/ProofOfPossession.test.js.map +1 -0
- package/dist/__tests__/SQLExpressionValidator.dialect.test.d.ts +2 -0
- package/dist/__tests__/SQLExpressionValidator.dialect.test.d.ts.map +1 -0
- package/dist/__tests__/SQLExpressionValidator.dialect.test.js +183 -0
- package/dist/__tests__/SQLExpressionValidator.dialect.test.js.map +1 -0
- package/dist/__tests__/SQLScreeningScanner.tokens.test.d.ts +2 -0
- package/dist/__tests__/SQLScreeningScanner.tokens.test.d.ts.map +1 -0
- package/dist/__tests__/SQLScreeningScanner.tokens.test.js +82 -0
- package/dist/__tests__/SQLScreeningScanner.tokens.test.js.map +1 -0
- package/dist/__tests__/SafeExpressionEvaluator.allowlist.test.d.ts +2 -0
- package/dist/__tests__/SafeExpressionEvaluator.allowlist.test.d.ts.map +1 -0
- package/dist/__tests__/SafeExpressionEvaluator.allowlist.test.js +200 -0
- package/dist/__tests__/SafeExpressionEvaluator.allowlist.test.js.map +1 -0
- package/dist/__tests__/SafeExpressionEvaluator.security.test.js +11 -0
- package/dist/__tests__/SafeExpressionEvaluator.security.test.js.map +1 -1
- package/dist/__tests__/SafeExpressionEvaluator.test.js +4 -2
- package/dist/__tests__/SafeExpressionEvaluator.test.js.map +1 -1
- package/dist/__tests__/UUIDCompliance.test.js +3 -4
- package/dist/__tests__/UUIDCompliance.test.js.map +1 -1
- package/dist/__tests__/UserScopedCacheKey.test.d.ts +2 -0
- package/dist/__tests__/UserScopedCacheKey.test.d.ts.map +1 -0
- package/dist/__tests__/UserScopedCacheKey.test.js +25 -0
- package/dist/__tests__/UserScopedCacheKey.test.js.map +1 -0
- package/dist/fieldRules/FieldTransformEngine.d.ts +12 -1
- package/dist/fieldRules/FieldTransformEngine.d.ts.map +1 -1
- package/dist/fieldRules/FieldTransformEngine.js +21 -8
- package/dist/fieldRules/FieldTransformEngine.js.map +1 -1
- package/dist/fieldRules/SafeTransformAstEvaluator.d.ts +16 -0
- package/dist/fieldRules/SafeTransformAstEvaluator.d.ts.map +1 -0
- package/dist/fieldRules/SafeTransformAstEvaluator.js +797 -0
- package/dist/fieldRules/SafeTransformAstEvaluator.js.map +1 -0
- package/dist/fieldRules/index.d.ts +1 -0
- package/dist/fieldRules/index.d.ts.map +1 -1
- package/dist/fieldRules/index.js +1 -0
- package/dist/fieldRules/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/util/BinaryEncoding.d.ts +2 -0
- package/dist/util/BinaryEncoding.d.ts.map +1 -1
- package/dist/util/BinaryEncoding.js +3 -5
- package/dist/util/BinaryEncoding.js.map +1 -1
- package/dist/util/ProofOfPossession.d.ts +68 -0
- package/dist/util/ProofOfPossession.d.ts.map +1 -0
- package/dist/util/ProofOfPossession.js +163 -0
- package/dist/util/ProofOfPossession.js.map +1 -0
- package/dist/util/UserScopedCacheKey.d.ts +42 -0
- package/dist/util/UserScopedCacheKey.d.ts.map +1 -0
- package/dist/util/UserScopedCacheKey.js +49 -0
- package/dist/util/UserScopedCacheKey.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-String Compliance (source guard)
|
|
3
|
+
*
|
|
4
|
+
* Flags branches on a platform name string in the SQL query path:
|
|
5
|
+
* - `=== 'sqlserver'`, `!== 'postgresql'`, `'sqlserver' === x` (either quote style, any operand order)
|
|
6
|
+
* - `case 'sqlserver':`, `case 'postgresql':`
|
|
7
|
+
*
|
|
8
|
+
* A branch on the platform name hard-codes a fact about one database where the dialect already
|
|
9
|
+
* states it, and leaves every other dialect (including the External Data Source dialects) on
|
|
10
|
+
* whichever branch is the default. Read the fact from the dialect instead: its lexical rules,
|
|
11
|
+
* grammar facts or writer (`@memberjunction/sql-dialect`).
|
|
12
|
+
*
|
|
13
|
+
* ## Scope
|
|
14
|
+
*
|
|
15
|
+
* `SQLParser/src`, `QueryProcessor/src`, the query-path files of `GenericDatabaseProvider/src`
|
|
16
|
+
* ({@link GDP_QUERY_PATH_FILES}), `MJCoreEntitiesServer/src/custom/query-extraction` and every
|
|
17
|
+
* `src/` folder under `ExternalDataSources/`. Tests, generated code, `dist/` and `.d.ts` files
|
|
18
|
+
* are skipped, as are pure comment lines.
|
|
19
|
+
*
|
|
20
|
+
* ## Baseline
|
|
21
|
+
*
|
|
22
|
+
* The branches that predate this check are listed one by one in
|
|
23
|
+
* `PlatformStringCompliance.baseline.ts` (file and line text). It is a ratchet: a branch not in
|
|
24
|
+
* the list fails, and a listed branch that no longer exists fails too, so the list must shrink as
|
|
25
|
+
* branches are converted to dialect facts. Burn it down; don't grow it.
|
|
26
|
+
*/
|
|
27
|
+
import { describe, it, expect } from 'vitest';
|
|
28
|
+
import * as fs from 'fs';
|
|
29
|
+
import * as path from 'path';
|
|
30
|
+
import { PLATFORM_STRING_BASELINE } from './PlatformStringCompliance.baseline.js';
|
|
31
|
+
const PACKAGES_ROOT = path.resolve(__dirname, '..', '..', '..'); // packages/
|
|
32
|
+
/** The GenericDatabaseProvider files on the query path (RunQuery, rendering, composition, paging, clause screening). */
|
|
33
|
+
const GDP_QUERY_PATH_FILES = [
|
|
34
|
+
'GenericDatabaseProvider/src/GenericDatabaseProvider.ts',
|
|
35
|
+
'GenericDatabaseProvider/src/renderPipeline.ts',
|
|
36
|
+
'GenericDatabaseProvider/src/queryCompositionEngine.ts',
|
|
37
|
+
'GenericDatabaseProvider/src/queryPagingEngine.ts',
|
|
38
|
+
'GenericDatabaseProvider/src/clientClauseScreen.ts',
|
|
39
|
+
'GenericDatabaseProvider/src/countOnlyBatch.ts',
|
|
40
|
+
'GenericDatabaseProvider/src/symbolTable.ts',
|
|
41
|
+
];
|
|
42
|
+
/** Folders and files scanned, relative to packages/. */
|
|
43
|
+
const SCAN_PATHS = [
|
|
44
|
+
'SQLParser/src',
|
|
45
|
+
'QueryProcessor/src',
|
|
46
|
+
...GDP_QUERY_PATH_FILES,
|
|
47
|
+
'MJCoreEntitiesServer/src/custom/query-extraction',
|
|
48
|
+
'ExternalDataSources',
|
|
49
|
+
];
|
|
50
|
+
const PLATFORM = `(['"\`])(?:sqlserver|postgresql)\\1`;
|
|
51
|
+
/** A comparison or `case` against a platform name literal. */
|
|
52
|
+
const PLATFORM_BRANCH_PATTERNS = [
|
|
53
|
+
new RegExp(`[!=]==\\s*${PLATFORM}`),
|
|
54
|
+
new RegExp(`${PLATFORM}\\s*[!=]==`),
|
|
55
|
+
new RegExp(`\\bcase\\s+${PLATFORM}\\s*:`),
|
|
56
|
+
];
|
|
57
|
+
const EXCLUDE_PATH_PATTERNS = [
|
|
58
|
+
/node_modules/,
|
|
59
|
+
/\/dist\//,
|
|
60
|
+
/\/generated\//,
|
|
61
|
+
/\.test\.ts$/,
|
|
62
|
+
/\.spec\.ts$/,
|
|
63
|
+
/__tests__/,
|
|
64
|
+
/\.d\.ts$/,
|
|
65
|
+
];
|
|
66
|
+
function normalizePath(p) {
|
|
67
|
+
return p.split(path.sep).join('/');
|
|
68
|
+
}
|
|
69
|
+
function shouldScanFile(filePath) {
|
|
70
|
+
if (!filePath.endsWith('.ts'))
|
|
71
|
+
return false;
|
|
72
|
+
if (EXCLUDE_PATH_PATTERNS.some(p => p.test(normalizePath(filePath))))
|
|
73
|
+
return false;
|
|
74
|
+
// Under ExternalDataSources only package source counts.
|
|
75
|
+
const relative = normalizePath(path.relative(PACKAGES_ROOT, filePath));
|
|
76
|
+
return !relative.startsWith('ExternalDataSources/') || relative.includes('/src/');
|
|
77
|
+
}
|
|
78
|
+
function findTsFiles(target) {
|
|
79
|
+
if (!fs.existsSync(target))
|
|
80
|
+
return [];
|
|
81
|
+
if (fs.statSync(target).isFile())
|
|
82
|
+
return shouldScanFile(target) ? [target] : [];
|
|
83
|
+
const results = [];
|
|
84
|
+
for (const entry of fs.readdirSync(target, { withFileTypes: true })) {
|
|
85
|
+
if (['node_modules', 'dist', '.git', 'generated', '__tests__'].includes(entry.name))
|
|
86
|
+
continue;
|
|
87
|
+
results.push(...findTsFiles(path.join(target, entry.name)));
|
|
88
|
+
}
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
/** The platform-string branches in one file's text. */
|
|
92
|
+
export function FindPlatformStringSites(relativeFile, text) {
|
|
93
|
+
const sites = [];
|
|
94
|
+
for (const line of text.split('\n')) {
|
|
95
|
+
const trimmed = line.trim();
|
|
96
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*'))
|
|
97
|
+
continue;
|
|
98
|
+
if (PLATFORM_BRANCH_PATTERNS.some(p => p.test(line)))
|
|
99
|
+
sites.push({ File: relativeFile, Text: trimmed });
|
|
100
|
+
}
|
|
101
|
+
return sites;
|
|
102
|
+
}
|
|
103
|
+
function scanAll() {
|
|
104
|
+
const sites = [];
|
|
105
|
+
for (const scanPath of SCAN_PATHS) {
|
|
106
|
+
for (const file of findTsFiles(path.join(PACKAGES_ROOT, scanPath))) {
|
|
107
|
+
const relative = normalizePath(path.relative(PACKAGES_ROOT, file));
|
|
108
|
+
sites.push(...FindPlatformStringSites(relative, fs.readFileSync(file, 'utf-8')));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return sites;
|
|
112
|
+
}
|
|
113
|
+
/** Removes each baseline entry from `sites` once; returns the sites left over and the entries not found. */
|
|
114
|
+
function diffAgainstBaseline(sites) {
|
|
115
|
+
const remaining = [...sites];
|
|
116
|
+
const stale = [];
|
|
117
|
+
for (const entry of PLATFORM_STRING_BASELINE) {
|
|
118
|
+
const index = remaining.findIndex(s => s.File === entry.File && s.Text === entry.Text);
|
|
119
|
+
if (index === -1)
|
|
120
|
+
stale.push(entry);
|
|
121
|
+
else
|
|
122
|
+
remaining.splice(index, 1);
|
|
123
|
+
}
|
|
124
|
+
return { Added: remaining, Stale: stale };
|
|
125
|
+
}
|
|
126
|
+
const format = (sites) => sites.map(s => ` ${s.File}: ${s.Text}`).join('\n');
|
|
127
|
+
describe('Platform-string compliance', () => {
|
|
128
|
+
it('flags comparisons and cases against a platform name, and nothing else', () => {
|
|
129
|
+
const flagged = (code) => FindPlatformStringSites('sample.ts', code).length;
|
|
130
|
+
expect(flagged(`if (platform === 'postgresql') {`)).toBe(1);
|
|
131
|
+
expect(flagged(`const pg = "sqlserver" !== key;`)).toBe(1);
|
|
132
|
+
expect(flagged(` case 'sqlserver':`)).toBe(1);
|
|
133
|
+
expect(flagged('if (x === `postgresql`) {}')).toBe(1);
|
|
134
|
+
expect(flagged(`// if (platform === 'postgresql')`)).toBe(0);
|
|
135
|
+
expect(flagged(`const key: DialectKey = 'postgresql';`)).toBe(0);
|
|
136
|
+
expect(flagged(`if (dialect.Grammar.RecursiveKeyword === 'RECURSIVE') {}`)).toBe(0);
|
|
137
|
+
});
|
|
138
|
+
it('adds no platform-string branch beyond the recorded baseline, and the baseline is current', () => {
|
|
139
|
+
const { Added, Stale } = diffAgainstBaseline(scanAll());
|
|
140
|
+
if (Added.length === 0 && Stale.length === 0)
|
|
141
|
+
return;
|
|
142
|
+
expect.fail((Added.length > 0
|
|
143
|
+
? `New platform-string branch(es) in the SQL query path:\n${format(Added)}\n\n` +
|
|
144
|
+
`Read the fact from the dialect instead of branching on its name: its Lexical, Grammar or\n` +
|
|
145
|
+
`Writer facet (@memberjunction/sql-dialect), or a SQLDialect member.\n\n`
|
|
146
|
+
: '') +
|
|
147
|
+
(Stale.length > 0
|
|
148
|
+
? `Baseline entries no longer found (converted or edited — remove them from PlatformStringCompliance.baseline.ts):\n${format(Stale)}\n`
|
|
149
|
+
: ''));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
//# sourceMappingURL=PlatformStringCompliance.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlatformStringCompliance.test.js","sourceRoot":"","sources":["../../src/__tests__/PlatformStringCompliance.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAE/E,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,YAAY;AAE7E,wHAAwH;AACxH,MAAM,oBAAoB,GAAG;IACzB,wDAAwD;IACxD,+CAA+C;IAC/C,uDAAuD;IACvD,kDAAkD;IAClD,mDAAmD;IACnD,+CAA+C;IAC/C,4CAA4C;CAC/C,CAAC;AAEF,wDAAwD;AACxD,MAAM,UAAU,GAAG;IACf,eAAe;IACf,oBAAoB;IACpB,GAAG,oBAAoB;IACvB,kDAAkD;IAClD,qBAAqB;CACxB,CAAC;AAEF,MAAM,QAAQ,GAAG,qCAAqC,CAAC;AAEvD,8DAA8D;AAC9D,MAAM,wBAAwB,GAAG;IAC7B,IAAI,MAAM,CAAC,aAAa,QAAQ,EAAE,CAAC;IACnC,IAAI,MAAM,CAAC,GAAG,QAAQ,YAAY,CAAC;IACnC,IAAI,MAAM,CAAC,cAAc,QAAQ,OAAO,CAAC;CAC5C,CAAC;AAEF,MAAM,qBAAqB,GAAG;IAC1B,cAAc;IACd,UAAU;IACV,eAAe;IACf,aAAa;IACb,aAAa;IACb,WAAW;IACX,UAAU;CACb,CAAC;AAQF,SAAS,aAAa,CAAC,CAAS;IAC5B,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB;IACpC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACnF,wDAAwD;IACxD,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC;IACvE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,sBAAsB,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACtF,CAAC;AAED,SAAS,WAAW,CAAC,MAAc;IAC/B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IACtC,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE;QAAE,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChF,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAClE,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAS;QAC9F,OAAO,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,uBAAuB,CAAC,YAAoB,EAAE,IAAY;IACtE,MAAM,KAAK,GAAyB,EAAE,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAS;QAC9F,IAAI,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC5G,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,OAAO;IACZ,MAAM,KAAK,GAAyB,EAAE,CAAC;IACvC,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAChC,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;YACjE,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;YACnE,KAAK,CAAC,IAAI,CAAC,GAAG,uBAAuB,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QACrF,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,4GAA4G;AAC5G,SAAS,mBAAmB,CAAC,KAA2B;IACpD,MAAM,SAAS,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAyB,EAAE,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,wBAAwB,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC;QACvF,IAAI,KAAK,KAAK,CAAC,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YAC/B,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC9C,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,KAA2B,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEpG,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC7E,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,uBAAuB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC;QACpF,MAAM,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACtD,MAAM,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7D,MAAM,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjE,MAAM,CAAC,OAAO,CAAC,0DAA0D,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0FAA0F,EAAE,GAAG,EAAE;QAChG,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAC;QACxD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACrD,MAAM,CAAC,IAAI,CACP,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;YACb,CAAC,CAAC,0DAA0D,MAAM,CAAC,KAAK,CAAC,MAAM;gBAC7E,4FAA4F;gBAC5F,yEAAyE;YAC3E,CAAC,CAAC,EAAE,CAAC;YACT,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;gBACb,CAAC,CAAC,oHAAoH,MAAM,CAAC,KAAK,CAAC,IAAI;gBACvI,CAAC,CAAC,EAAE,CAAC,CACZ,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProofOfPossession.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ProofOfPossession.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { CreateProofOfPossessionKey, ComputeJwkThumbprint, CreateProofOfPossessionJwt, VerifyProofOfPossessionJwt, } from '../util/ProofOfPossession.js';
|
|
4
|
+
const URL_PATH = '/widget/session/refresh';
|
|
5
|
+
const URL = `https://api.example.com/base${URL_PATH}`;
|
|
6
|
+
const TOKEN = 'header.payload.signature';
|
|
7
|
+
const NOW = 1_792_713_600;
|
|
8
|
+
function b64url(bytes) {
|
|
9
|
+
return bytes.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
10
|
+
}
|
|
11
|
+
function decodePart(part) {
|
|
12
|
+
return JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
|
|
13
|
+
}
|
|
14
|
+
function encodePart(value) {
|
|
15
|
+
return Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
16
|
+
}
|
|
17
|
+
async function proof(key, overrides = {}) {
|
|
18
|
+
return CreateProofOfPossessionJwt(key, {
|
|
19
|
+
Method: overrides.Method ?? 'POST',
|
|
20
|
+
Url: overrides.Url ?? URL,
|
|
21
|
+
AccessToken: overrides.AccessToken ?? TOKEN,
|
|
22
|
+
NowSeconds: overrides.NowSeconds ?? NOW,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** Signs an arbitrary header and payload with the key, so a test can change one part and still pass the signature check. */
|
|
26
|
+
async function signed(key, header, payload) {
|
|
27
|
+
const input = `${encodePart(header)}.${encodePart(payload)}`;
|
|
28
|
+
const signature = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, key.PrivateKey, new TextEncoder().encode(input));
|
|
29
|
+
return `${input}.${Buffer.from(signature).toString('base64url')}`;
|
|
30
|
+
}
|
|
31
|
+
function verify(jwt, overrides = {}) {
|
|
32
|
+
return VerifyProofOfPossessionJwt(jwt, {
|
|
33
|
+
Method: 'POST',
|
|
34
|
+
PathSuffix: URL_PATH,
|
|
35
|
+
AccessToken: overrides.AccessToken ?? TOKEN,
|
|
36
|
+
NowSeconds: overrides.NowSeconds ?? NOW,
|
|
37
|
+
MaxSkewSeconds: 60,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
describe('CreateProofOfPossessionKey', () => {
|
|
41
|
+
it('makes a P-256 key whose private half cannot be exported', async () => {
|
|
42
|
+
const key = await CreateProofOfPossessionKey();
|
|
43
|
+
expect(key.PrivateKey.extractable).toBe(false);
|
|
44
|
+
await expect(crypto.subtle.exportKey('jwk', key.PrivateKey)).rejects.toThrow();
|
|
45
|
+
expect(key.PublicJwk).toEqual({ kty: 'EC', crv: 'P-256', x: expect.any(String), y: expect.any(String) });
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('without Web Crypto (a page outside a secure context)', () => {
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
vi.unstubAllGlobals();
|
|
51
|
+
});
|
|
52
|
+
it('fails with a message a page author can act on: serve the page over HTTPS', async () => {
|
|
53
|
+
vi.stubGlobal('crypto', {});
|
|
54
|
+
await expect(CreateProofOfPossessionKey()).rejects.toThrow(/HTTPS/);
|
|
55
|
+
await expect(CreateProofOfPossessionKey()).rejects.not.toThrow(/Node\.js/);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('ComputeJwkThumbprint', () => {
|
|
59
|
+
it('is the RFC 7638 SHA-256 thumbprint: required members only, in lexicographic order', async () => {
|
|
60
|
+
const jwk = { kty: 'EC', crv: 'P-256', x: 'xValue', y: 'yValue' };
|
|
61
|
+
const expected = b64url(createHash('sha256').update('{"crv":"P-256","kty":"EC","x":"xValue","y":"yValue"}').digest());
|
|
62
|
+
await expect(ComputeJwkThumbprint(jwk)).resolves.toBe(expected);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('VerifyProofOfPossessionJwt', () => {
|
|
66
|
+
it('accepts a proof made by the key for this request and token, and returns the key thumbprint', async () => {
|
|
67
|
+
const key = await CreateProofOfPossessionKey();
|
|
68
|
+
const result = await verify(await proof(key));
|
|
69
|
+
expect(result).toEqual({ Valid: true, Thumbprint: await ComputeJwkThumbprint(key.PublicJwk) });
|
|
70
|
+
});
|
|
71
|
+
it('carries the standard DPoP header and claims', async () => {
|
|
72
|
+
const key = await CreateProofOfPossessionKey();
|
|
73
|
+
const [header, payload] = (await proof(key)).split('.');
|
|
74
|
+
expect(decodePart(header)).toEqual({ typ: 'dpop+jwt', alg: 'ES256', jwk: key.PublicJwk });
|
|
75
|
+
expect(decodePart(payload)).toMatchObject({ htm: 'POST', htu: URL, iat: NOW, jti: expect.any(String) });
|
|
76
|
+
});
|
|
77
|
+
it('refuses a proof whose signature does not match its key', async () => {
|
|
78
|
+
const key = await CreateProofOfPossessionKey();
|
|
79
|
+
const other = await CreateProofOfPossessionKey();
|
|
80
|
+
const [, payload, signature] = (await proof(key)).split('.');
|
|
81
|
+
const swapped = `${encodePart({ typ: 'dpop+jwt', alg: 'ES256', jwk: other.PublicJwk })}.${payload}.${signature}`;
|
|
82
|
+
expect((await verify(swapped)).Valid).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
it('refuses a proof made for another token', async () => {
|
|
85
|
+
const key = await CreateProofOfPossessionKey();
|
|
86
|
+
expect(await verify(await proof(key, { AccessToken: 'another.token.value' }))).toEqual({ Valid: false, Reason: 'token_mismatch' });
|
|
87
|
+
});
|
|
88
|
+
it('refuses a proof made for another method or another endpoint', async () => {
|
|
89
|
+
const key = await CreateProofOfPossessionKey();
|
|
90
|
+
expect(await verify(await proof(key, { Method: 'GET' }))).toEqual({ Valid: false, Reason: 'request_mismatch' });
|
|
91
|
+
expect(await verify(await proof(key, { Url: 'https://api.example.com/widget/session' }))).toEqual({ Valid: false, Reason: 'request_mismatch' });
|
|
92
|
+
});
|
|
93
|
+
it('refuses a proof older or newer than the allowed skew', async () => {
|
|
94
|
+
const key = await CreateProofOfPossessionKey();
|
|
95
|
+
expect(await verify(await proof(key, { NowSeconds: NOW - 61 }))).toEqual({ Valid: false, Reason: 'stale' });
|
|
96
|
+
expect(await verify(await proof(key, { NowSeconds: NOW + 61 }))).toEqual({ Valid: false, Reason: 'stale' });
|
|
97
|
+
});
|
|
98
|
+
it('refuses a header that names another algorithm or type, or carries a private key, even when validly signed', async () => {
|
|
99
|
+
const key = await CreateProofOfPossessionKey();
|
|
100
|
+
const payload = decodePart((await proof(key)).split('.')[1]);
|
|
101
|
+
expect(await verify(await signed(key, { typ: 'dpop+jwt', alg: 'none', jwk: key.PublicJwk }, payload))).toEqual({ Valid: false, Reason: 'malformed' });
|
|
102
|
+
expect(await verify(await signed(key, { typ: 'JWT', alg: 'ES256', jwk: key.PublicJwk }, payload))).toEqual({ Valid: false, Reason: 'malformed' });
|
|
103
|
+
expect(await verify(await signed(key, { typ: 'dpop+jwt', alg: 'ES256', jwk: { ...key.PublicJwk, d: 'secret' } }, payload))).toEqual({ Valid: false, Reason: 'malformed' });
|
|
104
|
+
});
|
|
105
|
+
it('accepts a proof re-signed by its own key (control for the header and claim tests)', async () => {
|
|
106
|
+
const key = await CreateProofOfPossessionKey();
|
|
107
|
+
const [header, payload] = (await proof(key)).split('.');
|
|
108
|
+
expect((await verify(await signed(key, decodePart(header), decodePart(payload)))).Valid).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
it('refuses a validly signed proof with no iat, or a non-numeric one', async () => {
|
|
111
|
+
const key = await CreateProofOfPossessionKey();
|
|
112
|
+
const header = decodePart((await proof(key)).split('.')[0]);
|
|
113
|
+
const { iat: _iat, ...withoutIat } = decodePart((await proof(key)).split('.')[1]);
|
|
114
|
+
expect(await verify(await signed(key, header, withoutIat))).toEqual({ Valid: false, Reason: 'stale' });
|
|
115
|
+
expect(await verify(await signed(key, header, { ...withoutIat, iat: String(NOW) }))).toEqual({ Valid: false, Reason: 'stale' });
|
|
116
|
+
});
|
|
117
|
+
it('refuses malformed input without throwing', async () => {
|
|
118
|
+
expect(await verify('')).toEqual({ Valid: false, Reason: 'malformed' });
|
|
119
|
+
expect(await verify('not-a-jwt')).toEqual({ Valid: false, Reason: 'malformed' });
|
|
120
|
+
expect(await verify('a.b.c')).toEqual({ Valid: false, Reason: 'malformed' });
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
//# sourceMappingURL=ProofOfPossession.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProofOfPossession.test.js","sourceRoot":"","sources":["../../src/__tests__/ProofOfPossession.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SQLExpressionValidator.dialect.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/SQLExpressionValidator.dialect.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SQL safety allowlist must not be a T-SQL grammar.
|
|
3
|
+
*
|
|
4
|
+
* `ALLOWED_SQL_FUNCTIONS` carried only SQL Server's read-only vocabulary, so an
|
|
5
|
+
* ordinary PostgreSQL expression — `DATE_TRUNC('month', CreatedAt)`,
|
|
6
|
+
* `ARRAY_AGG(Name)`, `NOW()` — was rejected with "Function 'X' is not allowed"
|
|
7
|
+
* on every PostgreSQL tenant, before the query ever reached a database that
|
|
8
|
+
* would have run it happily.
|
|
9
|
+
*
|
|
10
|
+
* The list is now the UNION of the supported dialects' read-only vocabulary.
|
|
11
|
+
* That is deliberate and is the security-relevant claim these tests defend: a
|
|
12
|
+
* deny-by-default safety screen widened by a set of read-only names is still
|
|
13
|
+
* deny-by-default. The two properties pinned below are therefore inseparable —
|
|
14
|
+
*
|
|
15
|
+
* 1. the PostgreSQL names pass, and
|
|
16
|
+
* 2. injection shapes (semicolons, comments, DDL/DML keywords, and any
|
|
17
|
+
* function NOT on the union) are rejected exactly as before.
|
|
18
|
+
*
|
|
19
|
+
* A PostgreSQL name that reaches a SQL Server tenant is rejected by the SQL
|
|
20
|
+
* Server parser on its own, which is why the union does not need a dialect
|
|
21
|
+
* switch to be safe.
|
|
22
|
+
*/
|
|
23
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
24
|
+
import { SQLExpressionValidator, ALLOWED_SQL_FUNCTIONS } from '../SQLExpressionValidator.js';
|
|
25
|
+
describe('SQLExpressionValidator PostgreSQL vocabulary', () => {
|
|
26
|
+
let validator;
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
validator = SQLExpressionValidator.Instance;
|
|
29
|
+
});
|
|
30
|
+
const where = { context: 'where_clause' };
|
|
31
|
+
const orderBy = { context: 'order_by' };
|
|
32
|
+
// ───────────────────────────────────────────────────────────────────
|
|
33
|
+
// 1. The PostgreSQL names pass
|
|
34
|
+
// ───────────────────────────────────────────────────────────────────
|
|
35
|
+
it('accepts a PostgreSQL filter using ILIKE and DATE_TRUNC', () => {
|
|
36
|
+
const r = validator.validate("Name ILIKE '%acme%' AND DATE_TRUNC('month', CreatedAt) = DATE_TRUNC('month', NOW())", where);
|
|
37
|
+
expect(r.error).toBeUndefined();
|
|
38
|
+
expect(r.valid).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
it('accepts DATE_TRUNC on its own', () => {
|
|
41
|
+
const r = validator.validate("DATE_TRUNC('month', CreatedAt)", where);
|
|
42
|
+
expect(r.valid).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
it('accepts the PostgreSQL date/time functions that replace GETDATE and DATEPART', () => {
|
|
45
|
+
for (const expr of [
|
|
46
|
+
'NOW()',
|
|
47
|
+
'DATE_PART(\'year\', CreatedAt)',
|
|
48
|
+
'EXTRACT(YEAR FROM CreatedAt)',
|
|
49
|
+
'AGE(UpdatedAt, CreatedAt)',
|
|
50
|
+
'TO_TIMESTAMP(Epoch)',
|
|
51
|
+
]) {
|
|
52
|
+
const r = validator.validate(expr, where);
|
|
53
|
+
expect(r.error, `${expr} should be accepted`).toBeUndefined();
|
|
54
|
+
expect(r.valid, `${expr} should be accepted`).toBe(true);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
it('accepts the PostgreSQL string functions that replace CHARINDEX and friends', () => {
|
|
58
|
+
for (const expr of [
|
|
59
|
+
"STRPOS(Name, 'x')",
|
|
60
|
+
"SPLIT_PART(Email, '@', 2)",
|
|
61
|
+
'INITCAP(Name)',
|
|
62
|
+
"LPAD(Code, 5, '0')",
|
|
63
|
+
"REGEXP_REPLACE(Name, '[0-9]', '')",
|
|
64
|
+
]) {
|
|
65
|
+
const r = validator.validate(expr, where);
|
|
66
|
+
expect(r.error, `${expr} should be accepted`).toBeUndefined();
|
|
67
|
+
expect(r.valid, `${expr} should be accepted`).toBe(true);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
it('accepts the PostgreSQL math functions that replace CEILING and RAND', () => {
|
|
71
|
+
for (const expr of ['CEIL(Amount)', 'RANDOM()', 'TRUNC(Amount, 2)', 'MOD(Qty, 3)', 'GREATEST(A, B)', 'LEAST(A, B)']) {
|
|
72
|
+
const r = validator.validate(expr, where);
|
|
73
|
+
expect(r.error, `${expr} should be accepted`).toBeUndefined();
|
|
74
|
+
expect(r.valid, `${expr} should be accepted`).toBe(true);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
it('accepts TO_CHAR / TO_DATE / TO_NUMBER in place of CAST and FORMAT', () => {
|
|
78
|
+
for (const expr of ["TO_CHAR(CreatedAt, 'YYYY-MM')", "TO_DATE(Stamp, 'YYYY-MM-DD')", 'TO_NUMBER(Amount)']) {
|
|
79
|
+
const r = validator.validate(expr, where);
|
|
80
|
+
expect(r.error, `${expr} should be accepted`).toBeUndefined();
|
|
81
|
+
expect(r.valid, `${expr} should be accepted`).toBe(true);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
it('counts ARRAY_AGG as an aggregate, so an aggregate-context expression is satisfied by it', () => {
|
|
85
|
+
// Two distinct gates had to agree: the function allowlist, and
|
|
86
|
+
// `checkContextRules`, which requires that an aggregate context actually
|
|
87
|
+
// contain an aggregate — a check driven by ALLOWED_SQL_FUNCTIONS.aggregates.
|
|
88
|
+
const r = validator.validate('ARRAY_AGG(Name)', { context: 'aggregate' });
|
|
89
|
+
expect(r.error).toBeUndefined();
|
|
90
|
+
expect(r.valid).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it('accepts the other PostgreSQL aggregates', () => {
|
|
93
|
+
for (const expr of ['JSONB_AGG(Name)', 'JSON_AGG(Name)', 'BOOL_AND(IsActive)', 'BOOL_OR(IsActive)']) {
|
|
94
|
+
const r = validator.validate(expr, { context: 'aggregate' });
|
|
95
|
+
expect(r.error, `${expr} should be accepted`).toBeUndefined();
|
|
96
|
+
expect(r.valid, `${expr} should be accepted`).toBe(true);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
it('accepts an aggregate FILTER clause in ORDER BY / aggregate context', () => {
|
|
100
|
+
const r = validator.validate('COUNT(ID) FILTER (WHERE IsActive)', { context: 'aggregate' });
|
|
101
|
+
expect(r.error).toBeUndefined();
|
|
102
|
+
expect(r.valid).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
it('accepts NULLS FIRST / NULLS LAST ordering', () => {
|
|
105
|
+
const r = validator.validate('CreatedAt DESC NULLS LAST', orderBy);
|
|
106
|
+
expect(r.error).toBeUndefined();
|
|
107
|
+
expect(r.valid).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
// ───────────────────────────────────────────────────────────────────
|
|
110
|
+
// 2. The safety property has NOT regressed
|
|
111
|
+
// Each of these uses the SAME PostgreSQL expression that now passes,
|
|
112
|
+
// so a pass here would mean the widening, not the input, did it.
|
|
113
|
+
// ───────────────────────────────────────────────────────────────────
|
|
114
|
+
const PG_EXPR = "Name ILIKE '%acme%' AND DATE_TRUNC('month', CreatedAt) = DATE_TRUNC('month', NOW())";
|
|
115
|
+
it('still rejects the same PostgreSQL expression when it carries a semicolon', () => {
|
|
116
|
+
const r = validator.validate(`${PG_EXPR};`, where);
|
|
117
|
+
expect(r.valid).toBe(false);
|
|
118
|
+
expect(r.trigger).toBe(';');
|
|
119
|
+
});
|
|
120
|
+
it('still rejects the same PostgreSQL expression when it carries a line comment', () => {
|
|
121
|
+
const r = validator.validate(`${PG_EXPR} -- and now the payload`, where);
|
|
122
|
+
expect(r.valid).toBe(false);
|
|
123
|
+
expect(r.trigger).toBe('comment');
|
|
124
|
+
});
|
|
125
|
+
it('still rejects the same PostgreSQL expression when it carries a block comment', () => {
|
|
126
|
+
const r = validator.validate(`${PG_EXPR} /* payload */`, where);
|
|
127
|
+
expect(r.valid).toBe(false);
|
|
128
|
+
expect(r.trigger).toBe('comment');
|
|
129
|
+
});
|
|
130
|
+
it('still rejects stacked DDL/DML dressed up in PostgreSQL syntax', () => {
|
|
131
|
+
for (const expr of [
|
|
132
|
+
`${PG_EXPR} ; DROP TABLE Users`,
|
|
133
|
+
"DATE_TRUNC('month', CreatedAt) AND 1=1 ; DELETE FROM Users",
|
|
134
|
+
'ILIKE ANY (SELECT Name FROM Users)',
|
|
135
|
+
]) {
|
|
136
|
+
const r = validator.validate(expr, where);
|
|
137
|
+
expect(r.valid, `${expr} must be rejected`).toBe(false);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
it('still rejects PostgreSQL functions that are NOT read-only — the union is not a blanket pass', () => {
|
|
141
|
+
for (const fn of ['PG_SLEEP(5)', 'PG_READ_FILE(\'/etc/passwd\')', 'DBLINK(\'\', \'\')', 'LO_IMPORT(\'/etc/passwd\')', 'QUERY_TO_XML(\'\')']) {
|
|
142
|
+
const r = validator.validate(fn, where);
|
|
143
|
+
expect(r.valid, `${fn} must be rejected`).toBe(false);
|
|
144
|
+
expect(r.error, `${fn} must be rejected as a function`).toMatch(/is not allowed/);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
it('still blocks the PostgreSQL system catalogs', () => {
|
|
148
|
+
for (const expr of ['ID IN (SELECT oid FROM pg_catalog.pg_class)', 'Name = pg_authid.rolname']) {
|
|
149
|
+
const r = validator.validate(expr, where);
|
|
150
|
+
expect(r.valid, `${expr} must be rejected`).toBe(false);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
// ───────────────────────────────────────────────────────────────────
|
|
154
|
+
// 3. The list itself — so a bucket cannot be quietly trimmed back
|
|
155
|
+
// ───────────────────────────────────────────────────────────────────
|
|
156
|
+
it('carries the PostgreSQL names in the buckets that own them', () => {
|
|
157
|
+
const expected = {
|
|
158
|
+
aggregates: ['ARRAY_AGG', 'JSONB_AGG', 'JSON_AGG', 'BOOL_AND', 'BOOL_OR', 'EVERY'],
|
|
159
|
+
math: ['CEIL', 'RANDOM', 'TRUNC', 'MOD', 'DIV', 'GREATEST', 'LEAST', 'WIDTH_BUCKET'],
|
|
160
|
+
string: ['POSITION', 'STRPOS', 'SPLIT_PART', 'INITCAP', 'LPAD', 'RPAD'],
|
|
161
|
+
date: ['NOW', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'DATE_TRUNC', 'DATE_PART', 'EXTRACT', 'AGE'],
|
|
162
|
+
conversion: ['TO_CHAR', 'TO_DATE', 'TO_NUMBER', 'TO_TIMESTAMP'],
|
|
163
|
+
nullHandling: ['GREATEST', 'LEAST'],
|
|
164
|
+
logical: ['ILIKE', 'SIMILAR', 'ANY', 'ALL', 'SOME', 'EXISTS'],
|
|
165
|
+
ordering: ['NULLS', 'FIRST', 'LAST', 'FILTER'],
|
|
166
|
+
conditional: [],
|
|
167
|
+
};
|
|
168
|
+
for (const [bucket, names] of Object.entries(expected)) {
|
|
169
|
+
const actual = ALLOWED_SQL_FUNCTIONS[bucket];
|
|
170
|
+
for (const name of names) {
|
|
171
|
+
expect(actual, `${bucket} must carry ${name}`).toContain(name);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
it('keeps the SQL Server names it already had — the union adds, it does not replace', () => {
|
|
176
|
+
for (const expr of ['ISNULL(Name, \'\')', 'GETDATE()', "DATEADD(day, 1, CreatedAt)", 'CHARINDEX(\'x\', Name)', 'CEILING(Amount)']) {
|
|
177
|
+
const r = validator.validate(expr, where);
|
|
178
|
+
expect(r.error, `${expr} should still be accepted`).toBeUndefined();
|
|
179
|
+
expect(r.valid, `${expr} should still be accepted`).toBe(true);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
//# sourceMappingURL=SQLExpressionValidator.dialect.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SQLExpressionValidator.dialect.test.js","sourceRoot":"","sources":["../../src/__tests__/SQLExpressionValidator.dialect.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SQLScreeningScanner.tokens.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/SQLScreeningScanner.tokens.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ScanSQLForScreening } from '../SQLScreeningScanner.js';
|
|
3
|
+
/** Each token's kind and text. */
|
|
4
|
+
function tokensOf(sql, dialect) {
|
|
5
|
+
return ScanSQLForScreening(sql, dialect).Tokens.map(t => [t.Kind, t.Text]);
|
|
6
|
+
}
|
|
7
|
+
describe('ScanSQLForScreening tokens', () => {
|
|
8
|
+
it.each(['sqlserver', 'postgresql'])('splits words, numbers, strings and symbols (%s)', dialect => {
|
|
9
|
+
expect(tokensOf("e.Salary > 10 AND Name = N'x'", dialect)).toEqual([
|
|
10
|
+
['word', 'e'], ['symbol', '.'], ['word', 'Salary'], ['symbol', '>'], ['number', '10'],
|
|
11
|
+
['word', 'AND'], ['word', 'Name'], ['symbol', '='], ['string', "N'x'"],
|
|
12
|
+
]);
|
|
13
|
+
});
|
|
14
|
+
it('reads a bracket identifier as its bare name, with doubled brackets undone', () => {
|
|
15
|
+
expect(tokensOf('[Base Salary] = [a]]b]', 'sqlserver')).toEqual([
|
|
16
|
+
['identifier', 'Base Salary'], ['symbol', '='], ['identifier', 'a]b'],
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
it.each(['sqlserver', 'postgresql'])('reads a double-quoted identifier as its bare name (%s)', dialect => {
|
|
20
|
+
expect(tokensOf('"Sal""ary" IS NULL', dialect)).toEqual([
|
|
21
|
+
['identifier', 'Sal"ary'], ['word', 'IS'], ['word', 'NULL'],
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
it.each(['sqlserver', 'postgresql'])('reports no token for comments or whitespace (%s)', dialect => {
|
|
25
|
+
expect(tokensOf("a /* Salary */ = -- Salary\n 'Salary'", dialect)).toEqual([
|
|
26
|
+
['word', 'a'], ['symbol', '='], ['string', "'Salary'"],
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
it('splits a number from a word written against it, as the database does', () => {
|
|
30
|
+
expect(tokensOf('1Salary', 'sqlserver')).toEqual([['number', '1'], ['word', 'Salary']]);
|
|
31
|
+
});
|
|
32
|
+
it('keeps the offsets of each token', () => {
|
|
33
|
+
const scan = ScanSQLForScreening('x = [y]', 'sqlserver');
|
|
34
|
+
expect(scan.Tokens.map(t => [t.Start, t.End])).toEqual([[0, 1], [2, 3], [4, 7]]);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('ScanSQLForScreening PostgreSQL Unicode-escape identifiers (U&"…")', () => {
|
|
38
|
+
it('reads U&"…" as one identifier and decodes its four- and six-digit escapes', () => {
|
|
39
|
+
expect(tokensOf('U&"\\0053alary" > 1', 'postgresql')).toEqual([['identifier', 'Salary'], ['symbol', '>'], ['number', '1']]);
|
|
40
|
+
expect(tokensOf('U&"S\\+000061lary"', 'postgresql')).toEqual([['identifier', 'Salary']]);
|
|
41
|
+
});
|
|
42
|
+
it('reads the prefix in either case', () => {
|
|
43
|
+
expect(tokensOf('u&"S\\0061lary" > 1', 'postgresql')).toEqual([['identifier', 'Salary'], ['symbol', '>'], ['number', '1']]);
|
|
44
|
+
});
|
|
45
|
+
it('honors a UESCAPE clause, after whitespace or a comment, and consumes it', () => {
|
|
46
|
+
expect(tokensOf("U&\"S!0061lary\" UESCAPE '!' > 1", 'postgresql')).toEqual([['identifier', 'Salary'], ['symbol', '>'], ['number', '1']]);
|
|
47
|
+
expect(tokensOf("U&\"S!0061lary\" /* c */ uescape\n'!'", 'postgresql')).toEqual([['identifier', 'Salary']]);
|
|
48
|
+
});
|
|
49
|
+
it('undoes a doubled escape character and a doubled quote', () => {
|
|
50
|
+
expect(tokensOf('U&"a\\\\b""c"', 'postgresql')).toEqual([['identifier', 'a\\b"c']]);
|
|
51
|
+
});
|
|
52
|
+
it('decodes a surrogate pair written as two escapes', () => {
|
|
53
|
+
expect(tokensOf('U&"\\D83D\\DE00"', 'postgresql')).toEqual([['identifier', '\u{1F600}']]);
|
|
54
|
+
});
|
|
55
|
+
it.each([
|
|
56
|
+
['a bad escape', 'U&"\\00zz"'],
|
|
57
|
+
['a short escape', 'U&"\\005"'],
|
|
58
|
+
['a lone surrogate', 'U&"\\D800x"'],
|
|
59
|
+
['a NUL escape', 'U&"\\0000"'],
|
|
60
|
+
['a code point past U+10FFFF', 'U&"\\+110000"'],
|
|
61
|
+
['a UESCAPE of two characters', "U&\"x\" UESCAPE 'ab'"],
|
|
62
|
+
['a UESCAPE of a hex digit', "U&\"x\" UESCAPE 'a'"],
|
|
63
|
+
['a UESCAPE of a quote', "U&\"x\" UESCAPE ''''"],
|
|
64
|
+
['a UESCAPE with no string', 'U&"x" UESCAPE 1'],
|
|
65
|
+
['an unclosed identifier', 'U&"Salary'],
|
|
66
|
+
])('fails closed on %s', (_label, sql) => {
|
|
67
|
+
expect(ScanSQLForScreening(sql, 'postgresql').Classified).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
it('writes the decoded name into CodeWithIdentifiers and spans the whole construct', () => {
|
|
70
|
+
const sql = "U&\"S!0061lary\" UESCAPE '!' = 1";
|
|
71
|
+
const scan = ScanSQLForScreening(sql, 'postgresql');
|
|
72
|
+
expect(scan.Spans).toEqual([{ Kind: 'identifier', Start: 0, End: sql.indexOf(' =') }]);
|
|
73
|
+
expect(scan.CodeWithIdentifiers).toBe(' Salary = 1');
|
|
74
|
+
});
|
|
75
|
+
it('leaves SQL Server reading U&"x" as a word, a symbol and an identifier', () => {
|
|
76
|
+
expect(tokensOf('U&"x"', 'sqlserver')).toEqual([['word', 'U'], ['symbol', '&'], ['identifier', 'x']]);
|
|
77
|
+
});
|
|
78
|
+
it("reads E'…' as a string, never an identifier", () => {
|
|
79
|
+
expect(tokensOf("E'Salary' = x", 'postgresql')).toEqual([['string', "E'Salary'"], ['symbol', '='], ['word', 'x']]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
//# sourceMappingURL=SQLScreeningScanner.tokens.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SQLScreeningScanner.tokens.test.js","sourceRoot":"","sources":["../../src/__tests__/SQLScreeningScanner.tokens.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAA8C,MAAM,wBAAwB,CAAC;AAEzG,kCAAkC;AAClC,SAAS,QAAQ,CAAC,GAAW,EAAE,OAA4B;IACzD,OAAO,mBAAmB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,IAAI,CAAsB,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC,iDAAiD,EAAE,OAAO,CAAC,EAAE;QACrH,MAAM,CAAC,QAAQ,CAAC,+BAA+B,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;YACjE,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC;YACrF,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;SACvE,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,GAAG,EAAE;QACnF,MAAM,CAAC,QAAQ,CAAC,wBAAwB,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC;YAC9D,CAAC,YAAY,EAAE,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC;SACtE,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAsB,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC,wDAAwD,EAAE,OAAO,CAAC,EAAE;QAC5H,MAAM,CAAC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;YACtD,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;SAC5D,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAsB,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC,kDAAkD,EAAE,OAAO,CAAC,EAAE;QACtH,MAAM,CAAC,QAAQ,CAAC,uCAAuC,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;YACzE,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;SACvD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sEAAsE,EAAE,GAAG,EAAE;QAC9E,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,IAAI,GAAG,mBAAmB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACzD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mEAAmE,EAAE,GAAG,EAAE;IACjF,EAAE,CAAC,2EAA2E,EAAE,GAAG,EAAE;QACnF,MAAM,CAAC,QAAQ,CAAC,qBAAqB,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5H,MAAM,CAAC,QAAQ,CAAC,oBAAoB,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC3F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9H,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACjF,MAAM,CAAC,QAAQ,CAAC,kCAAkC,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QACzI,MAAM,CAAC,QAAQ,CAAC,uCAAuC,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9G,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,QAAQ,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,CAAC,cAAc,EAAE,YAAY,CAAC;QAC9B,CAAC,gBAAgB,EAAE,WAAW,CAAC;QAC/B,CAAC,kBAAkB,EAAE,aAAa,CAAC;QACnC,CAAC,cAAc,EAAE,YAAY,CAAC;QAC9B,CAAC,4BAA4B,EAAE,eAAe,CAAC;QAC/C,CAAC,6BAA6B,EAAE,sBAAsB,CAAC;QACvD,CAAC,0BAA0B,EAAE,qBAAqB,CAAC;QACnD,CAAC,sBAAsB,EAAE,sBAAsB,CAAC;QAChD,CAAC,0BAA0B,EAAE,iBAAiB,CAAC;QAC/C,CAAC,wBAAwB,EAAE,WAAW,CAAC;KACxC,CAAC,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACvC,MAAM,CAAC,mBAAmB,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACxF,MAAM,GAAG,GAAG,kCAAkC,CAAC;QAC/C,MAAM,IAAI,GAAG,mBAAmB,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QACpD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACvF,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACxG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACrH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|