@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.
Files changed (251) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +321 -0
  3. package/package.json +31 -0
  4. package/src/advanced-features.test.ts +465 -0
  5. package/src/aws/cloudformation.ts +421 -0
  6. package/src/aws/cloudfront.ts +158 -0
  7. package/src/aws/credentials.test.ts +132 -0
  8. package/src/aws/credentials.ts +545 -0
  9. package/src/aws/index.ts +87 -0
  10. package/src/aws/s3.test.ts +188 -0
  11. package/src/aws/s3.ts +1088 -0
  12. package/src/aws/signature.test.ts +670 -0
  13. package/src/aws/signature.ts +1155 -0
  14. package/src/backup/disaster-recovery.test.ts +726 -0
  15. package/src/backup/disaster-recovery.ts +500 -0
  16. package/src/backup/index.ts +34 -0
  17. package/src/backup/manager.test.ts +498 -0
  18. package/src/backup/manager.ts +432 -0
  19. package/src/cicd/circleci.ts +430 -0
  20. package/src/cicd/github-actions.ts +424 -0
  21. package/src/cicd/gitlab-ci.ts +255 -0
  22. package/src/cicd/index.ts +8 -0
  23. package/src/cli/history.ts +396 -0
  24. package/src/cli/index.ts +10 -0
  25. package/src/cli/progress.ts +458 -0
  26. package/src/cli/repl.ts +454 -0
  27. package/src/cli/suggestions.ts +327 -0
  28. package/src/cli/table.test.ts +319 -0
  29. package/src/cli/table.ts +332 -0
  30. package/src/cloudformation/builder.test.ts +327 -0
  31. package/src/cloudformation/builder.ts +378 -0
  32. package/src/cloudformation/builders/api-gateway.ts +449 -0
  33. package/src/cloudformation/builders/cache.ts +334 -0
  34. package/src/cloudformation/builders/cdn.ts +278 -0
  35. package/src/cloudformation/builders/compute.ts +485 -0
  36. package/src/cloudformation/builders/database.ts +392 -0
  37. package/src/cloudformation/builders/functions.ts +343 -0
  38. package/src/cloudformation/builders/messaging.ts +140 -0
  39. package/src/cloudformation/builders/monitoring.ts +300 -0
  40. package/src/cloudformation/builders/network.ts +264 -0
  41. package/src/cloudformation/builders/queue.ts +147 -0
  42. package/src/cloudformation/builders/security.ts +399 -0
  43. package/src/cloudformation/builders/storage.ts +285 -0
  44. package/src/cloudformation/index.ts +30 -0
  45. package/src/cloudformation/types.ts +173 -0
  46. package/src/compliance/aws-config.ts +543 -0
  47. package/src/compliance/cloudtrail.ts +376 -0
  48. package/src/compliance/compliance.test.ts +423 -0
  49. package/src/compliance/guardduty.ts +446 -0
  50. package/src/compliance/index.ts +66 -0
  51. package/src/compliance/security-hub.ts +456 -0
  52. package/src/containers/build-optimization.ts +416 -0
  53. package/src/containers/containers.test.ts +508 -0
  54. package/src/containers/image-scanning.ts +360 -0
  55. package/src/containers/index.ts +9 -0
  56. package/src/containers/registry.ts +293 -0
  57. package/src/containers/service-mesh.ts +520 -0
  58. package/src/database/database.test.ts +762 -0
  59. package/src/database/index.ts +9 -0
  60. package/src/database/migrations.ts +444 -0
  61. package/src/database/performance.ts +528 -0
  62. package/src/database/replicas.ts +534 -0
  63. package/src/database/users.ts +494 -0
  64. package/src/dependency-graph.ts +143 -0
  65. package/src/deployment/ab-testing.ts +582 -0
  66. package/src/deployment/blue-green.ts +452 -0
  67. package/src/deployment/canary.ts +500 -0
  68. package/src/deployment/deployment.test.ts +526 -0
  69. package/src/deployment/index.ts +61 -0
  70. package/src/deployment/progressive.ts +62 -0
  71. package/src/dns/dns.test.ts +641 -0
  72. package/src/dns/dnssec.ts +315 -0
  73. package/src/dns/index.ts +8 -0
  74. package/src/dns/resolver.ts +496 -0
  75. package/src/dns/routing.ts +593 -0
  76. package/src/email/advanced/analytics.ts +445 -0
  77. package/src/email/advanced/index.ts +11 -0
  78. package/src/email/advanced/rules.ts +465 -0
  79. package/src/email/advanced/scheduling.ts +352 -0
  80. package/src/email/advanced/search.ts +412 -0
  81. package/src/email/advanced/shared-mailboxes.ts +404 -0
  82. package/src/email/advanced/templates.ts +455 -0
  83. package/src/email/advanced/threading.ts +281 -0
  84. package/src/email/analytics.ts +467 -0
  85. package/src/email/bounce-handling.ts +425 -0
  86. package/src/email/email.test.ts +431 -0
  87. package/src/email/handlers/__tests__/inbound.test.ts +38 -0
  88. package/src/email/handlers/__tests__/outbound.test.ts +37 -0
  89. package/src/email/handlers/converter.ts +227 -0
  90. package/src/email/handlers/feedback.ts +228 -0
  91. package/src/email/handlers/inbound.ts +169 -0
  92. package/src/email/handlers/outbound.ts +178 -0
  93. package/src/email/index.ts +15 -0
  94. package/src/email/reputation.ts +303 -0
  95. package/src/email/templates.ts +352 -0
  96. package/src/errors/index.test.ts +434 -0
  97. package/src/errors/index.ts +416 -0
  98. package/src/health-checks/index.ts +40 -0
  99. package/src/index.ts +360 -0
  100. package/src/intrinsic-functions.ts +118 -0
  101. package/src/lambda/concurrency.ts +330 -0
  102. package/src/lambda/destinations.ts +345 -0
  103. package/src/lambda/dlq.ts +425 -0
  104. package/src/lambda/index.ts +11 -0
  105. package/src/lambda/lambda.test.ts +840 -0
  106. package/src/lambda/layers.ts +263 -0
  107. package/src/lambda/versions.ts +376 -0
  108. package/src/lambda/vpc.ts +399 -0
  109. package/src/local/config.ts +114 -0
  110. package/src/local/index.ts +6 -0
  111. package/src/local/mock-aws.ts +351 -0
  112. package/src/modules/ai.ts +340 -0
  113. package/src/modules/api.ts +478 -0
  114. package/src/modules/auth.ts +805 -0
  115. package/src/modules/cache.ts +417 -0
  116. package/src/modules/cdn.ts +1062 -0
  117. package/src/modules/communication.ts +1094 -0
  118. package/src/modules/compute.ts +3348 -0
  119. package/src/modules/database.ts +554 -0
  120. package/src/modules/deployment.ts +1079 -0
  121. package/src/modules/dns.ts +337 -0
  122. package/src/modules/email.ts +1538 -0
  123. package/src/modules/filesystem.ts +515 -0
  124. package/src/modules/index.ts +32 -0
  125. package/src/modules/messaging.ts +486 -0
  126. package/src/modules/monitoring.ts +2086 -0
  127. package/src/modules/network.ts +664 -0
  128. package/src/modules/parameter-store.ts +325 -0
  129. package/src/modules/permissions.ts +1081 -0
  130. package/src/modules/phone.ts +494 -0
  131. package/src/modules/queue.ts +1260 -0
  132. package/src/modules/redirects.ts +464 -0
  133. package/src/modules/registry.ts +699 -0
  134. package/src/modules/search.ts +401 -0
  135. package/src/modules/secrets.ts +416 -0
  136. package/src/modules/security.ts +731 -0
  137. package/src/modules/sms.ts +389 -0
  138. package/src/modules/storage.ts +1120 -0
  139. package/src/modules/workflow.ts +680 -0
  140. package/src/multi-account/config.ts +521 -0
  141. package/src/multi-account/index.ts +7 -0
  142. package/src/multi-account/manager.ts +427 -0
  143. package/src/multi-region/cross-region.ts +410 -0
  144. package/src/multi-region/index.ts +8 -0
  145. package/src/multi-region/manager.ts +483 -0
  146. package/src/multi-region/regions.ts +435 -0
  147. package/src/network-security/index.ts +48 -0
  148. package/src/observability/index.ts +9 -0
  149. package/src/observability/logs.ts +522 -0
  150. package/src/observability/metrics.ts +460 -0
  151. package/src/observability/observability.test.ts +782 -0
  152. package/src/observability/synthetics.ts +568 -0
  153. package/src/observability/xray.ts +358 -0
  154. package/src/phone/advanced/analytics.ts +349 -0
  155. package/src/phone/advanced/callbacks.ts +428 -0
  156. package/src/phone/advanced/index.ts +8 -0
  157. package/src/phone/advanced/ivr-builder.ts +504 -0
  158. package/src/phone/advanced/recording.ts +310 -0
  159. package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
  160. package/src/phone/handlers/incoming-call.ts +117 -0
  161. package/src/phone/handlers/missed-call.ts +116 -0
  162. package/src/phone/handlers/voicemail.ts +179 -0
  163. package/src/phone/index.ts +9 -0
  164. package/src/presets/api-backend.ts +134 -0
  165. package/src/presets/data-pipeline.ts +204 -0
  166. package/src/presets/extend.test.ts +295 -0
  167. package/src/presets/extend.ts +297 -0
  168. package/src/presets/fullstack-app.ts +144 -0
  169. package/src/presets/index.ts +27 -0
  170. package/src/presets/jamstack.ts +135 -0
  171. package/src/presets/microservices.ts +167 -0
  172. package/src/presets/ml-api.ts +208 -0
  173. package/src/presets/nodejs-server.ts +104 -0
  174. package/src/presets/nodejs-serverless.ts +114 -0
  175. package/src/presets/realtime-app.ts +184 -0
  176. package/src/presets/static-site.ts +64 -0
  177. package/src/presets/traditional-web-app.ts +339 -0
  178. package/src/presets/wordpress.ts +138 -0
  179. package/src/preview/github.test.ts +249 -0
  180. package/src/preview/github.ts +297 -0
  181. package/src/preview/index.ts +37 -0
  182. package/src/preview/manager.test.ts +440 -0
  183. package/src/preview/manager.ts +326 -0
  184. package/src/preview/notifications.test.ts +582 -0
  185. package/src/preview/notifications.ts +341 -0
  186. package/src/queue/batch-processing.ts +402 -0
  187. package/src/queue/dlq-monitoring.ts +402 -0
  188. package/src/queue/fifo.ts +342 -0
  189. package/src/queue/index.ts +9 -0
  190. package/src/queue/management.ts +428 -0
  191. package/src/queue/queue.test.ts +429 -0
  192. package/src/resource-mgmt/index.ts +39 -0
  193. package/src/resource-naming.ts +62 -0
  194. package/src/s3/index.ts +523 -0
  195. package/src/schema/cloud-config.schema.json +554 -0
  196. package/src/schema/index.ts +68 -0
  197. package/src/security/certificate-manager.ts +492 -0
  198. package/src/security/index.ts +9 -0
  199. package/src/security/scanning.ts +545 -0
  200. package/src/security/secrets-manager.ts +476 -0
  201. package/src/security/secrets-rotation.ts +456 -0
  202. package/src/security/security.test.ts +738 -0
  203. package/src/sms/advanced/ab-testing.ts +389 -0
  204. package/src/sms/advanced/analytics.ts +336 -0
  205. package/src/sms/advanced/campaigns.ts +523 -0
  206. package/src/sms/advanced/chatbot.ts +224 -0
  207. package/src/sms/advanced/index.ts +10 -0
  208. package/src/sms/advanced/link-tracking.ts +248 -0
  209. package/src/sms/advanced/mms.ts +308 -0
  210. package/src/sms/handlers/__tests__/send.test.ts +40 -0
  211. package/src/sms/handlers/delivery-status.ts +133 -0
  212. package/src/sms/handlers/receive.ts +162 -0
  213. package/src/sms/handlers/send.ts +174 -0
  214. package/src/sms/index.ts +9 -0
  215. package/src/stack-diff.ts +389 -0
  216. package/src/static-site/index.ts +85 -0
  217. package/src/template-builder.ts +110 -0
  218. package/src/template-validator.ts +574 -0
  219. package/src/utils/cache.ts +291 -0
  220. package/src/utils/diff.ts +269 -0
  221. package/src/utils/hash.ts +227 -0
  222. package/src/utils/index.ts +8 -0
  223. package/src/utils/parallel.ts +294 -0
  224. package/src/validators/credentials.test.ts +274 -0
  225. package/src/validators/credentials.ts +233 -0
  226. package/src/validators/quotas.test.ts +434 -0
  227. package/src/validators/quotas.ts +217 -0
  228. package/test/ai.test.ts +327 -0
  229. package/test/api.test.ts +511 -0
  230. package/test/auth.test.ts +632 -0
  231. package/test/cache.test.ts +406 -0
  232. package/test/cdn.test.ts +247 -0
  233. package/test/compute.test.ts +861 -0
  234. package/test/database.test.ts +523 -0
  235. package/test/deployment.test.ts +499 -0
  236. package/test/dns.test.ts +270 -0
  237. package/test/email.test.ts +439 -0
  238. package/test/filesystem.test.ts +382 -0
  239. package/test/integration.test.ts +350 -0
  240. package/test/messaging.test.ts +514 -0
  241. package/test/monitoring.test.ts +634 -0
  242. package/test/network.test.ts +425 -0
  243. package/test/permissions.test.ts +488 -0
  244. package/test/queue.test.ts +484 -0
  245. package/test/registry.test.ts +306 -0
  246. package/test/security.test.ts +462 -0
  247. package/test/storage.test.ts +463 -0
  248. package/test/template-validator.test.ts +559 -0
  249. package/test/workflow.test.ts +592 -0
  250. package/tsconfig.json +16 -0
  251. 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
+ }