@stacksjs/ts-cloud-core 0.1.1
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/LICENSE.md +21 -0
- package/README.md +321 -0
- package/package.json +31 -0
- package/src/advanced-features.test.ts +465 -0
- package/src/aws/cloudformation.ts +421 -0
- package/src/aws/cloudfront.ts +158 -0
- package/src/aws/credentials.test.ts +132 -0
- package/src/aws/credentials.ts +545 -0
- package/src/aws/index.ts +87 -0
- package/src/aws/s3.test.ts +188 -0
- package/src/aws/s3.ts +1088 -0
- package/src/aws/signature.test.ts +670 -0
- package/src/aws/signature.ts +1155 -0
- package/src/backup/disaster-recovery.test.ts +726 -0
- package/src/backup/disaster-recovery.ts +500 -0
- package/src/backup/index.ts +34 -0
- package/src/backup/manager.test.ts +498 -0
- package/src/backup/manager.ts +432 -0
- package/src/cicd/circleci.ts +430 -0
- package/src/cicd/github-actions.ts +424 -0
- package/src/cicd/gitlab-ci.ts +255 -0
- package/src/cicd/index.ts +8 -0
- package/src/cli/history.ts +396 -0
- package/src/cli/index.ts +10 -0
- package/src/cli/progress.ts +458 -0
- package/src/cli/repl.ts +454 -0
- package/src/cli/suggestions.ts +327 -0
- package/src/cli/table.test.ts +319 -0
- package/src/cli/table.ts +332 -0
- package/src/cloudformation/builder.test.ts +327 -0
- package/src/cloudformation/builder.ts +378 -0
- package/src/cloudformation/builders/api-gateway.ts +449 -0
- package/src/cloudformation/builders/cache.ts +334 -0
- package/src/cloudformation/builders/cdn.ts +278 -0
- package/src/cloudformation/builders/compute.ts +485 -0
- package/src/cloudformation/builders/database.ts +392 -0
- package/src/cloudformation/builders/functions.ts +343 -0
- package/src/cloudformation/builders/messaging.ts +140 -0
- package/src/cloudformation/builders/monitoring.ts +300 -0
- package/src/cloudformation/builders/network.ts +264 -0
- package/src/cloudformation/builders/queue.ts +147 -0
- package/src/cloudformation/builders/security.ts +399 -0
- package/src/cloudformation/builders/storage.ts +285 -0
- package/src/cloudformation/index.ts +30 -0
- package/src/cloudformation/types.ts +173 -0
- package/src/compliance/aws-config.ts +543 -0
- package/src/compliance/cloudtrail.ts +376 -0
- package/src/compliance/compliance.test.ts +423 -0
- package/src/compliance/guardduty.ts +446 -0
- package/src/compliance/index.ts +66 -0
- package/src/compliance/security-hub.ts +456 -0
- package/src/containers/build-optimization.ts +416 -0
- package/src/containers/containers.test.ts +508 -0
- package/src/containers/image-scanning.ts +360 -0
- package/src/containers/index.ts +9 -0
- package/src/containers/registry.ts +293 -0
- package/src/containers/service-mesh.ts +520 -0
- package/src/database/database.test.ts +762 -0
- package/src/database/index.ts +9 -0
- package/src/database/migrations.ts +444 -0
- package/src/database/performance.ts +528 -0
- package/src/database/replicas.ts +534 -0
- package/src/database/users.ts +494 -0
- package/src/dependency-graph.ts +143 -0
- package/src/deployment/ab-testing.ts +582 -0
- package/src/deployment/blue-green.ts +452 -0
- package/src/deployment/canary.ts +500 -0
- package/src/deployment/deployment.test.ts +526 -0
- package/src/deployment/index.ts +61 -0
- package/src/deployment/progressive.ts +62 -0
- package/src/dns/dns.test.ts +641 -0
- package/src/dns/dnssec.ts +315 -0
- package/src/dns/index.ts +8 -0
- package/src/dns/resolver.ts +496 -0
- package/src/dns/routing.ts +593 -0
- package/src/email/advanced/analytics.ts +445 -0
- package/src/email/advanced/index.ts +11 -0
- package/src/email/advanced/rules.ts +465 -0
- package/src/email/advanced/scheduling.ts +352 -0
- package/src/email/advanced/search.ts +412 -0
- package/src/email/advanced/shared-mailboxes.ts +404 -0
- package/src/email/advanced/templates.ts +455 -0
- package/src/email/advanced/threading.ts +281 -0
- package/src/email/analytics.ts +467 -0
- package/src/email/bounce-handling.ts +425 -0
- package/src/email/email.test.ts +431 -0
- package/src/email/handlers/__tests__/inbound.test.ts +38 -0
- package/src/email/handlers/__tests__/outbound.test.ts +37 -0
- package/src/email/handlers/converter.ts +227 -0
- package/src/email/handlers/feedback.ts +228 -0
- package/src/email/handlers/inbound.ts +169 -0
- package/src/email/handlers/outbound.ts +178 -0
- package/src/email/index.ts +15 -0
- package/src/email/reputation.ts +303 -0
- package/src/email/templates.ts +352 -0
- package/src/errors/index.test.ts +434 -0
- package/src/errors/index.ts +416 -0
- package/src/health-checks/index.ts +40 -0
- package/src/index.ts +360 -0
- package/src/intrinsic-functions.ts +118 -0
- package/src/lambda/concurrency.ts +330 -0
- package/src/lambda/destinations.ts +345 -0
- package/src/lambda/dlq.ts +425 -0
- package/src/lambda/index.ts +11 -0
- package/src/lambda/lambda.test.ts +840 -0
- package/src/lambda/layers.ts +263 -0
- package/src/lambda/versions.ts +376 -0
- package/src/lambda/vpc.ts +399 -0
- package/src/local/config.ts +114 -0
- package/src/local/index.ts +6 -0
- package/src/local/mock-aws.ts +351 -0
- package/src/modules/ai.ts +340 -0
- package/src/modules/api.ts +478 -0
- package/src/modules/auth.ts +805 -0
- package/src/modules/cache.ts +417 -0
- package/src/modules/cdn.ts +1062 -0
- package/src/modules/communication.ts +1094 -0
- package/src/modules/compute.ts +3348 -0
- package/src/modules/database.ts +554 -0
- package/src/modules/deployment.ts +1079 -0
- package/src/modules/dns.ts +337 -0
- package/src/modules/email.ts +1538 -0
- package/src/modules/filesystem.ts +515 -0
- package/src/modules/index.ts +32 -0
- package/src/modules/messaging.ts +486 -0
- package/src/modules/monitoring.ts +2086 -0
- package/src/modules/network.ts +664 -0
- package/src/modules/parameter-store.ts +325 -0
- package/src/modules/permissions.ts +1081 -0
- package/src/modules/phone.ts +494 -0
- package/src/modules/queue.ts +1260 -0
- package/src/modules/redirects.ts +464 -0
- package/src/modules/registry.ts +699 -0
- package/src/modules/search.ts +401 -0
- package/src/modules/secrets.ts +416 -0
- package/src/modules/security.ts +731 -0
- package/src/modules/sms.ts +389 -0
- package/src/modules/storage.ts +1120 -0
- package/src/modules/workflow.ts +680 -0
- package/src/multi-account/config.ts +521 -0
- package/src/multi-account/index.ts +7 -0
- package/src/multi-account/manager.ts +427 -0
- package/src/multi-region/cross-region.ts +410 -0
- package/src/multi-region/index.ts +8 -0
- package/src/multi-region/manager.ts +483 -0
- package/src/multi-region/regions.ts +435 -0
- package/src/network-security/index.ts +48 -0
- package/src/observability/index.ts +9 -0
- package/src/observability/logs.ts +522 -0
- package/src/observability/metrics.ts +460 -0
- package/src/observability/observability.test.ts +782 -0
- package/src/observability/synthetics.ts +568 -0
- package/src/observability/xray.ts +358 -0
- package/src/phone/advanced/analytics.ts +349 -0
- package/src/phone/advanced/callbacks.ts +428 -0
- package/src/phone/advanced/index.ts +8 -0
- package/src/phone/advanced/ivr-builder.ts +504 -0
- package/src/phone/advanced/recording.ts +310 -0
- package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
- package/src/phone/handlers/incoming-call.ts +117 -0
- package/src/phone/handlers/missed-call.ts +116 -0
- package/src/phone/handlers/voicemail.ts +179 -0
- package/src/phone/index.ts +9 -0
- package/src/presets/api-backend.ts +134 -0
- package/src/presets/data-pipeline.ts +204 -0
- package/src/presets/extend.test.ts +295 -0
- package/src/presets/extend.ts +297 -0
- package/src/presets/fullstack-app.ts +144 -0
- package/src/presets/index.ts +27 -0
- package/src/presets/jamstack.ts +135 -0
- package/src/presets/microservices.ts +167 -0
- package/src/presets/ml-api.ts +208 -0
- package/src/presets/nodejs-server.ts +104 -0
- package/src/presets/nodejs-serverless.ts +114 -0
- package/src/presets/realtime-app.ts +184 -0
- package/src/presets/static-site.ts +64 -0
- package/src/presets/traditional-web-app.ts +339 -0
- package/src/presets/wordpress.ts +138 -0
- package/src/preview/github.test.ts +249 -0
- package/src/preview/github.ts +297 -0
- package/src/preview/index.ts +37 -0
- package/src/preview/manager.test.ts +440 -0
- package/src/preview/manager.ts +326 -0
- package/src/preview/notifications.test.ts +582 -0
- package/src/preview/notifications.ts +341 -0
- package/src/queue/batch-processing.ts +402 -0
- package/src/queue/dlq-monitoring.ts +402 -0
- package/src/queue/fifo.ts +342 -0
- package/src/queue/index.ts +9 -0
- package/src/queue/management.ts +428 -0
- package/src/queue/queue.test.ts +429 -0
- package/src/resource-mgmt/index.ts +39 -0
- package/src/resource-naming.ts +62 -0
- package/src/s3/index.ts +523 -0
- package/src/schema/cloud-config.schema.json +554 -0
- package/src/schema/index.ts +68 -0
- package/src/security/certificate-manager.ts +492 -0
- package/src/security/index.ts +9 -0
- package/src/security/scanning.ts +545 -0
- package/src/security/secrets-manager.ts +476 -0
- package/src/security/secrets-rotation.ts +456 -0
- package/src/security/security.test.ts +738 -0
- package/src/sms/advanced/ab-testing.ts +389 -0
- package/src/sms/advanced/analytics.ts +336 -0
- package/src/sms/advanced/campaigns.ts +523 -0
- package/src/sms/advanced/chatbot.ts +224 -0
- package/src/sms/advanced/index.ts +10 -0
- package/src/sms/advanced/link-tracking.ts +248 -0
- package/src/sms/advanced/mms.ts +308 -0
- package/src/sms/handlers/__tests__/send.test.ts +40 -0
- package/src/sms/handlers/delivery-status.ts +133 -0
- package/src/sms/handlers/receive.ts +162 -0
- package/src/sms/handlers/send.ts +174 -0
- package/src/sms/index.ts +9 -0
- package/src/stack-diff.ts +389 -0
- package/src/static-site/index.ts +85 -0
- package/src/template-builder.ts +110 -0
- package/src/template-validator.ts +574 -0
- package/src/utils/cache.ts +291 -0
- package/src/utils/diff.ts +269 -0
- package/src/utils/hash.ts +227 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/parallel.ts +294 -0
- package/src/validators/credentials.test.ts +274 -0
- package/src/validators/credentials.ts +233 -0
- package/src/validators/quotas.test.ts +434 -0
- package/src/validators/quotas.ts +217 -0
- package/test/ai.test.ts +327 -0
- package/test/api.test.ts +511 -0
- package/test/auth.test.ts +632 -0
- package/test/cache.test.ts +406 -0
- package/test/cdn.test.ts +247 -0
- package/test/compute.test.ts +861 -0
- package/test/database.test.ts +523 -0
- package/test/deployment.test.ts +499 -0
- package/test/dns.test.ts +270 -0
- package/test/email.test.ts +439 -0
- package/test/filesystem.test.ts +382 -0
- package/test/integration.test.ts +350 -0
- package/test/messaging.test.ts +514 -0
- package/test/monitoring.test.ts +634 -0
- package/test/network.test.ts +425 -0
- package/test/permissions.test.ts +488 -0
- package/test/queue.test.ts +484 -0
- package/test/registry.test.ts +306 -0
- package/test/security.test.ts +462 -0
- package/test/storage.test.ts +463 -0
- package/test/template-validator.test.ts +559 -0
- package/test/workflow.test.ts +592 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database User Management
|
|
3
|
+
* User creation, permissions, and access control
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface DatabaseUser {
|
|
7
|
+
id: string
|
|
8
|
+
username: string
|
|
9
|
+
database: string
|
|
10
|
+
privileges: DatabasePrivilege[]
|
|
11
|
+
passwordSecretArn?: string
|
|
12
|
+
createdAt: Date
|
|
13
|
+
lastRotated?: Date
|
|
14
|
+
rotationEnabled?: boolean
|
|
15
|
+
rotationDays?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DatabasePrivilege {
|
|
19
|
+
database?: string
|
|
20
|
+
table?: string
|
|
21
|
+
privileges: PrivilegeType[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type PrivilegeType =
|
|
25
|
+
| 'SELECT'
|
|
26
|
+
| 'INSERT'
|
|
27
|
+
| 'UPDATE'
|
|
28
|
+
| 'DELETE'
|
|
29
|
+
| 'CREATE'
|
|
30
|
+
| 'DROP'
|
|
31
|
+
| 'ALTER'
|
|
32
|
+
| 'INDEX'
|
|
33
|
+
| 'EXECUTE'
|
|
34
|
+
| 'ALL'
|
|
35
|
+
|
|
36
|
+
export interface UserRole {
|
|
37
|
+
id: string
|
|
38
|
+
name: string
|
|
39
|
+
description?: string
|
|
40
|
+
privileges: DatabasePrivilege[]
|
|
41
|
+
users: string[] // user IDs
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface AccessAudit {
|
|
45
|
+
id: string
|
|
46
|
+
username: string
|
|
47
|
+
action: 'LOGIN' | 'QUERY' | 'MODIFY' | 'GRANT' | 'REVOKE'
|
|
48
|
+
database?: string
|
|
49
|
+
table?: string
|
|
50
|
+
query?: string
|
|
51
|
+
timestamp: Date
|
|
52
|
+
success: boolean
|
|
53
|
+
ipAddress?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Database user manager
|
|
58
|
+
*/
|
|
59
|
+
export class DatabaseUserManager {
|
|
60
|
+
private users: Map<string, DatabaseUser> = new Map()
|
|
61
|
+
private roles: Map<string, UserRole> = new Map()
|
|
62
|
+
private audits: Map<string, AccessAudit> = new Map()
|
|
63
|
+
private userCounter = 0
|
|
64
|
+
private roleCounter = 0
|
|
65
|
+
private auditCounter = 0
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create database user
|
|
69
|
+
*/
|
|
70
|
+
createUser(user: Omit<DatabaseUser, 'id' | 'createdAt'>): DatabaseUser {
|
|
71
|
+
const id = `db-user-${Date.now()}-${this.userCounter++}`
|
|
72
|
+
|
|
73
|
+
const dbUser: DatabaseUser = {
|
|
74
|
+
id,
|
|
75
|
+
createdAt: new Date(),
|
|
76
|
+
...user,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.users.set(id, dbUser)
|
|
80
|
+
|
|
81
|
+
return dbUser
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create read-only user
|
|
86
|
+
*/
|
|
87
|
+
createReadOnlyUser(options: {
|
|
88
|
+
username: string
|
|
89
|
+
database: string
|
|
90
|
+
passwordSecretArn?: string
|
|
91
|
+
tables?: string[]
|
|
92
|
+
}): DatabaseUser {
|
|
93
|
+
const privileges: DatabasePrivilege[] = options.tables
|
|
94
|
+
? options.tables.map(table => ({
|
|
95
|
+
database: options.database,
|
|
96
|
+
table,
|
|
97
|
+
privileges: ['SELECT' as PrivilegeType],
|
|
98
|
+
}))
|
|
99
|
+
: [
|
|
100
|
+
{
|
|
101
|
+
database: options.database,
|
|
102
|
+
privileges: ['SELECT' as PrivilegeType],
|
|
103
|
+
},
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
return this.createUser({
|
|
107
|
+
username: options.username,
|
|
108
|
+
database: options.database,
|
|
109
|
+
privileges,
|
|
110
|
+
passwordSecretArn: options.passwordSecretArn,
|
|
111
|
+
rotationEnabled: true,
|
|
112
|
+
rotationDays: 90,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create read-write user
|
|
118
|
+
*/
|
|
119
|
+
createReadWriteUser(options: {
|
|
120
|
+
username: string
|
|
121
|
+
database: string
|
|
122
|
+
passwordSecretArn?: string
|
|
123
|
+
tables?: string[]
|
|
124
|
+
}): DatabaseUser {
|
|
125
|
+
const privileges: DatabasePrivilege[] = options.tables
|
|
126
|
+
? options.tables.map(table => ({
|
|
127
|
+
database: options.database,
|
|
128
|
+
table,
|
|
129
|
+
privileges: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'] as PrivilegeType[],
|
|
130
|
+
}))
|
|
131
|
+
: [
|
|
132
|
+
{
|
|
133
|
+
database: options.database,
|
|
134
|
+
privileges: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'] as PrivilegeType[],
|
|
135
|
+
},
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
return this.createUser({
|
|
139
|
+
username: options.username,
|
|
140
|
+
database: options.database,
|
|
141
|
+
privileges,
|
|
142
|
+
passwordSecretArn: options.passwordSecretArn,
|
|
143
|
+
rotationEnabled: true,
|
|
144
|
+
rotationDays: 60,
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create admin user
|
|
150
|
+
*/
|
|
151
|
+
createAdminUser(options: {
|
|
152
|
+
username: string
|
|
153
|
+
database: string
|
|
154
|
+
passwordSecretArn?: string
|
|
155
|
+
}): DatabaseUser {
|
|
156
|
+
return this.createUser({
|
|
157
|
+
username: options.username,
|
|
158
|
+
database: options.database,
|
|
159
|
+
privileges: [
|
|
160
|
+
{
|
|
161
|
+
database: options.database,
|
|
162
|
+
privileges: ['ALL' as PrivilegeType],
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
passwordSecretArn: options.passwordSecretArn,
|
|
166
|
+
rotationEnabled: true,
|
|
167
|
+
rotationDays: 30,
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Create application user with specific table access
|
|
173
|
+
*/
|
|
174
|
+
createApplicationUser(options: {
|
|
175
|
+
username: string
|
|
176
|
+
database: string
|
|
177
|
+
tables: { name: string; privileges: PrivilegeType[] }[]
|
|
178
|
+
passwordSecretArn?: string
|
|
179
|
+
}): DatabaseUser {
|
|
180
|
+
const privileges: DatabasePrivilege[] = options.tables.map(table => ({
|
|
181
|
+
database: options.database,
|
|
182
|
+
table: table.name,
|
|
183
|
+
privileges: table.privileges,
|
|
184
|
+
}))
|
|
185
|
+
|
|
186
|
+
return this.createUser({
|
|
187
|
+
username: options.username,
|
|
188
|
+
database: options.database,
|
|
189
|
+
privileges,
|
|
190
|
+
passwordSecretArn: options.passwordSecretArn,
|
|
191
|
+
rotationEnabled: true,
|
|
192
|
+
rotationDays: 90,
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Create user role
|
|
198
|
+
*/
|
|
199
|
+
createRole(role: Omit<UserRole, 'id' | 'users'>): UserRole {
|
|
200
|
+
const id = `role-${Date.now()}-${this.roleCounter++}`
|
|
201
|
+
|
|
202
|
+
const userRole: UserRole = {
|
|
203
|
+
id,
|
|
204
|
+
users: [],
|
|
205
|
+
...role,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this.roles.set(id, userRole)
|
|
209
|
+
|
|
210
|
+
return userRole
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Assign user to role
|
|
215
|
+
*/
|
|
216
|
+
assignUserToRole(userId: string, roleId: string): void {
|
|
217
|
+
const user = this.users.get(userId)
|
|
218
|
+
const role = this.roles.get(roleId)
|
|
219
|
+
|
|
220
|
+
if (!user) {
|
|
221
|
+
throw new Error(`User not found: ${userId}`)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!role) {
|
|
225
|
+
throw new Error(`Role not found: ${roleId}`)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
role.users.push(userId)
|
|
229
|
+
|
|
230
|
+
// Merge role privileges with user privileges
|
|
231
|
+
for (const privilege of role.privileges) {
|
|
232
|
+
const existingPrivilege = user.privileges.find(
|
|
233
|
+
p => p.database === privilege.database && p.table === privilege.table
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if (existingPrivilege) {
|
|
237
|
+
// Merge privileges
|
|
238
|
+
existingPrivilege.privileges = Array.from(
|
|
239
|
+
new Set([...existingPrivilege.privileges, ...privilege.privileges])
|
|
240
|
+
)
|
|
241
|
+
} else {
|
|
242
|
+
user.privileges.push({ ...privilege })
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Grant privileges to user
|
|
249
|
+
*/
|
|
250
|
+
grantPrivileges(
|
|
251
|
+
userId: string,
|
|
252
|
+
privileges: DatabasePrivilege[]
|
|
253
|
+
): { success: boolean; message: string } {
|
|
254
|
+
const user = this.users.get(userId)
|
|
255
|
+
|
|
256
|
+
if (!user) {
|
|
257
|
+
return { success: false, message: 'User not found' }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for (const privilege of privileges) {
|
|
261
|
+
const existing = user.privileges.find(
|
|
262
|
+
p => p.database === privilege.database && p.table === privilege.table
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if (existing) {
|
|
266
|
+
existing.privileges = Array.from(new Set([...existing.privileges, ...privilege.privileges]))
|
|
267
|
+
} else {
|
|
268
|
+
user.privileges.push(privilege)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
this.auditAccess({
|
|
273
|
+
username: user.username,
|
|
274
|
+
action: 'GRANT',
|
|
275
|
+
database: privileges[0]?.database,
|
|
276
|
+
table: privileges[0]?.table,
|
|
277
|
+
success: true,
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
return { success: true, message: 'Privileges granted successfully' }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Revoke privileges from user
|
|
285
|
+
*/
|
|
286
|
+
revokePrivileges(
|
|
287
|
+
userId: string,
|
|
288
|
+
privileges: DatabasePrivilege[]
|
|
289
|
+
): { success: boolean; message: string } {
|
|
290
|
+
const user = this.users.get(userId)
|
|
291
|
+
|
|
292
|
+
if (!user) {
|
|
293
|
+
return { success: false, message: 'User not found' }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
for (const privilege of privileges) {
|
|
297
|
+
const existingIndex = user.privileges.findIndex(
|
|
298
|
+
p => p.database === privilege.database && p.table === privilege.table
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
if (existingIndex !== -1) {
|
|
302
|
+
const existing = user.privileges[existingIndex]
|
|
303
|
+
existing.privileges = existing.privileges.filter(
|
|
304
|
+
p => !privilege.privileges.includes(p)
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
// Remove privilege entry if no privileges left
|
|
308
|
+
if (existing.privileges.length === 0) {
|
|
309
|
+
user.privileges.splice(existingIndex, 1)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
this.auditAccess({
|
|
315
|
+
username: user.username,
|
|
316
|
+
action: 'REVOKE',
|
|
317
|
+
database: privileges[0]?.database,
|
|
318
|
+
table: privileges[0]?.table,
|
|
319
|
+
success: true,
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
return { success: true, message: 'Privileges revoked successfully' }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Rotate user password
|
|
327
|
+
*/
|
|
328
|
+
rotatePassword(userId: string): { success: boolean; newSecretArn?: string } {
|
|
329
|
+
const user = this.users.get(userId)
|
|
330
|
+
|
|
331
|
+
if (!user) {
|
|
332
|
+
return { success: false }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// In production, this would trigger Secrets Manager rotation
|
|
336
|
+
const newSecretArn = `arn:aws:secretsmanager:us-east-1:123456789012:secret:db-${user.username}-${Date.now()}`
|
|
337
|
+
|
|
338
|
+
user.passwordSecretArn = newSecretArn
|
|
339
|
+
user.lastRotated = new Date()
|
|
340
|
+
|
|
341
|
+
console.log(`Password rotated for user: ${user.username}`)
|
|
342
|
+
|
|
343
|
+
return { success: true, newSecretArn }
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Check if password rotation needed
|
|
348
|
+
*/
|
|
349
|
+
needsPasswordRotation(userId: string): boolean {
|
|
350
|
+
const user = this.users.get(userId)
|
|
351
|
+
|
|
352
|
+
if (!user || !user.rotationEnabled || !user.lastRotated || !user.rotationDays) {
|
|
353
|
+
return false
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const daysSinceRotation =
|
|
357
|
+
(Date.now() - user.lastRotated.getTime()) / (1000 * 60 * 60 * 24)
|
|
358
|
+
|
|
359
|
+
return daysSinceRotation >= user.rotationDays
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Audit access
|
|
364
|
+
*/
|
|
365
|
+
auditAccess(audit: Omit<AccessAudit, 'id' | 'timestamp'>): AccessAudit {
|
|
366
|
+
const id = `audit-${Date.now()}-${this.auditCounter++}`
|
|
367
|
+
|
|
368
|
+
const accessAudit: AccessAudit = {
|
|
369
|
+
id,
|
|
370
|
+
timestamp: new Date(),
|
|
371
|
+
...audit,
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
this.audits.set(id, accessAudit)
|
|
375
|
+
|
|
376
|
+
return accessAudit
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get user access history
|
|
381
|
+
*/
|
|
382
|
+
getUserAccessHistory(username: string, limit: number = 100): AccessAudit[] {
|
|
383
|
+
return Array.from(this.audits.values())
|
|
384
|
+
.filter(audit => audit.username === username)
|
|
385
|
+
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
|
|
386
|
+
.slice(0, limit)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Get failed login attempts
|
|
391
|
+
*/
|
|
392
|
+
getFailedLoginAttempts(username: string, hours: number = 24): AccessAudit[] {
|
|
393
|
+
const cutoffTime = Date.now() - hours * 60 * 60 * 1000
|
|
394
|
+
|
|
395
|
+
return Array.from(this.audits.values()).filter(
|
|
396
|
+
audit =>
|
|
397
|
+
audit.username === username &&
|
|
398
|
+
audit.action === 'LOGIN' &&
|
|
399
|
+
!audit.success &&
|
|
400
|
+
audit.timestamp.getTime() > cutoffTime
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Generate SQL for user creation
|
|
406
|
+
*/
|
|
407
|
+
generateCreateUserSQL(user: DatabaseUser, engine: 'postgres' | 'mysql' = 'postgres'): string {
|
|
408
|
+
const statements: string[] = []
|
|
409
|
+
|
|
410
|
+
if (engine === 'postgres') {
|
|
411
|
+
statements.push(`CREATE USER ${user.username} WITH PASSWORD '${user.passwordSecretArn}';`)
|
|
412
|
+
|
|
413
|
+
for (const privilege of user.privileges) {
|
|
414
|
+
if (privilege.privileges.includes('ALL')) {
|
|
415
|
+
statements.push(`GRANT ALL PRIVILEGES ON DATABASE ${privilege.database} TO ${user.username};`)
|
|
416
|
+
} else {
|
|
417
|
+
const privs = privilege.privileges.join(', ')
|
|
418
|
+
if (privilege.table) {
|
|
419
|
+
statements.push(
|
|
420
|
+
`GRANT ${privs} ON ${privilege.database}.${privilege.table} TO ${user.username};`
|
|
421
|
+
)
|
|
422
|
+
} else {
|
|
423
|
+
statements.push(`GRANT ${privs} ON DATABASE ${privilege.database} TO ${user.username};`)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
// MySQL
|
|
429
|
+
statements.push(
|
|
430
|
+
`CREATE USER '${user.username}'@'%' IDENTIFIED BY '${user.passwordSecretArn}';`
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
for (const privilege of user.privileges) {
|
|
434
|
+
const privs = privilege.privileges.includes('ALL')
|
|
435
|
+
? 'ALL PRIVILEGES'
|
|
436
|
+
: privilege.privileges.join(', ')
|
|
437
|
+
const target = privilege.table
|
|
438
|
+
? `${privilege.database}.${privilege.table}`
|
|
439
|
+
: `${privilege.database}.*`
|
|
440
|
+
|
|
441
|
+
statements.push(`GRANT ${privs} ON ${target} TO '${user.username}'@'%';`)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
statements.push('FLUSH PRIVILEGES;')
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return statements.join('\n')
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Get user
|
|
452
|
+
*/
|
|
453
|
+
getUser(id: string): DatabaseUser | undefined {
|
|
454
|
+
return this.users.get(id)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* List users
|
|
459
|
+
*/
|
|
460
|
+
listUsers(): DatabaseUser[] {
|
|
461
|
+
return Array.from(this.users.values())
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Get role
|
|
466
|
+
*/
|
|
467
|
+
getRole(id: string): UserRole | undefined {
|
|
468
|
+
return this.roles.get(id)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* List roles
|
|
473
|
+
*/
|
|
474
|
+
listRoles(): UserRole[] {
|
|
475
|
+
return Array.from(this.roles.values())
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Clear all data
|
|
480
|
+
*/
|
|
481
|
+
clear(): void {
|
|
482
|
+
this.users.clear()
|
|
483
|
+
this.roles.clear()
|
|
484
|
+
this.audits.clear()
|
|
485
|
+
this.userCounter = 0
|
|
486
|
+
this.roleCounter = 0
|
|
487
|
+
this.auditCounter = 0
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Global database user manager instance
|
|
493
|
+
*/
|
|
494
|
+
export const databaseUserManager: DatabaseUserManager = new DatabaseUserManager()
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { CloudFormationResource } from '@stacksjs/ts-cloud-aws-types'
|
|
2
|
+
|
|
3
|
+
export interface ResourceNode {
|
|
4
|
+
logicalId: string
|
|
5
|
+
resource: CloudFormationResource
|
|
6
|
+
dependencies: Set<string>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Dependency Graph for CloudFormation resources
|
|
11
|
+
* Ensures resources are created in the correct order
|
|
12
|
+
*/
|
|
13
|
+
export class DependencyGraph {
|
|
14
|
+
private nodes: Map<string, ResourceNode> = new Map()
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Add a resource to the dependency graph
|
|
18
|
+
*/
|
|
19
|
+
addResource(logicalId: string, resource: CloudFormationResource): void {
|
|
20
|
+
const dependencies = this.extractDependencies(resource)
|
|
21
|
+
|
|
22
|
+
this.nodes.set(logicalId, {
|
|
23
|
+
logicalId,
|
|
24
|
+
resource,
|
|
25
|
+
dependencies,
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extract dependencies from a resource
|
|
31
|
+
*/
|
|
32
|
+
private extractDependencies(resource: CloudFormationResource): Set<string> {
|
|
33
|
+
const dependencies = new Set<string>()
|
|
34
|
+
|
|
35
|
+
// Add explicit DependsOn
|
|
36
|
+
if (resource.DependsOn) {
|
|
37
|
+
if (Array.isArray(resource.DependsOn)) {
|
|
38
|
+
resource.DependsOn.forEach(dep => dependencies.add(dep))
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
dependencies.add(resource.DependsOn)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Extract Ref dependencies
|
|
46
|
+
this.findReferences(resource.Properties || {}, dependencies)
|
|
47
|
+
|
|
48
|
+
return dependencies
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Find all Ref references in an object
|
|
53
|
+
*/
|
|
54
|
+
private findReferences(obj: any, dependencies: Set<string>): void {
|
|
55
|
+
if (!obj || typeof obj !== 'object')
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
if ('Ref' in obj && typeof obj.Ref === 'string') {
|
|
59
|
+
// Ignore pseudo parameters
|
|
60
|
+
if (!obj.Ref.startsWith('AWS::')) {
|
|
61
|
+
dependencies.add(obj.Ref)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if ('Fn::GetAtt' in obj && Array.isArray(obj['Fn::GetAtt'])) {
|
|
66
|
+
dependencies.add(obj['Fn::GetAtt'][0])
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Recursively search
|
|
70
|
+
for (const value of Object.values(obj)) {
|
|
71
|
+
if (typeof value === 'object') {
|
|
72
|
+
this.findReferences(value, dependencies)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Perform topological sort to determine resource creation order
|
|
79
|
+
*/
|
|
80
|
+
topologicalSort(): string[] {
|
|
81
|
+
const sorted: string[] = []
|
|
82
|
+
const visited = new Set<string>()
|
|
83
|
+
const visiting = new Set<string>()
|
|
84
|
+
|
|
85
|
+
const visit = (nodeId: string): void => {
|
|
86
|
+
if (visited.has(nodeId))
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
if (visiting.has(nodeId)) {
|
|
90
|
+
throw new Error(`Circular dependency detected: ${nodeId}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
visiting.add(nodeId)
|
|
94
|
+
|
|
95
|
+
const node = this.nodes.get(nodeId)
|
|
96
|
+
if (node) {
|
|
97
|
+
for (const dep of node.dependencies) {
|
|
98
|
+
visit(dep)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
visiting.delete(nodeId)
|
|
103
|
+
visited.add(nodeId)
|
|
104
|
+
sorted.push(nodeId)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const nodeId of this.nodes.keys()) {
|
|
108
|
+
visit(nodeId)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return sorted
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Validate that all dependencies exist
|
|
116
|
+
*/
|
|
117
|
+
validate(): void {
|
|
118
|
+
for (const [nodeId, node] of this.nodes.entries()) {
|
|
119
|
+
for (const dep of node.dependencies) {
|
|
120
|
+
if (!this.nodes.has(dep)) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Resource "${nodeId}" depends on "${dep}" which does not exist`,
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get resources that depend on a given resource
|
|
131
|
+
*/
|
|
132
|
+
getDependents(logicalId: string): string[] {
|
|
133
|
+
const dependents: string[] = []
|
|
134
|
+
|
|
135
|
+
for (const [nodeId, node] of this.nodes.entries()) {
|
|
136
|
+
if (node.dependencies.has(logicalId)) {
|
|
137
|
+
dependents.push(nodeId)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return dependents
|
|
142
|
+
}
|
|
143
|
+
}
|