@friggframework/devtools 2.0.0-next.3 → 2.0.0-next.31
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/frigg-cli/.eslintrc.js +141 -0
- package/frigg-cli/__tests__/jest.config.js +102 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +483 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +418 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +592 -0
- package/frigg-cli/__tests__/utils/command-tester.js +170 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +286 -0
- package/frigg-cli/build-command/index.js +54 -0
- package/frigg-cli/deploy-command/index.js +36 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +312 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +332 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/generate-iam-command.js +115 -0
- package/frigg-cli/index.js +47 -1
- package/frigg-cli/index.test.js +1 -4
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/install-command/index.js +1 -4
- package/frigg-cli/package.json +51 -0
- package/frigg-cli/start-command/index.js +24 -4
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +16 -17
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +245 -0
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +596 -0
- package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
- package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
- package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
- package/infrastructure/README-TESTING.md +332 -0
- package/infrastructure/README.md +421 -0
- package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +277 -0
- package/infrastructure/aws-discovery.js +568 -0
- package/infrastructure/aws-discovery.test.js +373 -0
- package/infrastructure/build-time-discovery.js +206 -0
- package/infrastructure/build-time-discovery.test.js +375 -0
- package/infrastructure/create-frigg-infrastructure.js +3 -5
- package/infrastructure/frigg-deployment-iam-stack.yaml +379 -0
- package/infrastructure/iam-generator.js +687 -0
- package/infrastructure/iam-generator.test.js +169 -0
- package/infrastructure/iam-policy-basic.json +212 -0
- package/infrastructure/iam-policy-full.json +282 -0
- package/infrastructure/integration.test.js +383 -0
- package/infrastructure/run-discovery.js +110 -0
- package/infrastructure/serverless-template.js +923 -113
- package/infrastructure/serverless-template.test.js +541 -0
- package/management-ui/.eslintrc.js +22 -0
- package/management-ui/README.md +203 -0
- package/management-ui/components.json +21 -0
- package/management-ui/docs/phase2-integration-guide.md +320 -0
- package/management-ui/index.html +13 -0
- package/management-ui/package-lock.json +16517 -0
- package/management-ui/package.json +76 -0
- package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
- package/management-ui/postcss.config.js +6 -0
- package/management-ui/server/api/backend.js +256 -0
- package/management-ui/server/api/cli.js +315 -0
- package/management-ui/server/api/codegen.js +663 -0
- package/management-ui/server/api/connections.js +857 -0
- package/management-ui/server/api/discovery.js +185 -0
- package/management-ui/server/api/environment/index.js +1 -0
- package/management-ui/server/api/environment/router.js +378 -0
- package/management-ui/server/api/environment.js +328 -0
- package/management-ui/server/api/integrations.js +876 -0
- package/management-ui/server/api/logs.js +248 -0
- package/management-ui/server/api/monitoring.js +282 -0
- package/management-ui/server/api/open-ide.js +31 -0
- package/management-ui/server/api/project.js +1029 -0
- package/management-ui/server/api/users/sessions.js +371 -0
- package/management-ui/server/api/users/simulation.js +254 -0
- package/management-ui/server/api/users.js +362 -0
- package/management-ui/server/api-contract.md +275 -0
- package/management-ui/server/index.js +873 -0
- package/management-ui/server/middleware/errorHandler.js +93 -0
- package/management-ui/server/middleware/security.js +32 -0
- package/management-ui/server/processManager.js +296 -0
- package/management-ui/server/server.js +346 -0
- package/management-ui/server/services/aws-monitor.js +413 -0
- package/management-ui/server/services/npm-registry.js +347 -0
- package/management-ui/server/services/template-engine.js +538 -0
- package/management-ui/server/utils/cliIntegration.js +220 -0
- package/management-ui/server/utils/environment/auditLogger.js +471 -0
- package/management-ui/server/utils/environment/awsParameterStore.js +264 -0
- package/management-ui/server/utils/environment/encryption.js +278 -0
- package/management-ui/server/utils/environment/envFileManager.js +286 -0
- package/management-ui/server/utils/import-commonjs.js +28 -0
- package/management-ui/server/utils/response.js +83 -0
- package/management-ui/server/websocket/handler.js +325 -0
- package/management-ui/src/App.jsx +109 -0
- package/management-ui/src/assets/FriggLogo.svg +1 -0
- package/management-ui/src/components/AppRouter.jsx +65 -0
- package/management-ui/src/components/Button.jsx +70 -0
- package/management-ui/src/components/Card.jsx +97 -0
- package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
- package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
- package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
- package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
- package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
- package/management-ui/src/components/ErrorBoundary.jsx +73 -0
- package/management-ui/src/components/IntegrationCard.jsx +481 -0
- package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
- package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
- package/management-ui/src/components/IntegrationStatus.jsx +336 -0
- package/management-ui/src/components/Layout.jsx +716 -0
- package/management-ui/src/components/LoadingSpinner.jsx +113 -0
- package/management-ui/src/components/RepositoryPicker.jsx +248 -0
- package/management-ui/src/components/SessionMonitor.jsx +350 -0
- package/management-ui/src/components/StatusBadge.jsx +208 -0
- package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
- package/management-ui/src/components/UserSimulation.jsx +327 -0
- package/management-ui/src/components/Welcome.jsx +434 -0
- package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
- package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
- package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
- package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
- package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
- package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
- package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
- package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
- package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
- package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
- package/management-ui/src/components/codegen/index.js +10 -0
- package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
- package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
- package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
- package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
- package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
- package/management-ui/src/components/connections/index.js +5 -0
- package/management-ui/src/components/index.js +21 -0
- package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
- package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
- package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
- package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
- package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
- package/management-ui/src/components/monitoring/index.js +6 -0
- package/management-ui/src/components/monitoring/monitoring.css +218 -0
- package/management-ui/src/components/theme-provider.jsx +52 -0
- package/management-ui/src/components/theme-toggle.jsx +39 -0
- package/management-ui/src/components/ui/badge.tsx +36 -0
- package/management-ui/src/components/ui/button.test.jsx +56 -0
- package/management-ui/src/components/ui/button.tsx +57 -0
- package/management-ui/src/components/ui/card.tsx +76 -0
- package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
- package/management-ui/src/components/ui/select.tsx +157 -0
- package/management-ui/src/components/ui/skeleton.jsx +15 -0
- package/management-ui/src/hooks/useFrigg.jsx +601 -0
- package/management-ui/src/hooks/useSocket.jsx +58 -0
- package/management-ui/src/index.css +193 -0
- package/management-ui/src/lib/utils.ts +6 -0
- package/management-ui/src/main.jsx +10 -0
- package/management-ui/src/pages/CodeGeneration.jsx +14 -0
- package/management-ui/src/pages/Connections.jsx +252 -0
- package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
- package/management-ui/src/pages/Dashboard.jsx +311 -0
- package/management-ui/src/pages/Environment.jsx +314 -0
- package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
- package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
- package/management-ui/src/pages/IntegrationTest.jsx +742 -0
- package/management-ui/src/pages/Integrations.jsx +253 -0
- package/management-ui/src/pages/Monitoring.jsx +17 -0
- package/management-ui/src/pages/Simulation.jsx +155 -0
- package/management-ui/src/pages/Users.jsx +492 -0
- package/management-ui/src/services/api.js +41 -0
- package/management-ui/src/services/apiModuleService.js +193 -0
- package/management-ui/src/services/websocket-handlers.js +120 -0
- package/management-ui/src/test/api/project.test.js +273 -0
- package/management-ui/src/test/components/Welcome.test.jsx +378 -0
- package/management-ui/src/test/mocks/server.js +178 -0
- package/management-ui/src/test/setup.js +61 -0
- package/management-ui/src/test/utils/test-utils.jsx +134 -0
- package/management-ui/src/utils/repository.js +98 -0
- package/management-ui/src/utils/repository.test.js +118 -0
- package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
- package/management-ui/tailwind.config.js +63 -0
- package/management-ui/tsconfig.json +37 -0
- package/management-ui/tsconfig.node.json +10 -0
- package/management-ui/vite.config.js +26 -0
- package/management-ui/vitest.config.js +38 -0
- package/package.json +17 -9
- package/infrastructure/app-handler-helpers.js +0 -57
- package/infrastructure/backend-utils.js +0 -90
- package/infrastructure/routers/auth.js +0 -26
- package/infrastructure/routers/integration-defined-routers.js +0 -37
- package/infrastructure/routers/middleware/loadUser.js +0 -15
- package/infrastructure/routers/middleware/requireLoggedInUser.js +0 -12
- package/infrastructure/routers/user.js +0 -41
- package/infrastructure/routers/websocket.js +0 -55
- package/infrastructure/workers/integration-defined-workers.js +0 -24
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import AWS from 'aws-sdk';
|
|
2
|
+
|
|
3
|
+
class AWSParameterStore {
|
|
4
|
+
constructor(config = {}) {
|
|
5
|
+
this.ssm = new AWS.SSM({
|
|
6
|
+
region: config.region || process.env.AWS_REGION || 'us-east-1',
|
|
7
|
+
...config.awsConfig
|
|
8
|
+
});
|
|
9
|
+
this.prefix = config.prefix || '/frigg';
|
|
10
|
+
this.kmsKeyId = config.kmsKeyId || process.env.AWS_KMS_KEY_ID;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get all parameters for a specific environment
|
|
15
|
+
*/
|
|
16
|
+
async getParameters(environment) {
|
|
17
|
+
const path = `${this.prefix}/${environment}`;
|
|
18
|
+
const parameters = [];
|
|
19
|
+
let nextToken;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
do {
|
|
23
|
+
const params = {
|
|
24
|
+
Path: path,
|
|
25
|
+
Recursive: true,
|
|
26
|
+
WithDecryption: true,
|
|
27
|
+
MaxResults: 10,
|
|
28
|
+
NextToken: nextToken
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const response = await this.ssm.getParametersByPath(params).promise();
|
|
32
|
+
|
|
33
|
+
for (const param of response.Parameters) {
|
|
34
|
+
const key = this.extractKeyFromPath(param.Name, environment);
|
|
35
|
+
parameters.push({
|
|
36
|
+
id: `aws-${environment}-${key}`,
|
|
37
|
+
key,
|
|
38
|
+
value: param.Value,
|
|
39
|
+
description: this.getTagValue(param, 'Description'),
|
|
40
|
+
isSecret: param.Type === 'SecureString',
|
|
41
|
+
environment,
|
|
42
|
+
lastModified: param.LastModifiedDate,
|
|
43
|
+
version: param.Version,
|
|
44
|
+
awsName: param.Name
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
nextToken = response.NextToken;
|
|
49
|
+
} while (nextToken);
|
|
50
|
+
|
|
51
|
+
return parameters;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Error fetching parameters from AWS:', error);
|
|
54
|
+
throw new Error(`Failed to fetch parameters: ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Set a parameter in AWS Parameter Store
|
|
60
|
+
*/
|
|
61
|
+
async setParameter(environment, variable) {
|
|
62
|
+
const parameterName = `${this.prefix}/${environment}/${variable.key}`;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const params = {
|
|
66
|
+
Name: parameterName,
|
|
67
|
+
Value: variable.value,
|
|
68
|
+
Type: variable.isSecret ? 'SecureString' : 'String',
|
|
69
|
+
Overwrite: true,
|
|
70
|
+
Description: variable.description || `${variable.key} for ${environment} environment`,
|
|
71
|
+
Tags: [
|
|
72
|
+
{
|
|
73
|
+
Key: 'Environment',
|
|
74
|
+
Value: environment
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
Key: 'ManagedBy',
|
|
78
|
+
Value: 'frigg'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
Key: 'Description',
|
|
82
|
+
Value: variable.description || ''
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Add KMS key for secure strings
|
|
88
|
+
if (variable.isSecret && this.kmsKeyId) {
|
|
89
|
+
params.KeyId = this.kmsKeyId;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const response = await this.ssm.putParameter(params).promise();
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
version: response.Version,
|
|
97
|
+
awsName: parameterName
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('Error setting parameter in AWS:', error);
|
|
101
|
+
throw new Error(`Failed to set parameter: ${error.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Delete a parameter from AWS Parameter Store
|
|
107
|
+
*/
|
|
108
|
+
async deleteParameter(environment, key) {
|
|
109
|
+
const parameterName = `${this.prefix}/${environment}/${key}`;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
await this.ssm.deleteParameter({ Name: parameterName }).promise();
|
|
113
|
+
return { success: true };
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error.code === 'ParameterNotFound') {
|
|
116
|
+
return { success: true, notFound: true };
|
|
117
|
+
}
|
|
118
|
+
console.error('Error deleting parameter from AWS:', error);
|
|
119
|
+
throw new Error(`Failed to delete parameter: ${error.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Sync all variables for an environment to AWS
|
|
125
|
+
*/
|
|
126
|
+
async syncEnvironment(environment, variables) {
|
|
127
|
+
const results = {
|
|
128
|
+
created: [],
|
|
129
|
+
updated: [],
|
|
130
|
+
deleted: [],
|
|
131
|
+
errors: []
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
// Get existing parameters
|
|
136
|
+
const existingParams = await this.getParameters(environment);
|
|
137
|
+
const existingKeys = new Set(existingParams.map(p => p.key));
|
|
138
|
+
const newKeys = new Set(variables.map(v => v.key));
|
|
139
|
+
|
|
140
|
+
// Update or create parameters
|
|
141
|
+
for (const variable of variables) {
|
|
142
|
+
try {
|
|
143
|
+
const existing = existingParams.find(p => p.key === variable.key);
|
|
144
|
+
const result = await this.setParameter(environment, variable);
|
|
145
|
+
|
|
146
|
+
if (existing) {
|
|
147
|
+
results.updated.push({ key: variable.key, ...result });
|
|
148
|
+
} else {
|
|
149
|
+
results.created.push({ key: variable.key, ...result });
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
results.errors.push({
|
|
153
|
+
key: variable.key,
|
|
154
|
+
error: error.message
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Delete parameters that no longer exist
|
|
160
|
+
for (const param of existingParams) {
|
|
161
|
+
if (!newKeys.has(param.key)) {
|
|
162
|
+
try {
|
|
163
|
+
await this.deleteParameter(environment, param.key);
|
|
164
|
+
results.deleted.push({ key: param.key });
|
|
165
|
+
} catch (error) {
|
|
166
|
+
results.errors.push({
|
|
167
|
+
key: param.key,
|
|
168
|
+
error: error.message
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return results;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error('Error syncing environment:', error);
|
|
177
|
+
throw new Error(`Failed to sync environment: ${error.message}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extract key from parameter path
|
|
183
|
+
*/
|
|
184
|
+
extractKeyFromPath(path, environment) {
|
|
185
|
+
const prefix = `${this.prefix}/${environment}/`;
|
|
186
|
+
return path.startsWith(prefix) ? path.substring(prefix.length) : path;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get tag value from parameter
|
|
191
|
+
*/
|
|
192
|
+
getTagValue(parameter, tagKey) {
|
|
193
|
+
// Note: Tags are not returned by getParametersByPath, would need separate call
|
|
194
|
+
// This is a placeholder for when we implement tag fetching
|
|
195
|
+
return '';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Validate AWS credentials and permissions
|
|
200
|
+
*/
|
|
201
|
+
async validateAccess() {
|
|
202
|
+
try {
|
|
203
|
+
// Try to list parameters to check access
|
|
204
|
+
await this.ssm.describeParameters({ MaxResults: 1 }).promise();
|
|
205
|
+
return { valid: true };
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return {
|
|
208
|
+
valid: false,
|
|
209
|
+
error: error.message,
|
|
210
|
+
code: error.code
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Export parameters to .env format
|
|
217
|
+
*/
|
|
218
|
+
async exportToEnv(environment) {
|
|
219
|
+
const parameters = await this.getParameters(environment);
|
|
220
|
+
let content = `# AWS Parameter Store export for ${environment}\n`;
|
|
221
|
+
content += `# Generated on ${new Date().toISOString()}\n\n`;
|
|
222
|
+
|
|
223
|
+
const sorted = parameters.sort((a, b) => a.key.localeCompare(b.key));
|
|
224
|
+
|
|
225
|
+
for (const param of sorted) {
|
|
226
|
+
if (param.description) {
|
|
227
|
+
content += `# ${param.description}\n`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Mask secret values in export
|
|
231
|
+
const value = param.isSecret ? '**REDACTED**' : param.value;
|
|
232
|
+
content += `${param.key}=${value}\n\n`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return content;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get parameter history
|
|
240
|
+
*/
|
|
241
|
+
async getParameterHistory(environment, key, maxResults = 10) {
|
|
242
|
+
const parameterName = `${this.prefix}/${environment}/${key}`;
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const response = await this.ssm.getParameterHistory({
|
|
246
|
+
Name: parameterName,
|
|
247
|
+
WithDecryption: false,
|
|
248
|
+
MaxResults: maxResults
|
|
249
|
+
}).promise();
|
|
250
|
+
|
|
251
|
+
return response.Parameters.map(p => ({
|
|
252
|
+
version: p.Version,
|
|
253
|
+
value: p.Type === 'SecureString' ? '**ENCRYPTED**' : p.Value,
|
|
254
|
+
modifiedDate: p.LastModifiedDate,
|
|
255
|
+
modifiedBy: p.LastModifiedUser
|
|
256
|
+
}));
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error('Error fetching parameter history:', error);
|
|
259
|
+
throw new Error(`Failed to fetch parameter history: ${error.message}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export default AWSParameterStore;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
|
|
3
|
+
class EnvironmentEncryption {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.algorithm = options.algorithm || 'aes-256-gcm'
|
|
6
|
+
this.keyLength = options.keyLength || 32
|
|
7
|
+
this.ivLength = options.ivLength || 16
|
|
8
|
+
this.tagLength = options.tagLength || 16
|
|
9
|
+
this.saltLength = options.saltLength || 64
|
|
10
|
+
this.iterations = options.iterations || 100000
|
|
11
|
+
|
|
12
|
+
// Master key should be stored securely (e.g., environment variable, key management service)
|
|
13
|
+
this.masterKey = this.getMasterKey()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get or generate master encryption key
|
|
18
|
+
* In production, this should be stored securely (AWS KMS, HashiCorp Vault, etc.)
|
|
19
|
+
*/
|
|
20
|
+
getMasterKey() {
|
|
21
|
+
const envKey = process.env.ENCRYPTION_MASTER_KEY
|
|
22
|
+
if (envKey) {
|
|
23
|
+
return Buffer.from(envKey, 'base64')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// For development only - generate a key
|
|
27
|
+
console.warn('No ENCRYPTION_MASTER_KEY found. Generating temporary key for development.')
|
|
28
|
+
return crypto.randomBytes(this.keyLength)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Derive encryption key from master key and salt
|
|
33
|
+
*/
|
|
34
|
+
deriveKey(salt) {
|
|
35
|
+
return crypto.pbkdf2Sync(this.masterKey, salt, this.iterations, this.keyLength, 'sha256')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Encrypt a value
|
|
40
|
+
*/
|
|
41
|
+
encrypt(plaintext) {
|
|
42
|
+
try {
|
|
43
|
+
// Generate random salt and IV
|
|
44
|
+
const salt = crypto.randomBytes(this.saltLength)
|
|
45
|
+
const iv = crypto.randomBytes(this.ivLength)
|
|
46
|
+
|
|
47
|
+
// Derive key from master key and salt
|
|
48
|
+
const key = this.deriveKey(salt)
|
|
49
|
+
|
|
50
|
+
// Create cipher
|
|
51
|
+
const cipher = crypto.createCipheriv(this.algorithm, key, iv)
|
|
52
|
+
|
|
53
|
+
// Encrypt the plaintext
|
|
54
|
+
const encrypted = Buffer.concat([
|
|
55
|
+
cipher.update(plaintext, 'utf8'),
|
|
56
|
+
cipher.final()
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
// Get the authentication tag
|
|
60
|
+
const tag = cipher.getAuthTag()
|
|
61
|
+
|
|
62
|
+
// Combine salt, iv, tag, and encrypted data
|
|
63
|
+
const combined = Buffer.concat([salt, iv, tag, encrypted])
|
|
64
|
+
|
|
65
|
+
// Return base64 encoded string
|
|
66
|
+
return {
|
|
67
|
+
encrypted: combined.toString('base64'),
|
|
68
|
+
algorithm: this.algorithm,
|
|
69
|
+
isEncrypted: true
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Encryption error:', error)
|
|
73
|
+
throw new Error('Failed to encrypt value')
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Decrypt a value
|
|
79
|
+
*/
|
|
80
|
+
decrypt(encryptedData) {
|
|
81
|
+
try {
|
|
82
|
+
// Decode from base64
|
|
83
|
+
const combined = Buffer.from(encryptedData.encrypted, 'base64')
|
|
84
|
+
|
|
85
|
+
// Extract components
|
|
86
|
+
const salt = combined.slice(0, this.saltLength)
|
|
87
|
+
const iv = combined.slice(this.saltLength, this.saltLength + this.ivLength)
|
|
88
|
+
const tag = combined.slice(
|
|
89
|
+
this.saltLength + this.ivLength,
|
|
90
|
+
this.saltLength + this.ivLength + this.tagLength
|
|
91
|
+
)
|
|
92
|
+
const encrypted = combined.slice(this.saltLength + this.ivLength + this.tagLength)
|
|
93
|
+
|
|
94
|
+
// Derive key from master key and salt
|
|
95
|
+
const key = this.deriveKey(salt)
|
|
96
|
+
|
|
97
|
+
// Create decipher
|
|
98
|
+
const decipher = crypto.createDecipheriv(this.algorithm, key, iv)
|
|
99
|
+
decipher.setAuthTag(tag)
|
|
100
|
+
|
|
101
|
+
// Decrypt the data
|
|
102
|
+
const decrypted = Buffer.concat([
|
|
103
|
+
decipher.update(encrypted),
|
|
104
|
+
decipher.final()
|
|
105
|
+
])
|
|
106
|
+
|
|
107
|
+
return decrypted.toString('utf8')
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('Decryption error:', error)
|
|
110
|
+
throw new Error('Failed to decrypt value')
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if a value is encrypted
|
|
116
|
+
*/
|
|
117
|
+
isEncrypted(value) {
|
|
118
|
+
if (typeof value === 'object' && value.isEncrypted === true) {
|
|
119
|
+
return true
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check if string looks like encrypted data (base64 with minimum length)
|
|
123
|
+
if (typeof value === 'string') {
|
|
124
|
+
try {
|
|
125
|
+
const decoded = Buffer.from(value, 'base64')
|
|
126
|
+
return decoded.length >= this.saltLength + this.ivLength + this.tagLength + 16
|
|
127
|
+
} catch {
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return false
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Encrypt sensitive environment variables
|
|
137
|
+
*/
|
|
138
|
+
encryptVariables(variables, patterns = []) {
|
|
139
|
+
const defaultPatterns = [
|
|
140
|
+
/password/i,
|
|
141
|
+
/secret/i,
|
|
142
|
+
/key/i,
|
|
143
|
+
/token/i,
|
|
144
|
+
/credential/i,
|
|
145
|
+
/private/i,
|
|
146
|
+
/auth/i
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
const allPatterns = [...defaultPatterns, ...patterns]
|
|
150
|
+
|
|
151
|
+
return variables.map(variable => {
|
|
152
|
+
// Check if variable should be encrypted
|
|
153
|
+
const shouldEncrypt = allPatterns.some(pattern =>
|
|
154
|
+
pattern.test(variable.key)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if (shouldEncrypt && variable.value && !this.isEncrypted(variable.value)) {
|
|
158
|
+
const encrypted = this.encrypt(variable.value)
|
|
159
|
+
return {
|
|
160
|
+
...variable,
|
|
161
|
+
value: encrypted.encrypted,
|
|
162
|
+
encrypted: true,
|
|
163
|
+
algorithm: encrypted.algorithm
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return variable
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Decrypt variables for use
|
|
173
|
+
*/
|
|
174
|
+
decryptVariables(variables) {
|
|
175
|
+
return variables.map(variable => {
|
|
176
|
+
if (variable.encrypted && variable.value) {
|
|
177
|
+
try {
|
|
178
|
+
const decrypted = this.decrypt({
|
|
179
|
+
encrypted: variable.value,
|
|
180
|
+
algorithm: variable.algorithm || this.algorithm
|
|
181
|
+
})
|
|
182
|
+
return {
|
|
183
|
+
...variable,
|
|
184
|
+
value: decrypted,
|
|
185
|
+
encrypted: false
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error(`Failed to decrypt variable ${variable.key}:`, error)
|
|
189
|
+
return variable
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return variable
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Rotate encryption keys
|
|
199
|
+
*/
|
|
200
|
+
async rotateKeys(variables) {
|
|
201
|
+
// Decrypt all variables with old key
|
|
202
|
+
const decrypted = this.decryptVariables(variables)
|
|
203
|
+
|
|
204
|
+
// Generate new master key
|
|
205
|
+
this.masterKey = crypto.randomBytes(this.keyLength)
|
|
206
|
+
|
|
207
|
+
// Re-encrypt with new key
|
|
208
|
+
return this.encryptVariables(decrypted)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Generate encryption key for export
|
|
213
|
+
*/
|
|
214
|
+
exportKey() {
|
|
215
|
+
return {
|
|
216
|
+
key: this.masterKey.toString('base64'),
|
|
217
|
+
algorithm: this.algorithm,
|
|
218
|
+
generated: new Date().toISOString()
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Import encryption key
|
|
224
|
+
*/
|
|
225
|
+
importKey(keyData) {
|
|
226
|
+
if (!keyData.key) {
|
|
227
|
+
throw new Error('Invalid key data')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this.masterKey = Buffer.from(keyData.key, 'base64')
|
|
231
|
+
this.algorithm = keyData.algorithm || this.algorithm
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Singleton instance
|
|
236
|
+
let encryptionInstance = null
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get encryption instance
|
|
240
|
+
*/
|
|
241
|
+
export function getEncryption(options) {
|
|
242
|
+
if (!encryptionInstance) {
|
|
243
|
+
encryptionInstance = new EnvironmentEncryption(options)
|
|
244
|
+
}
|
|
245
|
+
return encryptionInstance
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Middleware to decrypt variables on read
|
|
250
|
+
*/
|
|
251
|
+
export function decryptMiddleware(req, res, next) {
|
|
252
|
+
const originalJson = res.json
|
|
253
|
+
|
|
254
|
+
res.json = function(data) {
|
|
255
|
+
if (data && data.variables && Array.isArray(data.variables)) {
|
|
256
|
+
const encryption = getEncryption()
|
|
257
|
+
data.variables = encryption.decryptVariables(data.variables)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return originalJson.call(this, data)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
next()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Middleware to encrypt variables on write
|
|
268
|
+
*/
|
|
269
|
+
export function encryptMiddleware(req, res, next) {
|
|
270
|
+
if (req.body && req.body.variables && Array.isArray(req.body.variables)) {
|
|
271
|
+
const encryption = getEncryption()
|
|
272
|
+
req.body.variables = encryption.encryptVariables(req.body.variables)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
next()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export default EnvironmentEncryption
|