tlc-claude-code 1.4.9 → 1.5.2
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/CLAUDE.md +23 -0
- package/CODING-STANDARDS.md +408 -0
- package/bin/install.js +2 -0
- package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
- package/dashboard/dist/components/QualityGatePane.js +31 -0
- package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
- package/dashboard/dist/components/QualityGatePane.test.js +147 -0
- package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
- package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
- package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
- package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
- package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
- package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
- package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
- package/dashboard/dist/components/orchestration/AgentList.js +47 -0
- package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
- package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
- package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
- package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
- package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
- package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
- package/dashboard/dist/components/orchestration/index.d.ts +8 -0
- package/dashboard/dist/components/orchestration/index.js +8 -0
- package/package.json +1 -1
- package/server/lib/access-control.js +352 -0
- package/server/lib/access-control.test.js +322 -0
- package/server/lib/agents-cancel-command.js +139 -0
- package/server/lib/agents-cancel-command.test.js +180 -0
- package/server/lib/agents-get-command.js +159 -0
- package/server/lib/agents-get-command.test.js +167 -0
- package/server/lib/agents-list-command.js +150 -0
- package/server/lib/agents-list-command.test.js +149 -0
- package/server/lib/agents-logs-command.js +126 -0
- package/server/lib/agents-logs-command.test.js +198 -0
- package/server/lib/agents-retry-command.js +117 -0
- package/server/lib/agents-retry-command.test.js +192 -0
- package/server/lib/budget-limits.js +222 -0
- package/server/lib/budget-limits.test.js +214 -0
- package/server/lib/code-generator.js +291 -0
- package/server/lib/code-generator.test.js +307 -0
- package/server/lib/cost-command.js +290 -0
- package/server/lib/cost-command.test.js +202 -0
- package/server/lib/cost-optimizer.js +404 -0
- package/server/lib/cost-optimizer.test.js +232 -0
- package/server/lib/cost-projections.js +302 -0
- package/server/lib/cost-projections.test.js +217 -0
- package/server/lib/cost-reports.js +277 -0
- package/server/lib/cost-reports.test.js +254 -0
- package/server/lib/cost-tracker.js +216 -0
- package/server/lib/cost-tracker.test.js +302 -0
- package/server/lib/crypto-patterns.js +433 -0
- package/server/lib/crypto-patterns.test.js +346 -0
- package/server/lib/design-command.js +385 -0
- package/server/lib/design-command.test.js +249 -0
- package/server/lib/design-parser.js +237 -0
- package/server/lib/design-parser.test.js +290 -0
- package/server/lib/gemini-vision.js +377 -0
- package/server/lib/gemini-vision.test.js +282 -0
- package/server/lib/input-validator.js +360 -0
- package/server/lib/input-validator.test.js +295 -0
- package/server/lib/litellm-client.js +232 -0
- package/server/lib/litellm-client.test.js +267 -0
- package/server/lib/litellm-command.js +291 -0
- package/server/lib/litellm-command.test.js +260 -0
- package/server/lib/litellm-config.js +273 -0
- package/server/lib/litellm-config.test.js +212 -0
- package/server/lib/model-pricing.js +189 -0
- package/server/lib/model-pricing.test.js +178 -0
- package/server/lib/models-command.js +223 -0
- package/server/lib/models-command.test.js +193 -0
- package/server/lib/optimize-command.js +197 -0
- package/server/lib/optimize-command.test.js +193 -0
- package/server/lib/orchestration-integration.js +206 -0
- package/server/lib/orchestration-integration.test.js +235 -0
- package/server/lib/output-encoder.js +308 -0
- package/server/lib/output-encoder.test.js +312 -0
- package/server/lib/quality-evaluator.js +396 -0
- package/server/lib/quality-evaluator.test.js +337 -0
- package/server/lib/quality-gate-command.js +340 -0
- package/server/lib/quality-gate-command.test.js +321 -0
- package/server/lib/quality-gate-scorer.js +378 -0
- package/server/lib/quality-gate-scorer.test.js +376 -0
- package/server/lib/quality-history.js +265 -0
- package/server/lib/quality-history.test.js +359 -0
- package/server/lib/quality-presets.js +288 -0
- package/server/lib/quality-presets.test.js +269 -0
- package/server/lib/quality-retry.js +323 -0
- package/server/lib/quality-retry.test.js +325 -0
- package/server/lib/quality-thresholds.js +255 -0
- package/server/lib/quality-thresholds.test.js +237 -0
- package/server/lib/secure-auth.js +333 -0
- package/server/lib/secure-auth.test.js +288 -0
- package/server/lib/secure-code-command.js +540 -0
- package/server/lib/secure-code-command.test.js +309 -0
- package/server/lib/secure-errors.js +521 -0
- package/server/lib/secure-errors.test.js +298 -0
- package/server/lib/vision-command.js +372 -0
- package/server/lib/vision-command.test.js +255 -0
- package/server/lib/visual-command.js +350 -0
- package/server/lib/visual-command.test.js +256 -0
- package/server/lib/visual-testing.js +315 -0
- package/server/lib/visual-testing.test.js +357 -0
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Thresholds Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for configurable quality thresholds per operation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { describe, it, beforeEach } = require('node:test');
|
|
8
|
+
const assert = require('node:assert');
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
createThresholds,
|
|
12
|
+
getThreshold,
|
|
13
|
+
getDimensionThreshold,
|
|
14
|
+
checkThreshold,
|
|
15
|
+
applyPreset,
|
|
16
|
+
saveThresholds,
|
|
17
|
+
loadThresholds,
|
|
18
|
+
PRESET_FAST,
|
|
19
|
+
PRESET_BALANCED,
|
|
20
|
+
PRESET_THOROUGH,
|
|
21
|
+
} = require('./quality-thresholds.js');
|
|
22
|
+
|
|
23
|
+
describe('Quality Thresholds', () => {
|
|
24
|
+
describe('createThresholds', () => {
|
|
25
|
+
it('creates thresholds with defaults', () => {
|
|
26
|
+
const thresholds = createThresholds();
|
|
27
|
+
assert.ok(thresholds);
|
|
28
|
+
assert.ok(thresholds.default >= 0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('accepts custom default threshold', () => {
|
|
32
|
+
const thresholds = createThresholds({ default: 80 });
|
|
33
|
+
assert.strictEqual(thresholds.default, 80);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('accepts per-operation thresholds', () => {
|
|
37
|
+
const thresholds = createThresholds({
|
|
38
|
+
operations: {
|
|
39
|
+
'code-gen': 85,
|
|
40
|
+
review: 90,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
assert.strictEqual(thresholds.operations['code-gen'], 85);
|
|
44
|
+
assert.strictEqual(thresholds.operations.review, 90);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('getThreshold', () => {
|
|
49
|
+
it('returns operation threshold when set', () => {
|
|
50
|
+
const thresholds = createThresholds({
|
|
51
|
+
default: 70,
|
|
52
|
+
operations: { 'code-gen': 85 },
|
|
53
|
+
});
|
|
54
|
+
const threshold = getThreshold(thresholds, 'code-gen');
|
|
55
|
+
assert.strictEqual(threshold, 85);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('falls back to default threshold', () => {
|
|
59
|
+
const thresholds = createThresholds({ default: 70 });
|
|
60
|
+
const threshold = getThreshold(thresholds, 'unknown-operation');
|
|
61
|
+
assert.strictEqual(threshold, 70);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns default for null operation', () => {
|
|
65
|
+
const thresholds = createThresholds({ default: 75 });
|
|
66
|
+
const threshold = getThreshold(thresholds, null);
|
|
67
|
+
assert.strictEqual(threshold, 75);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('getDimensionThreshold', () => {
|
|
72
|
+
it('returns per-dimension threshold when set', () => {
|
|
73
|
+
const thresholds = createThresholds({
|
|
74
|
+
dimensions: {
|
|
75
|
+
style: 90,
|
|
76
|
+
correctness: 100,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
const threshold = getDimensionThreshold(thresholds, 'style');
|
|
80
|
+
assert.strictEqual(threshold, 90);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('falls back to default when dimension not set', () => {
|
|
84
|
+
const thresholds = createThresholds({ default: 70 });
|
|
85
|
+
const threshold = getDimensionThreshold(thresholds, 'documentation');
|
|
86
|
+
assert.strictEqual(threshold, 70);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns operation-specific dimension threshold', () => {
|
|
90
|
+
const thresholds = createThresholds({
|
|
91
|
+
default: 70,
|
|
92
|
+
operations: {
|
|
93
|
+
'code-gen': {
|
|
94
|
+
default: 80,
|
|
95
|
+
dimensions: { style: 95 },
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
const threshold = getDimensionThreshold(thresholds, 'style', 'code-gen');
|
|
100
|
+
assert.strictEqual(threshold, 95);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('checkThreshold', () => {
|
|
105
|
+
it('returns pass when score meets threshold', () => {
|
|
106
|
+
const thresholds = createThresholds({ default: 70 });
|
|
107
|
+
const result = checkThreshold(thresholds, { composite: 80 });
|
|
108
|
+
assert.strictEqual(result.pass, true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('returns fail when score below threshold', () => {
|
|
112
|
+
const thresholds = createThresholds({ default: 70 });
|
|
113
|
+
const result = checkThreshold(thresholds, { composite: 60 });
|
|
114
|
+
assert.strictEqual(result.pass, false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('returns which dimensions failed', () => {
|
|
118
|
+
const thresholds = createThresholds({
|
|
119
|
+
default: 70,
|
|
120
|
+
dimensions: { style: 80, correctness: 90 },
|
|
121
|
+
});
|
|
122
|
+
const scores = { style: 75, correctness: 85, documentation: 90 };
|
|
123
|
+
const result = checkThreshold(thresholds, scores);
|
|
124
|
+
assert.ok(result.failed);
|
|
125
|
+
assert.ok(result.failed.includes('style'));
|
|
126
|
+
assert.ok(result.failed.includes('correctness'));
|
|
127
|
+
assert.ok(!result.failed.includes('documentation'));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('returns margin for each dimension', () => {
|
|
131
|
+
const thresholds = createThresholds({ default: 70 });
|
|
132
|
+
const scores = { style: 80, correctness: 60 };
|
|
133
|
+
const result = checkThreshold(thresholds, scores, { margins: true });
|
|
134
|
+
assert.ok(result.margins);
|
|
135
|
+
assert.strictEqual(result.margins.style, 10);
|
|
136
|
+
assert.strictEqual(result.margins.correctness, -10);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('passes when exactly at threshold', () => {
|
|
140
|
+
const thresholds = createThresholds({ default: 70 });
|
|
141
|
+
const result = checkThreshold(thresholds, { composite: 70 });
|
|
142
|
+
assert.strictEqual(result.pass, true);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('applyPreset', () => {
|
|
147
|
+
it('applies fast preset with lower thresholds', () => {
|
|
148
|
+
const thresholds = applyPreset(PRESET_FAST);
|
|
149
|
+
assert.ok(thresholds.default <= 60);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('applies balanced preset with moderate thresholds', () => {
|
|
153
|
+
const thresholds = applyPreset(PRESET_BALANCED);
|
|
154
|
+
assert.ok(thresholds.default >= 65 && thresholds.default <= 80);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('applies thorough preset with higher thresholds', () => {
|
|
158
|
+
const thresholds = applyPreset(PRESET_THOROUGH);
|
|
159
|
+
assert.ok(thresholds.default >= 80);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('merges preset with custom overrides', () => {
|
|
163
|
+
const thresholds = applyPreset(PRESET_BALANCED, { default: 85 });
|
|
164
|
+
assert.strictEqual(thresholds.default, 85);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('preserves preset name', () => {
|
|
168
|
+
const thresholds = applyPreset(PRESET_FAST);
|
|
169
|
+
assert.strictEqual(thresholds.preset, 'fast');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('presets', () => {
|
|
174
|
+
it('fast has lower thresholds than balanced', () => {
|
|
175
|
+
const fast = applyPreset(PRESET_FAST);
|
|
176
|
+
const balanced = applyPreset(PRESET_BALANCED);
|
|
177
|
+
assert.ok(fast.default < balanced.default);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('balanced has lower thresholds than thorough', () => {
|
|
181
|
+
const balanced = applyPreset(PRESET_BALANCED);
|
|
182
|
+
const thorough = applyPreset(PRESET_THOROUGH);
|
|
183
|
+
assert.ok(balanced.default < thorough.default);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('fast preset allows cheaper models', () => {
|
|
187
|
+
const fast = applyPreset(PRESET_FAST);
|
|
188
|
+
assert.ok(fast.allowedModels || fast.modelTier === 'basic');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('thorough preset requires better models', () => {
|
|
192
|
+
const thorough = applyPreset(PRESET_THOROUGH);
|
|
193
|
+
assert.ok(thorough.modelTier === 'premium' || thorough.minModel);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('saveThresholds', () => {
|
|
198
|
+
it('persists thresholds to config', async () => {
|
|
199
|
+
const thresholds = createThresholds({ default: 85 });
|
|
200
|
+
let savedData = null;
|
|
201
|
+
const mockSave = async (data) => {
|
|
202
|
+
savedData = data;
|
|
203
|
+
};
|
|
204
|
+
await saveThresholds(thresholds, { save: mockSave });
|
|
205
|
+
assert.ok(savedData);
|
|
206
|
+
assert.strictEqual(savedData.default, 85);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('returns success status', async () => {
|
|
210
|
+
const thresholds = createThresholds();
|
|
211
|
+
const result = await saveThresholds(thresholds, {
|
|
212
|
+
save: async () => {},
|
|
213
|
+
});
|
|
214
|
+
assert.strictEqual(result.success, true);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('loadThresholds', () => {
|
|
219
|
+
it('reads thresholds from config', async () => {
|
|
220
|
+
const mockLoad = async () => ({ default: 90 });
|
|
221
|
+
const thresholds = await loadThresholds({ load: mockLoad });
|
|
222
|
+
assert.strictEqual(thresholds.default, 90);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('returns defaults when no config', async () => {
|
|
226
|
+
const mockLoad = async () => null;
|
|
227
|
+
const thresholds = await loadThresholds({ load: mockLoad });
|
|
228
|
+
assert.ok(thresholds.default >= 0);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('validates loaded config', async () => {
|
|
232
|
+
const mockLoad = async () => ({ default: 'invalid' });
|
|
233
|
+
const thresholds = await loadThresholds({ load: mockLoad });
|
|
234
|
+
assert.ok(typeof thresholds.default === 'number');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure Auth Module
|
|
3
|
+
*
|
|
4
|
+
* Secure authentication patterns for code generation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a secure auth configuration
|
|
9
|
+
* @param {Object} options - Auth options
|
|
10
|
+
* @returns {Object} Auth configuration
|
|
11
|
+
*/
|
|
12
|
+
function createSecureAuth(options = {}) {
|
|
13
|
+
return {
|
|
14
|
+
hashAlgorithm: options.hashAlgorithm || 'argon2id',
|
|
15
|
+
memoryCost: options.memoryCost || 65536,
|
|
16
|
+
timeCost: options.timeCost || 3,
|
|
17
|
+
parallelism: options.parallelism || 4,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate password hashing configuration
|
|
23
|
+
* @param {Object} options - Hashing options
|
|
24
|
+
* @returns {Object} Hashing configuration
|
|
25
|
+
*/
|
|
26
|
+
function generatePasswordHash(options = {}) {
|
|
27
|
+
const algorithm = options.algorithm || 'argon2id';
|
|
28
|
+
const memoryCost = options.memoryCost || 65536;
|
|
29
|
+
const timeCost = options.timeCost || 3;
|
|
30
|
+
const parallelism = options.parallelism || 4;
|
|
31
|
+
|
|
32
|
+
const params = {
|
|
33
|
+
algorithm,
|
|
34
|
+
memoryCost,
|
|
35
|
+
timeCost,
|
|
36
|
+
parallelism,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let warning = null;
|
|
40
|
+
if (algorithm === 'bcrypt') {
|
|
41
|
+
warning = 'bcrypt is less secure than argon2id. Consider using argon2id as recommended by OWASP.';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const code = `
|
|
45
|
+
const argon2 = require('argon2');
|
|
46
|
+
|
|
47
|
+
async function hashPassword(password) {
|
|
48
|
+
return argon2.hash(password, {
|
|
49
|
+
type: argon2.argon2id,
|
|
50
|
+
memoryCost: ${memoryCost},
|
|
51
|
+
timeCost: ${timeCost},
|
|
52
|
+
parallelism: ${parallelism},
|
|
53
|
+
});
|
|
54
|
+
}`;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
params,
|
|
58
|
+
code,
|
|
59
|
+
warning,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate password verification code
|
|
65
|
+
* @param {Object} options - Verification options
|
|
66
|
+
* @returns {string} Generated code
|
|
67
|
+
*/
|
|
68
|
+
function verifyPassword(options = {}) {
|
|
69
|
+
const { language = 'javascript' } = options;
|
|
70
|
+
|
|
71
|
+
if (language === 'javascript') {
|
|
72
|
+
return `
|
|
73
|
+
const argon2 = require('argon2');
|
|
74
|
+
const crypto = require('crypto');
|
|
75
|
+
|
|
76
|
+
async function verifyPassword(password, hash) {
|
|
77
|
+
try {
|
|
78
|
+
// Use argon2's built-in timing-safe comparison
|
|
79
|
+
const isValid = await argon2.verify(hash, password);
|
|
80
|
+
return isValid;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
// Log error but don't expose details
|
|
83
|
+
console.error('Password verification error');
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Generate rate limiter configuration
|
|
94
|
+
* @param {Object} options - Rate limiter options
|
|
95
|
+
* @returns {Object} Rate limiter configuration
|
|
96
|
+
*/
|
|
97
|
+
function generateRateLimiter(options = {}) {
|
|
98
|
+
const {
|
|
99
|
+
type = 'sliding-window',
|
|
100
|
+
maxAttempts = 5,
|
|
101
|
+
windowMs = 60000,
|
|
102
|
+
keyBy = 'ip',
|
|
103
|
+
store = 'memory',
|
|
104
|
+
tokensPerInterval = 5,
|
|
105
|
+
} = options;
|
|
106
|
+
|
|
107
|
+
const code = store === 'redis'
|
|
108
|
+
? `
|
|
109
|
+
const rateLimit = require('express-rate-limit');
|
|
110
|
+
const RedisStore = require('rate-limit-redis');
|
|
111
|
+
|
|
112
|
+
const limiter = rateLimit({
|
|
113
|
+
store: new RedisStore({ /* redis config */ }),
|
|
114
|
+
windowMs: ${windowMs},
|
|
115
|
+
max: ${maxAttempts},
|
|
116
|
+
keyGenerator: (req) => req.${keyBy},
|
|
117
|
+
message: 'Too many attempts, please try again later',
|
|
118
|
+
});`
|
|
119
|
+
: `
|
|
120
|
+
const rateLimit = require('express-rate-limit');
|
|
121
|
+
|
|
122
|
+
const limiter = rateLimit({
|
|
123
|
+
windowMs: ${windowMs},
|
|
124
|
+
max: ${maxAttempts},
|
|
125
|
+
keyGenerator: (req) => req.${keyBy},
|
|
126
|
+
message: 'Too many attempts, please try again later',
|
|
127
|
+
});`;
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
type,
|
|
131
|
+
maxAttempts,
|
|
132
|
+
windowMs,
|
|
133
|
+
keyBy,
|
|
134
|
+
store,
|
|
135
|
+
tokensPerInterval,
|
|
136
|
+
code,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Generate account lockout configuration
|
|
142
|
+
* @param {Object} options - Lockout options
|
|
143
|
+
* @returns {Object} Lockout configuration
|
|
144
|
+
*/
|
|
145
|
+
function generateAccountLockout(options = {}) {
|
|
146
|
+
const {
|
|
147
|
+
maxAttempts = 5,
|
|
148
|
+
lockoutDuration = 900000,
|
|
149
|
+
progressive = false,
|
|
150
|
+
baseDelay = 60000,
|
|
151
|
+
notifyOnLockout = false,
|
|
152
|
+
unlockMethod = 'time',
|
|
153
|
+
auditLog = false,
|
|
154
|
+
} = options;
|
|
155
|
+
|
|
156
|
+
const code = `
|
|
157
|
+
class AccountLockout {
|
|
158
|
+
constructor() {
|
|
159
|
+
this.attempts = new Map();
|
|
160
|
+
this.locked = new Map();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async recordFailure(userId) {
|
|
164
|
+
const current = this.attempts.get(userId) || 0;
|
|
165
|
+
const newCount = current + 1;
|
|
166
|
+
this.attempts.set(userId, newCount);
|
|
167
|
+
|
|
168
|
+
if (newCount >= ${maxAttempts}) {
|
|
169
|
+
this.locked.set(userId, Date.now() + ${lockoutDuration});
|
|
170
|
+
${auditLog ? 'await this.logLockout(userId);' : ''}
|
|
171
|
+
${notifyOnLockout ? 'await this.notifyUser(userId);' : ''}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
isLocked(userId) {
|
|
176
|
+
const lockUntil = this.locked.get(userId);
|
|
177
|
+
if (!lockUntil) return false;
|
|
178
|
+
if (Date.now() > lockUntil) {
|
|
179
|
+
this.locked.delete(userId);
|
|
180
|
+
this.attempts.delete(userId);
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
}`;
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
maxAttempts,
|
|
189
|
+
lockoutDuration,
|
|
190
|
+
progressive,
|
|
191
|
+
baseDelay,
|
|
192
|
+
notifyOnLockout,
|
|
193
|
+
unlockMethod,
|
|
194
|
+
auditLog,
|
|
195
|
+
code,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Generate secure session configuration
|
|
201
|
+
* @param {Object} options - Session options
|
|
202
|
+
* @returns {Object} Session configuration
|
|
203
|
+
*/
|
|
204
|
+
function generateSessionConfig(options = {}) {
|
|
205
|
+
const { maxAge = 3600000, regenerateOnLogin = true } = options;
|
|
206
|
+
|
|
207
|
+
const cookie = {
|
|
208
|
+
httpOnly: true,
|
|
209
|
+
secure: true,
|
|
210
|
+
sameSite: 'strict',
|
|
211
|
+
maxAge,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const code = `
|
|
215
|
+
const crypto = require('crypto');
|
|
216
|
+
const session = require('express-session');
|
|
217
|
+
|
|
218
|
+
app.use(session({
|
|
219
|
+
secret: process.env.SESSION_SECRET,
|
|
220
|
+
resave: false,
|
|
221
|
+
saveUninitialized: false,
|
|
222
|
+
cookie: {
|
|
223
|
+
httpOnly: true,
|
|
224
|
+
secure: true,
|
|
225
|
+
sameSite: 'strict',
|
|
226
|
+
maxAge: ${maxAge},
|
|
227
|
+
},
|
|
228
|
+
genid: () => crypto.randomBytes(32).toString('hex'),
|
|
229
|
+
}));
|
|
230
|
+
|
|
231
|
+
// Regenerate session on login
|
|
232
|
+
async function loginUser(req, user) {
|
|
233
|
+
return new Promise((resolve, reject) => {
|
|
234
|
+
req.session.regenerate((err) => {
|
|
235
|
+
if (err) return reject(err);
|
|
236
|
+
req.session.userId = user.id;
|
|
237
|
+
resolve();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}`;
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
cookie,
|
|
244
|
+
regenerateOnLogin,
|
|
245
|
+
code,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Generate complete auth module code
|
|
251
|
+
* @param {Object} options - Generation options
|
|
252
|
+
* @returns {string} Generated code
|
|
253
|
+
*/
|
|
254
|
+
function generateAuthCode(options = {}) {
|
|
255
|
+
const { language = 'javascript', features = [], owaspCompliant = false } = options;
|
|
256
|
+
|
|
257
|
+
if (language === 'typescript') {
|
|
258
|
+
return `
|
|
259
|
+
interface AuthConfig {
|
|
260
|
+
maxAttempts: number;
|
|
261
|
+
lockoutDuration: number;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
interface AuthResult {
|
|
265
|
+
success: boolean;
|
|
266
|
+
error?: string;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function hashPassword(password: string): Promise<string> {
|
|
270
|
+
// Use argon2id
|
|
271
|
+
return hash;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
275
|
+
return verified;
|
|
276
|
+
}`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (language === 'python') {
|
|
280
|
+
return `
|
|
281
|
+
import argon2
|
|
282
|
+
from functools import wraps
|
|
283
|
+
|
|
284
|
+
def hash_password(password: str) -> str:
|
|
285
|
+
ph = argon2.PasswordHasher()
|
|
286
|
+
return ph.hash(password)
|
|
287
|
+
|
|
288
|
+
def verify_password(password: str, hash: str) -> bool:
|
|
289
|
+
ph = argon2.PasswordHasher()
|
|
290
|
+
try:
|
|
291
|
+
return ph.verify(hash, password)
|
|
292
|
+
except argon2.exceptions.VerifyMismatchError:
|
|
293
|
+
return False`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// JavaScript
|
|
297
|
+
return `
|
|
298
|
+
const argon2 = require('argon2');
|
|
299
|
+
const crypto = require('crypto');
|
|
300
|
+
|
|
301
|
+
async function hashPassword(password) {
|
|
302
|
+
return argon2.hash(password, {
|
|
303
|
+
type: argon2.argon2id,
|
|
304
|
+
memoryCost: 65536,
|
|
305
|
+
timeCost: 3,
|
|
306
|
+
parallelism: 4,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function verifyPassword(password, hash) {
|
|
311
|
+
try {
|
|
312
|
+
return await argon2.verify(hash, password);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function generateSecureToken(length = 32) {
|
|
319
|
+
return crypto.randomBytes(length).toString('hex');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
module.exports = { hashPassword, verifyPassword, generateSecureToken };`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
module.exports = {
|
|
326
|
+
createSecureAuth,
|
|
327
|
+
generatePasswordHash,
|
|
328
|
+
verifyPassword,
|
|
329
|
+
generateRateLimiter,
|
|
330
|
+
generateAccountLockout,
|
|
331
|
+
generateSessionConfig,
|
|
332
|
+
generateAuthCode,
|
|
333
|
+
};
|