From efd511ae90b4c9d40217e20949859d6a16d0fec9 Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Thu, 13 Aug 2026 10:26:23 +0100
Subject: [PATCH 1/2] feat(aws): add IAM to Cloud Explorer
---
README.md | 17 +
bun.lock | 59 ++-
packages/api/package.json | 1 +
.../api/src/adapter-aws/AwsIamAdapter.test.ts | 151 +++++++
packages/api/src/adapter-aws/AwsIamAdapter.ts | 133 +++++++
packages/api/src/adapter-aws/awsErrors.ts | 2 +
packages/api/src/aws.ts | 4 +
packages/api/src/cloud-spi/iamSchema.ts | 57 +++
packages/api/src/cloud-spi/serviceCatalog.ts | 1 +
packages/api/src/cloud-spi/types.ts | 2 +-
packages/api/src/cloudProxy.ts | 2 +
.../src/components/DynamicResourceView.tsx | 2 +
packages/frontend/src/types/resource.ts | 2 +-
pnpm-lock.yaml | 375 ++++++++++++++----
14 files changed, 736 insertions(+), 72 deletions(-)
create mode 100644 packages/api/src/adapter-aws/AwsIamAdapter.test.ts
create mode 100644 packages/api/src/adapter-aws/AwsIamAdapter.ts
create mode 100644 packages/api/src/cloud-spi/iamSchema.ts
diff --git a/README.md b/README.md
index b0b699e..3a68b0f 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,7 @@ cd packages/api && bun run scripts/service-matrix.ts
| Databases | Database | Yes (list, inspect) | Yes (list, create, delete, inspect) | Yes (list, create, inspect, delete) |
| Networking | Networking | Yes (list) | No | No |
| Provisioning | CloudFormation / Infrastructure as Code | No | No | No |
+| Security | Identity | Yes (list, create, delete, inspect) | No | No |
| Security | Secrets Manager / Key Vault | Yes (legacy page) | Yes (list, create, delete, inspect) | No |
Console Home is available for all three clouds.
@@ -181,6 +182,22 @@ Current gaps:
+
+Identity
+
+AWS only, through the generic identity service category.
+
+- List and inspect IAM users.
+- Create and delete IAM users.
+- IAM user paths are supported during creation.
+
+Current gaps:
+
+- Roles, groups, policies, access keys, and other advanced IAM workflows are not exposed yet.
+- No Azure or GCP identity adapter yet.
+
+
+
Serverless
diff --git a/bun.lock b/bun.lock
index 76e7b48..e38dae8 100644
--- a/bun.lock
+++ b/bun.lock
@@ -10,6 +10,7 @@
"dependencies": {
"@aws-sdk/client-ec2": "^3.1076.0",
"@aws-sdk/client-eks": "^3.1076.0",
+ "@aws-sdk/client-iam": "^3.1076.0",
"@aws-sdk/client-lambda": "^3.1076.0",
"@aws-sdk/client-rds": "^3.1076.0",
"@aws-sdk/client-s3": "^3.1076.0",
@@ -25,7 +26,7 @@
},
"packages/frontend": {
"name": "@floci/frontend",
- "version": "0.1.0",
+ "version": "0.2.0",
"dependencies": {
"@tanstack/react-query": "^5.101.2",
"@tanstack/react-query-devtools": "^5.101.2",
@@ -65,6 +66,8 @@
"@aws-sdk/client-eks": ["@aws-sdk/client-eks@3.1080.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/credential-provider-node": "^3.972.63", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-tN9O6f9vc9ns/uaPsN6vzEOrD7ftkWOMgzXoxY1J438WM2M8qcGbRJkhD2c+PvaZvZY3MVF7843mdIVrOA3zzQ=="],
+ "@aws-sdk/client-iam": ["@aws-sdk/client-iam@3.1096.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/credential-provider-node": "^3.972.73", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/fetch-http-handler": "^5.6.10", "@smithy/node-http-handler": "^4.9.10", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-39a+pXB0V9qWciaXsfPj4yhPNLBh69xiB6VG3auWCdj2PUW/oz1wjVOpuiH6mGUjWw8SMzVTcKrg0UaiUIPgXQ=="],
+
"@aws-sdk/client-lambda": ["@aws-sdk/client-lambda@3.1080.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/credential-provider-node": "^3.972.63", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-rTitNyvjgBrJzjDkzLRcLi1QcxSomomLRjhYXJWID97uaw1uxtmer8wOVi8l6UFxzlRVa83laZ3srAeWRs509A=="],
"@aws-sdk/client-rds": ["@aws-sdk/client-rds@3.1080.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/credential-provider-node": "^3.972.63", "@aws-sdk/middleware-sdk-rds": "^3.972.42", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-nkUu5ZN2TYTWslADZxMxNHMwl9NGdRGyWR+ZOPWL2MlDGbid3a2b7QAku6eaCoiDkWHonKbaHZYNtGoalkmyGA=="],
@@ -533,6 +536,20 @@
"zustand": ["zustand@5.0.14", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g=="],
+ "@aws-sdk/client-iam/@aws-sdk/core": ["@aws-sdk/core@3.977.1", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.29.8", "@smithy/signature-v4": "^5.6.9", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-KVtQRtc00ES/y+Sc3vYXeP6pCIcNlBJCZOwvqSy8ZpVGmbM5+IG+AfhuTKQ2oXmIVqZJewaGMMpzPkywC6xg0w=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.73", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.62", "@aws-sdk/credential-provider-http": "^3.972.64", "@aws-sdk/credential-provider-ini": "^3.973.7", "@aws-sdk/credential-provider-process": "^3.972.62", "@aws-sdk/credential-provider-sso": "^3.973.6", "@aws-sdk/credential-provider-web-identity": "^3.972.68", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/credential-provider-imds": "^4.4.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-VTzdbf8Ukjdb9yUubZzRI678CWZvKovhE8Nv3qihwhC187sRMGls+r9N8Wuht5q1xjKx2nmpS48ar8ppupjkCA=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
+
+ "@aws-sdk/client-iam/@smithy/core": ["@smithy/core@3.31.0", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-sylYk2l9d7CmRv8ts8p0SDQUr3VO+HMeS1nrjL6+UtbO8ktJHTOeQ1McX+aAyvGGccp5aZX9eNtdcXrSwzoZaw=="],
+
+ "@aws-sdk/client-iam/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.6.12", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-OpQgP6IGH4j0NJ2zjfYZLjQL85ai+Wi/q51EmZJovXsEwKSvu89qiXUq77Q6EmwZ/hSl7fKpn2Z9mhiDN6OM+Q=="],
+
+ "@aws-sdk/client-iam/@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.12", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-dWW5KRt4mnEvjNzbGqGeCuAvgum85Y9ZoyuMQqcTEfapndyVJ1k9BEHK7kdXJZ32enyRmmwcFjMwlB/KgLKI3Q=="],
+
+ "@aws-sdk/client-iam/@smithy/types": ["@smithy/types@4.16.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg=="],
+
"@babel/helper-compilation-targets/browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
@@ -543,6 +560,24 @@
"bun-types/@types/node": ["@types/node@20.19.39", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw=="],
+ "@aws-sdk/client-iam/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.6.11", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-7HsspeiNCZvZHEJ22vV5L/QYuJdTyJvPJvMrYD3AgkM3IJB0pkln4jkjPvtpTWRMkHXbO8WKwNjoVdVlBFwHmw=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.62", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-BkDrk2cNjed31IKin/Oksb2ziF+gfuyRskFVuT4EU9Mep7M8Y/d8DJG4+anHme4Vuse7CwaEscwEfGyR6mzBhQ=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.64", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/fetch-http-handler": "^5.6.10", "@smithy/node-http-handler": "^4.9.10", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-Wj1FGK2IxY5EccQCvH+niTYhIvDoDujJf2CpRRgS3NpYNEgiFNVItNbJYQjINRlu7fG7jSsXkKV0UWKriEplrw=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.7", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/credential-provider-env": "^3.972.62", "@aws-sdk/credential-provider-http": "^3.972.64", "@aws-sdk/credential-provider-login": "^3.972.69", "@aws-sdk/credential-provider-process": "^3.972.62", "@aws-sdk/credential-provider-sso": "^3.973.6", "@aws-sdk/credential-provider-web-identity": "^3.972.68", "@aws-sdk/nested-clients": "^3.997.36", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/credential-provider-imds": "^4.4.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-2CefB8cCxDu52P24B8Ay93/cTT199bcSvNHQ8e2f4BjSCF83yErBnTIZEBo0VeIgCfmw+PJKFUXnlQWxm2dkug=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.62", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zXYU9UWNL66gtMgNLhmxlrvEokuI7r6G2q7FRGu41Bya4iS30JLelUipJX9SV4zhyCPWJhI9Li54R1d9H8Tq6A=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.6", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/nested-clients": "^3.997.36", "@aws-sdk/token-providers": "3.1096.0", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-DobZggy3K49xdCpjeyMou0FQhkoYbluVGNydL6D+lcxF8GoAsttFX0xnH5GmiQ89We5dB6TRpW+CD/VowBH6HQ=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.68", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/nested-clients": "^3.997.36", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-bq+yTt+uWJx60VVp/OIAX5xqUAu/K2Uc3eknWnWl+KtfcU2CQe0uNw6lySrn2t5GKHq7jsV0Z63HiBGVtzr/lg=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.4.15", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-xYVGrisQqTJWhOnScUhbx8s9H63TMtoxzuUoxG6mP8J+B/YbX3vZxVsgV0xDf43abJnJP0fjP7BkQh7OESwuRA=="],
+
"@babel/helper-compilation-targets/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.10.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA=="],
"@babel/helper-compilation-targets/browserslist/caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="],
@@ -552,5 +587,27 @@
"@babel/helper-compilation-targets/browserslist/node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="],
"bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.69", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/nested-clients": "^3.997.36", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-gM3j0Ie9+FoLNTYODY+QWbg3vCRBc7mR9cRdntxTMkFYIrwfRmuucfavP6HNBlYSuaYww54TNJGej4GFgoPZAg=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.36", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/signature-v4-multi-region": "^3.996.42", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/fetch-http-handler": "^5.6.10", "@smithy/node-http-handler": "^4.9.10", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-b71Suv7L+DnhM0MsQHU4WO42I32kxLZi96PbVhZbxMYIoKnEZz3v+LSrG8fupAoA4cBSshCk1Dl/PeRz49qUSg=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.36", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/signature-v4-multi-region": "^3.996.42", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/fetch-http-handler": "^5.6.10", "@smithy/node-http-handler": "^4.9.10", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-b71Suv7L+DnhM0MsQHU4WO42I32kxLZi96PbVhZbxMYIoKnEZz3v+LSrG8fupAoA4cBSshCk1Dl/PeRz49qUSg=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1096.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/nested-clients": "^3.997.36", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-hdUS2hDppy3vkWeFl5y86RLNU6OWH2mQB09yOSsRefwhhGTSFPkaZvfLDD/9vFcvMzlr8QFQFw3fw2FtrurVQA=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.36", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/signature-v4-multi-region": "^3.996.42", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/fetch-http-handler": "^5.6.10", "@smithy/node-http-handler": "^4.9.10", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-b71Suv7L+DnhM0MsQHU4WO42I32kxLZi96PbVhZbxMYIoKnEZz3v+LSrG8fupAoA4cBSshCk1Dl/PeRz49qUSg=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.42", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.9", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-DBV4naZP6HYBlAvPpoQzOP12Wvfou/5rN8yJPXjBTBylU5qwCbh/tXr2MddHoIjgoRkEl/eS+IljiUqvmwey1Q=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.42", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.9", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-DBV4naZP6HYBlAvPpoQzOP12Wvfou/5rN8yJPXjBTBylU5qwCbh/tXr2MddHoIjgoRkEl/eS+IljiUqvmwey1Q=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.42", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.9", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-DBV4naZP6HYBlAvPpoQzOP12Wvfou/5rN8yJPXjBTBylU5qwCbh/tXr2MddHoIjgoRkEl/eS+IljiUqvmwey1Q=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.11", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-7HsspeiNCZvZHEJ22vV5L/QYuJdTyJvPJvMrYD3AgkM3IJB0pkln4jkjPvtpTWRMkHXbO8WKwNjoVdVlBFwHmw=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.11", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-7HsspeiNCZvZHEJ22vV5L/QYuJdTyJvPJvMrYD3AgkM3IJB0pkln4jkjPvtpTWRMkHXbO8WKwNjoVdVlBFwHmw=="],
+
+ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.11", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-7HsspeiNCZvZHEJ22vV5L/QYuJdTyJvPJvMrYD3AgkM3IJB0pkln4jkjPvtpTWRMkHXbO8WKwNjoVdVlBFwHmw=="],
}
}
diff --git a/packages/api/package.json b/packages/api/package.json
index 4c60859..5cc4d8a 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"@aws-sdk/client-ec2": "^3.1076.0",
"@aws-sdk/client-eks": "^3.1076.0",
+ "@aws-sdk/client-iam": "^3.1076.0",
"@aws-sdk/client-lambda": "^3.1076.0",
"@aws-sdk/client-rds": "^3.1076.0",
"@aws-sdk/client-s3": "^3.1076.0",
diff --git a/packages/api/src/adapter-aws/AwsIamAdapter.test.ts b/packages/api/src/adapter-aws/AwsIamAdapter.test.ts
new file mode 100644
index 0000000..6a752c7
--- /dev/null
+++ b/packages/api/src/adapter-aws/AwsIamAdapter.test.ts
@@ -0,0 +1,151 @@
+import {describe, expect, test} from 'bun:test'
+import {
+ CreateUserCommand,
+ DeleteUserCommand,
+ GetUserCommand,
+ ListUsersCommand,
+ type IAMClient,
+} from '@aws-sdk/client-iam'
+import {AwsIamAdapter} from './AwsIamAdapter'
+
+function fakeClient(handler: (command: unknown) => unknown): IAMClient {
+ return {
+ send: async (command: unknown) => handler(command),
+ } as unknown as IAMClient
+}
+
+const alice = {
+ UserName: 'alice',
+ UserId: 'AIDAALICE',
+ Arn: 'arn:aws:iam::000000000000:user/team/alice',
+ Path: '/team/',
+ CreateDate: new Date('2026-01-02T03:04:05.000Z'),
+}
+
+describe('AwsIamAdapter', () => {
+ test('lists every page and maps IAM users to normalized resources', async () => {
+ const adapter = new AwsIamAdapter(fakeClient((command) => {
+ if (!(command instanceof ListUsersCommand)) throw new Error('Unexpected command')
+ if (!command.input.Marker) return {Users: [alice], IsTruncated: true, Marker: 'next'}
+ return {Users: [{...alice, UserName: 'bob', UserId: 'AIDABOB'}], IsTruncated: false}
+ }))
+
+ const result = await adapter.list()
+
+ expect(result).toHaveLength(2)
+ expect(result[0]).toMatchObject({
+ id: 'alice',
+ name: 'alice',
+ cloud: 'aws',
+ service: 'identity',
+ type: 'iam-user',
+ region: null,
+ createdAt: '2026-01-02T03:04:05.000Z',
+ })
+ expect(result[0].metadata).toMatchObject({
+ identityService: 'iam',
+ userId: 'AIDAALICE',
+ path: '/team/',
+ })
+ })
+
+ test('filters users by search term', async () => {
+ const adapter = new AwsIamAdapter(fakeClient(() => ({
+ Users: [alice, {...alice, UserName: 'bob'}],
+ IsTruncated: false,
+ })))
+
+ const result = await adapter.list({search: 'ALI'})
+
+ expect(result.map((resource) => resource.name)).toEqual(['alice'])
+ })
+
+ test('gets and maps one IAM user', async () => {
+ const adapter = new AwsIamAdapter(fakeClient((command) => {
+ expect(command).toBeInstanceOf(GetUserCommand)
+ return {User: alice}
+ }))
+
+ const result = await adapter.get('alice')
+
+ expect(result?.id).toBe('alice')
+ expect(result?.metadata.arn).toBe(alice.Arn)
+ })
+
+ test('returns null when IAM reports a missing user', async () => {
+ const adapter = new AwsIamAdapter(fakeClient(() => {
+ throw Object.assign(new Error('missing'), {name: 'NoSuchEntityException'})
+ }))
+
+ await expect(adapter.get('missing')).resolves.toBeNull()
+ })
+
+ test('creates an IAM user with an optional path', async () => {
+ const adapter = new AwsIamAdapter(fakeClient((command) => {
+ expect(command).toBeInstanceOf(CreateUserCommand)
+ expect((command as CreateUserCommand).input).toEqual({UserName: 'alice', Path: '/team/'})
+ return {User: alice}
+ }))
+
+ const result = await adapter.create({values: {userName: 'alice', path: '/team/'}})
+
+ expect(result.id).toBe('alice')
+ })
+
+ test('rejects invalid user names before calling IAM', async () => {
+ let called = false
+ const adapter = new AwsIamAdapter(fakeClient(() => {
+ called = true
+ return {}
+ }))
+
+ await expect(adapter.create({values: {userName: 'not valid'}})).rejects.toThrow('Use a valid IAM user name')
+ await expect(adapter.create({values: {userName: 'alice', path: 'team'}})).rejects.toThrow('Use a valid IAM path')
+ expect(called).toBeFalse()
+ })
+
+ test('deletes the requested IAM user', async () => {
+ const adapter = new AwsIamAdapter(fakeClient((command) => {
+ expect(command).toBeInstanceOf(DeleteUserCommand)
+ expect((command as DeleteUserCommand).input.UserName).toBe('alice')
+ return {}
+ }))
+
+ await adapter.delete('alice')
+ })
+
+ test('reports a delete of a missing IAM user as not found', async () => {
+ const adapter = new AwsIamAdapter(fakeClient(() => {
+ throw Object.assign(new Error('The user with name alice cannot be found.'), {name: 'NoSuchEntityException'})
+ }))
+
+ await expect(adapter.delete('alice')).rejects.toMatchObject({
+ name: 'NotFoundError',
+ message: 'IAM user alice not found',
+ })
+ })
+
+ test('reports a delete blocked by attached resources as a conflict', async () => {
+ const adapter = new AwsIamAdapter(fakeClient(() => {
+ throw Object.assign(new Error('Cannot delete entity, must delete access keys first.'), {
+ name: 'DeleteConflictException',
+ })
+ }))
+
+ await expect(adapter.delete('alice')).rejects.toMatchObject({
+ name: 'ConflictError',
+ message: expect.stringContaining('IAM user alice still has attached resources'),
+ })
+ })
+
+ test('returns the AWS IAM schema', () => {
+ const adapter = new AwsIamAdapter(fakeClient(() => ({})))
+
+ expect(adapter.schema()).toMatchObject({
+ cloud: 'aws',
+ service: 'identity',
+ displayName: 'AWS IAM users',
+ actions: ['list', 'create', 'delete', 'inspect'],
+ })
+ })
+})
diff --git a/packages/api/src/adapter-aws/AwsIamAdapter.ts b/packages/api/src/adapter-aws/AwsIamAdapter.ts
new file mode 100644
index 0000000..5b8093d
--- /dev/null
+++ b/packages/api/src/adapter-aws/AwsIamAdapter.ts
@@ -0,0 +1,133 @@
+import {
+ CreateUserCommand,
+ DeleteUserCommand,
+ GetUserCommand,
+ ListUsersCommand,
+ type IAMClient,
+ type User,
+} from '@aws-sdk/client-iam'
+import {iam as defaultIam} from '../aws'
+import {
+ awsIamSchema,
+ IAM_PATH_MAX_LENGTH,
+ IAM_PATH_MESSAGE,
+ IAM_PATH_PATTERN,
+ IAM_USER_NAME_MESSAGE,
+ IAM_USER_NAME_PATTERN,
+} from '../cloud-spi/iamSchema'
+import {ConflictError, NotFoundError, ValidationError} from '../cloud-spi/errors'
+import type {CloudResource, CloudServiceAdapter, CreateResourceInput, ResourceQuery, ServiceSchema} from '../cloud-spi/types'
+
+const MAX_LIST_PAGES = 50
+
+export class AwsIamAdapter implements CloudServiceAdapter {
+ readonly cloud = 'aws' as const
+ readonly service = 'identity' as const
+
+ constructor(private readonly iam: IAMClient = defaultIam) {}
+
+ schema(): ServiceSchema {
+ return awsIamSchema()
+ }
+
+ async list(query: ResourceQuery = {}): Promise {
+ const users: User[] = []
+ let marker: string | undefined
+ let pages = 0
+
+ do {
+ const res = await this.iam.send(new ListUsersCommand({Marker: marker}))
+ users.push(...(res.Users ?? []))
+ marker = res.IsTruncated ? res.Marker : undefined
+ pages += 1
+ } while (marker && pages < MAX_LIST_PAGES)
+
+ return filterBySearch(users.map(toResource), query.search)
+ }
+
+ async get(id: string): Promise {
+ try {
+ const res = await this.iam.send(new GetUserCommand({UserName: id}))
+ return res.User ? toResource(res.User) : null
+ } catch (error) {
+ if (isNotFound(error)) return null
+ throw error
+ }
+ }
+
+ async create(input: CreateResourceInput): Promise {
+ const userName = stringValue(input.values.userName)
+ const path = stringValue(input.values.path)
+ if (!userName) throw new ValidationError('userName is required')
+ if (!new RegExp(IAM_USER_NAME_PATTERN).test(userName)) throw new ValidationError(IAM_USER_NAME_MESSAGE)
+ if (path && (path.length > IAM_PATH_MAX_LENGTH || !new RegExp(IAM_PATH_PATTERN).test(path))) {
+ throw new ValidationError(IAM_PATH_MESSAGE)
+ }
+
+ const res = await this.iam.send(new CreateUserCommand({
+ UserName: userName,
+ Path: path || undefined,
+ }))
+ if (!res.User) throw new Error('AWS IAM did not return the created user')
+ return toResource(res.User)
+ }
+
+ async delete(id: string): Promise {
+ try {
+ await this.iam.send(new DeleteUserCommand({UserName: id}))
+ } catch (error) {
+ if (isNotFound(error)) throw new NotFoundError(`IAM user ${id} not found`, {cause: error})
+ if (isDeleteConflict(error)) {
+ throw new ConflictError(
+ `IAM user ${id} still has attached resources. Remove group memberships, access keys, `
+ + 'certificates, MFA devices, and policies before deleting the user.',
+ {cause: error},
+ )
+ }
+ throw error
+ }
+ }
+}
+
+function toResource(user: User): CloudResource {
+ const userName = user.UserName ?? ''
+ return {
+ id: userName,
+ name: userName,
+ cloud: 'aws',
+ service: 'identity',
+ type: 'iam-user',
+ region: null,
+ createdAt: user.CreateDate?.toISOString() ?? null,
+ metadata: {
+ provider: 'aws',
+ identityService: 'iam',
+ userId: user.UserId,
+ arn: user.Arn,
+ path: user.Path,
+ permissionsBoundary: user.PermissionsBoundary,
+ tags: user.Tags,
+ },
+ }
+}
+
+function stringValue(value: unknown): string {
+ return typeof value === 'string' ? value.trim() : ''
+}
+
+function filterBySearch(resources: CloudResource[], search?: string): CloudResource[] {
+ const normalized = search?.trim().toLowerCase()
+ if (!normalized) return resources
+ return resources.filter((resource) => resource.name.toLowerCase().includes(normalized))
+}
+
+function isNotFound(error: unknown): boolean {
+ if (typeof error !== 'object' || error === null) return false
+ const candidate = error as {name?: string; $metadata?: {httpStatusCode?: number}}
+ return candidate.name === 'NoSuchEntityException' || candidate.$metadata?.httpStatusCode === 404
+}
+
+function isDeleteConflict(error: unknown): boolean {
+ if (typeof error !== 'object' || error === null) return false
+ return (error as {name?: string}).name === 'DeleteConflictException'
+}
diff --git a/packages/api/src/adapter-aws/awsErrors.ts b/packages/api/src/adapter-aws/awsErrors.ts
index 63e807f..1b02a9a 100644
--- a/packages/api/src/adapter-aws/awsErrors.ts
+++ b/packages/api/src/adapter-aws/awsErrors.ts
@@ -56,6 +56,7 @@ const CONFLICT_NAMES = new Set([
'DBInstanceAlreadyExistsFault',
'ConcurrentModificationException',
'IncorrectState',
+ 'DeleteConflictException',
])
const ACCESS_DENIED_NAMES = new Set([
@@ -94,6 +95,7 @@ const NOT_FOUND_NAMES = new Set([
'NoSuchBucket',
'NoSuchKey',
'NoSuchEntity',
+ 'NoSuchEntityException',
'NotFound',
'NotFoundException',
'ResourceNotFoundException',
diff --git a/packages/api/src/aws.ts b/packages/api/src/aws.ts
index e9ae0c9..f14e47d 100644
--- a/packages/api/src/aws.ts
+++ b/packages/api/src/aws.ts
@@ -4,6 +4,7 @@ import { EKSClient } from "@aws-sdk/client-eks";
import { EC2Client } from "@aws-sdk/client-ec2";
import { RDSClient } from "@aws-sdk/client-rds";
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
+import { IAMClient } from "@aws-sdk/client-iam";
const endpoint = process.env.FLOCI_ENDPOINT;
const region = process.env.AWS_REGION || "us-east-1";
@@ -39,6 +40,7 @@ export type AwsClients = {
ec2: EC2Client;
rds: RDSClient;
secretsManager: SecretsManagerClient;
+ iam: IAMClient;
};
export type AwsClientName = keyof AwsClients;
@@ -58,6 +60,7 @@ function buildClients(accountId: string): AwsClients {
ec2: new EC2Client(base),
rds: new RDSClient(base),
secretsManager: new SecretsManagerClient(base),
+ iam: new IAMClient(base),
};
}
@@ -89,3 +92,4 @@ export const eks = awsClients.eks;
export const ec2 = awsClients.ec2;
export const rds = awsClients.rds;
export const secretsManager = awsClients.secretsManager;
+export const iam = awsClients.iam;
diff --git a/packages/api/src/cloud-spi/iamSchema.ts b/packages/api/src/cloud-spi/iamSchema.ts
new file mode 100644
index 0000000..95cc4f7
--- /dev/null
+++ b/packages/api/src/cloud-spi/iamSchema.ts
@@ -0,0 +1,57 @@
+import type {CapabilitySchema, CloudProvider, ResourceActionName, ServiceSchema} from './types'
+
+export const IAM_USER_NAME_PATTERN = '^[A-Za-z0-9_+=,.@-]{1,64}$'
+export const IAM_USER_NAME_MESSAGE = 'Use a valid IAM user name: 1-64 letters, numbers, and +=,.@_- characters.'
+export const IAM_PATH_PATTERN = '^/(?:[!-~]+/)?$'
+export const IAM_PATH_MAX_LENGTH = 512
+export const IAM_PATH_MESSAGE = 'Use a valid IAM path: begin and end with / and use only printable ASCII characters.'
+
+const resourceActions: CapabilitySchema[] = [
+ {name: 'list', label: 'List users', enabled: true, status: 'available', runtimeRequired: true},
+ {name: 'create', label: 'Create user', enabled: true, status: 'available', runtimeRequired: true},
+ {name: 'delete', label: 'Delete user', enabled: true, status: 'available', runtimeRequired: true},
+ {name: 'inspect', label: 'Inspect user', enabled: true, status: 'available', runtimeRequired: false},
+]
+
+export function awsIamSchema(): ServiceSchema {
+ return {
+ cloud: 'aws',
+ service: 'identity',
+ displayName: 'AWS IAM users',
+ fields: [
+ {
+ name: 'userName',
+ label: 'User Name',
+ type: 'text',
+ required: true,
+ description: '1-64 letters, numbers, and +=,.@_- characters.',
+ validation: {
+ pattern: IAM_USER_NAME_PATTERN,
+ minLength: 1,
+ maxLength: 64,
+ message: IAM_USER_NAME_MESSAGE,
+ },
+ },
+ {
+ name: 'path',
+ label: 'Path',
+ type: 'text',
+ required: false,
+ description: 'Optional IAM path, beginning and ending with /. Defaults to /.',
+ validation: {
+ pattern: IAM_PATH_PATTERN,
+ maxLength: IAM_PATH_MAX_LENGTH,
+ message: IAM_PATH_MESSAGE,
+ },
+ },
+ ],
+ actions: ['list', 'create', 'delete', 'inspect'],
+ capabilities: {resourceActions},
+ filters: [{name: 'search', label: 'Search', type: 'text', required: false}],
+ columns: [
+ {name: 'name', label: 'User Name'},
+ {name: 'type', label: 'Type'},
+ {name: 'createdAt', label: 'Created At'},
+ ],
+ }
+}
diff --git a/packages/api/src/cloud-spi/serviceCatalog.ts b/packages/api/src/cloud-spi/serviceCatalog.ts
index 92df8a1..e08dc9b 100644
--- a/packages/api/src/cloud-spi/serviceCatalog.ts
+++ b/packages/api/src/cloud-spi/serviceCatalog.ts
@@ -73,6 +73,7 @@ export const SERVICE_CATALOG = {
storage: {displayName: 'Storage', iconKey: 'storage', group: 'Storage', order: 10},
database: {displayName: 'Database', iconKey: 'database', group: 'Databases', order: 10},
networking: {displayName: 'Networking', iconKey: 'networking', group: 'Networking', order: 10},
+ identity: {displayName: 'Identity', iconKey: 'iam', group: 'Security', order: 5},
secrets: {
displayName: 'Secrets Manager',
displayNameByCloud: {azure: 'Key Vault'},
diff --git a/packages/api/src/cloud-spi/types.ts b/packages/api/src/cloud-spi/types.ts
index 69ea0ba..8262696 100644
--- a/packages/api/src/cloud-spi/types.ts
+++ b/packages/api/src/cloud-spi/types.ts
@@ -152,7 +152,7 @@ export interface CloudResource {
name: string
cloud: CloudProvider
service: CloudServiceType
- type: 'bucket' | 'container' | 'cluster' | 'db-instance' | 'cosmos-database' | 'instance' | 'image' | 'vpc' | 'lambda' | 'azure-function' | 'gcp-function' | 'secret'
+ type: 'bucket' | 'container' | 'cluster' | 'db-instance' | 'cosmos-database' | 'instance' | 'image' | 'vpc' | 'lambda' | 'azure-function' | 'gcp-function' | 'secret' | 'iam-user'
region: string | null
createdAt: string | null
status?: string | null
diff --git a/packages/api/src/cloudProxy.ts b/packages/api/src/cloudProxy.ts
index 6240686..3f20f35 100644
--- a/packages/api/src/cloudProxy.ts
+++ b/packages/api/src/cloudProxy.ts
@@ -14,6 +14,7 @@ import {CloudProxyService} from './service/CloudProxyService'
import {AzureServerlessAdapter} from './adapter-azure/AzureServerlessAdapter'
import {AzureKeyVaultAdapter} from './adapter-azure/AzureKeyVaultAdapter'
import {AwsServerlessAdapter} from './adapter-aws/AwsServerlessAdapter'
+import {AwsIamAdapter} from './adapter-aws/AwsIamAdapter'
import {awsClientsForAccount, resolveAccountId} from './aws'
import {createEc2Service} from './services/ec2'
import {createEksService} from './services/eks'
@@ -39,6 +40,7 @@ export function createCloudAdapterRegistry(accountId?: string | null): CloudAdap
new AwsComputeAdapter(ec2Service),
new AwsNetworkingAdapter(ec2Service),
new AwsServerlessAdapter(clients.lambda),
+ new AwsIamAdapter(clients.iam),
new AzureStorageAdapter(),
new AzureDatabaseAdapter(),
new GcpStorageAdapter(),
diff --git a/packages/frontend/src/components/DynamicResourceView.tsx b/packages/frontend/src/components/DynamicResourceView.tsx
index 8770a45..0f7fd68 100644
--- a/packages/frontend/src/components/DynamicResourceView.tsx
+++ b/packages/frontend/src/components/DynamicResourceView.tsx
@@ -359,6 +359,8 @@ function resourceCreateLabel(schema: ServiceSchema): string {
return "Create container";
if (schema.cloud === "azure" && schema.service === "database")
return "Create database";
+ if (schema.cloud === "aws" && schema.service === "identity")
+ return "Create user";
if (schema.cloud === "azure" && schema.service === "secrets")
return "Create secret";
return "Create resource";
diff --git a/packages/frontend/src/types/resource.ts b/packages/frontend/src/types/resource.ts
index a69e1df..c63a056 100644
--- a/packages/frontend/src/types/resource.ts
+++ b/packages/frontend/src/types/resource.ts
@@ -5,7 +5,7 @@ export interface CloudResource {
name: string
cloud: CloudProvider
service: CloudServiceType
- type: 'bucket' | 'container' | 'cluster' | 'db-instance' | 'cosmos-database' | 'instance' | 'image' | 'vpc' | 'lambda' | "azure-function" | 'gcp-function' | 'secret';
+ type: 'bucket' | 'container' | 'cluster' | 'db-instance' | 'cosmos-database' | 'instance' | 'image' | 'vpc' | 'lambda' | "azure-function" | 'gcp-function' | 'secret' | 'iam-user';
region: string | null
createdAt: string | null
status?: string | null
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f04df5d..1673560 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -16,6 +16,9 @@ importers:
'@aws-sdk/client-eks':
specifier: ^3.1076.0
version: 3.1076.0
+ '@aws-sdk/client-iam':
+ specifier: ^3.1076.0
+ version: 3.1076.0
'@aws-sdk/client-lambda':
specifier: ^3.1076.0
version: 3.1076.0
@@ -157,6 +160,10 @@ packages:
resolution: {integrity: sha512-Hqr3hdwUgqWAQbdx6UDy81UBF+jdu3ws6RrykQ4YmBhe/XOKv57lt2lhkjgBuF9fiSw4UUl2XQteks9oxgm4XA==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/client-iam@3.1076.0':
+ resolution: {integrity: sha512-9joUyOTN8KZUTi2UIuvD+kyiYImM9E40cXwNYUvc2plV18OQ0GoXcBE3Xey6czc+rtsgvq6Grp3UHFGuxfZmoQ==}
+ engines: {node: '>=20.0.0'}
+
'@aws-sdk/client-lambda@3.1076.0':
resolution: {integrity: sha512-uueu0Do1dy3cm42AdjL3zivHoc5b55vOL3g6BjxUv2xMifSLKZi5Ilh5FhXVDdS/Uwxak/JH+vVQDDnEuQ52gA==}
engines: {node: '>=20.0.0'}
@@ -177,38 +184,70 @@ packages:
resolution: {integrity: sha512-4/1DtLwgLqzIg2uFzkFaFjMQHhhhwHIZN4PfziIVqXYX7koO78omuchQlLHyzQBw80l255dtmTA/J4W5yhb7zw==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/core@3.975.3':
+ resolution: {integrity: sha512-7ur3kCKuvPLqlsZ2XlvnNBVQ7KkpSu6Y6dOTwSPHLrFpTEfZM8isLBJc4cgv96WB7GifeVM436mpycwxBd2vEA==}
+ engines: {node: '>=20.0.0'}
+
'@aws-sdk/credential-provider-env@3.972.54':
resolution: {integrity: sha512-F4WQCG8GULIt+XrMHsqUM9dZc0eTwZM3HUWByOjIKOwBqJSzUxX8CFAtbgMvWfCua52FS1oi5FXarH3+khFq8A==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/credential-provider-env@3.972.59':
+ resolution: {integrity: sha512-Ny5e4Mfh3QPmiAc0AiUe+cbTXDlxkU3Rc+EpWOfyWeWEy6yp7Fa1KmfNeCc+1a8by9zQ9gtohmiQUkMPScF3ng==}
+ engines: {node: '>=20.0.0'}
+
'@aws-sdk/credential-provider-http@3.972.56':
resolution: {integrity: sha512-PiwHgEK2srdfi/lFveyQ+3w/qikyh6MKvuZAdlMC+dwQi4K5iVBr32oh7cugnYw+jA8iGtnQMhCGvLm/mqplmw==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/credential-provider-http@3.972.61':
+ resolution: {integrity: sha512-8jAjgStl5Ytq4+HF3X/9f+EmRinaRbGRRtQGktlPfBRVx73H+R1y48vIeXerQtYGFaUqkEp3fT6jP854rVO2yQ==}
+ engines: {node: '>=20.0.0'}
+
'@aws-sdk/credential-provider-ini@3.972.61':
resolution: {integrity: sha512-9CZWMjhBlgfKlqJk40R7kvMOLEIoOr3HJ1J/ELjAH9H5LzR4bCxLujPVM/1PKAEjzSZKZCxWOalm7JUGZbhQqw==}
engines: {node: '>=20.0.0'}
- '@aws-sdk/credential-provider-login@3.972.60':
- resolution: {integrity: sha512-Ak6OOrCbXvACyxLFIP1mcS+JTLS9ZpW1ZqyBtqu6axvdpsbG1gVNhUlAfQq8TK/gar/h2w35LrzlQU0PcUzJpw==}
+ '@aws-sdk/credential-provider-ini@3.973.4':
+ resolution: {integrity: sha512-e6ZvVsj90aRALf1kHP+J4iqC1496ZpVgqI/+u0LJ5HL7q7ATauGy4gdDvRCP13L1pN/fMiZLah162PGIYkbUVQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-login@3.972.66':
+ resolution: {integrity: sha512-g2fsqm87r/nKthLZ0VkkDBElkGg0PvSa8d97HQ6EilMbJTZ6hxa8FxkSZyJfgPfFdZn0TTmkOffQmTSUcAHIng==}
engines: {node: '>=20.0.0'}
'@aws-sdk/credential-provider-node@3.972.63':
resolution: {integrity: sha512-YmgWtTPZDStyT74ApSHpApD3r7W9znsc+WEZjW0vceom+NAxRx9/F3TyukOKix8kJkPaa49aIREQJdpGeLDiEw==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/credential-provider-node@3.972.70':
+ resolution: {integrity: sha512-3xzvkGdykBunxqh8WudmUpSyLWvIhfI6aBQo1b5rb3mDO5mNLadK+0hiI0qBQBMVynJbfLO+Ajy9dztMwy9O8w==}
+ engines: {node: '>=20.0.0'}
+
'@aws-sdk/credential-provider-process@3.972.54':
resolution: {integrity: sha512-UNmUjtTnp3wH3YTZcctN7yK17S2AdaJpiNIdIf0l70hHOdGuDfdMxk62P5rt64GwGm6qkFOYG0js/cU6cdZDdg==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/credential-provider-process@3.972.59':
+ resolution: {integrity: sha512-DlZF2/MhLlatDdlrIy3CUCpfdbLrKx+3SMjVo+WyHnPpwzkc/M3vwAHw4OVJf7DMvO+4vfRqSCMc/E9I1auN0g==}
+ engines: {node: '>=20.0.0'}
+
'@aws-sdk/credential-provider-sso@3.972.60':
resolution: {integrity: sha512-zH8SvJkTRw1Kb7GARjGPjzkQPfL3jDi/OxK32I5SQq7bgwgFHJZaoDEwkEvFlfNfpByM6n4Rs20Y1mLyOdb6ag==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/credential-provider-sso@3.973.3':
+ resolution: {integrity: sha512-hmdDHoy2G5Es2e8IgelNMYUuSQI6uCIAKZMJ2u2PdKDhxvbk1uWD/g4+R7R5c/tJfKEB1+KjjWiaoCr/S+ZTiQ==}
+ engines: {node: '>=20.0.0'}
+
'@aws-sdk/credential-provider-web-identity@3.972.60':
resolution: {integrity: sha512-q2rJSQ/AMjemUS18OtQqczqSo2R6VjOCLmLVmLVcdrP5/AKoj632lGCrMUWQ6EgnaLMEHbq0UO4mWh/u6gjPhA==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/credential-provider-web-identity@3.972.65':
+ resolution: {integrity: sha512-gHQb/Kt0chjk/JQDa/GJDqmAvEuVn8n7z10wK2h0LFM9TUDRkohgOO4aEF+s2sBLM0br7Cl5W6P7phgjrrJvLQ==}
+ engines: {node: '>=20.0.0'}
+
'@aws-sdk/middleware-flexible-checksums@3.974.38':
resolution: {integrity: sha512-grguw9ssx2zVIn8/KpgQ7AQLjBE/YPwOns94n0seWkNWS3dJ7k7VFD2voAkh9Xvf8Azxw8Ajc5+KicdClucMqQ==}
engines: {node: '>=20.0.0'}
@@ -225,22 +264,34 @@ packages:
resolution: {integrity: sha512-KCf/UjbnzV3QIXR1C2MeE9eRUG8EUXUf0V4y65hf2bKWQXzol5f3I8wOhE6OBdEYohn8zHfnC/cyy5+s7/HwLw==}
engines: {node: '>=20.0.0'}
- '@aws-sdk/nested-clients@3.997.28':
- resolution: {integrity: sha512-1oG/mM3jmE2M3kad2zWJS6IKIY8hjRV4l5kAgg+xTQdLMTtehhcSucL/y4WqQpcHmQwi6+gRK8E46GYl1NBW9Q==}
+ '@aws-sdk/nested-clients@3.997.33':
+ resolution: {integrity: sha512-dVZOroI/r3/ENvqNGgjMPul+jjlz9GddfVusgTXlVjfZj5isibOxecLkGQbRPp8XOuX+RAfjXLFgPkD1JS5xrw==}
engines: {node: '>=20.0.0'}
'@aws-sdk/signature-v4-multi-region@3.996.38':
resolution: {integrity: sha512-C379Sk+MiFZCfWZphKlMyLHKxV22OjoGM5KJjj5IJNJcOCWL4IGIpnEGzv1FQiRwhYXfq55SJMfxlqPE08JJ9g==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/signature-v4-multi-region@3.996.41':
+ resolution: {integrity: sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==}
+ engines: {node: '>=20.0.0'}
+
'@aws-sdk/token-providers@3.1080.0':
resolution: {integrity: sha512-8PufAQvncWXvdZUvODbuyXa8l3aszefEzwSMBUcgheNbZOmJMcNn388Ebt/piVrUHyxKN1MlxnR2OonzTyZaGw==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/token-providers@3.1088.0':
+ resolution: {integrity: sha512-4ObatWt2qpJg5FBk4LOOKrTQYzaqeewAtdO3r9ZO8lH9YqLtpTzLyIdy0mJ+nVdfYOnqISkKNfmzP22bNDhwyw==}
+ engines: {node: '>=20.0.0'}
+
'@aws-sdk/types@3.973.15':
resolution: {integrity: sha512-IULn8uBV/SMtmOIANsm4WHXIOtVPBWfOWs3WGL0j/sI+KhaYehvOw0ET+9urnn8MBpiijuU/0JOpuwKOE451PQ==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/types@3.974.2':
+ resolution: {integrity: sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==}
+ engines: {node: '>=20.0.0'}
+
'@aws-sdk/util-locate-window@3.965.8':
resolution: {integrity: sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==}
engines: {node: '>=20.0.0'}
@@ -249,6 +300,10 @@ packages:
resolution: {integrity: sha512-ezbwz9WpuLctm6o7P2t2naDhVVPI5jFGrVefVybhcKGjU57VIyT46pQVO0RI2RYkUdhdj2Z9uSIlAzGZE9NW9A==}
engines: {node: '>=20.0.0'}
+ '@aws-sdk/xml-builder@3.972.36':
+ resolution: {integrity: sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==}
+ engines: {node: '>=20.0.0'}
+
'@aws/lambda-invoke-store@0.3.0':
resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==}
engines: {node: '>=18.0.0'}
@@ -509,6 +564,14 @@ packages:
resolution: {integrity: sha512-qoiY4nrk5OCu1+eIR1VB8l5DmON/oKiqrd5zZFAhXJXjJlLWQusKEW/SkBDAtGDcPaz86m9kfcE1lngU0GlM6A==}
engines: {node: '>=18.0.0'}
+ '@smithy/core@3.29.5':
+ resolution: {integrity: sha512-i0dk2t5B+CwV/dcJdUHILYkOQF5lof8f44dFCfDWToGCxjT9YQ+CgHqTAvJxzc3+zqQwm2QtVoJ5IqiNar/CnQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/credential-provider-imds@4.4.10':
+ resolution: {integrity: sha512-MJenAe4OKRZUo1LdYYFDCsSHxaHvInIU/z52GsheO9vl1/VSySVCr0zkyKD6TFiGkSUaWGxvKZ/70OvgUZR5HQ==}
+ engines: {node: '>=18.0.0'}
+
'@smithy/credential-provider-imds@4.4.6':
resolution: {integrity: sha512-B2WQ/PV/H6Jeg3lrIq6bKUfa6Hy01mtK7CGs6lhjzHA6k4aagldH6T6eEjnzKl4HI0cJnAsxfJ19pgb5PV+CVQ==}
engines: {node: '>=18.0.0'}
@@ -517,6 +580,10 @@ packages:
resolution: {integrity: sha512-CwCc/7SMTj45y97MUnDTbTaxvtAsiNNRm81z3abROIuMbMsC2Iy5EKfkkVdsKrz8WExQAAMx1EJapq+9j4fFTQ==}
engines: {node: '>=18.0.0'}
+ '@smithy/fetch-http-handler@5.6.7':
+ resolution: {integrity: sha512-3zpg8yqqyXzoK2TsRDdkqVOj2RDBFfLXwCczOZ5c7TWB4eiaebfSCsbMjDPYB3PJ9ihV62QaeadZ+wLadZtNGA==}
+ engines: {node: '>=18.0.0'}
+
'@smithy/is-array-buffer@2.2.0':
resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
engines: {node: '>=14.0.0'}
@@ -525,14 +592,26 @@ packages:
resolution: {integrity: sha512-qZTa4gQFUo8RM02rk6q5UVTDLNrQ1oS20LsepBzqq1QBVc/EHJ03OOUADcqMZiXHArW+Y7+OGY0BpdTwZRq/Yg==}
engines: {node: '>=18.0.0'}
+ '@smithy/node-http-handler@4.9.7':
+ resolution: {integrity: sha512-wCU8HCLjAtAVqxxe0j2xff9LcEPw3yjBbg5IdQDIYFnxnPxbxcSLc7rgex7kqm9L/WYOnJEgaWQlfDkZleozMA==}
+ engines: {node: '>=18.0.0'}
+
'@smithy/signature-v4@5.6.2':
resolution: {integrity: sha512-QgHflghMoPxCJ9axiCVh8KZfbC9fuP6vkXXyK//E3cq7nLaSSyyLj0GAoqVWezYeDQmXIZhmlRvLE16jsqDK6g==}
engines: {node: '>=18.0.0'}
+ '@smithy/signature-v4@5.6.6':
+ resolution: {integrity: sha512-efP6DN3UTFrzIsGO42/xcabv8jU7+9nwEdphFUH7yL0k010ERyAWaO41KFQIDLcFZLZ8xzIQr4wplFxNzslSGQ==}
+ engines: {node: '>=18.0.0'}
+
'@smithy/types@4.15.1':
resolution: {integrity: sha512-x3L0XSACF6UYzKpa9biqiRMgvH5+wnFFew9Tm/grFYqgaupPwx/+ojDPpPJM8dZON3S9tjz5U+PQYsCBd1Mw5Q==}
engines: {node: '>=18.0.0'}
+ '@smithy/types@4.16.1':
+ resolution: {integrity: sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==}
+ engines: {node: '>=18.0.0'}
+
'@smithy/util-buffer-from@2.2.0':
resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
engines: {node: '>=14.0.0'}
@@ -1302,16 +1381,16 @@ snapshots:
'@aws-crypto/util@5.2.0':
dependencies:
- '@aws-sdk/types': 3.973.15
+ '@aws-sdk/types': 3.974.2
'@smithy/util-utf8': 2.3.0
tslib: 2.8.1
'@aws-sdk/checksums@3.1000.13':
dependencies:
- '@aws-sdk/core': 3.974.28
- '@aws-sdk/types': 3.973.15
- '@smithy/core': 3.29.1
- '@smithy/types': 4.15.1
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/client-ec2@3.1076.0':
@@ -1341,6 +1420,19 @@ snapshots:
'@smithy/types': 4.15.1
tslib: 2.8.1
+ '@aws-sdk/client-iam@3.1076.0':
+ dependencies:
+ '@aws-crypto/sha256-browser': 5.2.0
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/credential-provider-node': 3.972.70
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/fetch-http-handler': 5.6.7
+ '@smithy/node-http-handler': 4.9.7
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
'@aws-sdk/client-lambda@3.1076.0':
dependencies:
'@aws-crypto/sha256-browser': 5.2.0
@@ -1409,47 +1501,92 @@ snapshots:
bowser: 2.14.1
tslib: 2.8.1
+ '@aws-sdk/core@3.975.3':
+ dependencies:
+ '@aws-sdk/types': 3.974.2
+ '@aws-sdk/xml-builder': 3.972.36
+ '@aws/lambda-invoke-store': 0.3.0
+ '@smithy/core': 3.29.5
+ '@smithy/signature-v4': 5.6.6
+ '@smithy/types': 4.16.1
+ bowser: 2.14.1
+ tslib: 2.8.1
+
'@aws-sdk/credential-provider-env@3.972.54':
dependencies:
- '@aws-sdk/core': 3.974.28
- '@aws-sdk/types': 3.973.15
- '@smithy/core': 3.29.1
- '@smithy/types': 4.15.1
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-env@3.972.59':
+ dependencies:
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-http@3.972.56':
dependencies:
- '@aws-sdk/core': 3.974.28
- '@aws-sdk/types': 3.973.15
- '@smithy/core': 3.29.1
- '@smithy/fetch-http-handler': 5.6.3
- '@smithy/node-http-handler': 4.9.3
- '@smithy/types': 4.15.1
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/fetch-http-handler': 5.6.7
+ '@smithy/node-http-handler': 4.9.7
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-http@3.972.61':
+ dependencies:
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/fetch-http-handler': 5.6.7
+ '@smithy/node-http-handler': 4.9.7
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-ini@3.972.61':
dependencies:
- '@aws-sdk/core': 3.974.28
- '@aws-sdk/credential-provider-env': 3.972.54
- '@aws-sdk/credential-provider-http': 3.972.56
- '@aws-sdk/credential-provider-login': 3.972.60
- '@aws-sdk/credential-provider-process': 3.972.54
- '@aws-sdk/credential-provider-sso': 3.972.60
- '@aws-sdk/credential-provider-web-identity': 3.972.60
- '@aws-sdk/nested-clients': 3.997.28
- '@aws-sdk/types': 3.973.15
- '@smithy/core': 3.29.1
- '@smithy/credential-provider-imds': 4.4.6
- '@smithy/types': 4.15.1
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/credential-provider-env': 3.972.59
+ '@aws-sdk/credential-provider-http': 3.972.61
+ '@aws-sdk/credential-provider-login': 3.972.66
+ '@aws-sdk/credential-provider-process': 3.972.59
+ '@aws-sdk/credential-provider-sso': 3.973.3
+ '@aws-sdk/credential-provider-web-identity': 3.972.65
+ '@aws-sdk/nested-clients': 3.997.33
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/credential-provider-imds': 4.4.10
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-ini@3.973.4':
+ dependencies:
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/credential-provider-env': 3.972.59
+ '@aws-sdk/credential-provider-http': 3.972.61
+ '@aws-sdk/credential-provider-login': 3.972.66
+ '@aws-sdk/credential-provider-process': 3.972.59
+ '@aws-sdk/credential-provider-sso': 3.973.3
+ '@aws-sdk/credential-provider-web-identity': 3.972.65
+ '@aws-sdk/nested-clients': 3.997.33
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/credential-provider-imds': 4.4.10
+ '@smithy/types': 4.16.1
tslib: 2.8.1
- '@aws-sdk/credential-provider-login@3.972.60':
+ '@aws-sdk/credential-provider-login@3.972.66':
dependencies:
- '@aws-sdk/core': 3.974.28
- '@aws-sdk/nested-clients': 3.997.28
- '@aws-sdk/types': 3.973.15
- '@smithy/core': 3.29.1
- '@smithy/types': 4.15.1
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/nested-clients': 3.997.33
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-node@3.972.63':
@@ -1466,31 +1603,72 @@ snapshots:
'@smithy/types': 4.15.1
tslib: 2.8.1
+ '@aws-sdk/credential-provider-node@3.972.70':
+ dependencies:
+ '@aws-sdk/credential-provider-env': 3.972.59
+ '@aws-sdk/credential-provider-http': 3.972.61
+ '@aws-sdk/credential-provider-ini': 3.973.4
+ '@aws-sdk/credential-provider-process': 3.972.59
+ '@aws-sdk/credential-provider-sso': 3.973.3
+ '@aws-sdk/credential-provider-web-identity': 3.972.65
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/credential-provider-imds': 4.4.10
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
'@aws-sdk/credential-provider-process@3.972.54':
dependencies:
- '@aws-sdk/core': 3.974.28
- '@aws-sdk/types': 3.973.15
- '@smithy/core': 3.29.1
- '@smithy/types': 4.15.1
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-process@3.972.59':
+ dependencies:
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-sso@3.972.60':
dependencies:
- '@aws-sdk/core': 3.974.28
- '@aws-sdk/nested-clients': 3.997.28
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/nested-clients': 3.997.33
'@aws-sdk/token-providers': 3.1080.0
- '@aws-sdk/types': 3.973.15
- '@smithy/core': 3.29.1
- '@smithy/types': 4.15.1
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-sso@3.973.3':
+ dependencies:
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/nested-clients': 3.997.33
+ '@aws-sdk/token-providers': 3.1088.0
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/credential-provider-web-identity@3.972.60':
dependencies:
- '@aws-sdk/core': 3.974.28
- '@aws-sdk/nested-clients': 3.997.28
- '@aws-sdk/types': 3.973.15
- '@smithy/core': 3.29.1
- '@smithy/types': 4.15.1
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/nested-clients': 3.997.33
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-web-identity@3.972.65':
+ dependencies:
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/nested-clients': 3.997.33
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/middleware-flexible-checksums@3.974.38':
@@ -1525,15 +1703,15 @@ snapshots:
'@smithy/types': 4.15.1
tslib: 2.8.1
- '@aws-sdk/nested-clients@3.997.28':
+ '@aws-sdk/nested-clients@3.997.33':
dependencies:
- '@aws-sdk/core': 3.974.28
- '@aws-sdk/signature-v4-multi-region': 3.996.38
- '@aws-sdk/types': 3.973.15
- '@smithy/core': 3.29.1
- '@smithy/fetch-http-handler': 5.6.3
- '@smithy/node-http-handler': 4.9.3
- '@smithy/types': 4.15.1
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/signature-v4-multi-region': 3.996.41
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/fetch-http-handler': 5.6.7
+ '@smithy/node-http-handler': 4.9.7
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/signature-v4-multi-region@3.996.38':
@@ -1543,13 +1721,29 @@ snapshots:
'@smithy/types': 4.15.1
tslib: 2.8.1
+ '@aws-sdk/signature-v4-multi-region@3.996.41':
+ dependencies:
+ '@aws-sdk/types': 3.974.2
+ '@smithy/signature-v4': 5.6.6
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
'@aws-sdk/token-providers@3.1080.0':
dependencies:
- '@aws-sdk/core': 3.974.28
- '@aws-sdk/nested-clients': 3.997.28
- '@aws-sdk/types': 3.973.15
- '@smithy/core': 3.29.1
- '@smithy/types': 4.15.1
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/nested-clients': 3.997.33
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/token-providers@3.1088.0':
+ dependencies:
+ '@aws-sdk/core': 3.975.3
+ '@aws-sdk/nested-clients': 3.997.33
+ '@aws-sdk/types': 3.974.2
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@aws-sdk/types@3.973.15':
@@ -1557,13 +1751,23 @@ snapshots:
'@smithy/types': 4.15.1
tslib: 2.8.1
+ '@aws-sdk/types@3.974.2':
+ dependencies:
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
'@aws-sdk/util-locate-window@3.965.8':
dependencies:
tslib: 2.8.1
'@aws-sdk/xml-builder@3.972.33':
dependencies:
- '@smithy/types': 4.15.1
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@aws-sdk/xml-builder@3.972.36':
+ dependencies:
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@aws/lambda-invoke-store@0.3.0': {}
@@ -1818,10 +2022,21 @@ snapshots:
'@smithy/types': 4.15.1
tslib: 2.8.1
+ '@smithy/core@3.29.5':
+ dependencies:
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@smithy/credential-provider-imds@4.4.10':
+ dependencies:
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
'@smithy/credential-provider-imds@4.4.6':
dependencies:
- '@smithy/core': 3.29.1
- '@smithy/types': 4.15.1
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@smithy/fetch-http-handler@5.6.3':
@@ -1830,6 +2045,12 @@ snapshots:
'@smithy/types': 4.15.1
tslib: 2.8.1
+ '@smithy/fetch-http-handler@5.6.7':
+ dependencies:
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
'@smithy/is-array-buffer@2.2.0':
dependencies:
tslib: 2.8.1
@@ -1840,16 +2061,32 @@ snapshots:
'@smithy/types': 4.15.1
tslib: 2.8.1
+ '@smithy/node-http-handler@4.9.7':
+ dependencies:
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
'@smithy/signature-v4@5.6.2':
dependencies:
- '@smithy/core': 3.29.1
- '@smithy/types': 4.15.1
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
+ tslib: 2.8.1
+
+ '@smithy/signature-v4@5.6.6':
+ dependencies:
+ '@smithy/core': 3.29.5
+ '@smithy/types': 4.16.1
tslib: 2.8.1
'@smithy/types@4.15.1':
dependencies:
tslib: 2.8.1
+ '@smithy/types@4.16.1':
+ dependencies:
+ tslib: 2.8.1
+
'@smithy/util-buffer-from@2.2.0':
dependencies:
'@smithy/is-array-buffer': 2.2.0
From 35edfb92539da83f41c05299e71ed9aeec7b0d27 Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Tue, 18 Aug 2026 06:36:15 +0100
Subject: [PATCH 2/2] fix(iam): type empty create responses
---
packages/api/src/adapter-aws/AwsIamAdapter.test.ts | 9 +++++++++
packages/api/src/adapter-aws/AwsIamAdapter.ts | 4 ++--
packages/api/src/cloud-spi/iamSchema.ts | 2 +-
3 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/packages/api/src/adapter-aws/AwsIamAdapter.test.ts b/packages/api/src/adapter-aws/AwsIamAdapter.test.ts
index 6a752c7..595c1c8 100644
--- a/packages/api/src/adapter-aws/AwsIamAdapter.test.ts
+++ b/packages/api/src/adapter-aws/AwsIamAdapter.test.ts
@@ -92,6 +92,15 @@ describe('AwsIamAdapter', () => {
expect(result.id).toBe('alice')
})
+ test('reports an empty create response as a runtime error', async () => {
+ const adapter = new AwsIamAdapter(fakeClient(() => ({})))
+
+ await expect(adapter.create({values: {userName: 'alice'}})).rejects.toMatchObject({
+ name: 'RuntimeError',
+ message: 'AWS IAM did not return the created user',
+ })
+ })
+
test('rejects invalid user names before calling IAM', async () => {
let called = false
const adapter = new AwsIamAdapter(fakeClient(() => {
diff --git a/packages/api/src/adapter-aws/AwsIamAdapter.ts b/packages/api/src/adapter-aws/AwsIamAdapter.ts
index 5b8093d..faef1fe 100644
--- a/packages/api/src/adapter-aws/AwsIamAdapter.ts
+++ b/packages/api/src/adapter-aws/AwsIamAdapter.ts
@@ -15,7 +15,7 @@ import {
IAM_USER_NAME_MESSAGE,
IAM_USER_NAME_PATTERN,
} from '../cloud-spi/iamSchema'
-import {ConflictError, NotFoundError, ValidationError} from '../cloud-spi/errors'
+import {ConflictError, NotFoundError, RuntimeError, ValidationError} from '../cloud-spi/errors'
import type {CloudResource, CloudServiceAdapter, CreateResourceInput, ResourceQuery, ServiceSchema} from '../cloud-spi/types'
const MAX_LIST_PAGES = 50
@@ -68,7 +68,7 @@ export class AwsIamAdapter implements CloudServiceAdapter {
UserName: userName,
Path: path || undefined,
}))
- if (!res.User) throw new Error('AWS IAM did not return the created user')
+ if (!res.User) throw new RuntimeError('AWS IAM did not return the created user')
return toResource(res.User)
}
diff --git a/packages/api/src/cloud-spi/iamSchema.ts b/packages/api/src/cloud-spi/iamSchema.ts
index 95cc4f7..0847013 100644
--- a/packages/api/src/cloud-spi/iamSchema.ts
+++ b/packages/api/src/cloud-spi/iamSchema.ts
@@ -1,4 +1,4 @@
-import type {CapabilitySchema, CloudProvider, ResourceActionName, ServiceSchema} from './types'
+import type {CapabilitySchema, ResourceActionName, ServiceSchema} from './types'
export const IAM_USER_NAME_PATTERN = '^[A-Za-z0-9_+=,.@-]{1,64}$'
export const IAM_USER_NAME_MESSAGE = 'Use a valid IAM user name: 1-64 letters, numbers, and +=,.@_- characters.'