@friggframework/devtools 2.0.0-next.4 → 2.0.0-next.41
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 +175 -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 +30 -4
- package/frigg-cli/start-command/start-command.test.js +155 -0
- 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/DEPLOYMENT-INSTRUCTIONS.md +268 -0
- package/infrastructure/GENERATE-IAM-DOCS.md +278 -0
- package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
- package/infrastructure/README.md +443 -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 +1176 -0
- package/infrastructure/aws-discovery.test.js +1220 -0
- package/infrastructure/build-time-discovery.js +206 -0
- package/infrastructure/build-time-discovery.test.js +378 -0
- package/infrastructure/create-frigg-infrastructure.js +3 -5
- package/infrastructure/env-validator.js +77 -0
- package/infrastructure/frigg-deployment-iam-stack.yaml +401 -0
- package/infrastructure/iam-generator.js +836 -0
- package/infrastructure/iam-generator.test.js +172 -0
- package/infrastructure/iam-policy-basic.json +218 -0
- package/infrastructure/iam-policy-full.json +288 -0
- package/infrastructure/integration.test.js +383 -0
- package/infrastructure/run-discovery.js +110 -0
- package/infrastructure/serverless-template.js +1493 -138
- package/infrastructure/serverless-template.test.js +1804 -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 +20 -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,220 @@
|
|
|
1
|
+
import { spawn } from 'child_process'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Utility to integrate with Frigg CLI commands
|
|
6
|
+
*/
|
|
7
|
+
class FriggCLIIntegration {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.cliPath = this.findCLIPath()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Find the Frigg CLI executable path
|
|
14
|
+
*/
|
|
15
|
+
findCLIPath() {
|
|
16
|
+
// Try to find the CLI in the project structure
|
|
17
|
+
const possiblePaths = [
|
|
18
|
+
path.resolve(process.cwd(), '../frigg-cli/index.js'),
|
|
19
|
+
path.resolve(process.cwd(), '../../frigg-cli/index.js'),
|
|
20
|
+
path.resolve(process.cwd(), 'packages/devtools/frigg-cli/index.js'),
|
|
21
|
+
'frigg' // Global installation
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
// For now, we'll use the relative path within the project
|
|
25
|
+
return path.resolve(process.cwd(), '../frigg-cli/index.js')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Execute a Frigg CLI command
|
|
30
|
+
*/
|
|
31
|
+
async executeCommand(command, args = [], options = {}) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const childProcess = spawn('node', [this.cliPath, command, ...args], {
|
|
34
|
+
cwd: options.cwd || process.cwd(),
|
|
35
|
+
env: { ...process.env, ...options.env },
|
|
36
|
+
stdio: options.stdio || 'pipe'
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
let stdout = ''
|
|
40
|
+
let stderr = ''
|
|
41
|
+
|
|
42
|
+
if (childProcess.stdout) {
|
|
43
|
+
childProcess.stdout.on('data', (data) => {
|
|
44
|
+
stdout += data.toString()
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (childProcess.stderr) {
|
|
49
|
+
childProcess.stderr.on('data', (data) => {
|
|
50
|
+
stderr += data.toString()
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
childProcess.on('close', (code) => {
|
|
55
|
+
if (code === 0) {
|
|
56
|
+
resolve({ stdout, stderr, code })
|
|
57
|
+
} else {
|
|
58
|
+
reject(new Error(`Command failed with code ${code}: ${stderr || stdout}`))
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
childProcess.on('error', (error) => {
|
|
63
|
+
reject(error)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Install an integration using CLI
|
|
70
|
+
*/
|
|
71
|
+
async installIntegration(integrationName, options = {}) {
|
|
72
|
+
const args = [integrationName]
|
|
73
|
+
|
|
74
|
+
if (options.verbose) {
|
|
75
|
+
args.push('--verbose')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return this.executeCommand('install', args, options)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Build the project using CLI
|
|
83
|
+
*/
|
|
84
|
+
async buildProject(options = {}) {
|
|
85
|
+
const args = []
|
|
86
|
+
|
|
87
|
+
if (options.stage) {
|
|
88
|
+
args.push('--stage', options.stage)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (options.verbose) {
|
|
92
|
+
args.push('--verbose')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return this.executeCommand('build', args, options)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Deploy the project using CLI
|
|
100
|
+
*/
|
|
101
|
+
async deployProject(options = {}) {
|
|
102
|
+
const args = []
|
|
103
|
+
|
|
104
|
+
if (options.stage) {
|
|
105
|
+
args.push('--stage', options.stage)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (options.verbose) {
|
|
109
|
+
args.push('--verbose')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return this.executeCommand('deploy', args, options)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Initialize a new project using CLI
|
|
117
|
+
*/
|
|
118
|
+
async initProject(projectDirectory, options = {}) {
|
|
119
|
+
const args = [projectDirectory]
|
|
120
|
+
|
|
121
|
+
if (options.template) {
|
|
122
|
+
args.push('--template', options.template)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (options.verbose) {
|
|
126
|
+
args.push('--verbose')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return this.executeCommand('init', args, options)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a new integration using CLI
|
|
134
|
+
*/
|
|
135
|
+
async createIntegration(integrationName, options = {}) {
|
|
136
|
+
const args = integrationName ? [integrationName] : []
|
|
137
|
+
|
|
138
|
+
return this.executeCommand('create', args, options)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generate IAM template using CLI
|
|
143
|
+
*/
|
|
144
|
+
async generateIAM(options = {}) {
|
|
145
|
+
const args = []
|
|
146
|
+
|
|
147
|
+
if (options.output) {
|
|
148
|
+
args.push('--output', options.output)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (options.user) {
|
|
152
|
+
args.push('--user', options.user)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (options.stackName) {
|
|
156
|
+
args.push('--stack-name', options.stackName)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (options.verbose) {
|
|
160
|
+
args.push('--verbose')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return this.executeCommand('generate-iam', args, options)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Launch the management UI using CLI
|
|
168
|
+
*/
|
|
169
|
+
async launchUI(options = {}) {
|
|
170
|
+
const args = []
|
|
171
|
+
|
|
172
|
+
if (options.port) {
|
|
173
|
+
args.push('--port', options.port)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (options.apiPort) {
|
|
177
|
+
args.push('--api-port', options.apiPort)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (options.noBrowser) {
|
|
181
|
+
args.push('--no-browser')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (options.uiOnly) {
|
|
185
|
+
args.push('--ui-only')
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return this.executeCommand('ui', args, options)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get CLI version and help information
|
|
193
|
+
*/
|
|
194
|
+
async getInfo() {
|
|
195
|
+
try {
|
|
196
|
+
const result = await this.executeCommand('--help')
|
|
197
|
+
return result.stdout
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.warn('Could not get CLI info:', error.message)
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Validate CLI is available
|
|
206
|
+
*/
|
|
207
|
+
async validateCLI() {
|
|
208
|
+
try {
|
|
209
|
+
await this.getInfo()
|
|
210
|
+
return true
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return false
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Create singleton instance
|
|
218
|
+
const cliIntegration = new FriggCLIIntegration()
|
|
219
|
+
|
|
220
|
+
export default cliIntegration
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import fs from 'fs/promises'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { EventEmitter } from 'events'
|
|
4
|
+
|
|
5
|
+
class EnvironmentAuditLogger extends EventEmitter {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
super()
|
|
8
|
+
|
|
9
|
+
this.logDir = options.logDir || path.join(process.cwd(), 'logs', 'audit')
|
|
10
|
+
this.maxLogSize = options.maxLogSize || 10 * 1024 * 1024 // 10MB
|
|
11
|
+
this.maxLogFiles = options.maxLogFiles || 10
|
|
12
|
+
this.currentLogFile = null
|
|
13
|
+
this.logStream = null
|
|
14
|
+
|
|
15
|
+
this.initializeLogger()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initialize the audit logger
|
|
20
|
+
*/
|
|
21
|
+
async initializeLogger() {
|
|
22
|
+
try {
|
|
23
|
+
// Ensure log directory exists
|
|
24
|
+
await fs.mkdir(this.logDir, { recursive: true })
|
|
25
|
+
|
|
26
|
+
// Get current log file
|
|
27
|
+
this.currentLogFile = await this.getCurrentLogFile()
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('Failed to initialize audit logger:', error)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get current log file path
|
|
35
|
+
*/
|
|
36
|
+
async getCurrentLogFile() {
|
|
37
|
+
const date = new Date().toISOString().split('T')[0]
|
|
38
|
+
const baseFileName = `env-audit-${date}.log`
|
|
39
|
+
let fileName = baseFileName
|
|
40
|
+
let counter = 1
|
|
41
|
+
|
|
42
|
+
// Check if file exists and size
|
|
43
|
+
while (true) {
|
|
44
|
+
const filePath = path.join(this.logDir, fileName)
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const stats = await fs.stat(filePath)
|
|
48
|
+
|
|
49
|
+
if (stats.size < this.maxLogSize) {
|
|
50
|
+
return filePath
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// File is too large, create new one
|
|
54
|
+
fileName = `env-audit-${date}-${counter}.log`
|
|
55
|
+
counter++
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// File doesn't exist, use it
|
|
58
|
+
return filePath
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Log an audit entry
|
|
65
|
+
*/
|
|
66
|
+
async log(entry) {
|
|
67
|
+
const logEntry = {
|
|
68
|
+
timestamp: new Date().toISOString(),
|
|
69
|
+
...entry,
|
|
70
|
+
id: this.generateId()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Write to file
|
|
75
|
+
const logLine = JSON.stringify(logEntry) + '\n'
|
|
76
|
+
await fs.appendFile(this.currentLogFile, logLine)
|
|
77
|
+
|
|
78
|
+
// Emit event for real-time monitoring
|
|
79
|
+
this.emit('audit', logEntry)
|
|
80
|
+
|
|
81
|
+
// Check if rotation needed
|
|
82
|
+
await this.checkRotation()
|
|
83
|
+
|
|
84
|
+
return logEntry
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('Failed to write audit log:', error)
|
|
87
|
+
throw error
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Log environment variable change
|
|
93
|
+
*/
|
|
94
|
+
async logVariableChange(action, data) {
|
|
95
|
+
const entry = {
|
|
96
|
+
category: 'environment',
|
|
97
|
+
action,
|
|
98
|
+
user: data.user || 'system',
|
|
99
|
+
environment: data.environment || 'unknown',
|
|
100
|
+
variable: data.variable,
|
|
101
|
+
details: {}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
switch (action) {
|
|
105
|
+
case 'create':
|
|
106
|
+
entry.details = {
|
|
107
|
+
key: data.key,
|
|
108
|
+
masked: this.shouldMask(data.key)
|
|
109
|
+
}
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
case 'update':
|
|
113
|
+
entry.details = {
|
|
114
|
+
key: data.key,
|
|
115
|
+
changed: data.changed || [],
|
|
116
|
+
masked: this.shouldMask(data.key)
|
|
117
|
+
}
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
case 'delete':
|
|
121
|
+
entry.details = {
|
|
122
|
+
key: data.key
|
|
123
|
+
}
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
case 'import':
|
|
127
|
+
entry.details = {
|
|
128
|
+
count: data.count,
|
|
129
|
+
format: data.format,
|
|
130
|
+
source: data.source
|
|
131
|
+
}
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
case 'export':
|
|
135
|
+
entry.details = {
|
|
136
|
+
count: data.count,
|
|
137
|
+
format: data.format,
|
|
138
|
+
excludedSecrets: data.excludedSecrets
|
|
139
|
+
}
|
|
140
|
+
break
|
|
141
|
+
|
|
142
|
+
case 'sync':
|
|
143
|
+
entry.details = {
|
|
144
|
+
source: data.source,
|
|
145
|
+
target: data.target,
|
|
146
|
+
count: data.count
|
|
147
|
+
}
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return this.log(entry)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Log access event
|
|
156
|
+
*/
|
|
157
|
+
async logAccess(action, data) {
|
|
158
|
+
const entry = {
|
|
159
|
+
category: 'access',
|
|
160
|
+
action,
|
|
161
|
+
user: data.user || 'anonymous',
|
|
162
|
+
environment: data.environment,
|
|
163
|
+
details: {
|
|
164
|
+
ip: data.ip,
|
|
165
|
+
userAgent: data.userAgent,
|
|
166
|
+
method: data.method,
|
|
167
|
+
path: data.path
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (action === 'denied') {
|
|
172
|
+
entry.details.reason = data.reason
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return this.log(entry)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Log security event
|
|
180
|
+
*/
|
|
181
|
+
async logSecurity(action, data) {
|
|
182
|
+
const entry = {
|
|
183
|
+
category: 'security',
|
|
184
|
+
action,
|
|
185
|
+
user: data.user || 'system',
|
|
186
|
+
details: data.details || {}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return this.log(entry)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Search audit logs
|
|
194
|
+
*/
|
|
195
|
+
async search(criteria = {}) {
|
|
196
|
+
const {
|
|
197
|
+
startDate,
|
|
198
|
+
endDate,
|
|
199
|
+
category,
|
|
200
|
+
action,
|
|
201
|
+
user,
|
|
202
|
+
environment,
|
|
203
|
+
variable,
|
|
204
|
+
limit = 100
|
|
205
|
+
} = criteria
|
|
206
|
+
|
|
207
|
+
const results = []
|
|
208
|
+
const files = await this.getLogFiles()
|
|
209
|
+
|
|
210
|
+
// Read files in reverse order (newest first)
|
|
211
|
+
for (const file of files.reverse()) {
|
|
212
|
+
const content = await fs.readFile(path.join(this.logDir, file), 'utf-8')
|
|
213
|
+
const lines = content.trim().split('\n')
|
|
214
|
+
|
|
215
|
+
for (const line of lines.reverse()) {
|
|
216
|
+
if (!line) continue
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const entry = JSON.parse(line)
|
|
220
|
+
|
|
221
|
+
// Apply filters
|
|
222
|
+
if (startDate && new Date(entry.timestamp) < new Date(startDate)) continue
|
|
223
|
+
if (endDate && new Date(entry.timestamp) > new Date(endDate)) continue
|
|
224
|
+
if (category && entry.category !== category) continue
|
|
225
|
+
if (action && entry.action !== action) continue
|
|
226
|
+
if (user && entry.user !== user) continue
|
|
227
|
+
if (environment && entry.environment !== environment) continue
|
|
228
|
+
if (variable && entry.variable !== variable) continue
|
|
229
|
+
|
|
230
|
+
results.push(entry)
|
|
231
|
+
|
|
232
|
+
if (results.length >= limit) {
|
|
233
|
+
return results
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
// Skip invalid lines
|
|
237
|
+
continue
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return results
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get audit statistics
|
|
247
|
+
*/
|
|
248
|
+
async getStatistics(timeRange = '24h') {
|
|
249
|
+
const stats = {
|
|
250
|
+
totalEvents: 0,
|
|
251
|
+
byCategory: {},
|
|
252
|
+
byAction: {},
|
|
253
|
+
byUser: {},
|
|
254
|
+
byEnvironment: {},
|
|
255
|
+
timeline: []
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const startTime = this.getStartTime(timeRange)
|
|
259
|
+
const entries = await this.search({ startDate: startTime })
|
|
260
|
+
|
|
261
|
+
entries.forEach(entry => {
|
|
262
|
+
stats.totalEvents++
|
|
263
|
+
|
|
264
|
+
// By category
|
|
265
|
+
stats.byCategory[entry.category] = (stats.byCategory[entry.category] || 0) + 1
|
|
266
|
+
|
|
267
|
+
// By action
|
|
268
|
+
stats.byAction[entry.action] = (stats.byAction[entry.action] || 0) + 1
|
|
269
|
+
|
|
270
|
+
// By user
|
|
271
|
+
stats.byUser[entry.user] = (stats.byUser[entry.user] || 0) + 1
|
|
272
|
+
|
|
273
|
+
// By environment
|
|
274
|
+
if (entry.environment) {
|
|
275
|
+
stats.byEnvironment[entry.environment] =
|
|
276
|
+
(stats.byEnvironment[entry.environment] || 0) + 1
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
return stats
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Export audit logs
|
|
285
|
+
*/
|
|
286
|
+
async export(format = 'json', criteria = {}) {
|
|
287
|
+
const entries = await this.search(criteria)
|
|
288
|
+
|
|
289
|
+
switch (format) {
|
|
290
|
+
case 'csv':
|
|
291
|
+
return this.exportToCsv(entries)
|
|
292
|
+
|
|
293
|
+
case 'json':
|
|
294
|
+
default:
|
|
295
|
+
return JSON.stringify(entries, null, 2)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Export to CSV format
|
|
301
|
+
*/
|
|
302
|
+
exportToCsv(entries) {
|
|
303
|
+
const headers = [
|
|
304
|
+
'Timestamp',
|
|
305
|
+
'Category',
|
|
306
|
+
'Action',
|
|
307
|
+
'User',
|
|
308
|
+
'Environment',
|
|
309
|
+
'Variable',
|
|
310
|
+
'Details'
|
|
311
|
+
]
|
|
312
|
+
|
|
313
|
+
const rows = entries.map(entry => [
|
|
314
|
+
entry.timestamp,
|
|
315
|
+
entry.category,
|
|
316
|
+
entry.action,
|
|
317
|
+
entry.user,
|
|
318
|
+
entry.environment || '',
|
|
319
|
+
entry.variable || '',
|
|
320
|
+
JSON.stringify(entry.details || {})
|
|
321
|
+
])
|
|
322
|
+
|
|
323
|
+
const csv = [
|
|
324
|
+
headers.join(','),
|
|
325
|
+
...rows.map(row => row.map(cell => `"${cell}"`).join(','))
|
|
326
|
+
].join('\n')
|
|
327
|
+
|
|
328
|
+
return csv
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Clean up old log files
|
|
333
|
+
*/
|
|
334
|
+
async cleanup() {
|
|
335
|
+
const files = await this.getLogFiles()
|
|
336
|
+
|
|
337
|
+
if (files.length > this.maxLogFiles) {
|
|
338
|
+
// Sort files by date (oldest first)
|
|
339
|
+
files.sort()
|
|
340
|
+
|
|
341
|
+
// Remove old files
|
|
342
|
+
const filesToRemove = files.slice(0, files.length - this.maxLogFiles)
|
|
343
|
+
|
|
344
|
+
for (const file of filesToRemove) {
|
|
345
|
+
await fs.unlink(path.join(this.logDir, file))
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Check if log rotation is needed
|
|
352
|
+
*/
|
|
353
|
+
async checkRotation() {
|
|
354
|
+
try {
|
|
355
|
+
const stats = await fs.stat(this.currentLogFile)
|
|
356
|
+
|
|
357
|
+
if (stats.size >= this.maxLogSize) {
|
|
358
|
+
this.currentLogFile = await this.getCurrentLogFile()
|
|
359
|
+
await this.cleanup()
|
|
360
|
+
}
|
|
361
|
+
} catch (error) {
|
|
362
|
+
// File doesn't exist, no rotation needed
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Get list of log files
|
|
368
|
+
*/
|
|
369
|
+
async getLogFiles() {
|
|
370
|
+
const files = await fs.readdir(this.logDir)
|
|
371
|
+
return files.filter(file => file.startsWith('env-audit-') && file.endsWith('.log'))
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Check if variable should be masked in logs
|
|
376
|
+
*/
|
|
377
|
+
shouldMask(key) {
|
|
378
|
+
const sensitivePatterns = [
|
|
379
|
+
/password/i,
|
|
380
|
+
/secret/i,
|
|
381
|
+
/key/i,
|
|
382
|
+
/token/i,
|
|
383
|
+
/credential/i,
|
|
384
|
+
/private/i,
|
|
385
|
+
/auth/i
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
return sensitivePatterns.some(pattern => pattern.test(key))
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Generate unique ID
|
|
393
|
+
*/
|
|
394
|
+
generateId() {
|
|
395
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Get start time based on time range
|
|
400
|
+
*/
|
|
401
|
+
getStartTime(timeRange) {
|
|
402
|
+
const now = new Date()
|
|
403
|
+
|
|
404
|
+
switch (timeRange) {
|
|
405
|
+
case '1h':
|
|
406
|
+
return new Date(now - 60 * 60 * 1000)
|
|
407
|
+
case '24h':
|
|
408
|
+
return new Date(now - 24 * 60 * 60 * 1000)
|
|
409
|
+
case '7d':
|
|
410
|
+
return new Date(now - 7 * 24 * 60 * 60 * 1000)
|
|
411
|
+
case '30d':
|
|
412
|
+
return new Date(now - 30 * 24 * 60 * 60 * 1000)
|
|
413
|
+
default:
|
|
414
|
+
return new Date(now - 24 * 60 * 60 * 1000)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Singleton instance
|
|
420
|
+
let auditLogger = null
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Get audit logger instance
|
|
424
|
+
*/
|
|
425
|
+
export function getAuditLogger(options) {
|
|
426
|
+
if (!auditLogger) {
|
|
427
|
+
auditLogger = new EnvironmentAuditLogger(options)
|
|
428
|
+
}
|
|
429
|
+
return auditLogger
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Audit middleware
|
|
434
|
+
*/
|
|
435
|
+
export function auditMiddleware(req, res, next) {
|
|
436
|
+
const logger = getAuditLogger()
|
|
437
|
+
|
|
438
|
+
// Log access
|
|
439
|
+
logger.logAccess('access', {
|
|
440
|
+
user: req.user?.email || 'anonymous',
|
|
441
|
+
environment: req.params.environment || req.query.environment,
|
|
442
|
+
ip: req.ip,
|
|
443
|
+
userAgent: req.get('user-agent'),
|
|
444
|
+
method: req.method,
|
|
445
|
+
path: req.path
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
// Intercept responses for logging changes
|
|
449
|
+
const originalJson = res.json
|
|
450
|
+
res.json = function(data) {
|
|
451
|
+
// Log successful changes
|
|
452
|
+
if (res.statusCode < 400 && ['POST', 'PUT', 'DELETE'].includes(req.method)) {
|
|
453
|
+
const action = req.method === 'POST' ? 'create' :
|
|
454
|
+
req.method === 'PUT' ? 'update' :
|
|
455
|
+
'delete'
|
|
456
|
+
|
|
457
|
+
logger.logVariableChange(action, {
|
|
458
|
+
user: req.user?.email || 'anonymous',
|
|
459
|
+
environment: req.params.environment || req.body.environment,
|
|
460
|
+
key: req.params.key || req.body.key,
|
|
461
|
+
...req.body
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return originalJson.call(this, data)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
next()
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export default EnvironmentAuditLogger
|