From e4e775234d5ec04e4a61e04d973baf84d8099698 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 21 Apr 2026 12:27:21 -0700 Subject: [PATCH 01/12] feat(integrations): add AWS SES, IAM Identity Center, and enhanced IAM/STS/CloudWatch/DynamoDB integrations - Add AWS SES v2 integration with 9 operations (send email, templated, bulk, templates, account) - Add AWS IAM Identity Center integration with 12 operations (account assignments, permission sets, users, groups) - Add 3 new IAM tools: list-attached-role-policies, list-attached-user-policies, simulate-principal-policy - Fix DynamoDB duplicate subBlock IDs, add operation-scoped field names, add subblock migrations - Add authMode: AuthMode.ApiKey to DynamoDB block - Fix CloudWatch routes: toError, client.destroy(), withRouteHandler, auth outside try - Fix STS/DynamoDB/IAM routes: nullable Zod schemas, withRouteHandler adoption - Fix Identity Center: list_instances pagination, list_groups instanceArn condition - Add subblock migrations for renamed DynamoDB fields (key, filterExpression, etc.) - Apply withRouteHandler to all new and existing AWS tool routes --- apps/docs/components/icons.tsx | 29 + apps/docs/components/ui/icon-mapping.ts | 4 + .../docs/content/docs/en/tools/cloudwatch.mdx | 46 +- apps/docs/content/docs/en/tools/dynamodb.mdx | 40 +- apps/docs/content/docs/en/tools/iam.mdx | 78 +- .../content/docs/en/tools/identity_center.mdx | 340 +++++++++ apps/docs/content/docs/en/tools/meta.json | 2 + apps/docs/content/docs/en/tools/ses.mdx | 226 ++++++ apps/docs/content/docs/en/tools/sts.mdx | 2 + .../content/docs/en/triggers/fireflies.mdx | 3 +- apps/docs/content/docs/en/triggers/jsm.mdx | 2 +- apps/docs/content/docs/en/triggers/slack.mdx | 1 + .../integrations/data/icon-mapping.ts | 4 + .../integrations/data/integrations.json | 138 +++- .../tools/cloudwatch/describe-alarms/route.ts | 95 +-- .../cloudwatch/describe-log-groups/route.ts | 51 +- .../cloudwatch/describe-log-streams/route.ts | 35 +- .../tools/cloudwatch/get-log-events/route.ts | 49 +- .../cloudwatch/get-metric-statistics/route.ts | 111 +-- .../tools/cloudwatch/list-metrics/route.ts | 58 +- .../tools/cloudwatch/put-metric-data/route.ts | 79 ++- .../api/tools/cloudwatch/query-logs/route.ts | 61 +- apps/sim/app/api/tools/cloudwatch/utils.ts | 2 +- .../app/api/tools/dynamodb/delete/route.ts | 35 +- apps/sim/app/api/tools/dynamodb/get/route.ts | 36 +- .../api/tools/dynamodb/introspect/route.ts | 18 +- apps/sim/app/api/tools/dynamodb/put/route.ts | 33 +- .../sim/app/api/tools/dynamodb/query/route.ts | 57 +- apps/sim/app/api/tools/dynamodb/scan/route.ts | 45 +- .../app/api/tools/dynamodb/update/route.ts | 48 +- apps/sim/app/api/tools/dynamodb/utils.ts | 48 +- .../api/tools/iam/add-user-to-group/route.ts | 4 +- .../api/tools/iam/attach-role-policy/route.ts | 4 +- .../api/tools/iam/attach-user-policy/route.ts | 4 +- .../api/tools/iam/create-access-key/route.ts | 6 +- .../app/api/tools/iam/create-role/route.ts | 10 +- .../app/api/tools/iam/create-user/route.ts | 6 +- .../api/tools/iam/delete-access-key/route.ts | 6 +- .../app/api/tools/iam/delete-role/route.ts | 4 +- .../app/api/tools/iam/delete-user/route.ts | 4 +- .../api/tools/iam/detach-role-policy/route.ts | 4 +- .../api/tools/iam/detach-user-policy/route.ts | 4 +- apps/sim/app/api/tools/iam/get-role/route.ts | 7 +- apps/sim/app/api/tools/iam/get-user/route.ts | 11 +- .../iam/list-attached-role-policies/route.ts | 71 ++ .../iam/list-attached-user-policies/route.ts | 71 ++ .../app/api/tools/iam/list-groups/route.ts | 10 +- .../app/api/tools/iam/list-policies/route.ts | 14 +- .../sim/app/api/tools/iam/list-roles/route.ts | 10 +- .../sim/app/api/tools/iam/list-users/route.ts | 10 +- .../tools/iam/remove-user-from-group/route.ts | 4 +- .../iam/simulate-principal-policy/route.ts | 73 ++ apps/sim/app/api/tools/iam/utils.ts | 119 +++- .../check-assignment-deletion-status/route.ts | 60 ++ .../check-assignment-status/route.ts | 60 ++ .../create-account-assignment/route.ts | 80 +++ .../delete-account-assignment/route.ts | 80 +++ .../identity-center/describe-account/route.ts | 52 ++ .../tools/identity-center/get-group/route.ts | 55 ++ .../tools/identity-center/get-user/route.ts | 53 ++ .../list-account-assignments/route.ts | 63 ++ .../identity-center/list-accounts/route.ts | 53 ++ .../identity-center/list-groups/route.ts | 59 ++ .../identity-center/list-instances/route.ts | 53 ++ .../list-permission-sets/route.ts | 59 ++ .../app/api/tools/identity-center/utils.ts | 304 ++++++++ .../api/tools/ses/create-template/route.ts | 69 ++ .../api/tools/ses/delete-template/route.ts | 61 ++ .../app/api/tools/ses/get-account/route.ts | 60 ++ .../app/api/tools/ses/get-template/route.ts | 61 ++ .../api/tools/ses/list-identities/route.ts | 65 ++ .../app/api/tools/ses/list-templates/route.ts | 65 ++ .../api/tools/ses/send-bulk-email/route.ts | 91 +++ .../sim/app/api/tools/ses/send-email/route.ts | 104 +++ .../tools/ses/send-templated-email/route.ts | 92 +++ apps/sim/app/api/tools/ses/utils.ts | 253 +++++++ .../app/api/tools/sts/assume-role/route.ts | 20 +- .../tools/sts/get-access-key-info/route.ts | 15 +- .../tools/sts/get-caller-identity/route.ts | 15 +- .../api/tools/sts/get-session-token/route.ts | 15 +- apps/sim/app/api/tools/sts/utils.ts | 3 + apps/sim/blocks/blocks/dynamodb.ts | 380 +++++++--- apps/sim/blocks/blocks/iam.ts | 122 +++- apps/sim/blocks/blocks/identity_center.ts | 438 ++++++++++++ apps/sim/blocks/blocks/ses.ts | 499 +++++++++++++ apps/sim/blocks/blocks/sts.ts | 43 +- apps/sim/blocks/registry.ts | 4 + apps/sim/components/icons.tsx | 29 + .../migrations/subblock-migrations.ts | 8 + apps/sim/package.json | 4 + apps/sim/tools/cloudwatch/describe_alarms.ts | 19 + .../tools/cloudwatch/describe_log_groups.ts | 15 +- .../tools/cloudwatch/describe_log_streams.ts | 22 +- apps/sim/tools/cloudwatch/get_log_events.ts | 8 + .../tools/cloudwatch/get_metric_statistics.ts | 19 +- apps/sim/tools/cloudwatch/list_metrics.ts | 13 +- apps/sim/tools/cloudwatch/query_logs.ts | 20 +- apps/sim/tools/dynamodb/delete.ts | 24 +- apps/sim/tools/dynamodb/get.ts | 6 +- apps/sim/tools/dynamodb/index.ts | 2 + apps/sim/tools/dynamodb/introspect.ts | 5 +- apps/sim/tools/dynamodb/put.ts | 34 +- apps/sim/tools/dynamodb/query.ts | 29 +- apps/sim/tools/dynamodb/scan.ts | 21 +- apps/sim/tools/dynamodb/types.ts | 9 + apps/sim/tools/dynamodb/update.ts | 10 +- apps/sim/tools/iam/get_user.ts | 6 +- apps/sim/tools/iam/index.ts | 8 + .../tools/iam/list_attached_role_policies.ts | 108 +++ .../tools/iam/list_attached_user_policies.ts | 108 +++ .../tools/iam/simulate_principal_policy.ts | 120 ++++ apps/sim/tools/iam/types.ts | 53 +- .../check_assignment_deletion_status.ts | 105 +++ .../check_assignment_status.ts | 105 +++ .../create_account_assignment.ts | 130 ++++ .../delete_account_assignment.ts | 130 ++++ .../tools/identity_center/describe_account.ts | 85 +++ apps/sim/tools/identity_center/get_group.ts | 82 +++ apps/sim/tools/identity_center/get_user.ts | 81 +++ apps/sim/tools/identity_center/index.ts | 13 + .../list_account_assignments.ts | 111 +++ .../tools/identity_center/list_accounts.ts | 89 +++ apps/sim/tools/identity_center/list_groups.ts | 96 +++ .../tools/identity_center/list_instances.ts | 90 +++ .../identity_center/list_permission_sets.ts | 97 +++ apps/sim/tools/identity_center/types.ts | 201 ++++++ apps/sim/tools/registry.ts | 52 ++ apps/sim/tools/ses/create_template.ts | 88 +++ apps/sim/tools/ses/delete_template.ts | 67 ++ apps/sim/tools/ses/get_account.ts | 69 ++ apps/sim/tools/ses/get_template.ts | 81 +++ apps/sim/tools/ses/index.ts | 21 + apps/sim/tools/ses/list_identities.ts | 87 +++ apps/sim/tools/ses/list_templates.ts | 85 +++ apps/sim/tools/ses/send_bulk_email.ts | 103 +++ apps/sim/tools/ses/send_email.ts | 123 ++++ apps/sim/tools/ses/send_templated_email.ts | 112 +++ apps/sim/tools/ses/types.ts | 146 ++++ apps/sim/tools/sts/assume_role.ts | 15 +- apps/sim/tools/sts/get_session_token.ts | 6 +- apps/sim/tools/sts/index.ts | 2 + apps/sim/tools/sts/types.ts | 2 + bun.lock | 670 +++++++++++++++++- 143 files changed, 8977 insertions(+), 605 deletions(-) create mode 100644 apps/docs/content/docs/en/tools/identity_center.mdx create mode 100644 apps/docs/content/docs/en/tools/ses.mdx create mode 100644 apps/sim/app/api/tools/iam/list-attached-role-policies/route.ts create mode 100644 apps/sim/app/api/tools/iam/list-attached-user-policies/route.ts create mode 100644 apps/sim/app/api/tools/iam/simulate-principal-policy/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/check-assignment-deletion-status/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/check-assignment-status/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/create-account-assignment/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/delete-account-assignment/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/describe-account/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/get-group/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/get-user/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/list-account-assignments/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/list-accounts/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/list-groups/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/list-instances/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/list-permission-sets/route.ts create mode 100644 apps/sim/app/api/tools/identity-center/utils.ts create mode 100644 apps/sim/app/api/tools/ses/create-template/route.ts create mode 100644 apps/sim/app/api/tools/ses/delete-template/route.ts create mode 100644 apps/sim/app/api/tools/ses/get-account/route.ts create mode 100644 apps/sim/app/api/tools/ses/get-template/route.ts create mode 100644 apps/sim/app/api/tools/ses/list-identities/route.ts create mode 100644 apps/sim/app/api/tools/ses/list-templates/route.ts create mode 100644 apps/sim/app/api/tools/ses/send-bulk-email/route.ts create mode 100644 apps/sim/app/api/tools/ses/send-email/route.ts create mode 100644 apps/sim/app/api/tools/ses/send-templated-email/route.ts create mode 100644 apps/sim/app/api/tools/ses/utils.ts create mode 100644 apps/sim/blocks/blocks/identity_center.ts create mode 100644 apps/sim/blocks/blocks/ses.ts create mode 100644 apps/sim/tools/iam/list_attached_role_policies.ts create mode 100644 apps/sim/tools/iam/list_attached_user_policies.ts create mode 100644 apps/sim/tools/iam/simulate_principal_policy.ts create mode 100644 apps/sim/tools/identity_center/check_assignment_deletion_status.ts create mode 100644 apps/sim/tools/identity_center/check_assignment_status.ts create mode 100644 apps/sim/tools/identity_center/create_account_assignment.ts create mode 100644 apps/sim/tools/identity_center/delete_account_assignment.ts create mode 100644 apps/sim/tools/identity_center/describe_account.ts create mode 100644 apps/sim/tools/identity_center/get_group.ts create mode 100644 apps/sim/tools/identity_center/get_user.ts create mode 100644 apps/sim/tools/identity_center/index.ts create mode 100644 apps/sim/tools/identity_center/list_account_assignments.ts create mode 100644 apps/sim/tools/identity_center/list_accounts.ts create mode 100644 apps/sim/tools/identity_center/list_groups.ts create mode 100644 apps/sim/tools/identity_center/list_instances.ts create mode 100644 apps/sim/tools/identity_center/list_permission_sets.ts create mode 100644 apps/sim/tools/identity_center/types.ts create mode 100644 apps/sim/tools/ses/create_template.ts create mode 100644 apps/sim/tools/ses/delete_template.ts create mode 100644 apps/sim/tools/ses/get_account.ts create mode 100644 apps/sim/tools/ses/get_template.ts create mode 100644 apps/sim/tools/ses/index.ts create mode 100644 apps/sim/tools/ses/list_identities.ts create mode 100644 apps/sim/tools/ses/list_templates.ts create mode 100644 apps/sim/tools/ses/send_bulk_email.ts create mode 100644 apps/sim/tools/ses/send_email.ts create mode 100644 apps/sim/tools/ses/send_templated_email.ts create mode 100644 apps/sim/tools/ses/types.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 208cec09b42..061e0c0cdf5 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4681,6 +4681,17 @@ export function IAMIcon(props: SVGProps) { ) } +export function IdentityCenterIcon(props: SVGProps) { + return ( + + + + ) +} + export function STSIcon(props: SVGProps) { return ( @@ -4699,6 +4710,24 @@ export function STSIcon(props: SVGProps) { ) } +export function SESIcon(props: SVGProps) { + return ( + + + + + + + + + + + ) +} + export function SecretsManagerIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 66570ec3af3..13061ba7819 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -91,6 +91,7 @@ import { HuggingFaceIcon, HunterIOIcon, IAMIcon, + IdentityCenterIcon, ImageIcon, IncidentioIcon, InfisicalIcon, @@ -152,6 +153,7 @@ import { RootlyIcon, S3Icon, SalesforceIcon, + SESIcon, SearchIcon, SecretsManagerIcon, SendgridIcon, @@ -294,6 +296,7 @@ export const blockTypeToIconMap: Record = { huggingface: HuggingFaceIcon, hunter: HunterIOIcon, iam: IAMIcon, + identity_center: IdentityCenterIcon, image_generator: ImageIcon, imap: MailServerIcon, incidentio: IncidentioIcon, @@ -370,6 +373,7 @@ export const blockTypeToIconMap: Record = { sentry: SentryIcon, serper: SerperIcon, servicenow: ServiceNowIcon, + ses: SESIcon, sftp: SftpIcon, sharepoint: MicrosoftSharepointIcon, shopify: ShopifyIcon, diff --git a/apps/docs/content/docs/en/tools/cloudwatch.mdx b/apps/docs/content/docs/en/tools/cloudwatch.mdx index 1fc1b19ea75..af0fc0a2b0e 100644 --- a/apps/docs/content/docs/en/tools/cloudwatch.mdx +++ b/apps/docs/content/docs/en/tools/cloudwatch.mdx @@ -57,9 +57,12 @@ Run a CloudWatch Log Insights query against one or more log groups | Parameter | Type | Description | | --------- | ---- | ----------- | -| `results` | array | Query result rows | -| `statistics` | object | Query statistics \(bytesScanned, recordsMatched, recordsScanned\) | -| `status` | string | Query completion status | +| `results` | array | Query result rows \(each row is a key/value map of field name to value\) | +| `statistics` | object | Query statistics | +| ↳ `bytesScanned` | number | Total bytes of log data scanned | +| ↳ `recordsMatched` | number | Number of log records that matched the query | +| ↳ `recordsScanned` | number | Total log records scanned | +| `status` | string | Query completion status \(Complete, Failed, Cancelled, or Timeout\) | ### `cloudwatch_describe_log_groups` @@ -80,6 +83,11 @@ List available CloudWatch log groups | Parameter | Type | Description | | --------- | ---- | ----------- | | `logGroups` | array | List of CloudWatch log groups with metadata | +| ↳ `logGroupName` | string | Log group name | +| ↳ `arn` | string | Log group ARN | +| ↳ `storedBytes` | number | Total stored bytes | +| ↳ `retentionInDays` | number | Retention period in days \(if set\) | +| ↳ `creationTime` | number | Creation time in epoch milliseconds | ### `cloudwatch_get_log_events` @@ -103,6 +111,9 @@ Retrieve log events from a specific CloudWatch log stream | Parameter | Type | Description | | --------- | ---- | ----------- | | `events` | array | Log events with timestamp, message, and ingestion time | +| ↳ `timestamp` | number | Event timestamp in epoch milliseconds | +| ↳ `message` | string | Log event message | +| ↳ `ingestionTime` | number | Ingestion time in epoch milliseconds | ### `cloudwatch_describe_log_streams` @@ -123,7 +134,12 @@ List log streams within a CloudWatch log group | Parameter | Type | Description | | --------- | ---- | ----------- | -| `logStreams` | array | List of log streams with metadata | +| `logStreams` | array | List of log streams with metadata, sorted by last event time \(most recent first\) unless a prefix filter is applied | +| ↳ `logStreamName` | string | Log stream name | +| ↳ `lastEventTimestamp` | number | Timestamp of the last log event in epoch milliseconds | +| ↳ `firstEventTimestamp` | number | Timestamp of the first log event in epoch milliseconds | +| ↳ `creationTime` | number | Stream creation time in epoch milliseconds | +| ↳ `storedBytes` | number | Total stored bytes | ### `cloudwatch_list_metrics` @@ -146,6 +162,9 @@ List available CloudWatch metrics | Parameter | Type | Description | | --------- | ---- | ----------- | | `metrics` | array | List of metrics with namespace, name, and dimensions | +| ↳ `namespace` | string | Metric namespace \(e.g., AWS/EC2\) | +| ↳ `metricName` | string | Metric name \(e.g., CPUUtilization\) | +| ↳ `dimensions` | array | Array of name/value dimension pairs | ### `cloudwatch_get_metric_statistics` @@ -170,8 +189,15 @@ Get statistics for a CloudWatch metric over a time range | Parameter | Type | Description | | --------- | ---- | ----------- | -| `label` | string | Metric label | -| `datapoints` | array | Datapoints with timestamp and statistics values | +| `label` | string | Metric label returned by CloudWatch | +| `datapoints` | array | Datapoints sorted by timestamp with statistics values | +| ↳ `timestamp` | number | Datapoint timestamp in epoch milliseconds | +| ↳ `average` | number | Average statistic value | +| ↳ `sum` | number | Sum statistic value | +| ↳ `minimum` | number | Minimum statistic value | +| ↳ `maximum` | number | Maximum statistic value | +| ↳ `sampleCount` | number | Sample count statistic value | +| ↳ `unit` | string | Unit of the metric | ### `cloudwatch_put_metric_data` @@ -222,5 +248,13 @@ List and filter CloudWatch alarms | Parameter | Type | Description | | --------- | ---- | ----------- | | `alarms` | array | List of CloudWatch alarms with state and configuration | +| ↳ `alarmName` | string | Alarm name | +| ↳ `alarmArn` | string | Alarm ARN | +| ↳ `stateValue` | string | Current state \(OK, ALARM, INSUFFICIENT_DATA\) | +| ↳ `stateReason` | string | Human-readable reason for the state | +| ↳ `metricName` | string | Metric name \(MetricAlarm only\) | +| ↳ `namespace` | string | Metric namespace \(MetricAlarm only\) | +| ↳ `threshold` | number | Threshold value \(MetricAlarm only\) | +| ↳ `stateUpdatedTimestamp` | number | Epoch ms when state last changed | diff --git a/apps/docs/content/docs/en/tools/dynamodb.mdx b/apps/docs/content/docs/en/tools/dynamodb.mdx index 36bc79f6f04..3d37f479dba 100644 --- a/apps/docs/content/docs/en/tools/dynamodb.mdx +++ b/apps/docs/content/docs/en/tools/dynamodb.mdx @@ -1,6 +1,6 @@ --- title: Amazon DynamoDB -description: Connect to Amazon DynamoDB +description: Get, put, query, scan, update, and delete items in Amazon DynamoDB tables --- import { BlockInfoCard } from "@/components/ui/block-info-card" @@ -55,7 +55,7 @@ Get an item from a DynamoDB table by primary key | `accessKeyId` | string | Yes | AWS access key ID | | `secretAccessKey` | string | Yes | AWS secret access key | | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | -| `key` | object | Yes | Primary key of the item to retrieve \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | +| `key` | json | Yes | Primary key of the item to retrieve \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | | `consistentRead` | boolean | No | Use strongly consistent read | #### Output @@ -63,7 +63,7 @@ Get an item from a DynamoDB table by primary key | Parameter | Type | Description | | --------- | ---- | ----------- | | `message` | string | Operation status message | -| `item` | object | Retrieved item | +| `item` | json | Retrieved item | ### `dynamodb_put` @@ -77,14 +77,17 @@ Put an item into a DynamoDB table | `accessKeyId` | string | Yes | AWS access key ID | | `secretAccessKey` | string | Yes | AWS secret access key | | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | -| `item` | object | Yes | Item to put into the table \(e.g., \{"pk": "USER#123", "name": "John", "email": "john@example.com"\}\) | +| `item` | json | Yes | Item to put into the table \(e.g., \{"pk": "USER#123", "name": "John", "email": "john@example.com"\}\) | +| `conditionExpression` | string | No | Condition that must be met for the put to succeed \(e.g., "attribute_not_exists\(pk\)" to prevent overwrites\) | +| `expressionAttributeNames` | json | No | Attribute name mappings for reserved words used in conditionExpression \(e.g., \{"#name": "name"\}\) | +| `expressionAttributeValues` | json | No | Expression attribute values used in conditionExpression \(e.g., \{":expected": "value"\}\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | | `message` | string | Operation status message | -| `item` | object | Created item | +| `item` | json | Created item | ### `dynamodb_query` @@ -100,10 +103,12 @@ Query items from a DynamoDB table using key conditions | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | | `keyConditionExpression` | string | Yes | Key condition expression \(e.g., "pk = :pk" or "pk = :pk AND sk BEGINS_WITH :prefix"\) | | `filterExpression` | string | No | Filter expression for results \(e.g., "age > :minAge AND #status = :status"\) | -| `expressionAttributeNames` | object | No | Attribute name mappings for reserved words \(e.g., \{"#status": "status"\}\) | -| `expressionAttributeValues` | object | No | Expression attribute values \(e.g., \{":pk": "USER#123", ":minAge": 18\}\) | +| `expressionAttributeNames` | json | No | Attribute name mappings for reserved words \(e.g., \{"#status": "status"\}\) | +| `expressionAttributeValues` | json | No | Expression attribute values \(e.g., \{":pk": "USER#123", ":minAge": 18\}\) | | `indexName` | string | No | Secondary index name to query \(e.g., "GSI1", "email-index"\) | | `limit` | number | No | Maximum number of items to return \(e.g., 10, 50, 100\) | +| `exclusiveStartKey` | json | No | Pagination token from a previous query's lastEvaluatedKey to continue fetching results | +| `scanIndexForward` | boolean | No | Sort order for the sort key: true for ascending \(default\), false for descending | #### Output @@ -112,6 +117,7 @@ Query items from a DynamoDB table using key conditions | `message` | string | Operation status message | | `items` | array | Array of items returned | | `count` | number | Number of items returned | +| `lastEvaluatedKey` | json | Pagination token to pass as exclusiveStartKey to fetch the next page of results | ### `dynamodb_scan` @@ -127,9 +133,10 @@ Scan all items in a DynamoDB table | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | | `filterExpression` | string | No | Filter expression for results \(e.g., "age > :minAge AND #status = :status"\) | | `projectionExpression` | string | No | Attributes to retrieve \(e.g., "pk, sk, #name, email"\) | -| `expressionAttributeNames` | object | No | Attribute name mappings for reserved words \(e.g., \{"#name": "name", "#status": "status"\}\) | -| `expressionAttributeValues` | object | No | Expression attribute values \(e.g., \{":minAge": 18, ":status": "active"\}\) | +| `expressionAttributeNames` | json | No | Attribute name mappings for reserved words \(e.g., \{"#name": "name", "#status": "status"\}\) | +| `expressionAttributeValues` | json | No | Expression attribute values \(e.g., \{":minAge": 18, ":status": "active"\}\) | | `limit` | number | No | Maximum number of items to return \(e.g., 10, 50, 100\) | +| `exclusiveStartKey` | json | No | Pagination token from a previous scan's lastEvaluatedKey to continue fetching results | #### Output @@ -138,6 +145,7 @@ Scan all items in a DynamoDB table | `message` | string | Operation status message | | `items` | array | Array of items returned | | `count` | number | Number of items returned | +| `lastEvaluatedKey` | json | Pagination token to pass as exclusiveStartKey to fetch the next page of results | ### `dynamodb_update` @@ -151,10 +159,10 @@ Update an item in a DynamoDB table | `accessKeyId` | string | Yes | AWS access key ID | | `secretAccessKey` | string | Yes | AWS secret access key | | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | -| `key` | object | Yes | Primary key of the item to update \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | +| `key` | json | Yes | Primary key of the item to update \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | | `updateExpression` | string | Yes | Update expression \(e.g., "SET #name = :name, age = :age" or "SET #count = #count + :inc"\) | -| `expressionAttributeNames` | object | No | Attribute name mappings for reserved words \(e.g., \{"#name": "name", "#count": "count"\}\) | -| `expressionAttributeValues` | object | No | Expression attribute values \(e.g., \{":name": "John", ":age": 30, ":inc": 1\}\) | +| `expressionAttributeNames` | json | No | Attribute name mappings for reserved words \(e.g., \{"#name": "name", "#count": "count"\}\) | +| `expressionAttributeValues` | json | No | Expression attribute values \(e.g., \{":name": "John", ":age": 30, ":inc": 1\}\) | | `conditionExpression` | string | No | Condition that must be met for the update to succeed \(e.g., "attribute_exists\(pk\)" or "version = :expectedVersion"\) | #### Output @@ -162,7 +170,7 @@ Update an item in a DynamoDB table | Parameter | Type | Description | | --------- | ---- | ----------- | | `message` | string | Operation status message | -| `item` | object | Updated item | +| `item` | json | Updated item with all attributes | ### `dynamodb_delete` @@ -176,8 +184,10 @@ Delete an item from a DynamoDB table | `accessKeyId` | string | Yes | AWS access key ID | | `secretAccessKey` | string | Yes | AWS secret access key | | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | -| `key` | object | Yes | Primary key of the item to delete \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | +| `key` | json | Yes | Primary key of the item to delete \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | | `conditionExpression` | string | No | Condition that must be met for the delete to succeed \(e.g., "attribute_exists\(pk\)"\) | +| `expressionAttributeNames` | json | No | Attribute name mappings for reserved words used in conditionExpression \(e.g., \{"#status": "status"\}\) | +| `expressionAttributeValues` | json | No | Expression attribute values used in conditionExpression \(e.g., \{":status": "active"\}\) | #### Output @@ -204,6 +214,6 @@ Introspect DynamoDB to list tables or get detailed schema information for a spec | --------- | ---- | ----------- | | `message` | string | Operation status message | | `tables` | array | List of table names in the region | -| `tableDetails` | object | Detailed schema information for a specific table | +| `tableDetails` | json | Detailed schema information for a specific table | diff --git a/apps/docs/content/docs/en/tools/iam.mdx b/apps/docs/content/docs/en/tools/iam.mdx index 5fd9263eadd..15f0447c043 100644 --- a/apps/docs/content/docs/en/tools/iam.mdx +++ b/apps/docs/content/docs/en/tools/iam.mdx @@ -68,7 +68,7 @@ Get detailed information about an IAM user | `region` | string | Yes | AWS region \(e.g., us-east-1\) | | `accessKeyId` | string | Yes | AWS access key ID | | `secretAccessKey` | string | Yes | AWS secret access key | -| `userName` | string | Yes | The name of the IAM user to retrieve | +| `userName` | string | No | The name of the IAM user to retrieve \(defaults to the caller if omitted\) | #### Output @@ -440,4 +440,80 @@ Remove an IAM user from a group | --------- | ---- | ----------- | | `message` | string | Operation status message | +### `iam_list_attached_role_policies` + +List all managed policies attached to an IAM role + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `roleName` | string | Yes | Name of the IAM role | +| `pathPrefix` | string | No | Path prefix to filter policies \(e.g., /application/\) | +| `maxItems` | number | No | Maximum number of policies to return \(1-1000\) | +| `marker` | string | No | Pagination marker from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `attachedPolicies` | json | List of attached policies with policyName and policyArn | +| `isTruncated` | boolean | Whether there are more results available | +| `marker` | string | Pagination marker for the next page of results | +| `count` | number | Number of attached policies returned | + +### `iam_list_attached_user_policies` + +List all managed policies attached to an IAM user + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `userName` | string | Yes | Name of the IAM user | +| `pathPrefix` | string | No | Path prefix to filter policies \(e.g., /application/\) | +| `maxItems` | number | No | Maximum number of policies to return \(1-1000\) | +| `marker` | string | No | Pagination marker from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `attachedPolicies` | json | List of attached policies with policyName and policyArn | +| `isTruncated` | boolean | Whether there are more results available | +| `marker` | string | Pagination marker for the next page of results | +| `count` | number | Number of attached policies returned | + +### `iam_simulate_principal_policy` + +Simulate whether a user, role, or group is allowed to perform specific AWS actions — useful for pre-flight access checks + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `policySourceArn` | string | Yes | ARN of the user, group, or role to simulate \(e.g., arn:aws:iam::123456789012:user/alice\) | +| `actionNames` | string | Yes | Comma-separated list of AWS actions to simulate \(e.g., s3:GetObject,ec2:DescribeInstances\) | +| `resourceArns` | string | No | Comma-separated list of resource ARNs to simulate against \(defaults to * if not provided\) | +| `maxResults` | number | No | Maximum number of simulation results to return \(1-1000\) | +| `marker` | string | No | Pagination marker from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `evaluationResults` | json | Simulation results per action: evalActionName, evalResourceName, evalDecision \(allowed/explicitDeny/implicitDeny\), matchedStatements, missingContextValues | +| `isTruncated` | boolean | Whether there are more results available | +| `marker` | string | Pagination marker for the next page of results | +| `count` | number | Number of evaluation results returned | + diff --git a/apps/docs/content/docs/en/tools/identity_center.mdx b/apps/docs/content/docs/en/tools/identity_center.mdx new file mode 100644 index 00000000000..4c000e8fe05 --- /dev/null +++ b/apps/docs/content/docs/en/tools/identity_center.mdx @@ -0,0 +1,340 @@ +--- +title: AWS Identity Center +description: Manage temporary elevated access in AWS IAM Identity Center +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[AWS IAM Identity Center](https://aws.amazon.com/iam/identity-center/) (formerly AWS Single Sign-On) is the recommended service for managing workforce access to multiple AWS accounts and applications. It provides a central place to assign users and groups temporary, permission-scoped access to AWS accounts using permission sets — without creating long-lived IAM credentials. + +With AWS IAM Identity Center, you can: + +- **Provision account assignments**: Grant a user or group access to a specific AWS account with a specific permission set — the core primitive of temporary elevated access +- **Revoke access on demand**: Delete account assignments to immediately remove elevated permissions when they are no longer needed +- **Look up users by email**: Resolve a federated identity (email address) to an Identity Store user ID for programmatic access provisioning +- **List permission sets**: Enumerate the available permission sets (e.g., ReadOnly, PowerUser, AdministratorAccess) defined in your Identity Center instance +- **Monitor assignment status**: Poll the provisioning status of create/delete operations, which are asynchronous in AWS +- **List accounts in your organization**: Enumerate all AWS accounts in your AWS Organizations structure to populate access request dropdowns +- **Manage groups**: List groups and resolve group IDs by display name for group-based access grants + +In Sim, the AWS Identity Center integration is designed to power **TEAM (Temporary Elevated Access Management)** workflows — automated pipelines where users request elevated access, approvers approve or deny it, access is provisioned with a time limit, and auto-revocation removes it when the window expires. This replaces manual console-based access management with auditable, agent-driven workflows that integrate with Slack, email, ticketing systems, and CloudTrail for full traceability. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Provision and revoke temporary access to AWS accounts via IAM Identity Center (SSO). Assign permission sets to users or groups, look up users by email, and list accounts and permission sets for access request workflows. + + + +## Tools + +### `identity_center_list_instances` + +List all AWS IAM Identity Center instances in your account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `maxResults` | number | No | Maximum number of instances to return \(1-100\) | +| `nextToken` | string | No | Pagination token from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `instances` | json | List of Identity Center instances with instanceArn, identityStoreId, name, status, statusReason | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of instances returned | + +### `identity_center_list_accounts` + +List all AWS accounts in your organization + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `maxResults` | number | No | Maximum number of accounts to return | +| `nextToken` | string | No | Pagination token from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `accounts` | json | List of AWS accounts with id, arn, name, email, status | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of accounts returned | + +### `identity_center_describe_account` + +Retrieve details about a specific AWS account by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `accountId` | string | Yes | AWS account ID to describe | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | AWS account ID | +| `arn` | string | AWS account ARN | +| `name` | string | Account name | +| `email` | string | Root email address of the account | +| `status` | string | Account status \(ACTIVE, SUSPENDED, etc.\) | +| `joinedTimestamp` | string | Date the account joined the organization | + +### `identity_center_list_permission_sets` + +List all permission sets defined in an IAM Identity Center instance + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `maxResults` | number | No | Maximum number of permission sets to return | +| `nextToken` | string | No | Pagination token from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `permissionSets` | json | List of permission sets with permissionSetArn, name, description, sessionDuration | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of permission sets returned | + +### `identity_center_get_user` + +Look up a user in the Identity Store by email address + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `identityStoreId` | string | Yes | Identity Store ID \(from the Identity Center instance\) | +| `email` | string | Yes | Email address of the user to look up | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `userId` | string | Identity Store user ID \(use as principalId\) | +| `userName` | string | Username in the Identity Store | +| `displayName` | string | Display name of the user | +| `email` | string | Email address of the user | + +### `identity_center_get_group` + +Look up a group in the Identity Store by display name + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `identityStoreId` | string | Yes | Identity Store ID \(from the Identity Center instance\) | +| `displayName` | string | Yes | Display name of the group to look up | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `groupId` | string | Identity Store group ID \(use as principalId\) | +| `displayName` | string | Display name of the group | +| `description` | string | Group description | + +### `identity_center_list_groups` + +List all groups in the Identity Store + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `identityStoreId` | string | Yes | Identity Store ID \(from the Identity Center instance\) | +| `maxResults` | number | No | Maximum number of groups to return | +| `nextToken` | string | No | Pagination token from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `groups` | json | List of groups with groupId, displayName, description | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of groups returned | + +### `identity_center_create_account_assignment` + +Grant a user or group access to an AWS account via a permission set (temporary elevated access) + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `accountId` | string | Yes | AWS account ID to grant access to | +| `permissionSetArn` | string | Yes | ARN of the permission set to assign | +| `principalType` | string | Yes | Type of principal: USER or GROUP | +| `principalId` | string | Yes | Identity Store ID of the user or group | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Status message | +| `status` | string | Provisioning status: IN_PROGRESS, FAILED, or SUCCEEDED | +| `requestId` | string | Request ID to use with Check Assignment Status | +| `accountId` | string | Target AWS account ID | +| `permissionSetArn` | string | Permission set ARN | +| `principalType` | string | Principal type \(USER or GROUP\) | +| `principalId` | string | Principal ID | +| `failureReason` | string | Reason for failure if status is FAILED | +| `createdDate` | string | Date the request was created | + +### `identity_center_delete_account_assignment` + +Revoke a user or group access to an AWS account by removing a permission set assignment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `accountId` | string | Yes | AWS account ID to revoke access from | +| `permissionSetArn` | string | Yes | ARN of the permission set to remove | +| `principalType` | string | Yes | Type of principal: USER or GROUP | +| `principalId` | string | Yes | Identity Store ID of the user or group | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Status message | +| `status` | string | Deprovisioning status: IN_PROGRESS, FAILED, or SUCCEEDED | +| `requestId` | string | Request ID to use with Check Assignment Status | +| `accountId` | string | Target AWS account ID | +| `permissionSetArn` | string | Permission set ARN | +| `principalType` | string | Principal type \(USER or GROUP\) | +| `principalId` | string | Principal ID | +| `failureReason` | string | Reason for failure if status is FAILED | +| `createdDate` | string | Date the request was created | + +### `identity_center_check_assignment_status` + +Check the provisioning status of an account assignment creation request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `requestId` | string | Yes | Request ID returned from Create or Delete Account Assignment | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Human-readable status message | +| `status` | string | Current status: IN_PROGRESS, FAILED, or SUCCEEDED | +| `requestId` | string | The request ID that was checked | +| `accountId` | string | Target AWS account ID | +| `permissionSetArn` | string | Permission set ARN | +| `principalType` | string | Principal type \(USER or GROUP\) | +| `principalId` | string | Principal ID | +| `failureReason` | string | Reason for failure if status is FAILED | +| `createdDate` | string | Date the request was created | + +### `identity_center_check_assignment_deletion_status` + +Check the deprovisioning status of an account assignment deletion request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `requestId` | string | Yes | Request ID returned from Delete Account Assignment | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Human-readable status message | +| `status` | string | Current deletion status: IN_PROGRESS, FAILED, or SUCCEEDED | +| `requestId` | string | The deletion request ID that was checked | +| `accountId` | string | Target AWS account ID | +| `permissionSetArn` | string | Permission set ARN | +| `principalType` | string | Principal type \(USER or GROUP\) | +| `principalId` | string | Principal ID | +| `failureReason` | string | Reason for failure if status is FAILED | +| `createdDate` | string | Date the request was created | + +### `identity_center_list_account_assignments` + +List all account assignments for a specific user or group across all accounts + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `principalId` | string | Yes | Identity Store ID of the user or group | +| `principalType` | string | Yes | Type of principal: USER or GROUP | +| `maxResults` | number | No | Maximum number of assignments to return | +| `nextToken` | string | No | Pagination token from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `assignments` | json | List of account assignments with accountId, permissionSetArn, principalType, principalId | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of assignments returned | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 2658fa2c390..91d84fa1e9e 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -86,6 +86,7 @@ "huggingface", "hunter", "iam", + "identity_center", "image_generator", "imap", "incidentio", @@ -154,6 +155,7 @@ "sentry", "serper", "servicenow", + "ses", "sftp", "sharepoint", "shopify", diff --git a/apps/docs/content/docs/en/tools/ses.mdx b/apps/docs/content/docs/en/tools/ses.mdx new file mode 100644 index 00000000000..05eade719bc --- /dev/null +++ b/apps/docs/content/docs/en/tools/ses.mdx @@ -0,0 +1,226 @@ +--- +title: AWS SES +description: Send emails and manage templates with AWS Simple Email Service +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +## Usage Instructions + +Integrate AWS SES v2 into the workflow. Send simple, templated, and bulk emails. Manage email templates and retrieve account sending quota and verified identity information. + + + +## Tools + +### `ses_send_email` + +Send an email via AWS SES using simple or HTML content + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `fromAddress` | string | Yes | Verified sender email address | +| `toAddresses` | string | Yes | Comma-separated list of recipient email addresses | +| `subject` | string | Yes | Email subject line | +| `bodyText` | string | No | Plain text email body | +| `bodyHtml` | string | No | HTML email body | +| `ccAddresses` | string | No | Comma-separated list of CC email addresses | +| `bccAddresses` | string | No | Comma-separated list of BCC email addresses | +| `replyToAddresses` | string | No | Comma-separated list of reply-to email addresses | +| `configurationSetName` | string | No | SES configuration set name for tracking | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `messageId` | string | SES message ID for the sent email | + +### `ses_send_templated_email` + +Send an email using an SES email template with dynamic template data + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `fromAddress` | string | Yes | Verified sender email address | +| `toAddresses` | string | Yes | Comma-separated list of recipient email addresses | +| `templateName` | string | Yes | Name of the SES email template to use | +| `templateData` | string | Yes | JSON string of key-value pairs for template variable substitution | +| `ccAddresses` | string | No | Comma-separated list of CC email addresses | +| `bccAddresses` | string | No | Comma-separated list of BCC email addresses | +| `configurationSetName` | string | No | SES configuration set name for tracking | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `messageId` | string | SES message ID for the sent email | + +### `ses_send_bulk_email` + +Send emails to multiple recipients using an SES template with per-recipient data + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `fromAddress` | string | Yes | Verified sender email address | +| `templateName` | string | Yes | Name of the SES email template to use | +| `destinations` | string | Yes | JSON array of destination objects with toAddresses \(string\[\]\) and templateData \(JSON string\) | +| `defaultTemplateData` | string | No | Default JSON template data used when a destination does not specify its own | +| `configurationSetName` | string | No | SES configuration set name for tracking | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `results` | array | Per-destination send results with status and messageId | +| `successCount` | number | Number of successfully sent emails | +| `failureCount` | number | Number of failed email sends | + +### `ses_list_identities` + +List all verified email identities (email addresses and domains) in your SES account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `pageSize` | number | No | Maximum number of identities to return \(1-1000\) | +| `nextToken` | string | No | Pagination token from a previous list response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `identities` | array | List of email identities with name, type, sending status, and verification status | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of identities returned | + +### `ses_get_account` + +Get SES account sending quota and status information + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `sendingEnabled` | boolean | Whether email sending is enabled for the account | +| `max24HourSend` | number | Maximum emails allowed per 24-hour period | +| `maxSendRate` | number | Maximum emails allowed per second | +| `sentLast24Hours` | number | Number of emails sent in the last 24 hours | + +### `ses_create_template` + +Create a new SES email template for use with templated email sending + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `templateName` | string | Yes | Unique name for the email template | +| `subjectPart` | string | Yes | Subject line template \(supports \{\{variable\}\} substitution\) | +| `textPart` | string | No | Plain text version of the template body | +| `htmlPart` | string | No | HTML version of the template body | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Confirmation message for the created template | + +### `ses_get_template` + +Retrieve the content and details of an SES email template + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `templateName` | string | Yes | Name of the template to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `templateName` | string | Name of the template | +| `subjectPart` | string | Subject line of the template | +| `textPart` | string | Plain text body of the template | +| `htmlPart` | string | HTML body of the template | + +### `ses_list_templates` + +List all SES email templates in your account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `pageSize` | number | No | Maximum number of templates to return | +| `nextToken` | string | No | Pagination token from a previous list response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `templates` | array | List of email templates with name and creation timestamp | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of templates returned | + +### `ses_delete_template` + +Delete an existing SES email template + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `templateName` | string | Yes | Name of the template to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Confirmation message for the deleted template | + + diff --git a/apps/docs/content/docs/en/tools/sts.mdx b/apps/docs/content/docs/en/tools/sts.mdx index 29fd0445e16..6c46a0ba784 100644 --- a/apps/docs/content/docs/en/tools/sts.mdx +++ b/apps/docs/content/docs/en/tools/sts.mdx @@ -46,6 +46,7 @@ Assume an IAM role and receive temporary security credentials | `roleArn` | string | Yes | ARN of the IAM role to assume | | `roleSessionName` | string | Yes | Identifier for the assumed role session | | `durationSeconds` | number | No | Duration of the session in seconds \(900-43200, default 3600\) | +| `policy` | string | No | JSON IAM policy to further restrict session permissions \(max 2048 chars\) | | `externalId` | string | No | External ID for cross-account access | | `serialNumber` | string | No | MFA device serial number or ARN | | `tokenCode` | string | No | MFA token code \(6 digits\) | @@ -61,6 +62,7 @@ Assume an IAM role and receive temporary security credentials | `assumedRoleArn` | string | ARN of the assumed role | | `assumedRoleId` | string | Assumed role ID with session name | | `packedPolicySize` | number | Percentage of allowed policy size used | +| `sourceIdentity` | string | Source identity set on the role session, if any | ### `sts_get_caller_identity` diff --git a/apps/docs/content/docs/en/triggers/fireflies.mdx b/apps/docs/content/docs/en/triggers/fireflies.mdx index 563f6608509..9a265f451b0 100644 --- a/apps/docs/content/docs/en/triggers/fireflies.mdx +++ b/apps/docs/content/docs/en/triggers/fireflies.mdx @@ -29,6 +29,7 @@ Trigger workflow when a Fireflies meeting transcription is complete | Parameter | Type | Description | | --------- | ---- | ----------- | | `meetingId` | string | The ID of the transcribed meeting | -| `eventType` | string | The type of event \(Transcription completed\) | +| `eventType` | string | The type of event \(e.g. Transcription completed, meeting.transcribed\) | | `clientReferenceId` | string | Custom reference ID if set during upload | +| `timestamp` | number | Unix timestamp in milliseconds when the event was fired \(V2 webhooks\) | diff --git a/apps/docs/content/docs/en/triggers/jsm.mdx b/apps/docs/content/docs/en/triggers/jsm.mdx index 6aabf82cade..4233fa04e7b 100644 --- a/apps/docs/content/docs/en/triggers/jsm.mdx +++ b/apps/docs/content/docs/en/triggers/jsm.mdx @@ -304,7 +304,7 @@ Trigger workflow on any Jira Service Management webhook event | ↳ `id` | string | Changelog ID | | `comment` | object | comment output from the tool | | ↳ `id` | string | Comment ID | -| ↳ `body` | string | Comment text/body | +| ↳ `body` | json | Comment body in Atlassian Document Format \(ADF\). On Jira Server this may be a plain string. | | ↳ `author` | object | author output from the tool | | ↳ `displayName` | string | Comment author display name | | ↳ `accountId` | string | Comment author account ID | diff --git a/apps/docs/content/docs/en/triggers/slack.mdx b/apps/docs/content/docs/en/triggers/slack.mdx index cdffda257ac..cc5d20b042c 100644 --- a/apps/docs/content/docs/en/triggers/slack.mdx +++ b/apps/docs/content/docs/en/triggers/slack.mdx @@ -25,6 +25,7 @@ Trigger workflow from Slack events like mentions, messages, and reactions | `signingSecret` | string | Yes | The signing secret from your Slack app to validate request authenticity. | | `botToken` | string | No | The bot token from your Slack app. Required for downloading files attached to messages. | | `includeFiles` | boolean | No | Download and include file attachments from messages. Requires a bot token with files:read scope. | +| `setupWizard` | modal | No | Walk through manifest creation, app install, and pasting credentials. | #### Output diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index 2b4fb9244ba..f5ac6f926a5 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -91,6 +91,7 @@ import { HuggingFaceIcon, HunterIOIcon, IAMIcon, + IdentityCenterIcon, ImageIcon, IncidentioIcon, InfisicalIcon, @@ -152,6 +153,7 @@ import { RootlyIcon, S3Icon, SalesforceIcon, + SESIcon, SearchIcon, SecretsManagerIcon, SendgridIcon, @@ -284,6 +286,7 @@ export const blockTypeToIconMap: Record = { huggingface: HuggingFaceIcon, hunter: HunterIOIcon, iam: IAMIcon, + identity_center: IdentityCenterIcon, image_generator: ImageIcon, imap: MailServerIcon, incidentio: IncidentioIcon, @@ -352,6 +355,7 @@ export const blockTypeToIconMap: Record = { sentry: SentryIcon, serper: SerperIcon, servicenow: ServiceNowIcon, + ses: SESIcon, sftp: SftpIcon, sharepoint: MicrosoftSharepointIcon, shopify: ShopifyIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 41eebfdb607..0adc9ac930d 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -484,7 +484,7 @@ "type": "dynamodb", "slug": "amazon-dynamodb", "name": "Amazon DynamoDB", - "description": "Connect to Amazon DynamoDB", + "description": "Get, put, query, scan, update, and delete items in Amazon DynamoDB tables", "longDescription": "Integrate Amazon DynamoDB into workflows. Supports Get, Put, Query, Scan, Update, Delete, and Introspect operations on DynamoDB tables.", "bgColor": "linear-gradient(45deg, #2E27AD 0%, #527FFF 100%)", "iconName": "DynamoDBIcon", @@ -1464,9 +1464,21 @@ { "name": "Remove User from Group", "description": "Remove an IAM user from a group" + }, + { + "name": "List Attached Role Policies", + "description": "List all managed policies attached to an IAM role" + }, + { + "name": "List Attached User Policies", + "description": "List all managed policies attached to an IAM user" + }, + { + "name": "Simulate Principal Policy", + "description": "Simulate whether a user, role, or group is allowed to perform specific AWS actions — useful for pre-flight access checks" } ], - "operationCount": 18, + "operationCount": 21, "triggers": [], "triggerCount": 0, "authType": "none", @@ -1474,6 +1486,73 @@ "integrationTypes": ["developer-tools", "security"], "tags": ["cloud", "identity"] }, + { + "type": "identity_center", + "slug": "aws-identity-center", + "name": "AWS Identity Center", + "description": "Manage temporary elevated access in AWS IAM Identity Center", + "longDescription": "Provision and revoke temporary access to AWS accounts via IAM Identity Center (SSO). Assign permission sets to users or groups, look up users by email, and list accounts and permission sets for access request workflows.", + "bgColor": "linear-gradient(45deg, #BD0816 0%, #FF5252 100%)", + "iconName": "IdentityCenterIcon", + "docsUrl": "https://docs.sim.ai/tools/identity-center", + "operations": [ + { + "name": "List Instances", + "description": "List all AWS IAM Identity Center instances in your account" + }, + { + "name": "List Accounts", + "description": "List all AWS accounts in your organization" + }, + { + "name": "Describe Account", + "description": "Retrieve details about a specific AWS account by its ID" + }, + { + "name": "List Permission Sets", + "description": "List all permission sets defined in an IAM Identity Center instance" + }, + { + "name": "Get User", + "description": "Look up a user in the Identity Store by email address" + }, + { + "name": "Get Group", + "description": "Look up a group in the Identity Store by display name" + }, + { + "name": "List Groups", + "description": "List all groups in the Identity Store" + }, + { + "name": "Create Account Assignment", + "description": "Grant a user or group access to an AWS account via a permission set (temporary elevated access)" + }, + { + "name": "Delete Account Assignment", + "description": "Revoke a user or group access to an AWS account by removing a permission set assignment" + }, + { + "name": "Check Assignment Status", + "description": "Check the provisioning status of an account assignment creation request" + }, + { + "name": "Check Assignment Deletion Status", + "description": "Check the deprovisioning status of an account assignment deletion request" + }, + { + "name": "List Account Assignments", + "description": "List all account assignments for a specific user or group across all accounts" + } + ], + "operationCount": 12, + "triggers": [], + "triggerCount": 0, + "authType": "none", + "category": "tools", + "integrationTypes": ["security", "developer-tools"], + "tags": ["cloud", "identity"] + }, { "type": "secrets_manager", "slug": "aws-secrets-manager", @@ -1513,6 +1592,61 @@ "integrationTypes": ["developer-tools", "security"], "tags": ["cloud", "secrets-management"] }, + { + "type": "ses", + "slug": "aws-ses", + "name": "AWS SES", + "description": "Send emails and manage templates with AWS Simple Email Service", + "longDescription": "Integrate AWS SES v2 into the workflow. Send simple, templated, and bulk emails. Manage email templates and retrieve account sending quota and verified identity information.", + "bgColor": "linear-gradient(45deg, #BD0816 0%, #FF5252 100%)", + "iconName": "SESIcon", + "docsUrl": "https://docs.sim.ai/tools/ses", + "operations": [ + { + "name": "Send Email", + "description": "Send an email via AWS SES using simple or HTML content" + }, + { + "name": "Send Templated Email", + "description": "Send an email using an SES email template with dynamic template data" + }, + { + "name": "Send Bulk Email", + "description": "Send emails to multiple recipients using an SES template with per-recipient data" + }, + { + "name": "List Identities", + "description": "List all verified email identities (email addresses and domains) in your SES account" + }, + { + "name": "Get Account", + "description": "Get SES account sending quota and status information" + }, + { + "name": "Create Template", + "description": "Create a new SES email template for use with templated email sending" + }, + { + "name": "Get Template", + "description": "Retrieve the content and details of an SES email template" + }, + { + "name": "List Templates", + "description": "List all SES email templates in your account" + }, + { + "name": "Delete Template", + "description": "Delete an existing SES email template" + } + ], + "operationCount": 9, + "triggers": [], + "triggerCount": 0, + "authType": "none", + "category": "tools", + "integrationTypes": ["email", "analytics", "developer-tools"], + "tags": ["cloud", "marketing"] + }, { "type": "sts", "slug": "aws-sts", diff --git a/apps/sim/app/api/tools/cloudwatch/describe-alarms/route.ts b/apps/sim/app/api/tools/cloudwatch/describe-alarms/route.ts index 00016cccd8a..60e3113ce0d 100644 --- a/apps/sim/app/api/tools/cloudwatch/describe-alarms/route.ts +++ b/apps/sim/app/api/tools/cloudwatch/describe-alarms/route.ts @@ -5,6 +5,7 @@ import { type StateValue, } from '@aws-sdk/client-cloudwatch' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -41,6 +42,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = DescribeAlarmsSchema.parse(body) + logger.info('Describing CloudWatch alarms') + const client = new CloudWatchClient({ region: validatedData.region, credentials: { @@ -49,57 +52,67 @@ export const POST = withRouteHandler(async (request: NextRequest) => { }, }) - const command = new DescribeAlarmsCommand({ - ...(validatedData.alarmNamePrefix && { AlarmNamePrefix: validatedData.alarmNamePrefix }), - ...(validatedData.stateValue && { StateValue: validatedData.stateValue as StateValue }), - AlarmTypes: validatedData.alarmType - ? [validatedData.alarmType as AlarmType] - : (['MetricAlarm', 'CompositeAlarm'] as AlarmType[]), - ...(validatedData.limit !== undefined && { MaxRecords: validatedData.limit }), - }) + try { + const command = new DescribeAlarmsCommand({ + ...(validatedData.alarmNamePrefix && { AlarmNamePrefix: validatedData.alarmNamePrefix }), + ...(validatedData.stateValue && { StateValue: validatedData.stateValue as StateValue }), + AlarmTypes: validatedData.alarmType + ? [validatedData.alarmType as AlarmType] + : (['MetricAlarm', 'CompositeAlarm'] as AlarmType[]), + ...(validatedData.limit !== undefined && { MaxRecords: validatedData.limit }), + }) - const response = await client.send(command) + const response = await client.send(command) - const metricAlarms = (response.MetricAlarms ?? []).map((a) => ({ - alarmName: a.AlarmName ?? '', - alarmArn: a.AlarmArn ?? '', - stateValue: a.StateValue ?? 'UNKNOWN', - stateReason: a.StateReason ?? '', - metricName: a.MetricName, - namespace: a.Namespace, - comparisonOperator: a.ComparisonOperator, - threshold: a.Threshold, - evaluationPeriods: a.EvaluationPeriods, - stateUpdatedTimestamp: a.StateUpdatedTimestamp?.getTime(), - })) + const metricAlarms = (response.MetricAlarms ?? []).map((a) => ({ + alarmName: a.AlarmName ?? '', + alarmArn: a.AlarmArn ?? '', + stateValue: a.StateValue ?? 'UNKNOWN', + stateReason: a.StateReason ?? '', + metricName: a.MetricName, + namespace: a.Namespace, + comparisonOperator: a.ComparisonOperator, + threshold: a.Threshold, + evaluationPeriods: a.EvaluationPeriods, + stateUpdatedTimestamp: a.StateUpdatedTimestamp?.getTime(), + })) - const compositeAlarms = (response.CompositeAlarms ?? []).map((a) => ({ - alarmName: a.AlarmName ?? '', - alarmArn: a.AlarmArn ?? '', - stateValue: a.StateValue ?? 'UNKNOWN', - stateReason: a.StateReason ?? '', - metricName: undefined, - namespace: undefined, - comparisonOperator: undefined, - threshold: undefined, - evaluationPeriods: undefined, - stateUpdatedTimestamp: a.StateUpdatedTimestamp?.getTime(), - })) + const compositeAlarms = (response.CompositeAlarms ?? []).map((a) => ({ + alarmName: a.AlarmName ?? '', + alarmArn: a.AlarmArn ?? '', + stateValue: a.StateValue ?? 'UNKNOWN', + stateReason: a.StateReason ?? '', + metricName: undefined, + namespace: undefined, + comparisonOperator: undefined, + threshold: undefined, + evaluationPeriods: undefined, + stateUpdatedTimestamp: a.StateUpdatedTimestamp?.getTime(), + })) - return NextResponse.json({ - success: true, - output: { alarms: [...metricAlarms, ...compositeAlarms] }, - }) + const alarms = [...metricAlarms, ...compositeAlarms] + + logger.info(`Successfully described ${alarms.length} alarms`) + + return NextResponse.json({ + success: true, + output: { alarms }, + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = - error instanceof Error ? error.message : 'Failed to describe CloudWatch alarms' - logger.error('DescribeAlarms failed', { error: errorMessage }) - return NextResponse.json({ error: errorMessage }, { status: 500 }) + logger.error('DescribeAlarms failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to describe CloudWatch alarms: ${toError(error).message}` }, + { status: 500 } + ) } }) diff --git a/apps/sim/app/api/tools/cloudwatch/describe-log-groups/route.ts b/apps/sim/app/api/tools/cloudwatch/describe-log-groups/route.ts index d72f5d0ddb4..9051d0ac64e 100644 --- a/apps/sim/app/api/tools/cloudwatch/describe-log-groups/route.ts +++ b/apps/sim/app/api/tools/cloudwatch/describe-log-groups/route.ts @@ -1,5 +1,6 @@ import { DescribeLogGroupsCommand } from '@aws-sdk/client-cloudwatch-logs' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' @@ -29,41 +30,51 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = DescribeLogGroupsSchema.parse(body) + logger.info('Describing CloudWatch log groups') + const client = createCloudWatchLogsClient({ region: validatedData.region, accessKeyId: validatedData.accessKeyId, secretAccessKey: validatedData.secretAccessKey, }) - const command = new DescribeLogGroupsCommand({ - ...(validatedData.prefix && { logGroupNamePrefix: validatedData.prefix }), - ...(validatedData.limit !== undefined && { limit: validatedData.limit }), - }) + try { + const command = new DescribeLogGroupsCommand({ + ...(validatedData.prefix && { logGroupNamePrefix: validatedData.prefix }), + ...(validatedData.limit !== undefined && { limit: validatedData.limit }), + }) - const response = await client.send(command) + const response = await client.send(command) - const logGroups = (response.logGroups ?? []).map((lg) => ({ - logGroupName: lg.logGroupName ?? '', - arn: lg.arn ?? '', - storedBytes: lg.storedBytes ?? 0, - retentionInDays: lg.retentionInDays, - creationTime: lg.creationTime, - })) + const logGroups = (response.logGroups ?? []).map((lg) => ({ + logGroupName: lg.logGroupName ?? '', + arn: lg.arn ?? '', + storedBytes: lg.storedBytes ?? 0, + retentionInDays: lg.retentionInDays, + creationTime: lg.creationTime, + })) - return NextResponse.json({ - success: true, - output: { logGroups }, - }) + logger.info(`Successfully described ${logGroups.length} log groups`) + + return NextResponse.json({ + success: true, + output: { logGroups }, + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = - error instanceof Error ? error.message : 'Failed to describe CloudWatch log groups' - logger.error('DescribeLogGroups failed', { error: errorMessage }) - return NextResponse.json({ error: errorMessage }, { status: 500 }) + logger.error('DescribeLogGroups failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to describe CloudWatch log groups: ${toError(error).message}` }, + { status: 500 } + ) } }) diff --git a/apps/sim/app/api/tools/cloudwatch/describe-log-streams/route.ts b/apps/sim/app/api/tools/cloudwatch/describe-log-streams/route.ts index ceda4c95b31..09db38dcc4f 100644 --- a/apps/sim/app/api/tools/cloudwatch/describe-log-streams/route.ts +++ b/apps/sim/app/api/tools/cloudwatch/describe-log-streams/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' @@ -29,31 +30,41 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = DescribeLogStreamsSchema.parse(body) + logger.info(`Describing log streams for group: ${validatedData.logGroupName}`) + const client = createCloudWatchLogsClient({ region: validatedData.region, accessKeyId: validatedData.accessKeyId, secretAccessKey: validatedData.secretAccessKey, }) - const result = await describeLogStreams(client, validatedData.logGroupName, { - prefix: validatedData.prefix, - limit: validatedData.limit, - }) + try { + const result = await describeLogStreams(client, validatedData.logGroupName, { + prefix: validatedData.prefix, + limit: validatedData.limit, + }) - return NextResponse.json({ - success: true, - output: { logStreams: result.logStreams }, - }) + logger.info(`Successfully described ${result.logStreams.length} log streams`) + + return NextResponse.json({ + success: true, + output: { logStreams: result.logStreams }, + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = - error instanceof Error ? error.message : 'Failed to describe CloudWatch log streams' - logger.error('DescribeLogStreams failed', { error: errorMessage }) - return NextResponse.json({ error: errorMessage }, { status: 500 }) + logger.error('DescribeLogStreams failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to describe CloudWatch log streams: ${toError(error).message}` }, + { status: 500 } + ) } }) diff --git a/apps/sim/app/api/tools/cloudwatch/get-log-events/route.ts b/apps/sim/app/api/tools/cloudwatch/get-log-events/route.ts index c6413da8f0c..4ccd1bc7929 100644 --- a/apps/sim/app/api/tools/cloudwatch/get-log-events/route.ts +++ b/apps/sim/app/api/tools/cloudwatch/get-log-events/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -31,37 +32,49 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = GetLogEventsSchema.parse(body) + logger.info( + `Getting log events from ${validatedData.logGroupName}/${validatedData.logStreamName}` + ) + const client = createCloudWatchLogsClient({ region: validatedData.region, accessKeyId: validatedData.accessKeyId, secretAccessKey: validatedData.secretAccessKey, }) - const result = await getLogEvents( - client, - validatedData.logGroupName, - validatedData.logStreamName, - { - startTime: validatedData.startTime, - endTime: validatedData.endTime, - limit: validatedData.limit, - } - ) + try { + const result = await getLogEvents( + client, + validatedData.logGroupName, + validatedData.logStreamName, + { + startTime: validatedData.startTime, + endTime: validatedData.endTime, + limit: validatedData.limit, + } + ) - return NextResponse.json({ - success: true, - output: { events: result.events }, - }) + logger.info(`Successfully retrieved ${result.events.length} log events`) + + return NextResponse.json({ + success: true, + output: { events: result.events }, + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = - error instanceof Error ? error.message : 'Failed to get CloudWatch log events' - logger.error('GetLogEvents failed', { error: errorMessage }) - return NextResponse.json({ error: errorMessage }, { status: 500 }) + logger.error('GetLogEvents failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to get CloudWatch log events: ${toError(error).message}` }, + { status: 500 } + ) } }) diff --git a/apps/sim/app/api/tools/cloudwatch/get-metric-statistics/route.ts b/apps/sim/app/api/tools/cloudwatch/get-metric-statistics/route.ts index 33ece1c084f..acd8756f109 100644 --- a/apps/sim/app/api/tools/cloudwatch/get-metric-statistics/route.ts +++ b/apps/sim/app/api/tools/cloudwatch/get-metric-statistics/route.ts @@ -1,5 +1,6 @@ import { CloudWatchClient, GetMetricStatisticsCommand } from '@aws-sdk/client-cloudwatch' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -30,6 +31,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = GetMetricStatisticsSchema.parse(body) + logger.info( + `Getting metric statistics for ${validatedData.namespace}/${validatedData.metricName}` + ) + const client = new CloudWatchClient({ region: validatedData.region, credentials: { @@ -38,67 +43,75 @@ export const POST = withRouteHandler(async (request: NextRequest) => { }, }) - let parsedDimensions: { Name: string; Value: string }[] | undefined - if (validatedData.dimensions) { - try { - const dims = JSON.parse(validatedData.dimensions) - if (Array.isArray(dims)) { - parsedDimensions = dims.map((d: Record) => ({ - Name: d.name, - Value: d.value, - })) - } else if (typeof dims === 'object') { - parsedDimensions = Object.entries(dims).map(([name, value]) => ({ - Name: name, - Value: String(value), - })) + try { + let parsedDimensions: { Name: string; Value: string }[] | undefined + if (validatedData.dimensions) { + try { + const dims = JSON.parse(validatedData.dimensions) + if (Array.isArray(dims)) { + parsedDimensions = dims.map((d: Record) => ({ + Name: d.name, + Value: d.value, + })) + } else if (typeof dims === 'object') { + parsedDimensions = Object.entries(dims).map(([name, value]) => ({ + Name: name, + Value: String(value), + })) + } + } catch { + return NextResponse.json({ error: 'Invalid dimensions JSON format' }, { status: 400 }) } - } catch { - return NextResponse.json({ error: 'Invalid dimensions JSON format' }, { status: 400 }) } - } - const command = new GetMetricStatisticsCommand({ - Namespace: validatedData.namespace, - MetricName: validatedData.metricName, - StartTime: new Date(validatedData.startTime * 1000), - EndTime: new Date(validatedData.endTime * 1000), - Period: validatedData.period, - Statistics: validatedData.statistics, - ...(parsedDimensions && { Dimensions: parsedDimensions }), - }) + const command = new GetMetricStatisticsCommand({ + Namespace: validatedData.namespace, + MetricName: validatedData.metricName, + StartTime: new Date(validatedData.startTime * 1000), + EndTime: new Date(validatedData.endTime * 1000), + Period: validatedData.period, + Statistics: validatedData.statistics, + ...(parsedDimensions && { Dimensions: parsedDimensions }), + }) - const response = await client.send(command) + const response = await client.send(command) - const datapoints = (response.Datapoints ?? []) - .sort((a, b) => (a.Timestamp?.getTime() ?? 0) - (b.Timestamp?.getTime() ?? 0)) - .map((dp) => ({ - timestamp: dp.Timestamp ? dp.Timestamp.getTime() : 0, - average: dp.Average, - sum: dp.Sum, - minimum: dp.Minimum, - maximum: dp.Maximum, - sampleCount: dp.SampleCount, - unit: dp.Unit, - })) + const datapoints = (response.Datapoints ?? []) + .sort((a, b) => (a.Timestamp?.getTime() ?? 0) - (b.Timestamp?.getTime() ?? 0)) + .map((dp) => ({ + timestamp: dp.Timestamp ? dp.Timestamp.getTime() : 0, + average: dp.Average, + sum: dp.Sum, + minimum: dp.Minimum, + maximum: dp.Maximum, + sampleCount: dp.SampleCount, + unit: dp.Unit, + })) - return NextResponse.json({ - success: true, - output: { - label: response.Label ?? validatedData.metricName, - datapoints, - }, - }) + logger.info(`Successfully retrieved ${datapoints.length} datapoints`) + + return NextResponse.json({ + success: true, + output: { + label: response.Label ?? validatedData.metricName, + datapoints, + }, + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = - error instanceof Error ? error.message : 'Failed to get CloudWatch metric statistics' - logger.error('GetMetricStatistics failed', { error: errorMessage }) - return NextResponse.json({ error: errorMessage }, { status: 500 }) + logger.error('GetMetricStatistics failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to get CloudWatch metric statistics: ${toError(error).message}` }, + { status: 500 } + ) } }) diff --git a/apps/sim/app/api/tools/cloudwatch/list-metrics/route.ts b/apps/sim/app/api/tools/cloudwatch/list-metrics/route.ts index 7d342d41fb5..711d2265438 100644 --- a/apps/sim/app/api/tools/cloudwatch/list-metrics/route.ts +++ b/apps/sim/app/api/tools/cloudwatch/list-metrics/route.ts @@ -1,5 +1,6 @@ import { CloudWatchClient, ListMetricsCommand } from '@aws-sdk/client-cloudwatch' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -30,6 +31,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = ListMetricsSchema.parse(body) + logger.info('Listing CloudWatch metrics') + const client = new CloudWatchClient({ region: validatedData.region, credentials: { @@ -38,40 +41,47 @@ export const POST = withRouteHandler(async (request: NextRequest) => { }, }) - const limit = validatedData.limit ?? 500 + try { + const limit = validatedData.limit ?? 500 - const command = new ListMetricsCommand({ - ...(validatedData.namespace && { Namespace: validatedData.namespace }), - ...(validatedData.metricName && { MetricName: validatedData.metricName }), - ...(validatedData.recentlyActive && { RecentlyActive: 'PT3H' }), - ...(limit <= 500 && { MaxResults: limit }), - }) + const command = new ListMetricsCommand({ + ...(validatedData.namespace && { Namespace: validatedData.namespace }), + ...(validatedData.metricName && { MetricName: validatedData.metricName }), + ...(validatedData.recentlyActive && { RecentlyActive: 'PT3H' }), + }) - const response = await client.send(command) + const response = await client.send(command) - const metrics = (response.Metrics ?? []).slice(0, limit).map((m) => ({ - namespace: m.Namespace ?? '', - metricName: m.MetricName ?? '', - dimensions: (m.Dimensions ?? []).map((d) => ({ - name: d.Name ?? '', - value: d.Value ?? '', - })), - })) + const metrics = (response.Metrics ?? []).slice(0, limit).map((m) => ({ + namespace: m.Namespace ?? '', + metricName: m.MetricName ?? '', + dimensions: (m.Dimensions ?? []).map((d) => ({ + name: d.Name ?? '', + value: d.Value ?? '', + })), + })) - return NextResponse.json({ - success: true, - output: { metrics }, - }) + logger.info(`Successfully listed ${metrics.length} metrics`) + + return NextResponse.json({ + success: true, + output: { metrics }, + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = - error instanceof Error ? error.message : 'Failed to list CloudWatch metrics' - logger.error('ListMetrics failed', { error: errorMessage }) - return NextResponse.json({ error: errorMessage }, { status: 500 }) + logger.error('ListMetrics failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to list CloudWatch metrics: ${toError(error).message}` }, + { status: 500 } + ) } }) diff --git a/apps/sim/app/api/tools/cloudwatch/put-metric-data/route.ts b/apps/sim/app/api/tools/cloudwatch/put-metric-data/route.ts index 28a6d490c6e..86886d7a925 100644 --- a/apps/sim/app/api/tools/cloudwatch/put-metric-data/route.ts +++ b/apps/sim/app/api/tools/cloudwatch/put-metric-data/route.ts @@ -4,6 +4,7 @@ import { type StandardUnit, } from '@aws-sdk/client-cloudwatch' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -78,6 +79,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = PutMetricDataSchema.parse(body) + logger.info(`Publishing metric ${validatedData.namespace}/${validatedData.metricName}`) + const client = new CloudWatchClient({ region: validatedData.region, credentials: { @@ -86,52 +89,60 @@ export const POST = withRouteHandler(async (request: NextRequest) => { }, }) - const timestamp = new Date() + try { + const timestamp = new Date() - const dimensions: { Name: string; Value: string }[] = [] - if (validatedData.dimensions) { - const parsed = JSON.parse(validatedData.dimensions) - for (const [name, value] of Object.entries(parsed)) { - dimensions.push({ Name: name, Value: String(value) }) + const dimensions: { Name: string; Value: string }[] = [] + if (validatedData.dimensions) { + const parsed = JSON.parse(validatedData.dimensions) + for (const [name, value] of Object.entries(parsed)) { + dimensions.push({ Name: name, Value: String(value) }) + } } - } - const command = new PutMetricDataCommand({ - Namespace: validatedData.namespace, - MetricData: [ - { - MetricName: validatedData.metricName, - Value: validatedData.value, - Timestamp: timestamp, - ...(validatedData.unit && { Unit: validatedData.unit as StandardUnit }), - ...(dimensions.length > 0 && { Dimensions: dimensions }), - }, - ], - }) + const command = new PutMetricDataCommand({ + Namespace: validatedData.namespace, + MetricData: [ + { + MetricName: validatedData.metricName, + Value: validatedData.value, + Timestamp: timestamp, + ...(validatedData.unit && { Unit: validatedData.unit as StandardUnit }), + ...(dimensions.length > 0 && { Dimensions: dimensions }), + }, + ], + }) - await client.send(command) + await client.send(command) - return NextResponse.json({ - success: true, - output: { + logger.info('Successfully published metric') + + return NextResponse.json({ success: true, - namespace: validatedData.namespace, - metricName: validatedData.metricName, - value: validatedData.value, - unit: validatedData.unit ?? 'None', - timestamp: timestamp.toISOString(), - }, - }) + output: { + success: true, + namespace: validatedData.namespace, + metricName: validatedData.metricName, + value: validatedData.value, + unit: validatedData.unit ?? 'None', + timestamp: timestamp.toISOString(), + }, + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = - error instanceof Error ? error.message : 'Failed to publish CloudWatch metric' - logger.error('PutMetricData failed', { error: errorMessage }) - return NextResponse.json({ error: errorMessage }, { status: 500 }) + logger.error('PutMetricData failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to publish CloudWatch metric: ${toError(error).message}` }, + { status: 500 } + ) } }) diff --git a/apps/sim/app/api/tools/cloudwatch/query-logs/route.ts b/apps/sim/app/api/tools/cloudwatch/query-logs/route.ts index 01a0b753220..822eb14a5dd 100644 --- a/apps/sim/app/api/tools/cloudwatch/query-logs/route.ts +++ b/apps/sim/app/api/tools/cloudwatch/query-logs/route.ts @@ -1,5 +1,6 @@ import { StartQueryCommand } from '@aws-sdk/client-cloudwatch-logs' import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -32,47 +33,57 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = QueryLogsSchema.parse(body) + logger.info('Running CloudWatch Log Insights query') + const client = createCloudWatchLogsClient({ region: validatedData.region, accessKeyId: validatedData.accessKeyId, secretAccessKey: validatedData.secretAccessKey, }) - const startQueryCommand = new StartQueryCommand({ - logGroupNames: validatedData.logGroupNames, - queryString: validatedData.queryString, - startTime: validatedData.startTime, - endTime: validatedData.endTime, - ...(validatedData.limit !== undefined && { limit: validatedData.limit }), - }) + try { + const startQueryCommand = new StartQueryCommand({ + logGroupNames: validatedData.logGroupNames, + queryString: validatedData.queryString, + startTime: validatedData.startTime, + endTime: validatedData.endTime, + ...(validatedData.limit !== undefined && { limit: validatedData.limit }), + }) - const startQueryResponse = await client.send(startQueryCommand) - const queryId = startQueryResponse.queryId + const startQueryResponse = await client.send(startQueryCommand) + const queryId = startQueryResponse.queryId - if (!queryId) { - throw new Error('Failed to start CloudWatch Log Insights query: no queryId returned') - } + if (!queryId) { + throw new Error('Failed to start CloudWatch Log Insights query: no queryId returned') + } - const result = await pollQueryResults(client, queryId) + const result = await pollQueryResults(client, queryId) - return NextResponse.json({ - success: true, - output: { - results: result.results, - statistics: result.statistics, - status: result.status, - }, - }) + logger.info(`Query completed with status: ${result.status}`) + + return NextResponse.json({ + success: true, + output: { + results: result.results, + statistics: result.statistics, + status: result.status, + }, + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = - error instanceof Error ? error.message : 'CloudWatch Log Insights query failed' - logger.error('QueryLogs failed', { error: errorMessage }) - return NextResponse.json({ error: errorMessage }, { status: 500 }) + logger.error('QueryLogs failed', { error: toError(error).message }) + return NextResponse.json( + { error: `CloudWatch Log Insights query failed: ${toError(error).message}` }, + { status: 500 } + ) } }) diff --git a/apps/sim/app/api/tools/cloudwatch/utils.ts b/apps/sim/app/api/tools/cloudwatch/utils.ts index 5d4b4dd9a6a..966aa67b2b4 100644 --- a/apps/sim/app/api/tools/cloudwatch/utils.ts +++ b/apps/sim/app/api/tools/cloudwatch/utils.ts @@ -143,7 +143,7 @@ export async function getLogEvents( }[] }> { const command = new GetLogEventsCommand({ - logGroupIdentifier: logGroupName, + logGroupName, logStreamName, ...(options?.startTime !== undefined && { startTime: options.startTime * 1000 }), ...(options?.endTime !== undefined && { endTime: options.endTime * 1000 }), diff --git a/apps/sim/app/api/tools/dynamodb/delete/route.ts b/apps/sim/app/api/tools/dynamodb/delete/route.ts index 2915b96ede6..0d883c7c104 100644 --- a/apps/sim/app/api/tools/dynamodb/delete/route.ts +++ b/apps/sim/app/api/tools/dynamodb/delete/route.ts @@ -1,9 +1,13 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { createDynamoDBClient, deleteItem } from '@/app/api/tools/dynamodb/utils' +const logger = createLogger('DynamoDBDeleteAPI') + const DeleteSchema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), @@ -13,6 +17,8 @@ const DeleteSchema = z.object({ message: 'Key is required', }), conditionExpression: z.string().optional(), + expressionAttributeNames: z.record(z.string()).optional(), + expressionAttributeValues: z.record(z.unknown()).optional(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -25,30 +31,39 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = DeleteSchema.parse(body) + logger.info(`Deleting item from table '${validatedData.tableName}'`) + const client = createDynamoDBClient({ region: validatedData.region, accessKeyId: validatedData.accessKeyId, secretAccessKey: validatedData.secretAccessKey, }) - await deleteItem( - client, - validatedData.tableName, - validatedData.key, - validatedData.conditionExpression - ) + try { + await deleteItem(client, validatedData.tableName, validatedData.key, { + conditionExpression: validatedData.conditionExpression, + expressionAttributeNames: validatedData.expressionAttributeNames, + expressionAttributeValues: validatedData.expressionAttributeValues, + }) - return NextResponse.json({ - message: 'Item deleted successfully', - }) + logger.info(`Delete completed for table '${validatedData.tableName}'`) + + return NextResponse.json({ + message: 'Item deleted successfully', + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'DynamoDB delete failed' + const errorMessage = toError(error).message || 'DynamoDB delete failed' + logger.error('DynamoDB delete failed:', error) return NextResponse.json({ error: errorMessage }, { status: 500 }) } }) diff --git a/apps/sim/app/api/tools/dynamodb/get/route.ts b/apps/sim/app/api/tools/dynamodb/get/route.ts index 173f958aafe..752f7360b01 100644 --- a/apps/sim/app/api/tools/dynamodb/get/route.ts +++ b/apps/sim/app/api/tools/dynamodb/get/route.ts @@ -1,9 +1,13 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { createDynamoDBClient, getItem } from '@/app/api/tools/dynamodb/utils' +const logger = createLogger('DynamoDBGetAPI') + const GetSchema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), @@ -31,31 +35,41 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = GetSchema.parse(body) + logger.info(`Getting item from table '${validatedData.tableName}'`) + const client = createDynamoDBClient({ region: validatedData.region, accessKeyId: validatedData.accessKeyId, secretAccessKey: validatedData.secretAccessKey, }) - const result = await getItem( - client, - validatedData.tableName, - validatedData.key, - validatedData.consistentRead - ) + try { + const result = await getItem( + client, + validatedData.tableName, + validatedData.key, + validatedData.consistentRead + ) + + logger.info(`Get item completed for table '${validatedData.tableName}'`) - return NextResponse.json({ - message: result.item ? 'Item retrieved successfully' : 'Item not found', - item: result.item, - }) + return NextResponse.json({ + message: result.item ? 'Item retrieved successfully' : 'Item not found', + item: result.item, + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'DynamoDB get failed' + const errorMessage = toError(error).message || 'DynamoDB get failed' + logger.error('DynamoDB get failed:', error) return NextResponse.json({ error: errorMessage }, { status: 500 }) } }) diff --git a/apps/sim/app/api/tools/dynamodb/introspect/route.ts b/apps/sim/app/api/tools/dynamodb/introspect/route.ts index 7111e8f0b73..33db3e79ff8 100644 --- a/apps/sim/app/api/tools/dynamodb/introspect/route.ts +++ b/apps/sim/app/api/tools/dynamodb/introspect/route.ts @@ -1,5 +1,5 @@ import { createLogger } from '@sim/logger' -import { generateId } from '@sim/utils/id' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -16,8 +16,6 @@ const IntrospectSchema = z.object({ }) export const POST = withRouteHandler(async (request: NextRequest) => { - const requestId = generateId().slice(0, 8) - try { const auth = await checkInternalAuth(request) if (!auth.success || !auth.userId) { @@ -27,7 +25,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const params = IntrospectSchema.parse(body) - logger.info(`[${requestId}] Introspecting DynamoDB in region ${params.region}`) + logger.info(`Introspecting DynamoDB in region ${params.region}`) const client = createRawDynamoDBClient({ region: params.region, @@ -39,10 +37,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const { tables } = await listTables(client) if (params.tableName) { - logger.info(`[${requestId}] Describing table: ${params.tableName}`) + logger.info(`Describing table: ${params.tableName}`) const { tableDetails } = await describeTable(client, params.tableName) - logger.info(`[${requestId}] Table description completed for '${params.tableName}'`) + logger.info(`Table description completed for '${params.tableName}'`) return NextResponse.json({ message: `Table '${params.tableName}' described successfully.`, @@ -51,7 +49,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { }) } - logger.info(`[${requestId}] Listed ${tables.length} tables`) + logger.info(`Listed ${tables.length} tables`) return NextResponse.json({ message: `Found ${tables.length} table(s) in region '${params.region}'.`, @@ -62,15 +60,15 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } } catch (error) { if (error instanceof z.ZodError) { - logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors }) + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: 'Invalid request data', details: error.errors }, { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' - logger.error(`[${requestId}] DynamoDB introspection failed:`, error) + const errorMessage = toError(error).message || 'Unknown error occurred' + logger.error('DynamoDB introspection failed:', error) return NextResponse.json( { error: `DynamoDB introspection failed: ${errorMessage}` }, diff --git a/apps/sim/app/api/tools/dynamodb/put/route.ts b/apps/sim/app/api/tools/dynamodb/put/route.ts index d094c630ec1..9adb667ab9d 100644 --- a/apps/sim/app/api/tools/dynamodb/put/route.ts +++ b/apps/sim/app/api/tools/dynamodb/put/route.ts @@ -1,9 +1,13 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { createDynamoDBClient, putItem } from '@/app/api/tools/dynamodb/utils' +const logger = createLogger('DynamoDBPutAPI') + const PutSchema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), @@ -12,6 +16,9 @@ const PutSchema = z.object({ item: z.record(z.unknown()).refine((val) => Object.keys(val).length > 0, { message: 'Item is required', }), + conditionExpression: z.string().optional(), + expressionAttributeNames: z.record(z.string()).optional(), + expressionAttributeValues: z.record(z.unknown()).optional(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -24,26 +31,40 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = PutSchema.parse(body) + logger.info(`Putting item into table '${validatedData.tableName}'`) + const client = createDynamoDBClient({ region: validatedData.region, accessKeyId: validatedData.accessKeyId, secretAccessKey: validatedData.secretAccessKey, }) - await putItem(client, validatedData.tableName, validatedData.item) + try { + await putItem(client, validatedData.tableName, validatedData.item, { + conditionExpression: validatedData.conditionExpression, + expressionAttributeNames: validatedData.expressionAttributeNames, + expressionAttributeValues: validatedData.expressionAttributeValues, + }) - return NextResponse.json({ - message: 'Item created successfully', - item: validatedData.item, - }) + logger.info(`Put item completed for table '${validatedData.tableName}'`) + + return NextResponse.json({ + message: 'Item created successfully', + item: validatedData.item, + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'DynamoDB put failed' + const errorMessage = toError(error).message || 'DynamoDB put failed' + logger.error('DynamoDB put failed:', error) return NextResponse.json({ error: errorMessage }, { status: 500 }) } }) diff --git a/apps/sim/app/api/tools/dynamodb/query/route.ts b/apps/sim/app/api/tools/dynamodb/query/route.ts index 6d9d75f020e..3b0d137c1f3 100644 --- a/apps/sim/app/api/tools/dynamodb/query/route.ts +++ b/apps/sim/app/api/tools/dynamodb/query/route.ts @@ -1,9 +1,13 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { createDynamoDBClient, queryItems } from '@/app/api/tools/dynamodb/utils' +const logger = createLogger('DynamoDBQueryAPI') + const QuerySchema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), @@ -15,6 +19,8 @@ const QuerySchema = z.object({ expressionAttributeValues: z.record(z.unknown()).optional(), indexName: z.string().optional(), limit: z.number().positive().optional(), + exclusiveStartKey: z.record(z.unknown()).optional(), + scanIndexForward: z.boolean().optional(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -27,38 +33,53 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = QuerySchema.parse(body) + logger.info(`Querying table '${validatedData.tableName}'`) + const client = createDynamoDBClient({ region: validatedData.region, accessKeyId: validatedData.accessKeyId, secretAccessKey: validatedData.secretAccessKey, }) - const result = await queryItems( - client, - validatedData.tableName, - validatedData.keyConditionExpression, - { - filterExpression: validatedData.filterExpression, - expressionAttributeNames: validatedData.expressionAttributeNames, - expressionAttributeValues: validatedData.expressionAttributeValues, - indexName: validatedData.indexName, - limit: validatedData.limit, - } - ) + try { + const result = await queryItems( + client, + validatedData.tableName, + validatedData.keyConditionExpression, + { + filterExpression: validatedData.filterExpression, + expressionAttributeNames: validatedData.expressionAttributeNames, + expressionAttributeValues: validatedData.expressionAttributeValues, + indexName: validatedData.indexName, + limit: validatedData.limit, + exclusiveStartKey: validatedData.exclusiveStartKey, + scanIndexForward: validatedData.scanIndexForward, + } + ) - return NextResponse.json({ - message: `Query returned ${result.count} items`, - items: result.items, - count: result.count, - }) + logger.info( + `Query completed for table '${validatedData.tableName}', returned ${result.count} items` + ) + + return NextResponse.json({ + message: `Query returned ${result.count} items`, + items: result.items, + count: result.count, + ...(result.lastEvaluatedKey && { lastEvaluatedKey: result.lastEvaluatedKey }), + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'DynamoDB query failed' + const errorMessage = toError(error).message || 'DynamoDB query failed' + logger.error('DynamoDB query failed:', error) return NextResponse.json({ error: errorMessage }, { status: 500 }) } }) diff --git a/apps/sim/app/api/tools/dynamodb/scan/route.ts b/apps/sim/app/api/tools/dynamodb/scan/route.ts index 8033d1e84ba..8e9d401c031 100644 --- a/apps/sim/app/api/tools/dynamodb/scan/route.ts +++ b/apps/sim/app/api/tools/dynamodb/scan/route.ts @@ -1,9 +1,13 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { createDynamoDBClient, scanItems } from '@/app/api/tools/dynamodb/utils' +const logger = createLogger('DynamoDBScanAPI') + const ScanSchema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), @@ -14,6 +18,7 @@ const ScanSchema = z.object({ expressionAttributeNames: z.record(z.string()).optional(), expressionAttributeValues: z.record(z.unknown()).optional(), limit: z.number().positive().optional(), + exclusiveStartKey: z.record(z.unknown()).optional(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -26,33 +31,47 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = ScanSchema.parse(body) + logger.info(`Scanning table '${validatedData.tableName}'`) + const client = createDynamoDBClient({ region: validatedData.region, accessKeyId: validatedData.accessKeyId, secretAccessKey: validatedData.secretAccessKey, }) - const result = await scanItems(client, validatedData.tableName, { - filterExpression: validatedData.filterExpression, - projectionExpression: validatedData.projectionExpression, - expressionAttributeNames: validatedData.expressionAttributeNames, - expressionAttributeValues: validatedData.expressionAttributeValues, - limit: validatedData.limit, - }) + try { + const result = await scanItems(client, validatedData.tableName, { + filterExpression: validatedData.filterExpression, + projectionExpression: validatedData.projectionExpression, + expressionAttributeNames: validatedData.expressionAttributeNames, + expressionAttributeValues: validatedData.expressionAttributeValues, + limit: validatedData.limit, + exclusiveStartKey: validatedData.exclusiveStartKey, + }) - return NextResponse.json({ - message: `Scan returned ${result.count} items`, - items: result.items, - count: result.count, - }) + logger.info( + `Scan completed for table '${validatedData.tableName}', returned ${result.count} items` + ) + + return NextResponse.json({ + message: `Scan returned ${result.count} items`, + items: result.items, + count: result.count, + ...(result.lastEvaluatedKey && { lastEvaluatedKey: result.lastEvaluatedKey }), + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'DynamoDB scan failed' + const errorMessage = toError(error).message || 'DynamoDB scan failed' + logger.error('DynamoDB scan failed:', error) return NextResponse.json({ error: errorMessage }, { status: 500 }) } }) diff --git a/apps/sim/app/api/tools/dynamodb/update/route.ts b/apps/sim/app/api/tools/dynamodb/update/route.ts index c84b7d3f86e..f7884ad5024 100644 --- a/apps/sim/app/api/tools/dynamodb/update/route.ts +++ b/apps/sim/app/api/tools/dynamodb/update/route.ts @@ -1,9 +1,13 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { createDynamoDBClient, updateItem } from '@/app/api/tools/dynamodb/utils' +const logger = createLogger('DynamoDBUpdateAPI') + const UpdateSchema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), @@ -28,36 +32,46 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const validatedData = UpdateSchema.parse(body) + logger.info(`Updating item in table '${validatedData.tableName}'`) + const client = createDynamoDBClient({ region: validatedData.region, accessKeyId: validatedData.accessKeyId, secretAccessKey: validatedData.secretAccessKey, }) - const result = await updateItem( - client, - validatedData.tableName, - validatedData.key, - validatedData.updateExpression, - { - expressionAttributeNames: validatedData.expressionAttributeNames, - expressionAttributeValues: validatedData.expressionAttributeValues, - conditionExpression: validatedData.conditionExpression, - } - ) - - return NextResponse.json({ - message: 'Item updated successfully', - item: result.attributes, - }) + try { + const result = await updateItem( + client, + validatedData.tableName, + validatedData.key, + validatedData.updateExpression, + { + expressionAttributeNames: validatedData.expressionAttributeNames, + expressionAttributeValues: validatedData.expressionAttributeValues, + conditionExpression: validatedData.conditionExpression, + } + ) + + logger.info(`Update completed for table '${validatedData.tableName}'`) + + return NextResponse.json({ + message: 'Item updated successfully', + item: result.attributes, + }) + } finally { + client.destroy() + } } catch (error) { if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: error.errors[0]?.message ?? 'Invalid request' }, { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'DynamoDB update failed' + const errorMessage = toError(error).message || 'DynamoDB update failed' + logger.error('DynamoDB update failed:', error) return NextResponse.json({ error: errorMessage }, { status: 500 }) } }) diff --git a/apps/sim/app/api/tools/dynamodb/utils.ts b/apps/sim/app/api/tools/dynamodb/utils.ts index c71d8bbe6cb..e9cf2cb1b54 100644 --- a/apps/sim/app/api/tools/dynamodb/utils.ts +++ b/apps/sim/app/api/tools/dynamodb/utils.ts @@ -51,11 +51,23 @@ export async function getItem( export async function putItem( client: DynamoDBDocumentClient, tableName: string, - item: Record + item: Record, + options?: { + conditionExpression?: string + expressionAttributeNames?: Record + expressionAttributeValues?: Record + } ): Promise<{ success: boolean }> { const command = new PutCommand({ TableName: tableName, Item: item, + ...(options?.conditionExpression && { ConditionExpression: options.conditionExpression }), + ...(options?.expressionAttributeNames && { + ExpressionAttributeNames: options.expressionAttributeNames, + }), + ...(options?.expressionAttributeValues && { + ExpressionAttributeValues: options.expressionAttributeValues, + }), }) await client.send(command) @@ -72,8 +84,14 @@ export async function queryItems( expressionAttributeValues?: Record indexName?: string limit?: number + exclusiveStartKey?: Record + scanIndexForward?: boolean } -): Promise<{ items: Record[]; count: number }> { +): Promise<{ + items: Record[] + count: number + lastEvaluatedKey?: Record +}> { const command = new QueryCommand({ TableName: tableName, KeyConditionExpression: keyConditionExpression, @@ -86,12 +104,15 @@ export async function queryItems( }), ...(options?.indexName && { IndexName: options.indexName }), ...(options?.limit && { Limit: options.limit }), + ...(options?.exclusiveStartKey && { ExclusiveStartKey: options.exclusiveStartKey }), + ...(options?.scanIndexForward !== undefined && { ScanIndexForward: options.scanIndexForward }), }) const response = await client.send(command) return { items: (response.Items as Record[]) || [], count: response.Count || 0, + lastEvaluatedKey: response.LastEvaluatedKey as Record | undefined, } } @@ -104,8 +125,13 @@ export async function scanItems( expressionAttributeNames?: Record expressionAttributeValues?: Record limit?: number + exclusiveStartKey?: Record } -): Promise<{ items: Record[]; count: number }> { +): Promise<{ + items: Record[] + count: number + lastEvaluatedKey?: Record +}> { const command = new ScanCommand({ TableName: tableName, ...(options?.filterExpression && { FilterExpression: options.filterExpression }), @@ -117,12 +143,14 @@ export async function scanItems( ExpressionAttributeValues: options.expressionAttributeValues, }), ...(options?.limit && { Limit: options.limit }), + ...(options?.exclusiveStartKey && { ExclusiveStartKey: options.exclusiveStartKey }), }) const response = await client.send(command) return { items: (response.Items as Record[]) || [], count: response.Count || 0, + lastEvaluatedKey: response.LastEvaluatedKey as Record | undefined, } } @@ -161,12 +189,22 @@ export async function deleteItem( client: DynamoDBDocumentClient, tableName: string, key: Record, - conditionExpression?: string + options?: { + conditionExpression?: string + expressionAttributeNames?: Record + expressionAttributeValues?: Record + } ): Promise<{ success: boolean }> { const command = new DeleteCommand({ TableName: tableName, Key: key, - ...(conditionExpression && { ConditionExpression: conditionExpression }), + ...(options?.conditionExpression && { ConditionExpression: options.conditionExpression }), + ...(options?.expressionAttributeNames && { + ExpressionAttributeNames: options.expressionAttributeNames, + }), + ...(options?.expressionAttributeValues && { + ExpressionAttributeValues: options.expressionAttributeValues, + }), }) await client.send(command) diff --git a/apps/sim/app/api/tools/iam/add-user-to-group/route.ts b/apps/sim/app/api/tools/iam/add-user-to-group/route.ts index 5959ad9ad91..2026516268d 100644 --- a/apps/sim/app/api/tools/iam/add-user-to-group/route.ts +++ b/apps/sim/app/api/tools/iam/add-user-to-group/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -55,10 +56,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to add user to group:`, error) return NextResponse.json( - { error: `Failed to add user to group: ${errorMessage}` }, + { error: `Failed to add user to group: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/attach-role-policy/route.ts b/apps/sim/app/api/tools/iam/attach-role-policy/route.ts index 34ccbfa833c..e8419901432 100644 --- a/apps/sim/app/api/tools/iam/attach-role-policy/route.ts +++ b/apps/sim/app/api/tools/iam/attach-role-policy/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -53,10 +54,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to attach role policy:`, error) return NextResponse.json( - { error: `Failed to attach role policy: ${errorMessage}` }, + { error: `Failed to attach role policy: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/attach-user-policy/route.ts b/apps/sim/app/api/tools/iam/attach-user-policy/route.ts index 1f966951560..8b3e6bbc070 100644 --- a/apps/sim/app/api/tools/iam/attach-user-policy/route.ts +++ b/apps/sim/app/api/tools/iam/attach-user-policy/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -53,10 +54,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to attach user policy:`, error) return NextResponse.json( - { error: `Failed to attach user policy: ${errorMessage}` }, + { error: `Failed to attach user policy: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/create-access-key/route.ts b/apps/sim/app/api/tools/iam/create-access-key/route.ts index 7b08b343ae5..d3be7142f4c 100644 --- a/apps/sim/app/api/tools/iam/create-access-key/route.ts +++ b/apps/sim/app/api/tools/iam/create-access-key/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -12,7 +13,7 @@ const Schema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), secretAccessKey: z.string().min(1, 'AWS secret access key is required'), - userName: z.string().optional(), + userName: z.string().optional().nullable(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -53,10 +54,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to create access key:`, error) return NextResponse.json( - { error: `Failed to create access key: ${errorMessage}` }, + { error: `Failed to create access key: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/create-role/route.ts b/apps/sim/app/api/tools/iam/create-role/route.ts index 2b035f4f743..fd0518f5ac3 100644 --- a/apps/sim/app/api/tools/iam/create-role/route.ts +++ b/apps/sim/app/api/tools/iam/create-role/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -14,9 +15,9 @@ const Schema = z.object({ secretAccessKey: z.string().min(1, 'AWS secret access key is required'), roleName: z.string().min(1, 'Role name is required'), assumeRolePolicyDocument: z.string().min(1, 'Assume role policy document is required'), - description: z.string().optional(), - path: z.string().optional(), - maxSessionDuration: z.number().min(3600).max(43200).optional(), + description: z.string().optional().nullable(), + path: z.string().optional().nullable(), + maxSessionDuration: z.number().min(3600).max(43200).optional().nullable(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -64,10 +65,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to create IAM role:`, error) return NextResponse.json( - { error: `Failed to create IAM role: ${errorMessage}` }, + { error: `Failed to create IAM role: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/create-user/route.ts b/apps/sim/app/api/tools/iam/create-user/route.ts index 9b1df15b622..970eb7deb81 100644 --- a/apps/sim/app/api/tools/iam/create-user/route.ts +++ b/apps/sim/app/api/tools/iam/create-user/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -13,7 +14,7 @@ const Schema = z.object({ accessKeyId: z.string().min(1, 'AWS access key ID is required'), secretAccessKey: z.string().min(1, 'AWS secret access key is required'), userName: z.string().min(1, 'User name is required'), - path: z.string().optional(), + path: z.string().optional().nullable(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -54,10 +55,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to create IAM user:`, error) return NextResponse.json( - { error: `Failed to create IAM user: ${errorMessage}` }, + { error: `Failed to create IAM user: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/delete-access-key/route.ts b/apps/sim/app/api/tools/iam/delete-access-key/route.ts index 9b99953434e..c11cfe96854 100644 --- a/apps/sim/app/api/tools/iam/delete-access-key/route.ts +++ b/apps/sim/app/api/tools/iam/delete-access-key/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -13,7 +14,7 @@ const Schema = z.object({ accessKeyId: z.string().min(1, 'AWS access key ID is required'), secretAccessKey: z.string().min(1, 'AWS secret access key is required'), accessKeyIdToDelete: z.string().min(1, 'Access key ID to delete is required'), - userName: z.string().optional(), + userName: z.string().optional().nullable(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -51,10 +52,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to delete access key:`, error) return NextResponse.json( - { error: `Failed to delete access key: ${errorMessage}` }, + { error: `Failed to delete access key: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/delete-role/route.ts b/apps/sim/app/api/tools/iam/delete-role/route.ts index 13bacdedb2f..e2ffc26b39a 100644 --- a/apps/sim/app/api/tools/iam/delete-role/route.ts +++ b/apps/sim/app/api/tools/iam/delete-role/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -50,10 +51,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to delete IAM role:`, error) return NextResponse.json( - { error: `Failed to delete IAM role: ${errorMessage}` }, + { error: `Failed to delete IAM role: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/delete-user/route.ts b/apps/sim/app/api/tools/iam/delete-user/route.ts index 54e2402d4f0..35869e71ce4 100644 --- a/apps/sim/app/api/tools/iam/delete-user/route.ts +++ b/apps/sim/app/api/tools/iam/delete-user/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -50,10 +51,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to delete IAM user:`, error) return NextResponse.json( - { error: `Failed to delete IAM user: ${errorMessage}` }, + { error: `Failed to delete IAM user: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/detach-role-policy/route.ts b/apps/sim/app/api/tools/iam/detach-role-policy/route.ts index e5b33f1f8da..a4db5f145f9 100644 --- a/apps/sim/app/api/tools/iam/detach-role-policy/route.ts +++ b/apps/sim/app/api/tools/iam/detach-role-policy/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -53,10 +54,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to detach role policy:`, error) return NextResponse.json( - { error: `Failed to detach role policy: ${errorMessage}` }, + { error: `Failed to detach role policy: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/detach-user-policy/route.ts b/apps/sim/app/api/tools/iam/detach-user-policy/route.ts index 05b4a03022c..af73ee4ead8 100644 --- a/apps/sim/app/api/tools/iam/detach-user-policy/route.ts +++ b/apps/sim/app/api/tools/iam/detach-user-policy/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -53,10 +54,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to detach user policy:`, error) return NextResponse.json( - { error: `Failed to detach user policy: ${errorMessage}` }, + { error: `Failed to detach user policy: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/get-role/route.ts b/apps/sim/app/api/tools/iam/get-role/route.ts index 66496de54d6..db41f765f04 100644 --- a/apps/sim/app/api/tools/iam/get-role/route.ts +++ b/apps/sim/app/api/tools/iam/get-role/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -50,8 +51,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to get IAM role:`, error) - return NextResponse.json({ error: `Failed to get IAM role: ${errorMessage}` }, { status: 500 }) + return NextResponse.json( + { error: `Failed to get IAM role: ${toError(error).message}` }, + { status: 500 } + ) } }) diff --git a/apps/sim/app/api/tools/iam/get-user/route.ts b/apps/sim/app/api/tools/iam/get-user/route.ts index 7b8e4df5d5e..c17902477e9 100644 --- a/apps/sim/app/api/tools/iam/get-user/route.ts +++ b/apps/sim/app/api/tools/iam/get-user/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -12,7 +13,7 @@ const Schema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), secretAccessKey: z.string().min(1, 'AWS secret access key is required'), - userName: z.string().min(1, 'User name is required'), + userName: z.string().min(1).optional().nullable(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -37,7 +38,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { try { const result = await getUser(client, params.userName) - logger.info(`[${requestId}] Successfully retrieved IAM user "${params.userName}"`) + logger.info(`[${requestId}] Successfully retrieved IAM user "${params.userName ?? 'caller'}"`) return NextResponse.json(result) } finally { client.destroy() @@ -50,8 +51,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to get IAM user:`, error) - return NextResponse.json({ error: `Failed to get IAM user: ${errorMessage}` }, { status: 500 }) + return NextResponse.json( + { error: `Failed to get IAM user: ${toError(error).message}` }, + { status: 500 } + ) } }) diff --git a/apps/sim/app/api/tools/iam/list-attached-role-policies/route.ts b/apps/sim/app/api/tools/iam/list-attached-role-policies/route.ts new file mode 100644 index 00000000000..2e654d83ae6 --- /dev/null +++ b/apps/sim/app/api/tools/iam/list-attached-role-policies/route.ts @@ -0,0 +1,71 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createIAMClient, listAttachedRolePolicies } from '../utils' + +const logger = createLogger('IAMListAttachedRolePoliciesAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + roleName: z.string().min(1, 'Role name is required'), + pathPrefix: z.string().optional().nullable(), + maxItems: z.number().int().min(1).max(1000).optional().nullable(), + marker: z.string().optional().nullable(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateId().slice(0, 8) + + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info(`[${requestId}] Listing policies attached to IAM role "${params.roleName}"`) + + const client = createIAMClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await listAttachedRolePolicies( + client, + params.roleName, + params.pathPrefix, + params.maxItems, + params.marker + ) + logger.info( + `[${requestId}] Found ${result.count} policies attached to role "${params.roleName}"` + ) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error(`[${requestId}] Failed to list attached role policies:`, error) + return NextResponse.json( + { error: `Failed to list attached role policies: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/iam/list-attached-user-policies/route.ts b/apps/sim/app/api/tools/iam/list-attached-user-policies/route.ts new file mode 100644 index 00000000000..1e47546f95d --- /dev/null +++ b/apps/sim/app/api/tools/iam/list-attached-user-policies/route.ts @@ -0,0 +1,71 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createIAMClient, listAttachedUserPolicies } from '../utils' + +const logger = createLogger('IAMListAttachedUserPoliciesAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + userName: z.string().min(1, 'User name is required'), + pathPrefix: z.string().optional().nullable(), + maxItems: z.number().int().min(1).max(1000).optional().nullable(), + marker: z.string().optional().nullable(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateId().slice(0, 8) + + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info(`[${requestId}] Listing policies attached to IAM user "${params.userName}"`) + + const client = createIAMClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await listAttachedUserPolicies( + client, + params.userName, + params.pathPrefix, + params.maxItems, + params.marker + ) + logger.info( + `[${requestId}] Found ${result.count} policies attached to user "${params.userName}"` + ) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error(`[${requestId}] Failed to list attached user policies:`, error) + return NextResponse.json( + { error: `Failed to list attached user policies: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/iam/list-groups/route.ts b/apps/sim/app/api/tools/iam/list-groups/route.ts index 8c22793c48e..2303ba392e0 100644 --- a/apps/sim/app/api/tools/iam/list-groups/route.ts +++ b/apps/sim/app/api/tools/iam/list-groups/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -12,9 +13,9 @@ const Schema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), secretAccessKey: z.string().min(1, 'AWS secret access key is required'), - pathPrefix: z.string().optional(), - maxItems: z.number().min(1).max(1000).optional(), - marker: z.string().optional(), + pathPrefix: z.string().optional().nullable(), + maxItems: z.number().min(1).max(1000).optional().nullable(), + marker: z.string().optional().nullable(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -52,10 +53,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to list IAM groups:`, error) return NextResponse.json( - { error: `Failed to list IAM groups: ${errorMessage}` }, + { error: `Failed to list IAM groups: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/list-policies/route.ts b/apps/sim/app/api/tools/iam/list-policies/route.ts index 76172b4ac23..4d56c7022b4 100644 --- a/apps/sim/app/api/tools/iam/list-policies/route.ts +++ b/apps/sim/app/api/tools/iam/list-policies/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -12,11 +13,11 @@ const Schema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), secretAccessKey: z.string().min(1, 'AWS secret access key is required'), - scope: z.string().optional(), - onlyAttached: z.boolean().optional(), - pathPrefix: z.string().optional(), - maxItems: z.number().min(1).max(1000).optional(), - marker: z.string().optional(), + scope: z.string().optional().nullable(), + onlyAttached: z.boolean().optional().nullable(), + pathPrefix: z.string().optional().nullable(), + maxItems: z.number().min(1).max(1000).optional().nullable(), + marker: z.string().optional().nullable(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -61,10 +62,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to list IAM policies:`, error) return NextResponse.json( - { error: `Failed to list IAM policies: ${errorMessage}` }, + { error: `Failed to list IAM policies: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/list-roles/route.ts b/apps/sim/app/api/tools/iam/list-roles/route.ts index 3cff739b12e..35f7720294b 100644 --- a/apps/sim/app/api/tools/iam/list-roles/route.ts +++ b/apps/sim/app/api/tools/iam/list-roles/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -12,9 +13,9 @@ const Schema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), secretAccessKey: z.string().min(1, 'AWS secret access key is required'), - pathPrefix: z.string().optional(), - maxItems: z.number().min(1).max(1000).optional(), - marker: z.string().optional(), + pathPrefix: z.string().optional().nullable(), + maxItems: z.number().min(1).max(1000).optional().nullable(), + marker: z.string().optional().nullable(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -52,10 +53,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to list IAM roles:`, error) return NextResponse.json( - { error: `Failed to list IAM roles: ${errorMessage}` }, + { error: `Failed to list IAM roles: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/list-users/route.ts b/apps/sim/app/api/tools/iam/list-users/route.ts index 521e3d11491..c9fcbd557c9 100644 --- a/apps/sim/app/api/tools/iam/list-users/route.ts +++ b/apps/sim/app/api/tools/iam/list-users/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -12,9 +13,9 @@ const Schema = z.object({ region: z.string().min(1, 'AWS region is required'), accessKeyId: z.string().min(1, 'AWS access key ID is required'), secretAccessKey: z.string().min(1, 'AWS secret access key is required'), - pathPrefix: z.string().optional(), - maxItems: z.number().min(1).max(1000).optional(), - marker: z.string().optional(), + pathPrefix: z.string().optional().nullable(), + maxItems: z.number().min(1).max(1000).optional().nullable(), + marker: z.string().optional().nullable(), }) export const POST = withRouteHandler(async (request: NextRequest) => { @@ -52,10 +53,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to list IAM users:`, error) return NextResponse.json( - { error: `Failed to list IAM users: ${errorMessage}` }, + { error: `Failed to list IAM users: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/remove-user-from-group/route.ts b/apps/sim/app/api/tools/iam/remove-user-from-group/route.ts index 854b7154df2..64ead12dd98 100644 --- a/apps/sim/app/api/tools/iam/remove-user-from-group/route.ts +++ b/apps/sim/app/api/tools/iam/remove-user-from-group/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -57,10 +58,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' logger.error(`[${requestId}] Failed to remove user from group:`, error) return NextResponse.json( - { error: `Failed to remove user from group: ${errorMessage}` }, + { error: `Failed to remove user from group: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/iam/simulate-principal-policy/route.ts b/apps/sim/app/api/tools/iam/simulate-principal-policy/route.ts new file mode 100644 index 00000000000..02954ea88dd --- /dev/null +++ b/apps/sim/app/api/tools/iam/simulate-principal-policy/route.ts @@ -0,0 +1,73 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createIAMClient, simulatePrincipalPolicy } from '../utils' + +const logger = createLogger('IAMSimulatePrincipalPolicyAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + policySourceArn: z.string().min(1, 'Policy source ARN is required'), + actionNames: z.string().min(1, 'Action names are required'), + resourceArns: z.string().optional().nullable(), + maxResults: z.number().int().min(1).max(1000).optional().nullable(), + marker: z.string().optional().nullable(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateId().slice(0, 8) + + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info( + `[${requestId}] Simulating principal policy for "${params.policySourceArn}" on actions: ${params.actionNames}` + ) + + const client = createIAMClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await simulatePrincipalPolicy( + client, + params.policySourceArn, + params.actionNames, + params.resourceArns, + params.maxResults, + params.marker + ) + logger.info(`[${requestId}] Simulation complete: ${result.count} results`) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error(`[${requestId}] Failed to simulate principal policy:`, error) + return NextResponse.json( + { error: `Failed to simulate principal policy: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/iam/utils.ts b/apps/sim/app/api/tools/iam/utils.ts index b711fc0c2ae..53cab56a3ba 100644 --- a/apps/sim/app/api/tools/iam/utils.ts +++ b/apps/sim/app/api/tools/iam/utils.ts @@ -1,4 +1,11 @@ -import type { Group, Policy, PolicyScopeType, Role, User } from '@aws-sdk/client-iam' +import type { + AttachedPolicy, + Group, + Policy, + PolicyScopeType, + Role, + User, +} from '@aws-sdk/client-iam' import { AddUserToGroupCommand, AttachRolePolicyCommand, @@ -14,11 +21,14 @@ import { GetRoleCommand, GetUserCommand, IAMClient, + ListAttachedRolePoliciesCommand, + ListAttachedUserPoliciesCommand, ListGroupsCommand, ListPoliciesCommand, ListRolesCommand, ListUsersCommand, RemoveUserFromGroupCommand, + SimulatePrincipalPolicyCommand, } from '@aws-sdk/client-iam' import type { IAMConnectionConfig } from '@/tools/iam/types' @@ -62,8 +72,8 @@ export async function listUsers( } } -export async function getUser(client: IAMClient, userName: string) { - const command = new GetUserCommand({ UserName: userName }) +export async function getUser(client: IAMClient, userName?: string | null) { + const command = new GetUserCommand(userName ? { UserName: userName } : {}) const response = await client.send(command) const user = response.User @@ -338,3 +348,106 @@ export async function removeUserFromGroup(client: IAMClient, userName: string, g }) await client.send(command) } + +export async function listAttachedRolePolicies( + client: IAMClient, + roleName: string, + pathPrefix?: string | null, + maxItems?: number | null, + marker?: string | null +) { + const command = new ListAttachedRolePoliciesCommand({ + RoleName: roleName, + ...(pathPrefix ? { PathPrefix: pathPrefix } : {}), + ...(maxItems ? { MaxItems: maxItems } : {}), + ...(marker ? { Marker: marker } : {}), + }) + + const response = await client.send(command) + const attachedPolicies = (response.AttachedPolicies ?? []).map((p: AttachedPolicy) => ({ + policyName: p.PolicyName ?? '', + policyArn: p.PolicyArn ?? '', + })) + + return { + attachedPolicies, + isTruncated: response.IsTruncated ?? false, + marker: response.Marker ?? null, + count: attachedPolicies.length, + } +} + +export async function listAttachedUserPolicies( + client: IAMClient, + userName: string, + pathPrefix?: string | null, + maxItems?: number | null, + marker?: string | null +) { + const command = new ListAttachedUserPoliciesCommand({ + UserName: userName, + ...(pathPrefix ? { PathPrefix: pathPrefix } : {}), + ...(maxItems ? { MaxItems: maxItems } : {}), + ...(marker ? { Marker: marker } : {}), + }) + + const response = await client.send(command) + const attachedPolicies = (response.AttachedPolicies ?? []).map((p: AttachedPolicy) => ({ + policyName: p.PolicyName ?? '', + policyArn: p.PolicyArn ?? '', + })) + + return { + attachedPolicies, + isTruncated: response.IsTruncated ?? false, + marker: response.Marker ?? null, + count: attachedPolicies.length, + } +} + +export async function simulatePrincipalPolicy( + client: IAMClient, + policySourceArn: string, + actionNames: string, + resourceArns?: string | null, + maxResults?: number | null, + marker?: string | null +) { + const actions = actionNames + .split(',') + .map((a) => a.trim()) + .filter(Boolean) + const resources = resourceArns + ? resourceArns + .split(',') + .map((r) => r.trim()) + .filter(Boolean) + : ['*'] + + const command = new SimulatePrincipalPolicyCommand({ + PolicySourceArn: policySourceArn, + ActionNames: actions, + ResourceArns: resources, + ...(maxResults ? { MaxItems: maxResults } : {}), + ...(marker ? { Marker: marker } : {}), + }) + + const response = await client.send(command) + const evaluationResults = (response.EvaluationResults ?? []).map((r) => ({ + evalActionName: r.EvalActionName ?? '', + evalResourceName: r.EvalResourceName ?? '', + evalDecision: r.EvalDecision ?? '', + matchedStatements: (r.MatchedStatements ?? []).map((s) => ({ + statementId: s.StatementId ?? '', + sourcePolicyId: s.SourcePolicyId ?? '', + })), + missingContextValues: (r.MissingContextValues ?? []).map((v) => v.ContextKeyName ?? ''), + })) + + return { + evaluationResults, + isTruncated: response.IsTruncated ?? false, + marker: response.Marker ?? null, + count: evaluationResults.length, + } +} diff --git a/apps/sim/app/api/tools/identity-center/check-assignment-deletion-status/route.ts b/apps/sim/app/api/tools/identity-center/check-assignment-deletion-status/route.ts new file mode 100644 index 00000000000..03ac550b77a --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/check-assignment-deletion-status/route.ts @@ -0,0 +1,60 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { checkAssignmentDeletionStatus, createSSOAdminClient } from '../utils' + +const logger = createLogger('IdentityCenterCheckAssignmentDeletionStatusAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + instanceArn: z.string().min(1, 'Instance ARN is required'), + requestId: z.string().min(1, 'Request ID is required'), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info(`Checking assignment deletion status for request ${params.requestId}`) + + const client = createSSOAdminClient(params) + try { + const result = await checkAssignmentDeletionStatus( + client, + params.instanceArn, + params.requestId + ) + logger.info(`Assignment deletion status: ${result.status}`) + return NextResponse.json({ + message: `Assignment deletion status: ${result.status}`, + ...result, + }) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to check assignment deletion status:', error) + return NextResponse.json( + { error: `Failed to check assignment deletion status: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/check-assignment-status/route.ts b/apps/sim/app/api/tools/identity-center/check-assignment-status/route.ts new file mode 100644 index 00000000000..3de196f9770 --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/check-assignment-status/route.ts @@ -0,0 +1,60 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { checkAssignmentCreationStatus, createSSOAdminClient } from '../utils' + +const logger = createLogger('IdentityCenterCheckAssignmentStatusAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + instanceArn: z.string().min(1, 'Instance ARN is required'), + requestId: z.string().min(1, 'Request ID is required'), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info(`Checking assignment status for request ${params.requestId}`) + + const client = createSSOAdminClient(params) + try { + const result = await checkAssignmentCreationStatus( + client, + params.instanceArn, + params.requestId + ) + logger.info(`Assignment status: ${result.status}`) + return NextResponse.json({ + message: `Assignment status: ${result.status}`, + ...result, + }) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to check assignment status:', error) + return NextResponse.json( + { error: `Failed to check assignment status: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/create-account-assignment/route.ts b/apps/sim/app/api/tools/identity-center/create-account-assignment/route.ts new file mode 100644 index 00000000000..1cc0e31266b --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/create-account-assignment/route.ts @@ -0,0 +1,80 @@ +import { + CreateAccountAssignmentCommand, + type PrincipalType, + type TargetType, +} from '@aws-sdk/client-sso-admin' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSSOAdminClient, mapAssignmentStatus } from '../utils' + +const logger = createLogger('IdentityCenterCreateAccountAssignmentAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + instanceArn: z.string().min(1, 'Instance ARN is required'), + accountId: z.string().min(1, 'Account ID is required'), + permissionSetArn: z.string().min(1, 'Permission set ARN is required'), + principalType: z.enum(['USER', 'GROUP']), + principalId: z.string().min(1, 'Principal ID is required'), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info( + `Creating account assignment for ${params.principalType} ${params.principalId} on account ${params.accountId}` + ) + + const client = createSSOAdminClient(params) + try { + const command = new CreateAccountAssignmentCommand({ + InstanceArn: params.instanceArn, + TargetId: params.accountId, + TargetType: 'AWS_ACCOUNT' as TargetType, + PermissionSetArn: params.permissionSetArn, + PrincipalType: params.principalType as PrincipalType, + PrincipalId: params.principalId, + }) + const response = await client.send(command) + const status = response.AccountAssignmentCreationStatus ?? {} + const result = mapAssignmentStatus(status) + + logger.info( + `Account assignment creation initiated with status ${result.status}, requestId ${result.requestId}` + ) + + return NextResponse.json({ + message: `Account assignment creation ${result.status === 'SUCCEEDED' ? 'succeeded' : 'initiated'}`, + ...result, + }) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to create account assignment:', error) + return NextResponse.json( + { error: `Failed to create account assignment: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/delete-account-assignment/route.ts b/apps/sim/app/api/tools/identity-center/delete-account-assignment/route.ts new file mode 100644 index 00000000000..8f15ba2dc12 --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/delete-account-assignment/route.ts @@ -0,0 +1,80 @@ +import { + DeleteAccountAssignmentCommand, + type PrincipalType, + type TargetType, +} from '@aws-sdk/client-sso-admin' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSSOAdminClient, mapAssignmentStatus } from '../utils' + +const logger = createLogger('IdentityCenterDeleteAccountAssignmentAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + instanceArn: z.string().min(1, 'Instance ARN is required'), + accountId: z.string().min(1, 'Account ID is required'), + permissionSetArn: z.string().min(1, 'Permission set ARN is required'), + principalType: z.enum(['USER', 'GROUP']), + principalId: z.string().min(1, 'Principal ID is required'), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info( + `Deleting account assignment for ${params.principalType} ${params.principalId} on account ${params.accountId}` + ) + + const client = createSSOAdminClient(params) + try { + const command = new DeleteAccountAssignmentCommand({ + InstanceArn: params.instanceArn, + TargetId: params.accountId, + TargetType: 'AWS_ACCOUNT' as TargetType, + PermissionSetArn: params.permissionSetArn, + PrincipalType: params.principalType as PrincipalType, + PrincipalId: params.principalId, + }) + const response = await client.send(command) + const status = response.AccountAssignmentDeletionStatus ?? {} + const result = mapAssignmentStatus(status) + + logger.info( + `Account assignment deletion initiated with status ${result.status}, requestId ${result.requestId}` + ) + + return NextResponse.json({ + message: `Account assignment deletion ${result.status === 'SUCCEEDED' ? 'succeeded' : 'initiated'}`, + ...result, + }) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to delete account assignment:', error) + return NextResponse.json( + { error: `Failed to delete account assignment: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/describe-account/route.ts b/apps/sim/app/api/tools/identity-center/describe-account/route.ts new file mode 100644 index 00000000000..4a4b213d26b --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/describe-account/route.ts @@ -0,0 +1,52 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createOrganizationsClient, describeAccount } from '../utils' + +const logger = createLogger('IdentityCenterDescribeAccountAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + accountId: z.string().min(12, 'Account ID must be 12 digits').max(12), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info(`Describing AWS account ${params.accountId}`) + + const client = createOrganizationsClient(params) + try { + const result = await describeAccount(client, params.accountId) + logger.info(`Successfully described account ${result.name}`) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to describe account:', error) + return NextResponse.json( + { error: `Failed to describe account: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/get-group/route.ts b/apps/sim/app/api/tools/identity-center/get-group/route.ts new file mode 100644 index 00000000000..fabdd67afa1 --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/get-group/route.ts @@ -0,0 +1,55 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createIdentityStoreClient, getGroupByDisplayName } from '../utils' + +const logger = createLogger('IdentityCenterGetGroupAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + identityStoreId: z.string().min(1, 'Identity Store ID is required'), + displayName: z.string().min(1, 'Group display name is required'), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info( + `Looking up group "${params.displayName}" in identity store ${params.identityStoreId}` + ) + + const client = createIdentityStoreClient(params) + try { + const result = await getGroupByDisplayName(client, params.identityStoreId, params.displayName) + logger.info(`Successfully found group ${result.groupId}`) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to get group:', error) + return NextResponse.json( + { error: `Failed to get group: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/get-user/route.ts b/apps/sim/app/api/tools/identity-center/get-user/route.ts new file mode 100644 index 00000000000..9023213ac7b --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/get-user/route.ts @@ -0,0 +1,53 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createIdentityStoreClient, getUserByEmail } from '../utils' + +const logger = createLogger('IdentityCenterGetUserAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + identityStoreId: z.string().min(1, 'Identity Store ID is required'), + email: z.string().email('Valid email address is required'), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info(`Looking up user by email in identity store ${params.identityStoreId}`) + + const client = createIdentityStoreClient(params) + try { + const result = await getUserByEmail(client, params.identityStoreId, params.email) + logger.info(`Successfully found user ${result.userId}`) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to get user:', error) + return NextResponse.json( + { error: `Failed to get user: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/list-account-assignments/route.ts b/apps/sim/app/api/tools/identity-center/list-account-assignments/route.ts new file mode 100644 index 00000000000..770a7e0fb0d --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/list-account-assignments/route.ts @@ -0,0 +1,63 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSSOAdminClient, listAccountAssignmentsForPrincipal } from '../utils' + +const logger = createLogger('IdentityCenterListAccountAssignmentsAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + instanceArn: z.string().min(1, 'Instance ARN is required'), + principalId: z.string().min(1, 'Principal ID is required'), + principalType: z.enum(['USER', 'GROUP']), + maxResults: z.number().min(1).max(100).optional(), + nextToken: z.string().optional(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info(`Listing account assignments for ${params.principalType} ${params.principalId}`) + + const client = createSSOAdminClient(params) + try { + const result = await listAccountAssignmentsForPrincipal( + client, + params.instanceArn, + params.principalId, + params.principalType, + params.maxResults, + params.nextToken + ) + logger.info(`Successfully listed ${result.count} account assignments`) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to list account assignments:', error) + return NextResponse.json( + { error: `Failed to list account assignments: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/list-accounts/route.ts b/apps/sim/app/api/tools/identity-center/list-accounts/route.ts new file mode 100644 index 00000000000..f183151642e --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/list-accounts/route.ts @@ -0,0 +1,53 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createOrganizationsClient, listAccounts } from '../utils' + +const logger = createLogger('IdentityCenterListAccountsAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + maxResults: z.number().min(1).max(20).optional(), + nextToken: z.string().optional(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info('Listing AWS accounts') + + const client = createOrganizationsClient(params) + try { + const result = await listAccounts(client, params.maxResults, params.nextToken) + logger.info(`Successfully listed ${result.count} accounts`) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to list AWS accounts:', error) + return NextResponse.json( + { error: `Failed to list AWS accounts: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/list-groups/route.ts b/apps/sim/app/api/tools/identity-center/list-groups/route.ts new file mode 100644 index 00000000000..e490f51dd25 --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/list-groups/route.ts @@ -0,0 +1,59 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createIdentityStoreClient, listGroups } from '../utils' + +const logger = createLogger('IdentityCenterListGroupsAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + identityStoreId: z.string().min(1, 'Identity Store ID is required'), + maxResults: z.number().min(1).max(100).optional(), + nextToken: z.string().optional(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info(`Listing groups in identity store ${params.identityStoreId}`) + + const client = createIdentityStoreClient(params) + try { + const result = await listGroups( + client, + params.identityStoreId, + params.maxResults, + params.nextToken + ) + logger.info(`Successfully listed ${result.count} groups`) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to list groups:', error) + return NextResponse.json( + { error: `Failed to list groups: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/list-instances/route.ts b/apps/sim/app/api/tools/identity-center/list-instances/route.ts new file mode 100644 index 00000000000..88fb4428af3 --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/list-instances/route.ts @@ -0,0 +1,53 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSSOAdminClient, listInstances } from '../utils' + +const logger = createLogger('IdentityCenterListInstancesAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + maxResults: z.number().min(1).max(100).optional(), + nextToken: z.string().optional(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info('Listing Identity Center instances') + + const client = createSSOAdminClient(params) + try { + const result = await listInstances(client, params.maxResults, params.nextToken) + logger.info(`Successfully listed ${result.count} instances`) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to list Identity Center instances:', error) + return NextResponse.json( + { error: `Failed to list Identity Center instances: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/list-permission-sets/route.ts b/apps/sim/app/api/tools/identity-center/list-permission-sets/route.ts new file mode 100644 index 00000000000..a3c4388ab61 --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/list-permission-sets/route.ts @@ -0,0 +1,59 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSSOAdminClient, listPermissionSets } from '../utils' + +const logger = createLogger('IdentityCenterListPermissionSetsAPI') + +const Schema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + instanceArn: z.string().min(1, 'Instance ARN is required'), + maxResults: z.number().min(1).max(100).optional(), + nextToken: z.string().optional(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = Schema.parse(body) + + logger.info(`Listing permission sets for instance ${params.instanceArn}`) + + const client = createSSOAdminClient(params) + try { + const result = await listPermissionSets( + client, + params.instanceArn, + params.maxResults, + params.nextToken + ) + logger.info(`Successfully listed ${result.count} permission sets`) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + logger.error('Failed to list permission sets:', error) + return NextResponse.json( + { error: `Failed to list permission sets: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/identity-center/utils.ts b/apps/sim/app/api/tools/identity-center/utils.ts new file mode 100644 index 00000000000..62c32132078 --- /dev/null +++ b/apps/sim/app/api/tools/identity-center/utils.ts @@ -0,0 +1,304 @@ +import { + DescribeGroupCommand, + DescribeUserCommand, + GetGroupIdCommand, + GetUserIdCommand, + IdentitystoreClient, + ListGroupsCommand, +} from '@aws-sdk/client-identitystore' +import { + DescribeAccountCommand, + ListAccountsCommand, + OrganizationsClient, +} from '@aws-sdk/client-organizations' +import { + type AccountAssignmentOperationStatus, + DescribeAccountAssignmentCreationStatusCommand, + DescribeAccountAssignmentDeletionStatusCommand, + DescribePermissionSetCommand, + ListAccountAssignmentsForPrincipalCommand, + ListInstancesCommand, + ListPermissionSetsCommand, + type PrincipalType, + SSOAdminClient, +} from '@aws-sdk/client-sso-admin' +import type { IdentityCenterConnectionConfig } from '@/tools/identity_center/types' + +export function createSSOAdminClient(config: IdentityCenterConnectionConfig): SSOAdminClient { + return new SSOAdminClient({ + region: config.region, + credentials: { + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + }, + }) +} + +export function createIdentityStoreClient( + config: IdentityCenterConnectionConfig +): IdentitystoreClient { + return new IdentitystoreClient({ + region: config.region, + credentials: { + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + }, + }) +} + +export function createOrganizationsClient(config: IdentityCenterConnectionConfig) { + return new OrganizationsClient({ + region: 'us-east-1', // Organizations API only available in us-east-1 + credentials: { + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + }, + }) +} + +export async function listInstances( + client: SSOAdminClient, + maxResults?: number | null, + nextToken?: string | null +) { + const command = new ListInstancesCommand({ + ...(maxResults ? { MaxResults: maxResults } : {}), + ...(nextToken ? { NextToken: nextToken } : {}), + }) + const response = await client.send(command) + const instances = (response.Instances ?? []).map((instance) => ({ + instanceArn: instance.InstanceArn ?? '', + identityStoreId: instance.IdentityStoreId ?? '', + name: instance.Name ?? null, + status: instance.Status ?? '', + statusReason: instance.StatusReason ?? null, + ownerAccountId: instance.OwnerAccountId ?? null, + createdDate: instance.CreatedDate?.toISOString() ?? null, + })) + return { instances, nextToken: response.NextToken ?? null, count: instances.length } +} + +export async function listAccounts( + client: OrganizationsClient, + maxResults?: number | null, + nextToken?: string | null +) { + const command = new ListAccountsCommand({ + ...(maxResults ? { MaxResults: maxResults } : {}), + ...(nextToken ? { NextToken: nextToken } : {}), + }) + const response = await client.send(command) + const accounts = (response.Accounts ?? []).map((account) => ({ + id: account.Id ?? '', + arn: account.Arn ?? '', + name: account.Name ?? '', + email: account.Email ?? '', + status: account.State ?? account.Status ?? '', + joinedTimestamp: account.JoinedTimestamp?.toISOString() ?? null, + })) + return { accounts, nextToken: response.NextToken ?? null, count: accounts.length } +} + +export async function listPermissionSets( + client: SSOAdminClient, + instanceArn: string, + maxResults?: number | null, + nextToken?: string | null +) { + const listCommand = new ListPermissionSetsCommand({ + InstanceArn: instanceArn, + ...(maxResults ? { MaxResults: maxResults } : {}), + ...(nextToken ? { NextToken: nextToken } : {}), + }) + const listResponse = await client.send(listCommand) + const permissionSetArns = listResponse.PermissionSets ?? [] + + const permissionSets = await Promise.all( + permissionSetArns.map(async (arn) => { + const describeCommand = new DescribePermissionSetCommand({ + InstanceArn: instanceArn, + PermissionSetArn: arn, + }) + const describeResponse = await client.send(describeCommand) + const ps = describeResponse.PermissionSet + return { + permissionSetArn: ps?.PermissionSetArn ?? arn, + name: ps?.Name ?? '', + description: ps?.Description ?? null, + sessionDuration: ps?.SessionDuration ?? null, + createdDate: ps?.CreatedDate?.toISOString() ?? null, + } + }) + ) + + return { + permissionSets, + nextToken: listResponse.NextToken ?? null, + count: permissionSets.length, + } +} + +export async function getUserByEmail( + ssoClient: IdentitystoreClient, + identityStoreId: string, + email: string +) { + const getUserIdCommand = new GetUserIdCommand({ + IdentityStoreId: identityStoreId, + AlternateIdentifier: { + UniqueAttribute: { + AttributePath: 'emails.value', + AttributeValue: email, + }, + }, + }) + const getUserIdResponse = await ssoClient.send(getUserIdCommand) + const userId = getUserIdResponse.UserId ?? '' + + const describeCommand = new DescribeUserCommand({ + IdentityStoreId: identityStoreId, + UserId: userId, + }) + const describeResponse = await ssoClient.send(describeCommand) + + const primaryEmail = + describeResponse.Emails?.find((e) => e.Primary)?.Value ?? + describeResponse.Emails?.[0]?.Value ?? + null + + return { + userId, + userName: describeResponse.UserName ?? '', + displayName: describeResponse.DisplayName ?? null, + email: primaryEmail, + } +} + +export function mapAssignmentStatus(status: AccountAssignmentOperationStatus) { + return { + status: status.Status ?? '', + requestId: status.RequestId ?? '', + accountId: status.TargetId ?? null, + permissionSetArn: status.PermissionSetArn ?? null, + principalType: status.PrincipalType ?? null, + principalId: status.PrincipalId ?? null, + failureReason: status.FailureReason ?? null, + createdDate: status.CreatedDate?.toISOString() ?? null, + } +} + +export async function checkAssignmentCreationStatus( + client: SSOAdminClient, + instanceArn: string, + requestId: string +) { + const command = new DescribeAccountAssignmentCreationStatusCommand({ + InstanceArn: instanceArn, + AccountAssignmentCreationRequestId: requestId, + }) + const response = await client.send(command) + return mapAssignmentStatus(response.AccountAssignmentCreationStatus ?? {}) +} + +export async function checkAssignmentDeletionStatus( + client: SSOAdminClient, + instanceArn: string, + requestId: string +) { + const command = new DescribeAccountAssignmentDeletionStatusCommand({ + InstanceArn: instanceArn, + AccountAssignmentDeletionRequestId: requestId, + }) + const response = await client.send(command) + return mapAssignmentStatus(response.AccountAssignmentDeletionStatus ?? {}) +} + +export async function listGroups( + client: IdentitystoreClient, + identityStoreId: string, + maxResults?: number | null, + nextToken?: string | null +) { + const command = new ListGroupsCommand({ + IdentityStoreId: identityStoreId, + ...(maxResults ? { MaxResults: maxResults } : {}), + ...(nextToken ? { NextToken: nextToken } : {}), + }) + const response = await client.send(command) + const groups = (response.Groups ?? []).map((group) => ({ + groupId: group.GroupId ?? '', + displayName: group.DisplayName ?? null, + description: group.Description ?? null, + externalIds: group.ExternalIds?.map((e) => ({ issuer: e.Issuer ?? '', id: e.Id ?? '' })) ?? [], + })) + return { groups, nextToken: response.NextToken ?? null, count: groups.length } +} + +export async function getGroupByDisplayName( + client: IdentitystoreClient, + identityStoreId: string, + displayName: string +) { + const getGroupIdCommand = new GetGroupIdCommand({ + IdentityStoreId: identityStoreId, + AlternateIdentifier: { + UniqueAttribute: { + AttributePath: 'displayName', + AttributeValue: displayName, + }, + }, + }) + const getGroupIdResponse = await client.send(getGroupIdCommand) + const groupId = getGroupIdResponse.GroupId ?? '' + + const describeCommand = new DescribeGroupCommand({ + IdentityStoreId: identityStoreId, + GroupId: groupId, + }) + const describeResponse = await client.send(describeCommand) + + return { + groupId, + displayName: describeResponse.DisplayName ?? null, + description: describeResponse.Description ?? null, + } +} + +export async function describeAccount(client: OrganizationsClient, accountId: string) { + const command = new DescribeAccountCommand({ AccountId: accountId }) + const response = await client.send(command) + const account = response.Account + return { + id: account?.Id ?? '', + arn: account?.Arn ?? '', + name: account?.Name ?? '', + email: account?.Email ?? '', + status: account?.State ?? account?.Status ?? '', + joinedTimestamp: account?.JoinedTimestamp?.toISOString() ?? null, + } +} + +export async function listAccountAssignmentsForPrincipal( + client: SSOAdminClient, + instanceArn: string, + principalId: string, + principalType: string, + maxResults?: number | null, + nextToken?: string | null +) { + const command = new ListAccountAssignmentsForPrincipalCommand({ + InstanceArn: instanceArn, + PrincipalId: principalId, + PrincipalType: principalType as PrincipalType, + ...(maxResults ? { MaxResults: maxResults } : {}), + ...(nextToken ? { NextToken: nextToken } : {}), + }) + const response = await client.send(command) + const assignments = (response.AccountAssignments ?? []).map((a) => ({ + accountId: a.AccountId ?? '', + permissionSetArn: a.PermissionSetArn ?? '', + principalType: a.PrincipalType ?? '', + principalId: a.PrincipalId ?? '', + })) + return { assignments, nextToken: response.NextToken ?? null, count: assignments.length } +} diff --git a/apps/sim/app/api/tools/ses/create-template/route.ts b/apps/sim/app/api/tools/ses/create-template/route.ts new file mode 100644 index 00000000000..5726aca3932 --- /dev/null +++ b/apps/sim/app/api/tools/ses/create-template/route.ts @@ -0,0 +1,69 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSESClient, createTemplate } from '../utils' + +const logger = createLogger('SESCreateTemplateAPI') + +const CreateTemplateSchema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + templateName: z.string().min(1, 'Template name is required'), + subjectPart: z.string().min(1, 'Subject is required'), + textPart: z.string().nullish(), + htmlPart: z.string().nullish(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = CreateTemplateSchema.parse(body) + + logger.info(`Creating SES template '${params.templateName}'`) + + const client = createSESClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await createTemplate(client, { + templateName: params.templateName, + subjectPart: params.subjectPart, + textPart: params.textPart, + htmlPart: params.htmlPart, + }) + + logger.info(`Template '${params.templateName}' created successfully`) + + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + + logger.error('Failed to create template:', error) + + return NextResponse.json( + { error: `Failed to create template: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/ses/delete-template/route.ts b/apps/sim/app/api/tools/ses/delete-template/route.ts new file mode 100644 index 00000000000..937eab8cd1e --- /dev/null +++ b/apps/sim/app/api/tools/ses/delete-template/route.ts @@ -0,0 +1,61 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSESClient, deleteTemplate } from '../utils' + +const logger = createLogger('SESDeleteTemplateAPI') + +const DeleteTemplateSchema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + templateName: z.string().min(1, 'Template name is required'), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = DeleteTemplateSchema.parse(body) + + logger.info(`Deleting SES template '${params.templateName}'`) + + const client = createSESClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await deleteTemplate(client, params.templateName) + + logger.info(`Template '${params.templateName}' deleted successfully`) + + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + + logger.error('Failed to delete template:', error) + + return NextResponse.json( + { error: `Failed to delete template: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/ses/get-account/route.ts b/apps/sim/app/api/tools/ses/get-account/route.ts new file mode 100644 index 00000000000..bf637654fae --- /dev/null +++ b/apps/sim/app/api/tools/ses/get-account/route.ts @@ -0,0 +1,60 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSESClient, getAccount } from '../utils' + +const logger = createLogger('SESGetAccountAPI') + +const GetAccountSchema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = GetAccountSchema.parse(body) + + logger.info('Getting SES account information') + + const client = createSESClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await getAccount(client) + + logger.info('SES account info retrieved successfully') + + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + + logger.error('Failed to get account information:', error) + + return NextResponse.json( + { error: `Failed to get account information: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/ses/get-template/route.ts b/apps/sim/app/api/tools/ses/get-template/route.ts new file mode 100644 index 00000000000..2dbf7ffe520 --- /dev/null +++ b/apps/sim/app/api/tools/ses/get-template/route.ts @@ -0,0 +1,61 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSESClient, getTemplate } from '../utils' + +const logger = createLogger('SESGetTemplateAPI') + +const GetTemplateSchema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + templateName: z.string().min(1, 'Template name is required'), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = GetTemplateSchema.parse(body) + + logger.info(`Getting SES template '${params.templateName}'`) + + const client = createSESClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await getTemplate(client, params.templateName) + + logger.info(`Template '${params.templateName}' retrieved successfully`) + + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + + logger.error('Failed to get template:', error) + + return NextResponse.json( + { error: `Failed to get template: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/ses/list-identities/route.ts b/apps/sim/app/api/tools/ses/list-identities/route.ts new file mode 100644 index 00000000000..94c07af8530 --- /dev/null +++ b/apps/sim/app/api/tools/ses/list-identities/route.ts @@ -0,0 +1,65 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSESClient, listIdentities } from '../utils' + +const logger = createLogger('SESListIdentitiesAPI') + +const ListIdentitiesSchema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + pageSize: z.number().int().min(0).max(1000).nullish(), + nextToken: z.string().nullish(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = ListIdentitiesSchema.parse(body) + + logger.info('Listing SES email identities') + + const client = createSESClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await listIdentities(client, { + pageSize: params.pageSize, + nextToken: params.nextToken, + }) + + logger.info(`Listed ${result.count} identities`) + + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + + logger.error('Failed to list identities:', error) + + return NextResponse.json( + { error: `Failed to list identities: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/ses/list-templates/route.ts b/apps/sim/app/api/tools/ses/list-templates/route.ts new file mode 100644 index 00000000000..9a70d6e8c3b --- /dev/null +++ b/apps/sim/app/api/tools/ses/list-templates/route.ts @@ -0,0 +1,65 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSESClient, listTemplates } from '../utils' + +const logger = createLogger('SESListTemplatesAPI') + +const ListTemplatesSchema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + pageSize: z.number().int().min(1).nullish(), + nextToken: z.string().nullish(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = ListTemplatesSchema.parse(body) + + logger.info('Listing SES email templates') + + const client = createSESClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await listTemplates(client, { + pageSize: params.pageSize, + nextToken: params.nextToken, + }) + + logger.info(`Listed ${result.count} templates`) + + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + + logger.error('Failed to list templates:', error) + + return NextResponse.json( + { error: `Failed to list templates: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/ses/send-bulk-email/route.ts b/apps/sim/app/api/tools/ses/send-bulk-email/route.ts new file mode 100644 index 00000000000..00eff16bbf7 --- /dev/null +++ b/apps/sim/app/api/tools/ses/send-bulk-email/route.ts @@ -0,0 +1,91 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSESClient, sendBulkEmail } from '../utils' + +const logger = createLogger('SESSendBulkEmailAPI') + +const DestinationSchema = z.object({ + toAddresses: z.array(z.string().email()), + templateData: z.string(), +}) + +const SendBulkEmailSchema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + fromAddress: z.string().email('Valid sender email address is required'), + templateName: z.string().min(1, 'Template name is required'), + destinations: z.string().min(1, 'Destinations JSON array is required'), + defaultTemplateData: z.string().nullish(), + configurationSetName: z.string().nullish(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = SendBulkEmailSchema.parse(body) + + let destinations: Array<{ toAddresses: string[]; templateData: string }> + try { + const parsed = JSON.parse(params.destinations) + destinations = z.array(DestinationSchema).parse(parsed) + } catch { + return NextResponse.json( + { error: 'destinations must be a valid JSON array of destination objects' }, + { status: 400 } + ) + } + + logger.info( + `Sending bulk email from ${params.fromAddress} to ${destinations.length} destination(s) using template '${params.templateName}'` + ) + + const client = createSESClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await sendBulkEmail(client, { + fromAddress: params.fromAddress, + templateName: params.templateName, + destinations, + defaultTemplateData: params.defaultTemplateData, + configurationSetName: params.configurationSetName, + }) + + logger.info( + `Bulk email sent: ${result.successCount} succeeded, ${result.failureCount} failed` + ) + + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + + logger.error('Failed to send bulk email:', error) + + return NextResponse.json( + { error: `Failed to send bulk email: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/ses/send-email/route.ts b/apps/sim/app/api/tools/ses/send-email/route.ts new file mode 100644 index 00000000000..fdfea2f0321 --- /dev/null +++ b/apps/sim/app/api/tools/ses/send-email/route.ts @@ -0,0 +1,104 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSESClient, sendEmail } from '../utils' + +const logger = createLogger('SESSendEmailAPI') + +const SendEmailSchema = z + .object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + fromAddress: z.string().email('Valid sender email address is required'), + toAddresses: z.string().min(1, 'At least one recipient address is required'), + subject: z.string().min(1, 'Email subject is required'), + bodyText: z.string().nullish(), + bodyHtml: z.string().nullish(), + ccAddresses: z.string().nullish(), + bccAddresses: z.string().nullish(), + replyToAddresses: z.string().nullish(), + configurationSetName: z.string().nullish(), + }) + .refine((data) => data.bodyText || data.bodyHtml, { + message: 'At least one of bodyText or bodyHtml is required', + path: ['bodyText'], + }) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = SendEmailSchema.parse(body) + + const toList = params.toAddresses + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + + logger.info(`Sending email from ${params.fromAddress} to ${toList.length} recipient(s)`) + + const client = createSESClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await sendEmail(client, { + fromAddress: params.fromAddress, + toAddresses: toList, + subject: params.subject, + bodyText: params.bodyText, + bodyHtml: params.bodyHtml, + ccAddresses: params.ccAddresses + ? params.ccAddresses + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + : null, + bccAddresses: params.bccAddresses + ? params.bccAddresses + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + : null, + replyToAddresses: params.replyToAddresses + ? params.replyToAddresses + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + : null, + configurationSetName: params.configurationSetName, + }) + + logger.info(`Email sent successfully, messageId: ${result.messageId}`) + + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + + logger.error('Failed to send email:', error) + + return NextResponse.json( + { error: `Failed to send email: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/ses/send-templated-email/route.ts b/apps/sim/app/api/tools/ses/send-templated-email/route.ts new file mode 100644 index 00000000000..391b5ae2e21 --- /dev/null +++ b/apps/sim/app/api/tools/ses/send-templated-email/route.ts @@ -0,0 +1,92 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createSESClient, sendTemplatedEmail } from '../utils' + +const logger = createLogger('SESSendTemplatedEmailAPI') + +const SendTemplatedEmailSchema = z.object({ + region: z.string().min(1, 'AWS region is required'), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + fromAddress: z.string().email('Valid sender email address is required'), + toAddresses: z.string().min(1, 'At least one recipient address is required'), + templateName: z.string().min(1, 'Template name is required'), + templateData: z.string().min(1, 'Template data is required'), + ccAddresses: z.string().nullish(), + bccAddresses: z.string().nullish(), + configurationSetName: z.string().nullish(), +}) + +export const POST = withRouteHandler(async (request: NextRequest) => { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + try { + const body = await request.json() + const params = SendTemplatedEmailSchema.parse(body) + + const toList = params.toAddresses + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + + logger.info( + `Sending templated email from ${params.fromAddress} using template '${params.templateName}'` + ) + + const client = createSESClient({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }) + + try { + const result = await sendTemplatedEmail(client, { + fromAddress: params.fromAddress, + toAddresses: toList, + templateName: params.templateName, + templateData: params.templateData, + ccAddresses: params.ccAddresses + ? params.ccAddresses + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + : null, + bccAddresses: params.bccAddresses + ? params.bccAddresses + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + : null, + configurationSetName: params.configurationSetName, + }) + + logger.info(`Templated email sent successfully, messageId: ${result.messageId}`) + + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn('Invalid request data', { errors: error.errors }) + return NextResponse.json( + { error: 'Invalid request data', details: error.errors }, + { status: 400 } + ) + } + + logger.error('Failed to send templated email:', error) + + return NextResponse.json( + { error: `Failed to send templated email: ${toError(error).message}` }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/tools/ses/utils.ts b/apps/sim/app/api/tools/ses/utils.ts new file mode 100644 index 00000000000..f2a9d6b0303 --- /dev/null +++ b/apps/sim/app/api/tools/ses/utils.ts @@ -0,0 +1,253 @@ +import { + CreateEmailTemplateCommand, + DeleteEmailTemplateCommand, + GetAccountCommand, + GetEmailTemplateCommand, + ListEmailIdentitiesCommand, + ListEmailTemplatesCommand, + SESv2Client, + SendBulkEmailCommand, + SendEmailCommand, +} from '@aws-sdk/client-sesv2' +import type { SESConnectionConfig } from '@/tools/ses/types' + +export function createSESClient(config: SESConnectionConfig): SESv2Client { + return new SESv2Client({ + region: config.region, + credentials: { + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + }, + }) +} + +export async function sendEmail( + client: SESv2Client, + params: { + fromAddress: string + toAddresses: string[] + subject: string + bodyText?: string | null + bodyHtml?: string | null + ccAddresses?: string[] | null + bccAddresses?: string[] | null + replyToAddresses?: string[] | null + configurationSetName?: string | null + } +) { + const command = new SendEmailCommand({ + FromEmailAddress: params.fromAddress, + Destination: { + ToAddresses: params.toAddresses, + ...(params.ccAddresses?.length ? { CcAddresses: params.ccAddresses } : {}), + ...(params.bccAddresses?.length ? { BccAddresses: params.bccAddresses } : {}), + }, + Content: { + Simple: { + Subject: { Data: params.subject }, + Body: { + ...(params.bodyText ? { Text: { Data: params.bodyText } } : {}), + ...(params.bodyHtml ? { Html: { Data: params.bodyHtml } } : {}), + }, + }, + }, + ...(params.replyToAddresses?.length ? { ReplyToAddresses: params.replyToAddresses } : {}), + ...(params.configurationSetName ? { ConfigurationSetName: params.configurationSetName } : {}), + }) + + const response = await client.send(command) + + return { + messageId: response.MessageId ?? '', + } +} + +export async function sendTemplatedEmail( + client: SESv2Client, + params: { + fromAddress: string + toAddresses: string[] + templateName: string + templateData: string + ccAddresses?: string[] | null + bccAddresses?: string[] | null + configurationSetName?: string | null + } +) { + const command = new SendEmailCommand({ + FromEmailAddress: params.fromAddress, + Destination: { + ToAddresses: params.toAddresses, + ...(params.ccAddresses?.length ? { CcAddresses: params.ccAddresses } : {}), + ...(params.bccAddresses?.length ? { BccAddresses: params.bccAddresses } : {}), + }, + Content: { + Template: { + TemplateName: params.templateName, + TemplateData: params.templateData, + }, + }, + ...(params.configurationSetName ? { ConfigurationSetName: params.configurationSetName } : {}), + }) + + const response = await client.send(command) + + return { + messageId: response.MessageId ?? '', + } +} + +export async function sendBulkEmail( + client: SESv2Client, + params: { + fromAddress: string + templateName: string + destinations: Array<{ toAddresses: string[]; templateData: string }> + defaultTemplateData?: string | null + configurationSetName?: string | null + } +) { + const command = new SendBulkEmailCommand({ + FromEmailAddress: params.fromAddress, + DefaultContent: { + Template: { + TemplateName: params.templateName, + ...(params.defaultTemplateData ? { TemplateData: params.defaultTemplateData } : {}), + }, + }, + BulkEmailEntries: params.destinations.map((dest) => ({ + Destination: { ToAddresses: dest.toAddresses }, + ReplacementEmailContent: { + ReplacementTemplate: { + ReplacementTemplateData: dest.templateData, + }, + }, + })), + ...(params.configurationSetName ? { ConfigurationSetName: params.configurationSetName } : {}), + }) + + const response = await client.send(command) + + const results = (response.BulkEmailEntryResults ?? []).map((r) => ({ + messageId: r.MessageId ?? null, + status: r.Status ?? 'UNKNOWN', + error: r.Error ?? null, + })) + + const successCount = results.filter((r) => r.status === 'SUCCESS').length + const failureCount = results.length - successCount + + return { results, successCount, failureCount } +} + +export async function listIdentities( + client: SESv2Client, + params: { + pageSize?: number | null + nextToken?: string | null + } +) { + const command = new ListEmailIdentitiesCommand({ + ...(params.pageSize ? { PageSize: params.pageSize } : {}), + ...(params.nextToken ? { NextToken: params.nextToken } : {}), + }) + + const response = await client.send(command) + + const identities = (response.EmailIdentities ?? []).map((identity) => ({ + identityName: identity.IdentityName ?? '', + identityType: identity.IdentityType ?? '', + sendingEnabled: identity.SendingEnabled ?? false, + verificationStatus: identity.VerificationStatus ?? '', + })) + + return { + identities, + nextToken: response.NextToken ?? null, + count: identities.length, + } +} + +export async function getAccount(client: SESv2Client) { + const command = new GetAccountCommand({}) + const response = await client.send(command) + + return { + sendingEnabled: response.SendingEnabled ?? false, + max24HourSend: response.SendQuota?.Max24HourSend ?? 0, + maxSendRate: response.SendQuota?.MaxSendRate ?? 0, + sentLast24Hours: response.SendQuota?.SentLast24Hours ?? 0, + } +} + +export async function createTemplate( + client: SESv2Client, + params: { + templateName: string + subjectPart: string + textPart?: string | null + htmlPart?: string | null + } +) { + const command = new CreateEmailTemplateCommand({ + TemplateName: params.templateName, + TemplateContent: { + Subject: params.subjectPart, + ...(params.textPart ? { Text: params.textPart } : {}), + ...(params.htmlPart ? { Html: params.htmlPart } : {}), + }, + }) + + await client.send(command) + + return { + message: `Template '${params.templateName}' created successfully`, + } +} + +export async function getTemplate(client: SESv2Client, templateName: string) { + const command = new GetEmailTemplateCommand({ TemplateName: templateName }) + const response = await client.send(command) + + return { + templateName: response.TemplateName ?? '', + subjectPart: response.TemplateContent?.Subject ?? '', + textPart: response.TemplateContent?.Text ?? null, + htmlPart: response.TemplateContent?.Html ?? null, + } +} + +export async function listTemplates( + client: SESv2Client, + params: { + pageSize?: number | null + nextToken?: string | null + } +) { + const command = new ListEmailTemplatesCommand({ + ...(params.pageSize ? { PageSize: params.pageSize } : {}), + ...(params.nextToken ? { NextToken: params.nextToken } : {}), + }) + + const response = await client.send(command) + + const templates = (response.TemplatesMetadata ?? []).map((t) => ({ + templateName: t.TemplateName ?? '', + createdTimestamp: t.CreatedTimestamp?.toISOString() ?? null, + })) + + return { + templates, + nextToken: response.NextToken ?? null, + count: templates.length, + } +} + +export async function deleteTemplate(client: SESv2Client, templateName: string) { + const command = new DeleteEmailTemplateCommand({ TemplateName: templateName }) + await client.send(command) + + return { + message: `Template '${templateName}' deleted successfully`, + } +} diff --git a/apps/sim/app/api/tools/sts/assume-role/route.ts b/apps/sim/app/api/tools/sts/assume-role/route.ts index 177cf5ef06a..3407562acba 100644 --- a/apps/sim/app/api/tools/sts/assume-role/route.ts +++ b/apps/sim/app/api/tools/sts/assume-role/route.ts @@ -1,5 +1,5 @@ import { createLogger } from '@sim/logger' -import { generateId } from '@sim/utils/id' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -15,14 +15,13 @@ const AssumeRoleSchema = z.object({ roleArn: z.string().min(1, 'Role ARN is required'), roleSessionName: z.string().min(1, 'Role session name is required'), durationSeconds: z.number().int().min(900).max(43200).nullish(), + policy: z.string().max(2048).nullish(), externalId: z.string().nullish(), serialNumber: z.string().nullish(), tokenCode: z.string().nullish(), }) export const POST = withRouteHandler(async (request: NextRequest) => { - const requestId = generateId().slice(0, 8) - const auth = await checkInternalAuth(request) if (!auth.success || !auth.userId) { return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) @@ -32,7 +31,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const params = AssumeRoleSchema.parse(body) - logger.info(`[${requestId}] Assuming role ${params.roleArn}`) + logger.info(`Assuming role ${params.roleArn}`) const client = createSTSClient({ region: params.region, @@ -46,12 +45,13 @@ export const POST = withRouteHandler(async (request: NextRequest) => { params.roleArn, params.roleSessionName, params.durationSeconds, + params.policy, params.externalId, params.serialNumber, params.tokenCode ) - logger.info(`[${requestId}] Role assumed successfully`) + logger.info('Role assumed successfully') return NextResponse.json(result) } finally { @@ -59,16 +59,18 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } } catch (error) { if (error instanceof z.ZodError) { - logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors }) + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: 'Invalid request data', details: error.errors }, { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' - logger.error(`[${requestId}] Failed to assume role:`, error) + logger.error('Failed to assume role:', error) - return NextResponse.json({ error: `Failed to assume role: ${errorMessage}` }, { status: 500 }) + return NextResponse.json( + { error: `Failed to assume role: ${toError(error).message}` }, + { status: 500 } + ) } }) diff --git a/apps/sim/app/api/tools/sts/get-access-key-info/route.ts b/apps/sim/app/api/tools/sts/get-access-key-info/route.ts index b161d95e23d..b2b20aebf55 100644 --- a/apps/sim/app/api/tools/sts/get-access-key-info/route.ts +++ b/apps/sim/app/api/tools/sts/get-access-key-info/route.ts @@ -1,5 +1,5 @@ import { createLogger } from '@sim/logger' -import { generateId } from '@sim/utils/id' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -16,8 +16,6 @@ const GetAccessKeyInfoSchema = z.object({ }) export const POST = withRouteHandler(async (request: NextRequest) => { - const requestId = generateId().slice(0, 8) - const auth = await checkInternalAuth(request) if (!auth.success || !auth.userId) { return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) @@ -27,7 +25,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const params = GetAccessKeyInfoSchema.parse(body) - logger.info(`[${requestId}] Getting access key info for ${params.targetAccessKeyId}`) + logger.info(`Getting access key info for ${params.targetAccessKeyId}`) const client = createSTSClient({ region: params.region, @@ -38,7 +36,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { try { const result = await getAccessKeyInfo(client, params.targetAccessKeyId) - logger.info(`[${requestId}] Access key info retrieved successfully`) + logger.info('Access key info retrieved successfully') return NextResponse.json(result) } finally { @@ -46,18 +44,17 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } } catch (error) { if (error instanceof z.ZodError) { - logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors }) + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: 'Invalid request data', details: error.errors }, { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' - logger.error(`[${requestId}] Failed to get access key info:`, error) + logger.error('Failed to get access key info:', error) return NextResponse.json( - { error: `Failed to get access key info: ${errorMessage}` }, + { error: `Failed to get access key info: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/sts/get-caller-identity/route.ts b/apps/sim/app/api/tools/sts/get-caller-identity/route.ts index e72c81bfbc3..080f00b7ab3 100644 --- a/apps/sim/app/api/tools/sts/get-caller-identity/route.ts +++ b/apps/sim/app/api/tools/sts/get-caller-identity/route.ts @@ -1,5 +1,5 @@ import { createLogger } from '@sim/logger' -import { generateId } from '@sim/utils/id' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -15,8 +15,6 @@ const GetCallerIdentitySchema = z.object({ }) export const POST = withRouteHandler(async (request: NextRequest) => { - const requestId = generateId().slice(0, 8) - const auth = await checkInternalAuth(request) if (!auth.success || !auth.userId) { return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) @@ -26,7 +24,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const params = GetCallerIdentitySchema.parse(body) - logger.info(`[${requestId}] Getting caller identity`) + logger.info('Getting caller identity') const client = createSTSClient({ region: params.region, @@ -37,7 +35,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { try { const result = await getCallerIdentity(client) - logger.info(`[${requestId}] Caller identity retrieved successfully`) + logger.info('Caller identity retrieved successfully') return NextResponse.json(result) } finally { @@ -45,18 +43,17 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } } catch (error) { if (error instanceof z.ZodError) { - logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors }) + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: 'Invalid request data', details: error.errors }, { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' - logger.error(`[${requestId}] Failed to get caller identity:`, error) + logger.error('Failed to get caller identity:', error) return NextResponse.json( - { error: `Failed to get caller identity: ${errorMessage}` }, + { error: `Failed to get caller identity: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/sts/get-session-token/route.ts b/apps/sim/app/api/tools/sts/get-session-token/route.ts index 33ea1b187d0..3915a08c1a5 100644 --- a/apps/sim/app/api/tools/sts/get-session-token/route.ts +++ b/apps/sim/app/api/tools/sts/get-session-token/route.ts @@ -1,5 +1,5 @@ import { createLogger } from '@sim/logger' -import { generateId } from '@sim/utils/id' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalAuth } from '@/lib/auth/hybrid' @@ -18,8 +18,6 @@ const GetSessionTokenSchema = z.object({ }) export const POST = withRouteHandler(async (request: NextRequest) => { - const requestId = generateId().slice(0, 8) - const auth = await checkInternalAuth(request) if (!auth.success || !auth.userId) { return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) @@ -29,7 +27,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const body = await request.json() const params = GetSessionTokenSchema.parse(body) - logger.info(`[${requestId}] Getting session token`) + logger.info('Getting session token') const client = createSTSClient({ region: params.region, @@ -45,7 +43,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { params.tokenCode ) - logger.info(`[${requestId}] Session token retrieved successfully`) + logger.info('Session token retrieved successfully') return NextResponse.json(result) } finally { @@ -53,18 +51,17 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } } catch (error) { if (error instanceof z.ZodError) { - logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors }) + logger.warn('Invalid request data', { errors: error.errors }) return NextResponse.json( { error: 'Invalid request data', details: error.errors }, { status: 400 } ) } - const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' - logger.error(`[${requestId}] Failed to get session token:`, error) + logger.error('Failed to get session token:', error) return NextResponse.json( - { error: `Failed to get session token: ${errorMessage}` }, + { error: `Failed to get session token: ${toError(error).message}` }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/sts/utils.ts b/apps/sim/app/api/tools/sts/utils.ts index f674b7939ab..cf4bee12b68 100644 --- a/apps/sim/app/api/tools/sts/utils.ts +++ b/apps/sim/app/api/tools/sts/utils.ts @@ -22,6 +22,7 @@ export async function assumeRole( roleArn: string, roleSessionName: string, durationSeconds?: number | null, + policy?: string | null, externalId?: string | null, serialNumber?: string | null, tokenCode?: string | null @@ -30,6 +31,7 @@ export async function assumeRole( RoleArn: roleArn, RoleSessionName: roleSessionName, ...(durationSeconds ? { DurationSeconds: durationSeconds } : {}), + ...(policy ? { Policy: policy } : {}), ...(externalId ? { ExternalId: externalId } : {}), ...(serialNumber ? { SerialNumber: serialNumber } : {}), ...(tokenCode ? { TokenCode: tokenCode } : {}), @@ -45,6 +47,7 @@ export async function assumeRole( assumedRoleArn: response.AssumedRoleUser?.Arn ?? '', assumedRoleId: response.AssumedRoleUser?.AssumedRoleId ?? '', packedPolicySize: response.PackedPolicySize ?? null, + sourceIdentity: response.SourceIdentity ?? null, } } diff --git a/apps/sim/blocks/blocks/dynamodb.ts b/apps/sim/blocks/blocks/dynamodb.ts index 628174f2099..6153286ad4f 100644 --- a/apps/sim/blocks/blocks/dynamodb.ts +++ b/apps/sim/blocks/blocks/dynamodb.ts @@ -1,12 +1,14 @@ +import { toError } from '@sim/utils/errors' import { DynamoDBIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' -import { IntegrationType } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' import type { DynamoDBIntrospectResponse, DynamoDBResponse } from '@/tools/dynamodb/types' export const DynamoDBBlock: BlockConfig = { type: 'dynamodb', name: 'Amazon DynamoDB', - description: 'Connect to Amazon DynamoDB', + description: 'Get, put, query, scan, update, and delete items in Amazon DynamoDB tables', + authMode: AuthMode.ApiKey, longDescription: 'Integrate Amazon DynamoDB into workflows. Supports Get, Put, Query, Scan, Update, Delete, and Introspect operations on DynamoDB tables.', docsLink: 'https://docs.sim.ai/tools/dynamodb', @@ -67,16 +69,15 @@ export const DynamoDBBlock: BlockConfig 'false', condition: { field: 'operation', value: 'get' }, + mode: 'advanced', }, - // Item for put operation { id: 'item', title: 'Item (JSON)', @@ -167,7 +167,6 @@ Return ONLY valid JSON - no explanations, no markdown code blocks.`, generationType: 'json-object', }, }, - // Key condition expression for query { id: 'keyConditionExpression', title: 'Key Condition Expression', @@ -189,7 +188,6 @@ Return ONLY the expression - no explanations.`, placeholder: 'Describe the key condition...', }, }, - // Update expression for update operation { id: 'updateExpression', title: 'Update Expression', @@ -211,9 +209,8 @@ Return ONLY the expression - no explanations.`, placeholder: 'Describe what updates to make...', }, }, - // Filter expression for query and scan { - id: 'filterExpression', + id: 'queryFilterExpression', title: 'Filter Expression', type: 'short-input', placeholder: 'attribute_exists(email)', @@ -233,7 +230,7 @@ Return ONLY the expression - no explanations.`, }, }, { - id: 'filterExpression', + id: 'scanFilterExpression', title: 'Filter Expression', type: 'short-input', placeholder: 'attribute_exists(email)', @@ -252,7 +249,6 @@ Return ONLY the expression - no explanations.`, placeholder: 'Describe how to filter results...', }, }, - // Projection expression for scan { id: 'projectionExpression', title: 'Projection Expression', @@ -260,9 +256,8 @@ Return ONLY the expression - no explanations.`, placeholder: 'pk, #name, email', condition: { field: 'operation', value: 'scan' }, }, - // Expression attribute names for query, scan, update { - id: 'expressionAttributeNames', + id: 'queryExpressionAttributeNames', title: 'Expression Attribute Names (JSON)', type: 'code', placeholder: '{\n "#name": "name"\n}', @@ -280,7 +275,7 @@ Return ONLY valid JSON - no explanations, no markdown code blocks.`, }, }, { - id: 'expressionAttributeNames', + id: 'scanExpressionAttributeNames', title: 'Expression Attribute Names (JSON)', type: 'code', placeholder: '{\n "#name": "name"\n}', @@ -298,7 +293,7 @@ Return ONLY valid JSON - no explanations, no markdown code blocks.`, }, }, { - id: 'expressionAttributeNames', + id: 'updateExpressionAttributeNames', title: 'Expression Attribute Names (JSON)', type: 'code', placeholder: '{\n "#name": "name"\n}', @@ -315,9 +310,44 @@ Return ONLY valid JSON - no explanations, no markdown code blocks.`, generationType: 'json-object', }, }, - // Expression attribute values for query, scan, update { - id: 'expressionAttributeValues', + id: 'putExpressionAttributeNames', + title: 'Expression Attribute Names (JSON)', + type: 'code', + placeholder: '{\n "#name": "name"\n}', + condition: { field: 'operation', value: 'put' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate DynamoDB expression attribute names JSON based on the user's description. +Map placeholder names (starting with #) to actual attribute names used in the condition expression. +Example: {"#name": "name", "#status": "status"} + +Return ONLY valid JSON - no explanations, no markdown code blocks.`, + placeholder: 'Describe the attribute name mappings...', + generationType: 'json-object', + }, + }, + { + id: 'deleteExpressionAttributeNames', + title: 'Expression Attribute Names (JSON)', + type: 'code', + placeholder: '{\n "#status": "status"\n}', + condition: { field: 'operation', value: 'delete' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate DynamoDB expression attribute names JSON based on the user's description. +Map placeholder names (starting with #) to actual attribute names used in the condition expression. +Example: {"#status": "status"} + +Return ONLY valid JSON - no explanations, no markdown code blocks.`, + placeholder: 'Describe the attribute name mappings...', + generationType: 'json-object', + }, + }, + { + id: 'queryExpressionAttributeValues', title: 'Expression Attribute Values (JSON)', type: 'code', placeholder: '{\n ":pk": "user#123",\n ":name": "Jane"\n}', @@ -334,7 +364,7 @@ Return ONLY valid JSON - no explanations, no markdown code blocks.`, }, }, { - id: 'expressionAttributeValues', + id: 'scanExpressionAttributeValues', title: 'Expression Attribute Values (JSON)', type: 'code', placeholder: '{\n ":status": "active"\n}', @@ -351,7 +381,7 @@ Return ONLY valid JSON - no explanations, no markdown code blocks.`, }, }, { - id: 'expressionAttributeValues', + id: 'updateExpressionAttributeValues', title: 'Expression Attribute Values (JSON)', type: 'code', placeholder: '{\n ":name": "Jane Doe"\n}', @@ -367,36 +397,92 @@ Return ONLY valid JSON - no explanations, no markdown code blocks.`, generationType: 'json-object', }, }, - // Index name for query + { + id: 'putExpressionAttributeValues', + title: 'Expression Attribute Values (JSON)', + type: 'code', + placeholder: '{\n ":expected": "value"\n}', + condition: { field: 'operation', value: 'put' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate DynamoDB expression attribute values JSON based on the user's description. +Map placeholder values (starting with :) to actual values used in the condition expression. +Example: {":expectedVersion": 3} + +Return ONLY valid JSON - no explanations, no markdown code blocks.`, + placeholder: 'Describe the attribute values...', + generationType: 'json-object', + }, + }, + { + id: 'deleteExpressionAttributeValues', + title: 'Expression Attribute Values (JSON)', + type: 'code', + placeholder: '{\n ":status": "active"\n}', + condition: { field: 'operation', value: 'delete' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate DynamoDB expression attribute values JSON based on the user's description. +Map placeholder values (starting with :) to actual values used in the condition expression. +Example: {":status": "active", ":version": 3} + +Return ONLY valid JSON - no explanations, no markdown code blocks.`, + placeholder: 'Describe the attribute values...', + generationType: 'json-object', + }, + }, { id: 'indexName', title: 'Index Name', type: 'short-input', placeholder: 'GSI1', condition: { field: 'operation', value: 'query' }, + mode: 'advanced', }, - // Limit for query and scan { - id: 'limit', + id: 'queryLimit', title: 'Limit', type: 'short-input', placeholder: '100', condition: { field: 'operation', value: 'query' }, + mode: 'advanced', }, { - id: 'limit', + id: 'scanLimit', title: 'Limit', type: 'short-input', placeholder: '100', condition: { field: 'operation', value: 'scan' }, + mode: 'advanced', }, - // Condition expression for update and delete { - id: 'conditionExpression', + id: 'putConditionExpression', + title: 'Condition Expression', + type: 'short-input', + placeholder: 'attribute_not_exists(pk)', + condition: { field: 'operation', value: 'put' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate a DynamoDB condition expression based on the user's description. +Condition expressions prevent the operation if the condition is not met. +Examples: +- "attribute_not_exists(pk)" - Prevent overwriting an existing item +- "#version = :expectedVersion" - Optimistic locking + +Return ONLY the expression - no explanations.`, + placeholder: 'Describe the condition that must be true...', + }, + }, + { + id: 'updateConditionExpression', title: 'Condition Expression', type: 'short-input', placeholder: 'attribute_exists(pk)', condition: { field: 'operation', value: 'update' }, + mode: 'advanced', wandConfig: { enabled: true, prompt: `Generate a DynamoDB condition expression based on the user's description. @@ -411,11 +497,12 @@ Return ONLY the expression - no explanations.`, }, }, { - id: 'conditionExpression', + id: 'deleteConditionExpression', title: 'Condition Expression', type: 'short-input', placeholder: 'attribute_exists(pk)', condition: { field: 'operation', value: 'delete' }, + mode: 'advanced', wandConfig: { enabled: true, prompt: `Generate a DynamoDB condition expression based on the user's description. @@ -428,6 +515,34 @@ Return ONLY the expression - no explanations.`, placeholder: 'Describe the condition that must be true...', }, }, + { + id: 'queryExclusiveStartKey', + title: 'Exclusive Start Key (JSON)', + type: 'code', + placeholder: '{\n "pk": "user#123"\n}', + condition: { field: 'operation', value: 'query' }, + mode: 'advanced', + }, + { + id: 'scanExclusiveStartKey', + title: 'Exclusive Start Key (JSON)', + type: 'code', + placeholder: '{\n "pk": "user#123"\n}', + condition: { field: 'operation', value: 'scan' }, + mode: 'advanced', + }, + { + id: 'scanIndexForward', + title: 'Sort Order', + type: 'dropdown', + options: [ + { label: 'Ascending (default)', id: 'true' }, + { label: 'Descending', id: 'false' }, + ], + value: () => 'true', + condition: { field: 'operation', value: 'query' }, + mode: 'advanced', + }, ], tools: { access: [ @@ -461,18 +576,6 @@ Return ONLY the expression - no explanations.`, } }, params: (params) => { - const { - operation, - key, - item, - expressionAttributeNames, - expressionAttributeValues, - consistentRead, - limit, - ...rest - } = params - - // Parse JSON fields const parseJson = (value: unknown, fieldName: string) => { if (!value) return undefined if (typeof value === 'object') return value @@ -480,56 +583,108 @@ Return ONLY the expression - no explanations.`, try { return JSON.parse(value) } catch (parseError) { - const errorMsg = - parseError instanceof Error ? parseError.message : 'Unknown JSON error' - throw new Error(`Invalid JSON in ${fieldName}: ${errorMsg}`) + throw new Error(`Invalid JSON in ${fieldName}: ${toError(parseError).message}`) } } return undefined } - const parsedKey = parseJson(key, 'key') - const parsedItem = parseJson(item, 'item') - const parsedExpressionAttributeNames = parseJson( - expressionAttributeNames, - 'expressionAttributeNames' - ) - const parsedExpressionAttributeValues = parseJson( - expressionAttributeValues, - 'expressionAttributeValues' - ) + const op = params.operation as string + const result: Record = { + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + } - // Build connection config - const connectionConfig = { - region: rest.region, - accessKeyId: rest.accessKeyId, - secretAccessKey: rest.secretAccessKey, + // Table name (introspect uses introspectTableName, all others use tableName) + if (op === 'introspect') { + if (params.introspectTableName) result.tableName = params.introspectTableName + } else { + result.tableName = params.tableName } - // Build params object - const result: Record = { - ...connectionConfig, - tableName: rest.tableName, + // Operation-specific params — map unique subBlock IDs back to tool param names + if (op === 'get') { + const key = parseJson(params.getKey, 'key') + if (key !== undefined) result.key = key + if (params.consistentRead === 'true' || params.consistentRead === true) { + result.consistentRead = true + } + } + + if (op === 'put') { + const item = parseJson(params.item, 'item') + if (item !== undefined) result.item = item + if (params.putConditionExpression) + result.conditionExpression = params.putConditionExpression + const names = parseJson(params.putExpressionAttributeNames, 'expressionAttributeNames') + if (names !== undefined) result.expressionAttributeNames = names + const values = parseJson(params.putExpressionAttributeValues, 'expressionAttributeValues') + if (values !== undefined) result.expressionAttributeValues = values + } + + if (op === 'query') { + if (params.keyConditionExpression) + result.keyConditionExpression = params.keyConditionExpression + if (params.queryFilterExpression) result.filterExpression = params.queryFilterExpression + const names = parseJson(params.queryExpressionAttributeNames, 'expressionAttributeNames') + if (names !== undefined) result.expressionAttributeNames = names + const values = parseJson( + params.queryExpressionAttributeValues, + 'expressionAttributeValues' + ) + if (values !== undefined) result.expressionAttributeValues = values + if (params.indexName) result.indexName = params.indexName + if (params.queryLimit) result.limit = Number.parseInt(String(params.queryLimit), 10) + const esk = parseJson(params.queryExclusiveStartKey, 'exclusiveStartKey') + if (esk !== undefined) result.exclusiveStartKey = esk + if (params.scanIndexForward === 'false' || params.scanIndexForward === false) { + result.scanIndexForward = false + } } - if (parsedKey !== undefined) result.key = parsedKey - if (parsedItem !== undefined) result.item = parsedItem - if (rest.keyConditionExpression) result.keyConditionExpression = rest.keyConditionExpression - if (rest.updateExpression) result.updateExpression = rest.updateExpression - if (rest.filterExpression) result.filterExpression = rest.filterExpression - if (rest.projectionExpression) result.projectionExpression = rest.projectionExpression - if (parsedExpressionAttributeNames !== undefined) { - result.expressionAttributeNames = parsedExpressionAttributeNames + if (op === 'scan') { + if (params.scanFilterExpression) result.filterExpression = params.scanFilterExpression + if (params.projectionExpression) result.projectionExpression = params.projectionExpression + const names = parseJson(params.scanExpressionAttributeNames, 'expressionAttributeNames') + if (names !== undefined) result.expressionAttributeNames = names + const values = parseJson( + params.scanExpressionAttributeValues, + 'expressionAttributeValues' + ) + if (values !== undefined) result.expressionAttributeValues = values + if (params.scanLimit) result.limit = Number.parseInt(String(params.scanLimit), 10) + const esk = parseJson(params.scanExclusiveStartKey, 'exclusiveStartKey') + if (esk !== undefined) result.exclusiveStartKey = esk } - if (parsedExpressionAttributeValues !== undefined) { - result.expressionAttributeValues = parsedExpressionAttributeValues + + if (op === 'update') { + const key = parseJson(params.updateKey, 'key') + if (key !== undefined) result.key = key + if (params.updateExpression) result.updateExpression = params.updateExpression + const names = parseJson(params.updateExpressionAttributeNames, 'expressionAttributeNames') + if (names !== undefined) result.expressionAttributeNames = names + const values = parseJson( + params.updateExpressionAttributeValues, + 'expressionAttributeValues' + ) + if (values !== undefined) result.expressionAttributeValues = values + if (params.updateConditionExpression) + result.conditionExpression = params.updateConditionExpression } - if (rest.indexName) result.indexName = rest.indexName - if (limit) result.limit = Number.parseInt(String(limit), 10) - if (rest.conditionExpression) result.conditionExpression = rest.conditionExpression - // Handle consistentRead - dropdown sends 'true'/'false' strings or boolean - if (consistentRead === 'true' || consistentRead === true) { - result.consistentRead = true + + if (op === 'delete') { + const key = parseJson(params.deleteKey, 'key') + if (key !== undefined) result.key = key + if (params.deleteConditionExpression) + result.conditionExpression = params.deleteConditionExpression + const names = parseJson(params.deleteExpressionAttributeNames, 'expressionAttributeNames') + if (names !== undefined) result.expressionAttributeNames = names + const values = parseJson( + params.deleteExpressionAttributeValues, + 'expressionAttributeValues' + ) + if (values !== undefined) result.expressionAttributeValues = values } return result @@ -542,18 +697,66 @@ Return ONLY the expression - no explanations.`, accessKeyId: { type: 'string', description: 'AWS access key ID' }, secretAccessKey: { type: 'string', description: 'AWS secret access key' }, tableName: { type: 'string', description: 'DynamoDB table name' }, - key: { type: 'json', description: 'Primary key for get/update/delete operations' }, + introspectTableName: { + type: 'string', + description: 'Optional table name for introspect operation', + }, + getKey: { type: 'json', description: 'Primary key for get operation' }, + updateKey: { type: 'json', description: 'Primary key for update operation' }, + deleteKey: { type: 'json', description: 'Primary key for delete operation' }, item: { type: 'json', description: 'Item to put into the table' }, keyConditionExpression: { type: 'string', description: 'Key condition for query operations' }, updateExpression: { type: 'string', description: 'Update expression for update operations' }, - filterExpression: { type: 'string', description: 'Filter expression for query/scan' }, + queryFilterExpression: { type: 'string', description: 'Filter expression for query' }, + scanFilterExpression: { type: 'string', description: 'Filter expression for scan' }, projectionExpression: { type: 'string', description: 'Attributes to retrieve in scan' }, - expressionAttributeNames: { type: 'json', description: 'Attribute name mappings' }, - expressionAttributeValues: { type: 'json', description: 'Expression attribute values' }, + queryExpressionAttributeNames: { + type: 'json', + description: 'Attribute name mappings for query', + }, + scanExpressionAttributeNames: { type: 'json', description: 'Attribute name mappings for scan' }, + updateExpressionAttributeNames: { + type: 'json', + description: 'Attribute name mappings for update', + }, + putExpressionAttributeNames: { type: 'json', description: 'Attribute name mappings for put' }, + deleteExpressionAttributeNames: { + type: 'json', + description: 'Attribute name mappings for delete', + }, + queryExpressionAttributeValues: { + type: 'json', + description: 'Expression attribute values for query', + }, + scanExpressionAttributeValues: { + type: 'json', + description: 'Expression attribute values for scan', + }, + updateExpressionAttributeValues: { + type: 'json', + description: 'Expression attribute values for update', + }, + putExpressionAttributeValues: { + type: 'json', + description: 'Expression attribute values for put', + }, + deleteExpressionAttributeValues: { + type: 'json', + description: 'Expression attribute values for delete', + }, indexName: { type: 'string', description: 'Secondary index name for query' }, - limit: { type: 'number', description: 'Maximum items to return' }, - conditionExpression: { type: 'string', description: 'Condition for update/delete' }, + queryLimit: { type: 'number', description: 'Maximum items to return for query' }, + scanLimit: { type: 'number', description: 'Maximum items to return for scan' }, + putConditionExpression: { type: 'string', description: 'Condition for put operation' }, + updateConditionExpression: { type: 'string', description: 'Condition for update operation' }, + deleteConditionExpression: { type: 'string', description: 'Condition for delete operation' }, consistentRead: { type: 'string', description: 'Use strongly consistent read' }, + queryExclusiveStartKey: { type: 'json', description: 'Pagination token for query' }, + scanExclusiveStartKey: { type: 'json', description: 'Pagination token for scan' }, + scanIndexForward: { + type: 'string', + description: 'Sort order for query: true for ascending, false for descending', + }, }, outputs: { message: { @@ -572,6 +775,11 @@ Return ONLY the expression - no explanations.`, type: 'number', description: 'Number of items returned', }, + lastEvaluatedKey: { + type: 'json', + description: + 'Pagination token from query/scan — pass as exclusiveStartKey to fetch the next page', + }, tables: { type: 'array', description: 'List of table names from introspect operation', diff --git a/apps/sim/blocks/blocks/iam.ts b/apps/sim/blocks/blocks/iam.ts index 51e682108ef..a38d3040bbd 100644 --- a/apps/sim/blocks/blocks/iam.ts +++ b/apps/sim/blocks/blocks/iam.ts @@ -40,6 +40,9 @@ export const IAMBlock: BlockConfig = { { label: 'List Groups', id: 'list_groups' }, { label: 'Add User to Group', id: 'add_user_to_group' }, { label: 'Remove User from Group', id: 'remove_user_from_group' }, + { label: 'List Attached Role Policies', id: 'list_attached_role_policies' }, + { label: 'List Attached User Policies', id: 'list_attached_user_policies' }, + { label: 'Simulate Principal Policy', id: 'simulate_principal_policy' }, ], value: () => 'list_users', }, @@ -83,6 +86,7 @@ export const IAMBlock: BlockConfig = { 'delete_access_key', 'add_user_to_group', 'remove_user_from_group', + 'list_attached_user_policies', ], }, required: { @@ -95,6 +99,7 @@ export const IAMBlock: BlockConfig = { 'detach_user_policy', 'add_user_to_group', 'remove_user_from_group', + 'list_attached_user_policies', ], }, }, @@ -111,6 +116,7 @@ export const IAMBlock: BlockConfig = { 'delete_role', 'attach_role_policy', 'detach_role_policy', + 'list_attached_role_policies', ], }, required: { @@ -121,6 +127,7 @@ export const IAMBlock: BlockConfig = { 'delete_role', 'attach_role_policy', 'detach_role_policy', + 'list_attached_role_policies', ], }, }, @@ -239,6 +246,37 @@ export const IAMBlock: BlockConfig = { required: false, mode: 'advanced', }, + { + id: 'policySourceArn', + title: 'Principal ARN', + type: 'short-input', + placeholder: 'arn:aws:iam::123456789012:user/alice', + condition: { field: 'operation', value: 'simulate_principal_policy' }, + required: { field: 'operation', value: 'simulate_principal_policy' }, + }, + { + id: 'actionNames', + title: 'Actions (comma-separated)', + type: 'short-input', + placeholder: 's3:GetObject,ec2:DescribeInstances', + condition: { field: 'operation', value: 'simulate_principal_policy' }, + required: { field: 'operation', value: 'simulate_principal_policy' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a comma-separated list of AWS IAM action names to simulate (e.g., s3:GetObject,ec2:DescribeInstances,iam:ListUsers). Return ONLY the comma-separated list - no explanations, no extra text.', + placeholder: 'Describe the actions you want to check', + }, + }, + { + id: 'resourceArns', + title: 'Resource ARNs (comma-separated)', + type: 'short-input', + placeholder: 'arn:aws:s3:::my-bucket/*', + condition: { field: 'operation', value: 'simulate_principal_policy' }, + required: false, + mode: 'advanced', + }, { id: 'pathPrefix', title: 'Path Prefix', @@ -246,7 +284,14 @@ export const IAMBlock: BlockConfig = { placeholder: '/division_abc/', condition: { field: 'operation', - value: ['list_users', 'list_roles', 'list_policies', 'list_groups'], + value: [ + 'list_users', + 'list_roles', + 'list_policies', + 'list_groups', + 'list_attached_role_policies', + 'list_attached_user_policies', + ], }, required: false, mode: 'advanced', @@ -258,7 +303,15 @@ export const IAMBlock: BlockConfig = { placeholder: '100', condition: { field: 'operation', - value: ['list_users', 'list_roles', 'list_policies', 'list_groups'], + value: [ + 'list_users', + 'list_roles', + 'list_policies', + 'list_groups', + 'list_attached_role_policies', + 'list_attached_user_policies', + 'simulate_principal_policy', + ], }, required: false, mode: 'advanced', @@ -270,7 +323,15 @@ export const IAMBlock: BlockConfig = { placeholder: 'Pagination marker', condition: { field: 'operation', - value: ['list_users', 'list_roles', 'list_policies', 'list_groups'], + value: [ + 'list_users', + 'list_roles', + 'list_policies', + 'list_groups', + 'list_attached_role_policies', + 'list_attached_user_policies', + 'simulate_principal_policy', + ], }, required: false, mode: 'advanced', @@ -296,6 +357,9 @@ export const IAMBlock: BlockConfig = { 'iam_list_groups', 'iam_add_user_to_group', 'iam_remove_user_from_group', + 'iam_list_attached_role_policies', + 'iam_list_attached_user_policies', + 'iam_simulate_principal_policy', ], config: { tool: (params) => { @@ -336,12 +400,19 @@ export const IAMBlock: BlockConfig = { return 'iam_add_user_to_group' case 'remove_user_from_group': return 'iam_remove_user_from_group' + case 'list_attached_role_policies': + return 'iam_list_attached_role_policies' + case 'list_attached_user_policies': + return 'iam_list_attached_user_policies' + case 'simulate_principal_policy': + return 'iam_simulate_principal_policy' default: throw new Error(`Invalid IAM operation: ${params.operation}`) } }, params: (params) => { - const { operation, maxItems, maxSessionDuration, onlyAttached, ...rest } = params + const { operation, maxItems, maxSessionDuration, onlyAttached, resourceArns, ...rest } = + params const connectionConfig = { region: rest.region, @@ -416,6 +487,34 @@ export const IAMBlock: BlockConfig = { result.userName = rest.userName result.groupName = rest.groupName break + case 'list_attached_role_policies': + result.roleName = rest.roleName + if (rest.pathPrefix) result.pathPrefix = rest.pathPrefix + if (maxItems) { + const parsed = Number.parseInt(String(maxItems), 10) + if (!Number.isNaN(parsed)) result.maxItems = parsed + } + if (rest.marker) result.marker = rest.marker + break + case 'list_attached_user_policies': + result.userName = rest.userName + if (rest.pathPrefix) result.pathPrefix = rest.pathPrefix + if (maxItems) { + const parsed = Number.parseInt(String(maxItems), 10) + if (!Number.isNaN(parsed)) result.maxItems = parsed + } + if (rest.marker) result.marker = rest.marker + break + case 'simulate_principal_policy': + result.policySourceArn = rest.policySourceArn + result.actionNames = rest.actionNames + if (resourceArns) result.resourceArns = resourceArns + if (maxItems) { + const parsed = Number.parseInt(String(maxItems), 10) + if (!Number.isNaN(parsed)) result.maxResults = parsed + } + if (rest.marker) result.marker = rest.marker + break } return result @@ -441,6 +540,12 @@ export const IAMBlock: BlockConfig = { pathPrefix: { type: 'string', description: 'Path prefix filter' }, maxItems: { type: 'number', description: 'Maximum number of items to return' }, marker: { type: 'string', description: 'Pagination marker' }, + policySourceArn: { type: 'string', description: 'ARN of the principal to simulate' }, + actionNames: { type: 'string', description: 'Comma-separated AWS actions to simulate' }, + resourceArns: { + type: 'string', + description: 'Comma-separated resource ARNs to simulate against', + }, }, outputs: { message: { @@ -549,5 +654,14 @@ export const IAMBlock: BlockConfig = { type: 'number', description: 'Number of items returned', }, + attachedPolicies: { + type: 'json', + description: 'List of attached managed policies with policyName and policyArn', + }, + evaluationResults: { + type: 'json', + description: + 'Policy simulation results per action: evalActionName, evalResourceName, evalDecision (allowed/explicitDeny/implicitDeny), matchedStatements, missingContextValues', + }, }, } diff --git a/apps/sim/blocks/blocks/identity_center.ts b/apps/sim/blocks/blocks/identity_center.ts new file mode 100644 index 00000000000..5258d9a6086 --- /dev/null +++ b/apps/sim/blocks/blocks/identity_center.ts @@ -0,0 +1,438 @@ +import { IdentityCenterIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import type { IdentityCenterBaseResponse } from '@/tools/identity_center/types' + +export const IdentityCenterBlock: BlockConfig = { + type: 'identity_center', + name: 'AWS Identity Center', + description: 'Manage temporary elevated access in AWS IAM Identity Center', + longDescription: + 'Provision and revoke temporary access to AWS accounts via IAM Identity Center (SSO). Assign permission sets to users or groups, look up users by email, and list accounts and permission sets for access request workflows.', + docsLink: 'https://docs.sim.ai/tools/identity-center', + category: 'tools', + integrationType: IntegrationType.Security, + tags: ['cloud', 'identity'], + bgColor: 'linear-gradient(45deg, #BD0816 0%, #FF5252 100%)', + icon: IdentityCenterIcon, + authMode: AuthMode.ApiKey, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Instances', id: 'list_instances' }, + { label: 'List Accounts', id: 'list_accounts' }, + { label: 'Describe Account', id: 'describe_account' }, + { label: 'List Permission Sets', id: 'list_permission_sets' }, + { label: 'Get User', id: 'get_user' }, + { label: 'Get Group', id: 'get_group' }, + { label: 'List Groups', id: 'list_groups' }, + { label: 'Create Account Assignment', id: 'create_account_assignment' }, + { label: 'Delete Account Assignment', id: 'delete_account_assignment' }, + { label: 'Check Assignment Status', id: 'check_assignment_status' }, + { label: 'Check Assignment Deletion Status', id: 'check_assignment_deletion_status' }, + { label: 'List Account Assignments', id: 'list_account_assignments' }, + ], + value: () => 'list_instances', + }, + { + id: 'region', + title: 'AWS Region', + type: 'short-input', + placeholder: 'us-east-1', + required: true, + }, + { + id: 'accessKeyId', + title: 'AWS Access Key ID', + type: 'short-input', + placeholder: 'AKIA...', + password: true, + required: true, + }, + { + id: 'secretAccessKey', + title: 'AWS Secret Access Key', + type: 'short-input', + placeholder: 'Your secret access key', + password: true, + required: true, + }, + { + id: 'instanceArn', + title: 'Instance ARN', + type: 'short-input', + placeholder: 'arn:aws:sso:::instance/ssoins-...', + condition: { + field: 'operation', + value: [ + 'list_instances', + 'list_accounts', + 'get_user', + 'get_group', + 'describe_account', + 'list_groups', + ], + not: true, + }, + required: { + field: 'operation', + value: [ + 'list_instances', + 'list_accounts', + 'get_user', + 'get_group', + 'describe_account', + 'list_groups', + ], + not: true, + }, + }, + { + id: 'identityStoreId', + title: 'Identity Store ID', + type: 'short-input', + placeholder: 'd-1234567890', + condition: { field: 'operation', value: ['get_user', 'get_group', 'list_groups'] }, + required: { field: 'operation', value: ['get_user', 'get_group', 'list_groups'] }, + }, + { + id: 'email', + title: 'Email Address', + type: 'short-input', + placeholder: 'user@example.com', + condition: { field: 'operation', value: 'get_user' }, + required: { field: 'operation', value: 'get_user' }, + }, + { + id: 'displayName', + title: 'Group Display Name', + type: 'short-input', + placeholder: 'Engineering-Admins', + condition: { field: 'operation', value: 'get_group' }, + required: { field: 'operation', value: 'get_group' }, + }, + { + id: 'accountId', + title: 'AWS Account ID', + type: 'short-input', + placeholder: '123456789012', + condition: { + field: 'operation', + value: ['create_account_assignment', 'delete_account_assignment', 'describe_account'], + }, + required: { + field: 'operation', + value: ['create_account_assignment', 'delete_account_assignment', 'describe_account'], + }, + }, + { + id: 'permissionSetArn', + title: 'Permission Set ARN', + type: 'short-input', + placeholder: 'arn:aws:sso:::permissionSet/ssoins-.../ps-...', + condition: { + field: 'operation', + value: ['create_account_assignment', 'delete_account_assignment'], + }, + required: { + field: 'operation', + value: ['create_account_assignment', 'delete_account_assignment'], + }, + }, + { + id: 'principalType', + title: 'Principal Type', + type: 'dropdown', + options: [ + { label: 'User', id: 'USER' }, + { label: 'Group', id: 'GROUP' }, + ], + value: () => 'USER', + condition: { + field: 'operation', + value: [ + 'create_account_assignment', + 'delete_account_assignment', + 'list_account_assignments', + ], + }, + required: { + field: 'operation', + value: [ + 'create_account_assignment', + 'delete_account_assignment', + 'list_account_assignments', + ], + }, + }, + { + id: 'principalId', + title: 'Principal ID', + type: 'short-input', + placeholder: 'Identity Store user or group ID', + condition: { + field: 'operation', + value: [ + 'create_account_assignment', + 'delete_account_assignment', + 'list_account_assignments', + ], + }, + required: { + field: 'operation', + value: [ + 'create_account_assignment', + 'delete_account_assignment', + 'list_account_assignments', + ], + }, + }, + { + id: 'requestId', + title: 'Request ID', + type: 'short-input', + placeholder: 'Request ID from Create or Delete Assignment', + condition: { + field: 'operation', + value: ['check_assignment_status', 'check_assignment_deletion_status'], + }, + required: { + field: 'operation', + value: ['check_assignment_status', 'check_assignment_deletion_status'], + }, + }, + { + id: 'maxResults', + title: 'Max Results', + type: 'short-input', + placeholder: '20', + condition: { + field: 'operation', + value: [ + 'list_instances', + 'list_accounts', + 'list_permission_sets', + 'list_account_assignments', + 'list_groups', + ], + }, + required: false, + mode: 'advanced', + }, + { + id: 'nextToken', + title: 'Pagination Token', + type: 'short-input', + placeholder: 'Next page token from previous request', + condition: { + field: 'operation', + value: [ + 'list_instances', + 'list_accounts', + 'list_permission_sets', + 'list_account_assignments', + 'list_groups', + ], + }, + required: false, + mode: 'advanced', + }, + ], + tools: { + access: [ + 'identity_center_list_instances', + 'identity_center_list_accounts', + 'identity_center_describe_account', + 'identity_center_list_permission_sets', + 'identity_center_get_user', + 'identity_center_get_group', + 'identity_center_list_groups', + 'identity_center_create_account_assignment', + 'identity_center_delete_account_assignment', + 'identity_center_check_assignment_status', + 'identity_center_check_assignment_deletion_status', + 'identity_center_list_account_assignments', + ], + config: { + tool: (params) => { + switch (params.operation) { + case 'list_instances': + return 'identity_center_list_instances' + case 'list_accounts': + return 'identity_center_list_accounts' + case 'describe_account': + return 'identity_center_describe_account' + case 'list_permission_sets': + return 'identity_center_list_permission_sets' + case 'get_user': + return 'identity_center_get_user' + case 'get_group': + return 'identity_center_get_group' + case 'list_groups': + return 'identity_center_list_groups' + case 'create_account_assignment': + return 'identity_center_create_account_assignment' + case 'delete_account_assignment': + return 'identity_center_delete_account_assignment' + case 'check_assignment_status': + return 'identity_center_check_assignment_status' + case 'check_assignment_deletion_status': + return 'identity_center_check_assignment_deletion_status' + case 'list_account_assignments': + return 'identity_center_list_account_assignments' + default: + throw new Error(`Invalid Identity Center operation: ${params.operation}`) + } + }, + params: (params) => { + const { operation, maxResults, ...rest } = params + + const connectionConfig = { + region: rest.region, + accessKeyId: rest.accessKeyId, + secretAccessKey: rest.secretAccessKey, + } + + const result: Record = { ...connectionConfig } + + switch (operation) { + case 'list_instances': + if (maxResults) { + const parsed = Number.parseInt(String(maxResults), 10) + if (!Number.isNaN(parsed)) result.maxResults = parsed + } + if (rest.nextToken) result.nextToken = rest.nextToken + break + case 'list_accounts': + if (maxResults) { + const parsed = Number.parseInt(String(maxResults), 10) + if (!Number.isNaN(parsed)) result.maxResults = parsed + } + if (rest.nextToken) result.nextToken = rest.nextToken + break + case 'describe_account': + result.accountId = rest.accountId + break + case 'list_permission_sets': + result.instanceArn = rest.instanceArn + if (maxResults) { + const parsed = Number.parseInt(String(maxResults), 10) + if (!Number.isNaN(parsed)) result.maxResults = parsed + } + if (rest.nextToken) result.nextToken = rest.nextToken + break + case 'get_user': + result.identityStoreId = rest.identityStoreId + result.email = rest.email + break + case 'get_group': + result.identityStoreId = rest.identityStoreId + result.displayName = rest.displayName + break + case 'list_groups': + result.identityStoreId = rest.identityStoreId + if (maxResults) { + const parsed = Number.parseInt(String(maxResults), 10) + if (!Number.isNaN(parsed)) result.maxResults = parsed + } + if (rest.nextToken) result.nextToken = rest.nextToken + break + case 'create_account_assignment': + case 'delete_account_assignment': + result.instanceArn = rest.instanceArn + result.accountId = rest.accountId + result.permissionSetArn = rest.permissionSetArn + result.principalType = rest.principalType + result.principalId = rest.principalId + break + case 'check_assignment_status': + case 'check_assignment_deletion_status': + result.instanceArn = rest.instanceArn + result.requestId = rest.requestId + break + case 'list_account_assignments': + result.instanceArn = rest.instanceArn + result.principalId = rest.principalId + result.principalType = rest.principalType + if (maxResults) { + const parsed = Number.parseInt(String(maxResults), 10) + if (!Number.isNaN(parsed)) result.maxResults = parsed + } + if (rest.nextToken) result.nextToken = rest.nextToken + break + } + + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Identity Center operation to perform' }, + region: { type: 'string', description: 'AWS region' }, + accessKeyId: { type: 'string', description: 'AWS access key ID' }, + secretAccessKey: { type: 'string', description: 'AWS secret access key' }, + instanceArn: { type: 'string', description: 'Identity Center instance ARN' }, + identityStoreId: { type: 'string', description: 'Identity Store ID' }, + email: { type: 'string', description: 'User email address' }, + displayName: { type: 'string', description: 'Group display name' }, + accountId: { type: 'string', description: 'AWS account ID' }, + permissionSetArn: { type: 'string', description: 'Permission set ARN' }, + principalType: { type: 'string', description: 'Principal type: USER or GROUP' }, + principalId: { type: 'string', description: 'Identity Store user or group ID' }, + requestId: { type: 'string', description: 'Assignment creation/deletion request ID' }, + maxResults: { type: 'number', description: 'Maximum number of results to return' }, + nextToken: { type: 'string', description: 'Pagination token from previous request' }, + }, + outputs: { + message: { type: 'string', description: 'Operation status message' }, + instances: { + type: 'json', + description: + 'List of Identity Center instances (instanceArn, identityStoreId, name, status, statusReason)', + }, + accounts: { + type: 'json', + description: 'List of AWS accounts (id, arn, name, email, status)', + }, + permissionSets: { + type: 'json', + description: 'List of permission sets (permissionSetArn, name, description, sessionDuration)', + }, + groups: { + type: 'json', + description: 'List of Identity Store groups (groupId, displayName, description)', + }, + userId: { type: 'string', description: 'Identity Store user ID (use as principalId)' }, + userName: { type: 'string', description: 'Username in the Identity Store' }, + displayName: { type: 'string', description: 'Display name of the user or group' }, + email: { type: 'string', description: 'Email address of the user' }, + groupId: { type: 'string', description: 'Identity Store group ID (use as principalId)' }, + description: { type: 'string', description: 'Group description' }, + id: { type: 'string', description: 'AWS account ID (from describe_account)' }, + arn: { type: 'string', description: 'AWS account ARN' }, + name: { type: 'string', description: 'AWS account name' }, + status: { + type: 'string', + description: + 'Assignment provisioning status (IN_PROGRESS, FAILED, SUCCEEDED) or account status', + }, + requestId: { type: 'string', description: 'Request ID for polling assignment status' }, + accountId: { type: 'string', description: 'Target AWS account ID' }, + permissionSetArn: { type: 'string', description: 'Permission set ARN' }, + principalType: { type: 'string', description: 'Principal type (USER or GROUP)' }, + principalId: { type: 'string', description: 'Principal ID' }, + failureReason: { type: 'string', description: 'Failure reason if status is FAILED' }, + createdDate: { type: 'string', description: 'Date the request was created' }, + joinedTimestamp: { type: 'string', description: 'Date the account joined the organization' }, + assignments: { + type: 'json', + description: + 'List of account assignments (accountId, permissionSetArn, principalType, principalId)', + }, + nextToken: { type: 'string', description: 'Pagination token for the next page' }, + count: { type: 'number', description: 'Number of items returned' }, + }, +} diff --git a/apps/sim/blocks/blocks/ses.ts b/apps/sim/blocks/blocks/ses.ts new file mode 100644 index 00000000000..8f2cb3d8b1a --- /dev/null +++ b/apps/sim/blocks/blocks/ses.ts @@ -0,0 +1,499 @@ +import { SESIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import type { ToolResponse } from '@/tools/types' + +export const SESBlock: BlockConfig = { + type: 'ses', + name: 'AWS SES', + description: 'Send emails and manage templates with AWS Simple Email Service', + longDescription: + 'Integrate AWS SES v2 into the workflow. Send simple, templated, and bulk emails. Manage email templates and retrieve account sending quota and verified identity information.', + docsLink: 'https://docs.sim.ai/tools/ses', + category: 'tools', + integrationType: IntegrationType.Email, + tags: ['cloud', 'marketing'], + authMode: AuthMode.ApiKey, + bgColor: 'linear-gradient(45deg, #BD0816 0%, #FF5252 100%)', + icon: SESIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Send Email', id: 'send_email' }, + { label: 'Send Templated Email', id: 'send_templated_email' }, + { label: 'Send Bulk Email', id: 'send_bulk_email' }, + { label: 'List Identities', id: 'list_identities' }, + { label: 'Get Account', id: 'get_account' }, + { label: 'Create Template', id: 'create_template' }, + { label: 'Get Template', id: 'get_template' }, + { label: 'List Templates', id: 'list_templates' }, + { label: 'Delete Template', id: 'delete_template' }, + ], + value: () => 'send_email', + }, + { + id: 'region', + title: 'AWS Region', + type: 'short-input', + placeholder: 'us-east-1', + required: true, + }, + { + id: 'accessKeyId', + title: 'AWS Access Key ID', + type: 'short-input', + placeholder: 'AKIA...', + password: true, + required: true, + }, + { + id: 'secretAccessKey', + title: 'AWS Secret Access Key', + type: 'short-input', + placeholder: 'Your secret access key', + password: true, + required: true, + }, + { + id: 'fromAddress', + title: 'From Address', + type: 'short-input', + placeholder: 'sender@example.com', + condition: { + field: 'operation', + value: ['send_email', 'send_templated_email', 'send_bulk_email'], + }, + required: { + field: 'operation', + value: ['send_email', 'send_templated_email', 'send_bulk_email'], + }, + }, + { + id: 'toAddresses', + title: 'To Addresses', + type: 'short-input', + placeholder: 'recipient@example.com, other@example.com', + condition: { + field: 'operation', + value: ['send_email', 'send_templated_email'], + }, + required: { + field: 'operation', + value: ['send_email', 'send_templated_email'], + }, + wandConfig: { + enabled: true, + prompt: + 'Generate a comma-separated list of recipient email addresses. Return ONLY the comma-separated addresses - no explanations, no extra text.', + placeholder: 'e.g. alice@example.com, bob@example.com', + }, + }, + { + id: 'subject', + title: 'Subject', + type: 'short-input', + placeholder: 'Your email subject', + condition: { field: 'operation', value: 'send_email' }, + required: { field: 'operation', value: 'send_email' }, + }, + { + id: 'bodyHtml', + title: 'HTML Body', + type: 'long-input', + placeholder: '

Hello

Your email content here

', + condition: { field: 'operation', value: 'send_email' }, + required: false, + }, + { + id: 'bodyText', + title: 'Plain Text Body', + type: 'long-input', + placeholder: 'Plain text version of your email', + condition: { field: 'operation', value: 'send_email' }, + required: false, + mode: 'advanced', + }, + { + id: 'templateName', + title: 'Template Name', + type: 'short-input', + placeholder: 'my-email-template', + condition: { + field: 'operation', + value: [ + 'send_templated_email', + 'send_bulk_email', + 'get_template', + 'create_template', + 'delete_template', + ], + }, + required: { + field: 'operation', + value: [ + 'send_templated_email', + 'send_bulk_email', + 'get_template', + 'create_template', + 'delete_template', + ], + }, + }, + { + id: 'templateData', + title: 'Template Data (JSON)', + type: 'code', + language: 'json', + placeholder: '{"name": "John", "link": "https://example.com"}', + condition: { field: 'operation', value: 'send_templated_email' }, + required: { field: 'operation', value: 'send_templated_email' }, + }, + { + id: 'destinations', + title: 'Destinations (JSON)', + type: 'code', + language: 'json', + placeholder: + '[{"toAddresses": ["user1@example.com"], "templateData": "{\"name\": \"User 1\"}"}]', + condition: { field: 'operation', value: 'send_bulk_email' }, + required: { field: 'operation', value: 'send_bulk_email' }, + }, + { + id: 'subjectPart', + title: 'Subject', + type: 'short-input', + placeholder: 'Hello, {{name}}!', + condition: { field: 'operation', value: 'create_template' }, + required: { field: 'operation', value: 'create_template' }, + }, + { + id: 'htmlPart', + title: 'HTML Body', + type: 'long-input', + placeholder: '

Hello, {{name}}!

', + condition: { field: 'operation', value: 'create_template' }, + required: false, + }, + { + id: 'textPart', + title: 'Plain Text Body', + type: 'long-input', + placeholder: 'Hello, {{name}}!', + condition: { field: 'operation', value: 'create_template' }, + required: false, + mode: 'advanced', + }, + { + id: 'ccAddresses', + title: 'CC Addresses', + type: 'short-input', + placeholder: 'cc@example.com', + condition: { + field: 'operation', + value: ['send_email', 'send_templated_email'], + }, + required: false, + mode: 'advanced', + }, + { + id: 'bccAddresses', + title: 'BCC Addresses', + type: 'short-input', + placeholder: 'bcc@example.com', + condition: { + field: 'operation', + value: ['send_email', 'send_templated_email'], + }, + required: false, + mode: 'advanced', + }, + { + id: 'replyToAddresses', + title: 'Reply-To Addresses', + type: 'short-input', + placeholder: 'replyto@example.com', + condition: { field: 'operation', value: 'send_email' }, + required: false, + mode: 'advanced', + }, + { + id: 'defaultTemplateData', + title: 'Default Template Data (JSON)', + type: 'code', + language: 'json', + placeholder: '{"company": "Acme"}', + condition: { field: 'operation', value: 'send_bulk_email' }, + required: false, + mode: 'advanced', + }, + { + id: 'configurationSetName', + title: 'Configuration Set', + type: 'short-input', + placeholder: 'my-configuration-set', + condition: { + field: 'operation', + value: ['send_email', 'send_templated_email', 'send_bulk_email'], + }, + required: false, + mode: 'advanced', + }, + { + id: 'pageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '100', + condition: { + field: 'operation', + value: ['list_identities', 'list_templates'], + }, + required: false, + mode: 'advanced', + }, + { + id: 'nextToken', + title: 'Next Token', + type: 'short-input', + placeholder: 'Pagination token from previous response', + condition: { + field: 'operation', + value: ['list_identities', 'list_templates'], + }, + required: false, + mode: 'advanced', + }, + ], + tools: { + access: [ + 'ses_send_email', + 'ses_send_templated_email', + 'ses_send_bulk_email', + 'ses_list_identities', + 'ses_get_account', + 'ses_create_template', + 'ses_get_template', + 'ses_list_templates', + 'ses_delete_template', + ], + config: { + tool: (params) => { + switch (params.operation) { + case 'send_email': + return 'ses_send_email' + case 'send_templated_email': + return 'ses_send_templated_email' + case 'send_bulk_email': + return 'ses_send_bulk_email' + case 'list_identities': + return 'ses_list_identities' + case 'get_account': + return 'ses_get_account' + case 'create_template': + return 'ses_create_template' + case 'get_template': + return 'ses_get_template' + case 'list_templates': + return 'ses_list_templates' + case 'delete_template': + return 'ses_delete_template' + default: + throw new Error(`Invalid SES operation: ${params.operation}`) + } + }, + params: (params) => { + const { operation, pageSize, ...rest } = params + + const connectionConfig = { + region: rest.region, + accessKeyId: rest.accessKeyId, + secretAccessKey: rest.secretAccessKey, + } + + const result: Record = { ...connectionConfig } + + switch (operation) { + case 'send_email': + result.fromAddress = rest.fromAddress + result.toAddresses = rest.toAddresses + result.subject = rest.subject + if (rest.bodyHtml) result.bodyHtml = rest.bodyHtml + if (rest.bodyText) result.bodyText = rest.bodyText + if (rest.ccAddresses) result.ccAddresses = rest.ccAddresses + if (rest.bccAddresses) result.bccAddresses = rest.bccAddresses + if (rest.replyToAddresses) result.replyToAddresses = rest.replyToAddresses + if (rest.configurationSetName) result.configurationSetName = rest.configurationSetName + break + case 'send_templated_email': + result.fromAddress = rest.fromAddress + result.toAddresses = rest.toAddresses + result.templateName = rest.templateName + result.templateData = rest.templateData + if (rest.ccAddresses) result.ccAddresses = rest.ccAddresses + if (rest.bccAddresses) result.bccAddresses = rest.bccAddresses + if (rest.configurationSetName) result.configurationSetName = rest.configurationSetName + break + case 'send_bulk_email': + result.fromAddress = rest.fromAddress + result.templateName = rest.templateName + result.destinations = rest.destinations + if (rest.defaultTemplateData) result.defaultTemplateData = rest.defaultTemplateData + if (rest.configurationSetName) result.configurationSetName = rest.configurationSetName + break + case 'list_identities': + if (pageSize) { + const parsed = Number.parseInt(String(pageSize), 10) + if (!Number.isNaN(parsed)) result.pageSize = parsed + } + if (rest.nextToken) result.nextToken = rest.nextToken + break + case 'get_account': + break + case 'create_template': + result.templateName = rest.templateName + result.subjectPart = rest.subjectPart + if (rest.htmlPart) result.htmlPart = rest.htmlPart + if (rest.textPart) result.textPart = rest.textPart + break + case 'get_template': + result.templateName = rest.templateName + break + case 'list_templates': + if (pageSize) { + const parsed = Number.parseInt(String(pageSize), 10) + if (!Number.isNaN(parsed)) result.pageSize = parsed + } + if (rest.nextToken) result.nextToken = rest.nextToken + break + case 'delete_template': + result.templateName = rest.templateName + break + } + + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'SES operation to perform' }, + region: { type: 'string', description: 'AWS region' }, + accessKeyId: { type: 'string', description: 'AWS access key ID' }, + secretAccessKey: { type: 'string', description: 'AWS secret access key' }, + fromAddress: { type: 'string', description: 'Verified sender email address' }, + toAddresses: { + type: 'string', + description: 'Comma-separated list of recipient email addresses', + }, + subject: { type: 'string', description: 'Email subject line' }, + bodyHtml: { type: 'string', description: 'HTML email body' }, + bodyText: { type: 'string', description: 'Plain text email body' }, + templateName: { type: 'string', description: 'SES template name' }, + templateData: { type: 'string', description: 'JSON template variable data' }, + destinations: { + type: 'string', + description: 'JSON array of bulk email destinations', + }, + subjectPart: { type: 'string', description: 'Template subject line' }, + htmlPart: { type: 'string', description: 'HTML body of the template' }, + textPart: { type: 'string', description: 'Plain text body of the template' }, + ccAddresses: { type: 'string', description: 'Comma-separated CC email addresses' }, + bccAddresses: { type: 'string', description: 'Comma-separated BCC email addresses' }, + replyToAddresses: { type: 'string', description: 'Comma-separated reply-to addresses' }, + defaultTemplateData: { + type: 'string', + description: 'Default JSON template data for bulk sends', + }, + configurationSetName: { type: 'string', description: 'SES configuration set name' }, + pageSize: { type: 'number', description: 'Maximum number of results to return' }, + nextToken: { type: 'string', description: 'Pagination token from previous response' }, + }, + outputs: { + messageId: { + type: 'string', + description: 'SES message ID (send_email, send_templated_email)', + condition: { field: 'operation', value: ['send_email', 'send_templated_email'] }, + }, + results: { + type: 'array', + description: 'Per-destination send results (send_bulk_email)', + condition: { field: 'operation', value: 'send_bulk_email' }, + }, + successCount: { + type: 'number', + description: 'Number of successfully sent emails (send_bulk_email)', + condition: { field: 'operation', value: 'send_bulk_email' }, + }, + failureCount: { + type: 'number', + description: 'Number of failed email sends (send_bulk_email)', + condition: { field: 'operation', value: 'send_bulk_email' }, + }, + identities: { + type: 'array', + description: 'List of verified email identities (list_identities)', + condition: { field: 'operation', value: 'list_identities' }, + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page (list_identities, list_templates)', + condition: { field: 'operation', value: ['list_identities', 'list_templates'] }, + }, + count: { + type: 'number', + description: 'Number of items returned (list_identities, list_templates)', + condition: { field: 'operation', value: ['list_identities', 'list_templates'] }, + }, + sendingEnabled: { + type: 'boolean', + description: 'Whether email sending is enabled (get_account)', + condition: { field: 'operation', value: 'get_account' }, + }, + max24HourSend: { + type: 'number', + description: 'Maximum emails per 24 hours (get_account)', + condition: { field: 'operation', value: 'get_account' }, + }, + maxSendRate: { + type: 'number', + description: 'Maximum emails per second (get_account)', + condition: { field: 'operation', value: 'get_account' }, + }, + sentLast24Hours: { + type: 'number', + description: 'Emails sent in the last 24 hours (get_account)', + condition: { field: 'operation', value: 'get_account' }, + }, + templateName: { + type: 'string', + description: 'Template name (get_template)', + condition: { field: 'operation', value: 'get_template' }, + }, + subjectPart: { + type: 'string', + description: 'Template subject (get_template)', + condition: { field: 'operation', value: 'get_template' }, + }, + textPart: { + type: 'string', + description: 'Template plain text body (get_template)', + condition: { field: 'operation', value: 'get_template' }, + }, + htmlPart: { + type: 'string', + description: 'Template HTML body (get_template)', + condition: { field: 'operation', value: 'get_template' }, + }, + templates: { + type: 'array', + description: 'List of email templates (list_templates)', + condition: { field: 'operation', value: 'list_templates' }, + }, + message: { + type: 'string', + description: 'Confirmation message (create_template, delete_template)', + condition: { field: 'operation', value: ['create_template', 'delete_template'] }, + }, + }, +} diff --git a/apps/sim/blocks/blocks/sts.ts b/apps/sim/blocks/blocks/sts.ts index 2f7608010be..d43aced9248 100644 --- a/apps/sim/blocks/blocks/sts.ts +++ b/apps/sim/blocks/blocks/sts.ts @@ -77,6 +77,15 @@ export const STSBlock: BlockConfig = { required: false, mode: 'advanced', }, + { + id: 'policy', + title: 'Session Policy (JSON)', + type: 'long-input', + placeholder: '{"Version":"2012-10-17","Statement":[...]}', + condition: { field: 'operation', value: 'assume_role' }, + required: false, + mode: 'advanced', + }, { id: 'externalId', title: 'External ID', @@ -154,6 +163,7 @@ export const STSBlock: BlockConfig = { const parsed = Number.parseInt(String(durationSeconds), 10) if (!Number.isNaN(parsed)) result.durationSeconds = parsed } + if (rest.policy) result.policy = rest.policy if (rest.externalId) result.externalId = rest.externalId if (rest.serialNumber) result.serialNumber = rest.serialNumber if (rest.tokenCode) result.tokenCode = rest.tokenCode @@ -184,7 +194,8 @@ export const STSBlock: BlockConfig = { secretAccessKey: { type: 'string', description: 'AWS secret access key' }, roleArn: { type: 'string', description: 'ARN of the role to assume' }, roleSessionName: { type: 'string', description: 'Session name for the assumed role' }, - durationSeconds: { type: 'number', description: 'Session duration in seconds' }, + durationSeconds: { type: 'string', description: 'Session duration in seconds' }, + policy: { type: 'string', description: 'JSON IAM session policy to restrict permissions' }, externalId: { type: 'string', description: 'External ID for cross-account access' }, serialNumber: { type: 'string', description: 'MFA device serial number' }, tokenCode: { type: 'string', description: 'MFA token code' }, @@ -193,43 +204,47 @@ export const STSBlock: BlockConfig = { outputs: { accessKeyId: { type: 'string', - description: 'Temporary access key ID', + description: 'Temporary access key ID (assume_role, get_session_token)', }, secretAccessKey: { type: 'string', - description: 'Temporary secret access key', + description: 'Temporary secret access key (assume_role, get_session_token)', }, sessionToken: { type: 'string', - description: 'Temporary session token', + description: 'Temporary session token (assume_role, get_session_token)', }, expiration: { type: 'string', - description: 'Credential expiration timestamp', + description: 'Credential expiration timestamp (assume_role, get_session_token)', }, assumedRoleArn: { type: 'string', - description: 'ARN of the assumed role', + description: 'ARN of the assumed role (assume_role only)', }, assumedRoleId: { type: 'string', - description: 'Assumed role ID with session name', + description: 'Assumed role ID with session name (assume_role only)', + }, + packedPolicySize: { + type: 'number', + description: 'Percentage of allowed policy size used (assume_role only)', + }, + sourceIdentity: { + type: 'string', + description: 'Source identity set on the role session (assume_role only)', }, account: { type: 'string', - description: 'AWS account ID', + description: 'AWS account ID (get_caller_identity, get_access_key_info)', }, arn: { type: 'string', - description: 'ARN of the calling entity', + description: 'ARN of the calling entity (get_caller_identity only)', }, userId: { type: 'string', - description: 'Unique identifier of the calling entity', - }, - packedPolicySize: { - type: 'number', - description: 'Percentage of allowed policy size used', + description: 'Unique identifier of the calling entity (get_caller_identity only)', }, }, } diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 69ae3bb3641..bb5eddb8756 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -93,6 +93,7 @@ import { HuggingFaceBlock } from '@/blocks/blocks/huggingface' import { HumanInTheLoopBlock } from '@/blocks/blocks/human_in_the_loop' import { HunterBlock } from '@/blocks/blocks/hunter' import { IAMBlock } from '@/blocks/blocks/iam' +import { IdentityCenterBlock } from '@/blocks/blocks/identity_center' import { ImageGeneratorBlock } from '@/blocks/blocks/image_generator' import { ImapBlock } from '@/blocks/blocks/imap' import { IncidentioBlock } from '@/blocks/blocks/incidentio' @@ -174,6 +175,7 @@ import { SendGridBlock } from '@/blocks/blocks/sendgrid' import { SentryBlock } from '@/blocks/blocks/sentry' import { SerperBlock } from '@/blocks/blocks/serper' import { ServiceNowBlock } from '@/blocks/blocks/servicenow' +import { SESBlock } from '@/blocks/blocks/ses' import { SftpBlock } from '@/blocks/blocks/sftp' import { SharepointBlock } from '@/blocks/blocks/sharepoint' import { ShopifyBlock } from '@/blocks/blocks/shopify' @@ -334,6 +336,7 @@ export const registry: Record = { human_in_the_loop: HumanInTheLoopBlock, hunter: HunterBlock, iam: IAMBlock, + identity_center: IdentityCenterBlock, image_generator: ImageGeneratorBlock, imap: ImapBlock, incidentio: IncidentioBlock, @@ -429,6 +432,7 @@ export const registry: Record = { smtp: SmtpBlock, spotify: SpotifyBlock, secrets_manager: SecretsManagerBlock, + ses: SESBlock, sqs: SQSBlock, ssh: SSHBlock, sts: STSBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 208cec09b42..061e0c0cdf5 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -4681,6 +4681,17 @@ export function IAMIcon(props: SVGProps) { ) } +export function IdentityCenterIcon(props: SVGProps) { + return ( + + + + ) +} + export function STSIcon(props: SVGProps) { return ( @@ -4699,6 +4710,24 @@ export function STSIcon(props: SVGProps) { ) } +export function SESIcon(props: SVGProps) { + return ( + + + + + + + + + + + ) +} + export function SecretsManagerIcon(props: SVGProps) { return ( diff --git a/apps/sim/lib/workflows/migrations/subblock-migrations.ts b/apps/sim/lib/workflows/migrations/subblock-migrations.ts index 34ebb293333..bc6ebdd465f 100644 --- a/apps/sim/lib/workflows/migrations/subblock-migrations.ts +++ b/apps/sim/lib/workflows/migrations/subblock-migrations.ts @@ -23,6 +23,14 @@ export const SUBBLOCK_ID_MIGRATIONS: Record> = { knowledge: { knowledgeBaseId: 'knowledgeBaseSelector', }, + dynamodb: { + key: 'getKey', + filterExpression: 'queryFilterExpression', + expressionAttributeNames: 'queryExpressionAttributeNames', + expressionAttributeValues: 'queryExpressionAttributeValues', + limit: 'queryLimit', + conditionExpression: 'updateConditionExpression', + }, ashby: { emailType: '_removed_emailType', phoneType: '_removed_phoneType', diff --git a/apps/sim/package.json b/apps/sim/package.json index ea5ec87c301..6794c71c55f 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -41,10 +41,14 @@ "@aws-sdk/client-cloudwatch-logs": "3.940.0", "@aws-sdk/client-dynamodb": "3.940.0", "@aws-sdk/client-iam": "3.1029.0", + "@aws-sdk/client-identitystore": "3.1032.0", + "@aws-sdk/client-organizations": "3.1032.0", "@aws-sdk/client-rds-data": "3.940.0", "@aws-sdk/client-s3": "^3.779.0", "@aws-sdk/client-secrets-manager": "3.940.0", + "@aws-sdk/client-sesv2": "3.940.0", "@aws-sdk/client-sqs": "3.947.0", + "@aws-sdk/client-sso-admin": "3.1032.0", "@aws-sdk/client-sts": "3.1029.0", "@aws-sdk/lib-dynamodb": "3.940.0", "@aws-sdk/s3-request-presigner": "^3.779.0", diff --git a/apps/sim/tools/cloudwatch/describe_alarms.ts b/apps/sim/tools/cloudwatch/describe_alarms.ts index 407e96e66f6..8014653068b 100644 --- a/apps/sim/tools/cloudwatch/describe_alarms.ts +++ b/apps/sim/tools/cloudwatch/describe_alarms.ts @@ -94,6 +94,25 @@ export const describeAlarmsTool: ToolConfig< alarms: { type: 'array', description: 'List of CloudWatch alarms with state and configuration', + items: { + type: 'object', + properties: { + alarmName: { type: 'string', description: 'Alarm name' }, + alarmArn: { type: 'string', description: 'Alarm ARN' }, + stateValue: { + type: 'string', + description: 'Current state (OK, ALARM, INSUFFICIENT_DATA)', + }, + stateReason: { type: 'string', description: 'Human-readable reason for the state' }, + metricName: { type: 'string', description: 'Metric name (MetricAlarm only)' }, + namespace: { type: 'string', description: 'Metric namespace (MetricAlarm only)' }, + threshold: { type: 'number', description: 'Threshold value (MetricAlarm only)' }, + stateUpdatedTimestamp: { + type: 'number', + description: 'Epoch ms when state last changed', + }, + }, + }, }, }, } diff --git a/apps/sim/tools/cloudwatch/describe_log_groups.ts b/apps/sim/tools/cloudwatch/describe_log_groups.ts index a297caa0263..b7187328164 100644 --- a/apps/sim/tools/cloudwatch/describe_log_groups.ts +++ b/apps/sim/tools/cloudwatch/describe_log_groups.ts @@ -77,6 +77,19 @@ export const describeLogGroupsTool: ToolConfig< }, outputs: { - logGroups: { type: 'array', description: 'List of CloudWatch log groups with metadata' }, + logGroups: { + type: 'array', + description: 'List of CloudWatch log groups with metadata', + items: { + type: 'object', + properties: { + logGroupName: { type: 'string', description: 'Log group name' }, + arn: { type: 'string', description: 'Log group ARN' }, + storedBytes: { type: 'number', description: 'Total stored bytes' }, + retentionInDays: { type: 'number', description: 'Retention period in days (if set)' }, + creationTime: { type: 'number', description: 'Creation time in epoch milliseconds' }, + }, + }, + }, }, } diff --git a/apps/sim/tools/cloudwatch/describe_log_streams.ts b/apps/sim/tools/cloudwatch/describe_log_streams.ts index e89496ea610..08015e6b700 100644 --- a/apps/sim/tools/cloudwatch/describe_log_streams.ts +++ b/apps/sim/tools/cloudwatch/describe_log_streams.ts @@ -86,7 +86,27 @@ export const describeLogStreamsTool: ToolConfig< outputs: { logStreams: { type: 'array', - description: 'List of log streams with metadata', + description: + 'List of log streams with metadata, sorted by last event time (most recent first) unless a prefix filter is applied', + items: { + type: 'object', + properties: { + logStreamName: { type: 'string', description: 'Log stream name' }, + lastEventTimestamp: { + type: 'number', + description: 'Timestamp of the last log event in epoch milliseconds', + }, + firstEventTimestamp: { + type: 'number', + description: 'Timestamp of the first log event in epoch milliseconds', + }, + creationTime: { + type: 'number', + description: 'Stream creation time in epoch milliseconds', + }, + storedBytes: { type: 'number', description: 'Total stored bytes' }, + }, + }, }, }, } diff --git a/apps/sim/tools/cloudwatch/get_log_events.ts b/apps/sim/tools/cloudwatch/get_log_events.ts index fe5cf774bf5..8454394c87b 100644 --- a/apps/sim/tools/cloudwatch/get_log_events.ts +++ b/apps/sim/tools/cloudwatch/get_log_events.ts @@ -101,6 +101,14 @@ export const getLogEventsTool: ToolConfig< events: { type: 'array', description: 'Log events with timestamp, message, and ingestion time', + items: { + type: 'object', + properties: { + timestamp: { type: 'number', description: 'Event timestamp in epoch milliseconds' }, + message: { type: 'string', description: 'Log event message' }, + ingestionTime: { type: 'number', description: 'Ingestion time in epoch milliseconds' }, + }, + }, }, }, } diff --git a/apps/sim/tools/cloudwatch/get_metric_statistics.ts b/apps/sim/tools/cloudwatch/get_metric_statistics.ts index 829230c94a6..947ea6ffa45 100644 --- a/apps/sim/tools/cloudwatch/get_metric_statistics.ts +++ b/apps/sim/tools/cloudwatch/get_metric_statistics.ts @@ -113,7 +113,22 @@ export const getMetricStatisticsTool: ToolConfig< }, outputs: { - label: { type: 'string', description: 'Metric label' }, - datapoints: { type: 'array', description: 'Datapoints with timestamp and statistics values' }, + label: { type: 'string', description: 'Metric label returned by CloudWatch' }, + datapoints: { + type: 'array', + description: 'Datapoints sorted by timestamp with statistics values', + items: { + type: 'object', + properties: { + timestamp: { type: 'number', description: 'Datapoint timestamp in epoch milliseconds' }, + average: { type: 'number', description: 'Average statistic value' }, + sum: { type: 'number', description: 'Sum statistic value' }, + minimum: { type: 'number', description: 'Minimum statistic value' }, + maximum: { type: 'number', description: 'Maximum statistic value' }, + sampleCount: { type: 'number', description: 'Sample count statistic value' }, + unit: { type: 'string', description: 'Unit of the metric' }, + }, + }, + }, }, } diff --git a/apps/sim/tools/cloudwatch/list_metrics.ts b/apps/sim/tools/cloudwatch/list_metrics.ts index dea2d1016e4..3916390941f 100644 --- a/apps/sim/tools/cloudwatch/list_metrics.ts +++ b/apps/sim/tools/cloudwatch/list_metrics.ts @@ -91,6 +91,17 @@ export const listMetricsTool: ToolConfig< }, outputs: { - metrics: { type: 'array', description: 'List of metrics with namespace, name, and dimensions' }, + metrics: { + type: 'array', + description: 'List of metrics with namespace, name, and dimensions', + items: { + type: 'object', + properties: { + namespace: { type: 'string', description: 'Metric namespace (e.g., AWS/EC2)' }, + metricName: { type: 'string', description: 'Metric name (e.g., CPUUtilization)' }, + dimensions: { type: 'array', description: 'Array of name/value dimension pairs' }, + }, + }, + }, }, } diff --git a/apps/sim/tools/cloudwatch/query_logs.ts b/apps/sim/tools/cloudwatch/query_logs.ts index 21548f8f666..a357974a28e 100644 --- a/apps/sim/tools/cloudwatch/query_logs.ts +++ b/apps/sim/tools/cloudwatch/query_logs.ts @@ -97,11 +97,25 @@ export const queryLogsTool: ToolConfig = { id: 'dynamodb_get', name: 'DynamoDB Get', description: 'Get an item from a DynamoDB table by primary key', - version: '1.0', + version: '1.0.0', params: { region: { @@ -33,7 +33,7 @@ export const getTool: ToolConfig = { description: 'DynamoDB table name (e.g., "Users", "Orders")', }, key: { - type: 'object', + type: 'json', required: true, visibility: 'user-or-llm', description: @@ -82,6 +82,6 @@ export const getTool: ToolConfig = { outputs: { message: { type: 'string', description: 'Operation status message' }, - item: { type: 'object', description: 'Retrieved item' }, + item: { type: 'json', description: 'Retrieved item', optional: true }, }, } diff --git a/apps/sim/tools/dynamodb/index.ts b/apps/sim/tools/dynamodb/index.ts index dd19b4bbbf3..4fcc71e8dd0 100644 --- a/apps/sim/tools/dynamodb/index.ts +++ b/apps/sim/tools/dynamodb/index.ts @@ -13,3 +13,5 @@ export const dynamodbPutTool = putTool export const dynamodbQueryTool = queryTool export const dynamodbScanTool = scanTool export const dynamodbUpdateTool = updateTool + +export * from './types' diff --git a/apps/sim/tools/dynamodb/introspect.ts b/apps/sim/tools/dynamodb/introspect.ts index e45c1a2baa0..7efff6ecb06 100644 --- a/apps/sim/tools/dynamodb/introspect.ts +++ b/apps/sim/tools/dynamodb/introspect.ts @@ -6,7 +6,7 @@ export const introspectTool: ToolConfig = { id: 'dynamodb_put', name: 'DynamoDB Put', description: 'Put an item into a DynamoDB table', - version: '1.0', + version: '1.0.0', params: { region: { @@ -33,12 +33,33 @@ export const putTool: ToolConfig = { description: 'DynamoDB table name (e.g., "Users", "Orders")', }, item: { - type: 'object', + type: 'json', required: true, visibility: 'user-or-llm', description: 'Item to put into the table (e.g., {"pk": "USER#123", "name": "John", "email": "john@example.com"})', }, + conditionExpression: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Condition that must be met for the put to succeed (e.g., "attribute_not_exists(pk)" to prevent overwrites)', + }, + expressionAttributeNames: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Attribute name mappings for reserved words used in conditionExpression (e.g., {"#name": "name"})', + }, + expressionAttributeValues: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Expression attribute values used in conditionExpression (e.g., {":expected": "value"})', + }, }, request: { @@ -53,6 +74,13 @@ export const putTool: ToolConfig = { secretAccessKey: params.secretAccessKey, tableName: params.tableName, item: params.item, + ...(params.conditionExpression && { conditionExpression: params.conditionExpression }), + ...(params.expressionAttributeNames && { + expressionAttributeNames: params.expressionAttributeNames, + }), + ...(params.expressionAttributeValues && { + expressionAttributeValues: params.expressionAttributeValues, + }), }), }, @@ -75,6 +103,6 @@ export const putTool: ToolConfig = { outputs: { message: { type: 'string', description: 'Operation status message' }, - item: { type: 'object', description: 'Created item' }, + item: { type: 'json', description: 'Created item', optional: true }, }, } diff --git a/apps/sim/tools/dynamodb/query.ts b/apps/sim/tools/dynamodb/query.ts index f6ed7b2a1ff..82e4c5224c7 100644 --- a/apps/sim/tools/dynamodb/query.ts +++ b/apps/sim/tools/dynamodb/query.ts @@ -5,7 +5,7 @@ export const queryTool: ToolConfig = id: 'dynamodb_query', name: 'DynamoDB Query', description: 'Query items from a DynamoDB table using key conditions', - version: '1.0', + version: '1.0.0', params: { region: { @@ -46,13 +46,13 @@ export const queryTool: ToolConfig = description: 'Filter expression for results (e.g., "age > :minAge AND #status = :status")', }, expressionAttributeNames: { - type: 'object', + type: 'json', required: false, visibility: 'user-or-llm', description: 'Attribute name mappings for reserved words (e.g., {"#status": "status"})', }, expressionAttributeValues: { - type: 'object', + type: 'json', required: false, visibility: 'user-or-llm', description: 'Expression attribute values (e.g., {":pk": "USER#123", ":minAge": 18})', @@ -69,6 +69,20 @@ export const queryTool: ToolConfig = visibility: 'user-or-llm', description: 'Maximum number of items to return (e.g., 10, 50, 100)', }, + exclusiveStartKey: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + "Pagination token from a previous query's lastEvaluatedKey to continue fetching results", + }, + scanIndexForward: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: + 'Sort order for the sort key: true for ascending (default), false for descending', + }, }, request: { @@ -92,6 +106,8 @@ export const queryTool: ToolConfig = }), ...(params.indexName && { indexName: params.indexName }), ...(params.limit && { limit: params.limit }), + ...(params.exclusiveStartKey && { exclusiveStartKey: params.exclusiveStartKey }), + ...(params.scanIndexForward !== undefined && { scanIndexForward: params.scanIndexForward }), }), }, @@ -108,6 +124,7 @@ export const queryTool: ToolConfig = message: data.message || 'Query executed successfully', items: data.items || [], count: data.count || 0, + ...(data.lastEvaluatedKey && { lastEvaluatedKey: data.lastEvaluatedKey }), }, error: undefined, } @@ -117,5 +134,11 @@ export const queryTool: ToolConfig = message: { type: 'string', description: 'Operation status message' }, items: { type: 'array', description: 'Array of items returned' }, count: { type: 'number', description: 'Number of items returned' }, + lastEvaluatedKey: { + type: 'json', + description: + 'Pagination token to pass as exclusiveStartKey to fetch the next page of results', + optional: true, + }, }, } diff --git a/apps/sim/tools/dynamodb/scan.ts b/apps/sim/tools/dynamodb/scan.ts index 4ee608b0171..fe740bdf099 100644 --- a/apps/sim/tools/dynamodb/scan.ts +++ b/apps/sim/tools/dynamodb/scan.ts @@ -5,7 +5,7 @@ export const scanTool: ToolConfig = { id: 'dynamodb_scan', name: 'DynamoDB Scan', description: 'Scan all items in a DynamoDB table', - version: '1.0', + version: '1.0.0', params: { region: { @@ -45,14 +45,14 @@ export const scanTool: ToolConfig = { description: 'Attributes to retrieve (e.g., "pk, sk, #name, email")', }, expressionAttributeNames: { - type: 'object', + type: 'json', required: false, visibility: 'user-or-llm', description: 'Attribute name mappings for reserved words (e.g., {"#name": "name", "#status": "status"})', }, expressionAttributeValues: { - type: 'object', + type: 'json', required: false, visibility: 'user-or-llm', description: 'Expression attribute values (e.g., {":minAge": 18, ":status": "active"})', @@ -63,6 +63,13 @@ export const scanTool: ToolConfig = { visibility: 'user-or-llm', description: 'Maximum number of items to return (e.g., 10, 50, 100)', }, + exclusiveStartKey: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + "Pagination token from a previous scan's lastEvaluatedKey to continue fetching results", + }, }, request: { @@ -85,6 +92,7 @@ export const scanTool: ToolConfig = { expressionAttributeValues: params.expressionAttributeValues, }), ...(params.limit && { limit: params.limit }), + ...(params.exclusiveStartKey && { exclusiveStartKey: params.exclusiveStartKey }), }), }, @@ -101,6 +109,7 @@ export const scanTool: ToolConfig = { message: data.message || 'Scan executed successfully', items: data.items || [], count: data.count || 0, + ...(data.lastEvaluatedKey && { lastEvaluatedKey: data.lastEvaluatedKey }), }, error: undefined, } @@ -110,5 +119,11 @@ export const scanTool: ToolConfig = { message: { type: 'string', description: 'Operation status message' }, items: { type: 'array', description: 'Array of items returned' }, count: { type: 'number', description: 'Number of items returned' }, + lastEvaluatedKey: { + type: 'json', + description: + 'Pagination token to pass as exclusiveStartKey to fetch the next page of results', + optional: true, + }, }, } diff --git a/apps/sim/tools/dynamodb/types.ts b/apps/sim/tools/dynamodb/types.ts index 8c5677fd932..d1affa26b5e 100644 --- a/apps/sim/tools/dynamodb/types.ts +++ b/apps/sim/tools/dynamodb/types.ts @@ -15,6 +15,9 @@ export interface DynamoDBGetParams extends DynamoDBConnectionConfig { export interface DynamoDBPutParams extends DynamoDBConnectionConfig { tableName: string item: Record + conditionExpression?: string + expressionAttributeNames?: Record + expressionAttributeValues?: Record } export interface DynamoDBQueryParams extends DynamoDBConnectionConfig { @@ -25,6 +28,8 @@ export interface DynamoDBQueryParams extends DynamoDBConnectionConfig { expressionAttributeValues?: Record indexName?: string limit?: number + exclusiveStartKey?: Record + scanIndexForward?: boolean } export interface DynamoDBScanParams extends DynamoDBConnectionConfig { @@ -34,6 +39,7 @@ export interface DynamoDBScanParams extends DynamoDBConnectionConfig { expressionAttributeNames?: Record expressionAttributeValues?: Record limit?: number + exclusiveStartKey?: Record } export interface DynamoDBUpdateParams extends DynamoDBConnectionConfig { @@ -49,6 +55,8 @@ export interface DynamoDBDeleteParams extends DynamoDBConnectionConfig { tableName: string key: Record conditionExpression?: string + expressionAttributeNames?: Record + expressionAttributeValues?: Record } export interface DynamoDBBaseResponse extends ToolResponse { @@ -57,6 +65,7 @@ export interface DynamoDBBaseResponse extends ToolResponse { item?: Record items?: Record[] count?: number + lastEvaluatedKey?: Record } error?: string } diff --git a/apps/sim/tools/dynamodb/update.ts b/apps/sim/tools/dynamodb/update.ts index 6fbd9e39d2a..5ce687283b7 100644 --- a/apps/sim/tools/dynamodb/update.ts +++ b/apps/sim/tools/dynamodb/update.ts @@ -5,7 +5,7 @@ export const updateTool: ToolConfig = { }, userName: { type: 'string', - required: true, + required: false, visibility: 'user-or-llm', - description: 'The name of the IAM user to retrieve', + description: 'The name of the IAM user to retrieve (defaults to the caller if omitted)', }, }, @@ -42,7 +42,7 @@ export const getUserTool: ToolConfig = { region: params.region, accessKeyId: params.accessKeyId, secretAccessKey: params.secretAccessKey, - userName: params.userName, + ...(params.userName ? { userName: params.userName } : {}), }), }, diff --git a/apps/sim/tools/iam/index.ts b/apps/sim/tools/iam/index.ts index bc2f9059c23..21acd0b2df3 100644 --- a/apps/sim/tools/iam/index.ts +++ b/apps/sim/tools/iam/index.ts @@ -1,3 +1,5 @@ +export * from './types' + import { addUserToGroupTool } from './add_user_to_group' import { attachRolePolicyTool } from './attach_role_policy' import { attachUserPolicyTool } from './attach_user_policy' @@ -11,11 +13,14 @@ import { detachRolePolicyTool } from './detach_role_policy' import { detachUserPolicyTool } from './detach_user_policy' import { getRoleTool } from './get_role' import { getUserTool } from './get_user' +import { listAttachedRolePoliciesTool } from './list_attached_role_policies' +import { listAttachedUserPoliciesTool } from './list_attached_user_policies' import { listGroupsTool } from './list_groups' import { listPoliciesTool } from './list_policies' import { listRolesTool } from './list_roles' import { listUsersTool } from './list_users' import { removeUserFromGroupTool } from './remove_user_from_group' +import { simulatePrincipalPolicyTool } from './simulate_principal_policy' export const iamListUsersTool = listUsersTool export const iamGetUserTool = getUserTool @@ -35,3 +40,6 @@ export const iamDeleteAccessKeyTool = deleteAccessKeyTool export const iamListGroupsTool = listGroupsTool export const iamAddUserToGroupTool = addUserToGroupTool export const iamRemoveUserFromGroupTool = removeUserFromGroupTool +export const iamListAttachedRolePoliciesTool = listAttachedRolePoliciesTool +export const iamListAttachedUserPoliciesTool = listAttachedUserPoliciesTool +export const iamSimulatePrincipalPolicyTool = simulatePrincipalPolicyTool diff --git a/apps/sim/tools/iam/list_attached_role_policies.ts b/apps/sim/tools/iam/list_attached_role_policies.ts new file mode 100644 index 00000000000..dc348a75158 --- /dev/null +++ b/apps/sim/tools/iam/list_attached_role_policies.ts @@ -0,0 +1,108 @@ +import type { + IAMListAttachedPoliciesResponse, + IAMListAttachedRolePoliciesParams, +} from '@/tools/iam/types' +import type { ToolConfig } from '@/tools/types' + +export const listAttachedRolePoliciesTool: ToolConfig< + IAMListAttachedRolePoliciesParams, + IAMListAttachedPoliciesResponse +> = { + id: 'iam_list_attached_role_policies', + name: 'IAM List Attached Role Policies', + description: 'List all managed policies attached to an IAM role', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + roleName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the IAM role', + }, + pathPrefix: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Path prefix to filter policies (e.g., /application/)', + }, + maxItems: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of policies to return (1-1000)', + }, + marker: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination marker from a previous request', + }, + }, + + request: { + url: '/api/tools/iam/list-attached-role-policies', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + roleName: params.roleName, + pathPrefix: params.pathPrefix, + maxItems: params.maxItems, + marker: params.marker, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to list attached role policies') + } + return { + success: true, + output: { + attachedPolicies: data.attachedPolicies ?? [], + isTruncated: data.isTruncated ?? false, + marker: data.marker ?? null, + count: data.count ?? 0, + }, + } + }, + + outputs: { + attachedPolicies: { + type: 'json', + description: 'List of attached policies with policyName and policyArn', + }, + isTruncated: { + type: 'boolean', + description: 'Whether there are more results available', + }, + marker: { + type: 'string', + description: 'Pagination marker for the next page of results', + optional: true, + }, + count: { type: 'number', description: 'Number of attached policies returned' }, + }, +} diff --git a/apps/sim/tools/iam/list_attached_user_policies.ts b/apps/sim/tools/iam/list_attached_user_policies.ts new file mode 100644 index 00000000000..a7f02bc1ed3 --- /dev/null +++ b/apps/sim/tools/iam/list_attached_user_policies.ts @@ -0,0 +1,108 @@ +import type { + IAMListAttachedPoliciesResponse, + IAMListAttachedUserPoliciesParams, +} from '@/tools/iam/types' +import type { ToolConfig } from '@/tools/types' + +export const listAttachedUserPoliciesTool: ToolConfig< + IAMListAttachedUserPoliciesParams, + IAMListAttachedPoliciesResponse +> = { + id: 'iam_list_attached_user_policies', + name: 'IAM List Attached User Policies', + description: 'List all managed policies attached to an IAM user', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + userName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the IAM user', + }, + pathPrefix: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Path prefix to filter policies (e.g., /application/)', + }, + maxItems: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of policies to return (1-1000)', + }, + marker: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination marker from a previous request', + }, + }, + + request: { + url: '/api/tools/iam/list-attached-user-policies', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + userName: params.userName, + pathPrefix: params.pathPrefix, + maxItems: params.maxItems, + marker: params.marker, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to list attached user policies') + } + return { + success: true, + output: { + attachedPolicies: data.attachedPolicies ?? [], + isTruncated: data.isTruncated ?? false, + marker: data.marker ?? null, + count: data.count ?? 0, + }, + } + }, + + outputs: { + attachedPolicies: { + type: 'json', + description: 'List of attached policies with policyName and policyArn', + }, + isTruncated: { + type: 'boolean', + description: 'Whether there are more results available', + }, + marker: { + type: 'string', + description: 'Pagination marker for the next page of results', + optional: true, + }, + count: { type: 'number', description: 'Number of attached policies returned' }, + }, +} diff --git a/apps/sim/tools/iam/simulate_principal_policy.ts b/apps/sim/tools/iam/simulate_principal_policy.ts new file mode 100644 index 00000000000..d59519f047c --- /dev/null +++ b/apps/sim/tools/iam/simulate_principal_policy.ts @@ -0,0 +1,120 @@ +import type { + IAMSimulatePrincipalPolicyParams, + IAMSimulatePrincipalPolicyResponse, +} from '@/tools/iam/types' +import type { ToolConfig } from '@/tools/types' + +export const simulatePrincipalPolicyTool: ToolConfig< + IAMSimulatePrincipalPolicyParams, + IAMSimulatePrincipalPolicyResponse +> = { + id: 'iam_simulate_principal_policy', + name: 'IAM Simulate Principal Policy', + description: + 'Simulate whether a user, role, or group is allowed to perform specific AWS actions — useful for pre-flight access checks', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + policySourceArn: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'ARN of the user, group, or role to simulate (e.g., arn:aws:iam::123456789012:user/alice)', + }, + actionNames: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Comma-separated list of AWS actions to simulate (e.g., s3:GetObject,ec2:DescribeInstances)', + }, + resourceArns: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated list of resource ARNs to simulate against (defaults to * if not provided)', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of simulation results to return (1-1000)', + }, + marker: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination marker from a previous request', + }, + }, + + request: { + url: '/api/tools/iam/simulate-principal-policy', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + policySourceArn: params.policySourceArn, + actionNames: params.actionNames, + resourceArns: params.resourceArns, + maxResults: params.maxResults, + marker: params.marker, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to simulate principal policy') + } + return { + success: true, + output: { + evaluationResults: data.evaluationResults ?? [], + isTruncated: data.isTruncated ?? false, + marker: data.marker ?? null, + count: data.count ?? 0, + }, + } + }, + + outputs: { + evaluationResults: { + type: 'json', + description: + 'Simulation results per action: evalActionName, evalResourceName, evalDecision (allowed/explicitDeny/implicitDeny), matchedStatements, missingContextValues', + }, + isTruncated: { + type: 'boolean', + description: 'Whether there are more results available', + }, + marker: { + type: 'string', + description: 'Pagination marker for the next page of results', + optional: true, + }, + count: { type: 'number', description: 'Number of evaluation results returned' }, + }, +} diff --git a/apps/sim/tools/iam/types.ts b/apps/sim/tools/iam/types.ts index 8c74710679f..ca6a2d69c62 100644 --- a/apps/sim/tools/iam/types.ts +++ b/apps/sim/tools/iam/types.ts @@ -13,7 +13,7 @@ export interface IAMListUsersParams extends IAMConnectionConfig { } export interface IAMGetUserParams extends IAMConnectionConfig { - userName: string + userName?: string | null } export interface IAMCreateUserParams extends IAMConnectionConfig { @@ -273,3 +273,54 @@ export interface IAMGroupMembershipResponse extends ToolResponse { output: { message: string } error?: string } + +export interface IAMListAttachedRolePoliciesParams extends IAMConnectionConfig { + roleName: string + pathPrefix?: string | null + maxItems?: number | null + marker?: string | null +} + +export interface IAMListAttachedUserPoliciesParams extends IAMConnectionConfig { + userName: string + pathPrefix?: string | null + maxItems?: number | null + marker?: string | null +} + +export interface IAMSimulatePrincipalPolicyParams extends IAMConnectionConfig { + policySourceArn: string + actionNames: string + resourceArns?: string | null + maxResults?: number | null + marker?: string | null +} + +export interface IAMListAttachedPoliciesResponse extends ToolResponse { + output: { + attachedPolicies: Array<{ + policyName: string + policyArn: string + }> + isTruncated: boolean + marker: string | null + count: number + } + error?: string +} + +export interface IAMSimulatePrincipalPolicyResponse extends ToolResponse { + output: { + evaluationResults: Array<{ + evalActionName: string + evalResourceName: string + evalDecision: string + matchedStatements: Array<{ statementId: string; sourcePolicyId: string }> + missingContextValues: string[] + }> + isTruncated: boolean + marker: string | null + count: number + } + error?: string +} diff --git a/apps/sim/tools/identity_center/check_assignment_deletion_status.ts b/apps/sim/tools/identity_center/check_assignment_deletion_status.ts new file mode 100644 index 00000000000..3b79bc3f8cc --- /dev/null +++ b/apps/sim/tools/identity_center/check_assignment_deletion_status.ts @@ -0,0 +1,105 @@ +import type { + IdentityCenterAssignmentStatusResponse, + IdentityCenterCheckAssignmentStatusParams, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const checkAssignmentDeletionStatusTool: ToolConfig< + IdentityCenterCheckAssignmentStatusParams, + IdentityCenterAssignmentStatusResponse +> = { + id: 'identity_center_check_assignment_deletion_status', + name: 'Identity Center Check Assignment Deletion Status', + description: 'Check the deprovisioning status of an account assignment deletion request', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + instanceArn: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ARN of the Identity Center instance', + }, + requestId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Request ID returned from Delete Account Assignment', + }, + }, + + request: { + url: '/api/tools/identity-center/check-assignment-deletion-status', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + instanceArn: params.instanceArn, + requestId: params.requestId, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to check assignment deletion status') + } + return { + success: true, + output: { + message: data.message ?? '', + status: data.status ?? '', + requestId: data.requestId ?? '', + accountId: data.accountId ?? null, + permissionSetArn: data.permissionSetArn ?? null, + principalType: data.principalType ?? null, + principalId: data.principalId ?? null, + failureReason: data.failureReason ?? null, + createdDate: data.createdDate ?? null, + }, + } + }, + + outputs: { + message: { type: 'string', description: 'Human-readable status message' }, + status: { + type: 'string', + description: 'Current deletion status: IN_PROGRESS, FAILED, or SUCCEEDED', + }, + requestId: { type: 'string', description: 'The deletion request ID that was checked' }, + accountId: { type: 'string', description: 'Target AWS account ID', optional: true }, + permissionSetArn: { type: 'string', description: 'Permission set ARN', optional: true }, + principalType: { + type: 'string', + description: 'Principal type (USER or GROUP)', + optional: true, + }, + principalId: { type: 'string', description: 'Principal ID', optional: true }, + failureReason: { + type: 'string', + description: 'Reason for failure if status is FAILED', + optional: true, + }, + createdDate: { type: 'string', description: 'Date the request was created', optional: true }, + }, +} diff --git a/apps/sim/tools/identity_center/check_assignment_status.ts b/apps/sim/tools/identity_center/check_assignment_status.ts new file mode 100644 index 00000000000..9d73589d943 --- /dev/null +++ b/apps/sim/tools/identity_center/check_assignment_status.ts @@ -0,0 +1,105 @@ +import type { + IdentityCenterAssignmentStatusResponse, + IdentityCenterCheckAssignmentStatusParams, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const checkAssignmentStatusTool: ToolConfig< + IdentityCenterCheckAssignmentStatusParams, + IdentityCenterAssignmentStatusResponse +> = { + id: 'identity_center_check_assignment_status', + name: 'Identity Center Check Assignment Status', + description: 'Check the provisioning status of an account assignment creation request', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + instanceArn: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ARN of the Identity Center instance', + }, + requestId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Request ID returned from Create or Delete Account Assignment', + }, + }, + + request: { + url: '/api/tools/identity-center/check-assignment-status', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + instanceArn: params.instanceArn, + requestId: params.requestId, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to check assignment status') + } + return { + success: true, + output: { + message: data.message ?? '', + status: data.status ?? '', + requestId: data.requestId ?? '', + accountId: data.accountId ?? null, + permissionSetArn: data.permissionSetArn ?? null, + principalType: data.principalType ?? null, + principalId: data.principalId ?? null, + failureReason: data.failureReason ?? null, + createdDate: data.createdDate ?? null, + }, + } + }, + + outputs: { + message: { type: 'string', description: 'Human-readable status message' }, + status: { + type: 'string', + description: 'Current status: IN_PROGRESS, FAILED, or SUCCEEDED', + }, + requestId: { type: 'string', description: 'The request ID that was checked' }, + accountId: { type: 'string', description: 'Target AWS account ID', optional: true }, + permissionSetArn: { type: 'string', description: 'Permission set ARN', optional: true }, + principalType: { + type: 'string', + description: 'Principal type (USER or GROUP)', + optional: true, + }, + principalId: { type: 'string', description: 'Principal ID', optional: true }, + failureReason: { + type: 'string', + description: 'Reason for failure if status is FAILED', + optional: true, + }, + createdDate: { type: 'string', description: 'Date the request was created', optional: true }, + }, +} diff --git a/apps/sim/tools/identity_center/create_account_assignment.ts b/apps/sim/tools/identity_center/create_account_assignment.ts new file mode 100644 index 00000000000..0086377620f --- /dev/null +++ b/apps/sim/tools/identity_center/create_account_assignment.ts @@ -0,0 +1,130 @@ +import type { + IdentityCenterAssignmentStatusResponse, + IdentityCenterCreateAccountAssignmentParams, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const createAccountAssignmentTool: ToolConfig< + IdentityCenterCreateAccountAssignmentParams, + IdentityCenterAssignmentStatusResponse +> = { + id: 'identity_center_create_account_assignment', + name: 'Identity Center Create Account Assignment', + description: + 'Grant a user or group access to an AWS account via a permission set (temporary elevated access)', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + instanceArn: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ARN of the Identity Center instance', + }, + accountId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'AWS account ID to grant access to', + }, + permissionSetArn: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ARN of the permission set to assign', + }, + principalType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Type of principal: USER or GROUP', + }, + principalId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Identity Store ID of the user or group', + }, + }, + + request: { + url: '/api/tools/identity-center/create-account-assignment', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + instanceArn: params.instanceArn, + accountId: params.accountId, + permissionSetArn: params.permissionSetArn, + principalType: params.principalType, + principalId: params.principalId, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to create account assignment') + } + return { + success: true, + output: { + message: data.message ?? 'Account assignment creation initiated', + status: data.status ?? '', + requestId: data.requestId ?? '', + accountId: data.accountId ?? null, + permissionSetArn: data.permissionSetArn ?? null, + principalType: data.principalType ?? null, + principalId: data.principalId ?? null, + failureReason: data.failureReason ?? null, + createdDate: data.createdDate ?? null, + }, + } + }, + + outputs: { + message: { type: 'string', description: 'Status message' }, + status: { + type: 'string', + description: 'Provisioning status: IN_PROGRESS, FAILED, or SUCCEEDED', + }, + requestId: { + type: 'string', + description: 'Request ID to use with Check Assignment Status', + }, + accountId: { type: 'string', description: 'Target AWS account ID', optional: true }, + permissionSetArn: { type: 'string', description: 'Permission set ARN', optional: true }, + principalType: { + type: 'string', + description: 'Principal type (USER or GROUP)', + optional: true, + }, + principalId: { type: 'string', description: 'Principal ID', optional: true }, + failureReason: { + type: 'string', + description: 'Reason for failure if status is FAILED', + optional: true, + }, + createdDate: { type: 'string', description: 'Date the request was created', optional: true }, + }, +} diff --git a/apps/sim/tools/identity_center/delete_account_assignment.ts b/apps/sim/tools/identity_center/delete_account_assignment.ts new file mode 100644 index 00000000000..5895ff5621c --- /dev/null +++ b/apps/sim/tools/identity_center/delete_account_assignment.ts @@ -0,0 +1,130 @@ +import type { + IdentityCenterAssignmentStatusResponse, + IdentityCenterDeleteAccountAssignmentParams, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const deleteAccountAssignmentTool: ToolConfig< + IdentityCenterDeleteAccountAssignmentParams, + IdentityCenterAssignmentStatusResponse +> = { + id: 'identity_center_delete_account_assignment', + name: 'Identity Center Delete Account Assignment', + description: + 'Revoke a user or group access to an AWS account by removing a permission set assignment', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + instanceArn: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ARN of the Identity Center instance', + }, + accountId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'AWS account ID to revoke access from', + }, + permissionSetArn: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ARN of the permission set to remove', + }, + principalType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Type of principal: USER or GROUP', + }, + principalId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Identity Store ID of the user or group', + }, + }, + + request: { + url: '/api/tools/identity-center/delete-account-assignment', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + instanceArn: params.instanceArn, + accountId: params.accountId, + permissionSetArn: params.permissionSetArn, + principalType: params.principalType, + principalId: params.principalId, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to delete account assignment') + } + return { + success: true, + output: { + message: data.message ?? 'Account assignment deletion initiated', + status: data.status ?? '', + requestId: data.requestId ?? '', + accountId: data.accountId ?? null, + permissionSetArn: data.permissionSetArn ?? null, + principalType: data.principalType ?? null, + principalId: data.principalId ?? null, + failureReason: data.failureReason ?? null, + createdDate: data.createdDate ?? null, + }, + } + }, + + outputs: { + message: { type: 'string', description: 'Status message' }, + status: { + type: 'string', + description: 'Deprovisioning status: IN_PROGRESS, FAILED, or SUCCEEDED', + }, + requestId: { + type: 'string', + description: 'Request ID to use with Check Assignment Status', + }, + accountId: { type: 'string', description: 'Target AWS account ID', optional: true }, + permissionSetArn: { type: 'string', description: 'Permission set ARN', optional: true }, + principalType: { + type: 'string', + description: 'Principal type (USER or GROUP)', + optional: true, + }, + principalId: { type: 'string', description: 'Principal ID', optional: true }, + failureReason: { + type: 'string', + description: 'Reason for failure if status is FAILED', + optional: true, + }, + createdDate: { type: 'string', description: 'Date the request was created', optional: true }, + }, +} diff --git a/apps/sim/tools/identity_center/describe_account.ts b/apps/sim/tools/identity_center/describe_account.ts new file mode 100644 index 00000000000..aa9ad3bcc4d --- /dev/null +++ b/apps/sim/tools/identity_center/describe_account.ts @@ -0,0 +1,85 @@ +import type { + IdentityCenterDescribeAccountParams, + IdentityCenterDescribeAccountResponse, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const describeAccountTool: ToolConfig< + IdentityCenterDescribeAccountParams, + IdentityCenterDescribeAccountResponse +> = { + id: 'identity_center_describe_account', + name: 'Identity Center Describe Account', + description: 'Retrieve details about a specific AWS account by its ID', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + accountId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'AWS account ID to describe', + }, + }, + + request: { + url: '/api/tools/identity-center/describe-account', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + accountId: params.accountId, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to describe AWS account') + } + return { + success: true, + output: { + id: data.id ?? '', + arn: data.arn ?? '', + name: data.name ?? '', + email: data.email ?? '', + status: data.status ?? '', + joinedTimestamp: data.joinedTimestamp ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'AWS account ID' }, + arn: { type: 'string', description: 'AWS account ARN' }, + name: { type: 'string', description: 'Account name' }, + email: { type: 'string', description: 'Root email address of the account' }, + status: { type: 'string', description: 'Account status (ACTIVE, SUSPENDED, etc.)' }, + joinedTimestamp: { + type: 'string', + description: 'Date the account joined the organization', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/identity_center/get_group.ts b/apps/sim/tools/identity_center/get_group.ts new file mode 100644 index 00000000000..7e8c2ad724a --- /dev/null +++ b/apps/sim/tools/identity_center/get_group.ts @@ -0,0 +1,82 @@ +import type { + IdentityCenterGetGroupParams, + IdentityCenterGetGroupResponse, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const getGroupTool: ToolConfig< + IdentityCenterGetGroupParams, + IdentityCenterGetGroupResponse +> = { + id: 'identity_center_get_group', + name: 'Identity Center Get Group', + description: 'Look up a group in the Identity Store by display name', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + identityStoreId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Identity Store ID (from the Identity Center instance)', + }, + displayName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Display name of the group to look up', + }, + }, + + request: { + url: '/api/tools/identity-center/get-group', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + identityStoreId: params.identityStoreId, + displayName: params.displayName, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to get group from Identity Store') + } + return { + success: true, + output: { + groupId: data.groupId ?? '', + displayName: data.displayName ?? null, + description: data.description ?? null, + }, + } + }, + + outputs: { + groupId: { type: 'string', description: 'Identity Store group ID (use as principalId)' }, + displayName: { type: 'string', description: 'Display name of the group', optional: true }, + description: { type: 'string', description: 'Group description', optional: true }, + }, +} diff --git a/apps/sim/tools/identity_center/get_user.ts b/apps/sim/tools/identity_center/get_user.ts new file mode 100644 index 00000000000..4f075bb129a --- /dev/null +++ b/apps/sim/tools/identity_center/get_user.ts @@ -0,0 +1,81 @@ +import type { + IdentityCenterGetUserParams, + IdentityCenterGetUserResponse, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const getUserTool: ToolConfig = { + id: 'identity_center_get_user', + name: 'Identity Center Get User', + description: 'Look up a user in the Identity Store by email address', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + identityStoreId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Identity Store ID (from the Identity Center instance)', + }, + email: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Email address of the user to look up', + }, + }, + + request: { + url: '/api/tools/identity-center/get-user', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + identityStoreId: params.identityStoreId, + email: params.email, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to get user from Identity Store') + } + return { + success: true, + output: { + userId: data.userId ?? '', + userName: data.userName ?? '', + displayName: data.displayName ?? null, + email: data.email ?? null, + }, + } + }, + + outputs: { + userId: { type: 'string', description: 'Identity Store user ID (use as principalId)' }, + userName: { type: 'string', description: 'Username in the Identity Store' }, + displayName: { type: 'string', description: 'Display name of the user', optional: true }, + email: { type: 'string', description: 'Email address of the user', optional: true }, + }, +} diff --git a/apps/sim/tools/identity_center/index.ts b/apps/sim/tools/identity_center/index.ts new file mode 100644 index 00000000000..9fb040b4ed4 --- /dev/null +++ b/apps/sim/tools/identity_center/index.ts @@ -0,0 +1,13 @@ +export { checkAssignmentDeletionStatusTool as identityCenterCheckAssignmentDeletionStatusTool } from './check_assignment_deletion_status' +export { checkAssignmentStatusTool as identityCenterCheckAssignmentStatusTool } from './check_assignment_status' +export { createAccountAssignmentTool as identityCenterCreateAccountAssignmentTool } from './create_account_assignment' +export { deleteAccountAssignmentTool as identityCenterDeleteAccountAssignmentTool } from './delete_account_assignment' +export { describeAccountTool as identityCenterDescribeAccountTool } from './describe_account' +export { getGroupTool as identityCenterGetGroupTool } from './get_group' +export { getUserTool as identityCenterGetUserTool } from './get_user' +export { listAccountAssignmentsTool as identityCenterListAccountAssignmentsTool } from './list_account_assignments' +export { listAccountsTool as identityCenterListAccountsTool } from './list_accounts' +export { listGroupsTool as identityCenterListGroupsTool } from './list_groups' +export { listInstancesTool as identityCenterListInstancesTool } from './list_instances' +export { listPermissionSetsTool as identityCenterListPermissionSetsTool } from './list_permission_sets' +export * from './types' diff --git a/apps/sim/tools/identity_center/list_account_assignments.ts b/apps/sim/tools/identity_center/list_account_assignments.ts new file mode 100644 index 00000000000..4bf6d8c7a10 --- /dev/null +++ b/apps/sim/tools/identity_center/list_account_assignments.ts @@ -0,0 +1,111 @@ +import type { + IdentityCenterListAccountAssignmentsParams, + IdentityCenterListAccountAssignmentsResponse, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const listAccountAssignmentsTool: ToolConfig< + IdentityCenterListAccountAssignmentsParams, + IdentityCenterListAccountAssignmentsResponse +> = { + id: 'identity_center_list_account_assignments', + name: 'Identity Center List Account Assignments', + description: 'List all account assignments for a specific user or group across all accounts', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + instanceArn: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ARN of the Identity Center instance', + }, + principalId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Identity Store ID of the user or group', + }, + principalType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Type of principal: USER or GROUP', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of assignments to return', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous request', + }, + }, + + request: { + url: '/api/tools/identity-center/list-account-assignments', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + instanceArn: params.instanceArn, + principalId: params.principalId, + principalType: params.principalType, + maxResults: params.maxResults, + nextToken: params.nextToken, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to list account assignments') + } + return { + success: true, + output: { + assignments: data.assignments ?? [], + nextToken: data.nextToken ?? null, + count: data.count ?? 0, + }, + } + }, + + outputs: { + assignments: { + type: 'json', + description: + 'List of account assignments with accountId, permissionSetArn, principalType, principalId', + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page of results', + optional: true, + }, + count: { type: 'number', description: 'Number of assignments returned' }, + }, +} diff --git a/apps/sim/tools/identity_center/list_accounts.ts b/apps/sim/tools/identity_center/list_accounts.ts new file mode 100644 index 00000000000..c7495eaac5c --- /dev/null +++ b/apps/sim/tools/identity_center/list_accounts.ts @@ -0,0 +1,89 @@ +import type { + IdentityCenterListAccountsParams, + IdentityCenterListAccountsResponse, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const listAccountsTool: ToolConfig< + IdentityCenterListAccountsParams, + IdentityCenterListAccountsResponse +> = { + id: 'identity_center_list_accounts', + name: 'Identity Center List Accounts', + description: 'List all AWS accounts in your organization', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of accounts to return', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous request', + }, + }, + + request: { + url: '/api/tools/identity-center/list-accounts', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + maxResults: params.maxResults, + nextToken: params.nextToken, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to list AWS accounts') + } + return { + success: true, + output: { + accounts: data.accounts ?? [], + nextToken: data.nextToken ?? null, + count: data.count ?? 0, + }, + } + }, + + outputs: { + accounts: { + type: 'json', + description: 'List of AWS accounts with id, arn, name, email, status', + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page of results', + optional: true, + }, + count: { type: 'number', description: 'Number of accounts returned' }, + }, +} diff --git a/apps/sim/tools/identity_center/list_groups.ts b/apps/sim/tools/identity_center/list_groups.ts new file mode 100644 index 00000000000..9399ee3fd3d --- /dev/null +++ b/apps/sim/tools/identity_center/list_groups.ts @@ -0,0 +1,96 @@ +import type { + IdentityCenterListGroupsParams, + IdentityCenterListGroupsResponse, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const listGroupsTool: ToolConfig< + IdentityCenterListGroupsParams, + IdentityCenterListGroupsResponse +> = { + id: 'identity_center_list_groups', + name: 'Identity Center List Groups', + description: 'List all groups in the Identity Store', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + identityStoreId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Identity Store ID (from the Identity Center instance)', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of groups to return', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous request', + }, + }, + + request: { + url: '/api/tools/identity-center/list-groups', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + identityStoreId: params.identityStoreId, + maxResults: params.maxResults, + nextToken: params.nextToken, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to list groups') + } + return { + success: true, + output: { + groups: data.groups ?? [], + nextToken: data.nextToken ?? null, + count: data.count ?? 0, + }, + } + }, + + outputs: { + groups: { + type: 'json', + description: 'List of groups with groupId, displayName, description', + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page of results', + optional: true, + }, + count: { type: 'number', description: 'Number of groups returned' }, + }, +} diff --git a/apps/sim/tools/identity_center/list_instances.ts b/apps/sim/tools/identity_center/list_instances.ts new file mode 100644 index 00000000000..6ced1c2b57c --- /dev/null +++ b/apps/sim/tools/identity_center/list_instances.ts @@ -0,0 +1,90 @@ +import type { + IdentityCenterListInstancesParams, + IdentityCenterListInstancesResponse, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const listInstancesTool: ToolConfig< + IdentityCenterListInstancesParams, + IdentityCenterListInstancesResponse +> = { + id: 'identity_center_list_instances', + name: 'Identity Center List Instances', + description: 'List all AWS IAM Identity Center instances in your account', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of instances to return (1-100)', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous request', + }, + }, + + request: { + url: '/api/tools/identity-center/list-instances', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + maxResults: params.maxResults, + nextToken: params.nextToken, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to list Identity Center instances') + } + return { + success: true, + output: { + instances: data.instances ?? [], + nextToken: data.nextToken ?? null, + count: data.count ?? 0, + }, + } + }, + + outputs: { + instances: { + type: 'json', + description: + 'List of Identity Center instances with instanceArn, identityStoreId, name, status, statusReason', + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page of results', + optional: true, + }, + count: { type: 'number', description: 'Number of instances returned' }, + }, +} diff --git a/apps/sim/tools/identity_center/list_permission_sets.ts b/apps/sim/tools/identity_center/list_permission_sets.ts new file mode 100644 index 00000000000..3f1d5ec9245 --- /dev/null +++ b/apps/sim/tools/identity_center/list_permission_sets.ts @@ -0,0 +1,97 @@ +import type { + IdentityCenterListPermissionSetsParams, + IdentityCenterListPermissionSetsResponse, +} from '@/tools/identity_center/types' +import type { ToolConfig } from '@/tools/types' + +export const listPermissionSetsTool: ToolConfig< + IdentityCenterListPermissionSetsParams, + IdentityCenterListPermissionSetsResponse +> = { + id: 'identity_center_list_permission_sets', + name: 'Identity Center List Permission Sets', + description: 'List all permission sets defined in an IAM Identity Center instance', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + instanceArn: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ARN of the Identity Center instance', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of permission sets to return', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous request', + }, + }, + + request: { + url: '/api/tools/identity-center/list-permission-sets', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + instanceArn: params.instanceArn, + maxResults: params.maxResults, + nextToken: params.nextToken, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'Failed to list permission sets') + } + return { + success: true, + output: { + permissionSets: data.permissionSets ?? [], + nextToken: data.nextToken ?? null, + count: data.count ?? 0, + }, + } + }, + + outputs: { + permissionSets: { + type: 'json', + description: + 'List of permission sets with permissionSetArn, name, description, sessionDuration', + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page of results', + optional: true, + }, + count: { type: 'number', description: 'Number of permission sets returned' }, + }, +} diff --git a/apps/sim/tools/identity_center/types.ts b/apps/sim/tools/identity_center/types.ts new file mode 100644 index 00000000000..8bc95ee73ed --- /dev/null +++ b/apps/sim/tools/identity_center/types.ts @@ -0,0 +1,201 @@ +import type { ToolResponse } from '@/tools/types' + +export interface IdentityCenterConnectionConfig { + region: string + accessKeyId: string + secretAccessKey: string +} + +export interface IdentityCenterListInstancesParams extends IdentityCenterConnectionConfig { + maxResults?: number | null + nextToken?: string | null +} + +export interface IdentityCenterListAccountsParams extends IdentityCenterConnectionConfig { + maxResults?: number | null + nextToken?: string | null +} + +export interface IdentityCenterListPermissionSetsParams extends IdentityCenterConnectionConfig { + instanceArn: string + maxResults?: number | null + nextToken?: string | null +} + +export interface IdentityCenterGetUserParams extends IdentityCenterConnectionConfig { + identityStoreId: string + email: string +} + +export interface IdentityCenterCreateAccountAssignmentParams + extends IdentityCenterConnectionConfig { + instanceArn: string + accountId: string + permissionSetArn: string + principalType: 'USER' | 'GROUP' + principalId: string +} + +export interface IdentityCenterDeleteAccountAssignmentParams + extends IdentityCenterConnectionConfig { + instanceArn: string + accountId: string + permissionSetArn: string + principalType: 'USER' | 'GROUP' + principalId: string +} + +export interface IdentityCenterCheckAssignmentStatusParams extends IdentityCenterConnectionConfig { + instanceArn: string + requestId: string +} + +export interface IdentityCenterListAccountAssignmentsParams extends IdentityCenterConnectionConfig { + instanceArn: string + principalId: string + principalType: 'USER' | 'GROUP' + maxResults?: number | null + nextToken?: string | null +} + +export interface IdentityCenterListGroupsParams extends IdentityCenterConnectionConfig { + identityStoreId: string + maxResults?: number | null + nextToken?: string | null +} + +export interface IdentityCenterGetGroupParams extends IdentityCenterConnectionConfig { + identityStoreId: string + displayName: string +} + +export interface IdentityCenterDescribeAccountParams extends IdentityCenterConnectionConfig { + accountId: string +} + +export interface IdentityCenterBaseResponse extends ToolResponse { + output: { message: string } + error?: string +} + +export interface IdentityCenterListInstancesResponse extends ToolResponse { + output: { + instances: Array<{ + instanceArn: string + identityStoreId: string + name: string | null + status: string + statusReason: string | null + ownerAccountId: string | null + createdDate: string | null + }> + nextToken: string | null + count: number + } + error?: string +} + +export interface IdentityCenterListAccountsResponse extends ToolResponse { + output: { + accounts: Array<{ + id: string + arn: string + name: string + email: string + status: string + joinedTimestamp: string | null + }> + nextToken: string | null + count: number + } + error?: string +} + +export interface IdentityCenterListPermissionSetsResponse extends ToolResponse { + output: { + permissionSets: Array<{ + permissionSetArn: string + name: string + description: string | null + sessionDuration: string | null + createdDate: string | null + }> + nextToken: string | null + count: number + } + error?: string +} + +export interface IdentityCenterGetUserResponse extends ToolResponse { + output: { + userId: string + userName: string + displayName: string | null + email: string | null + } + error?: string +} + +export interface IdentityCenterAssignmentStatusResponse extends ToolResponse { + output: { + message: string + status: string + requestId: string + accountId: string | null + permissionSetArn: string | null + principalType: string | null + principalId: string | null + failureReason: string | null + createdDate: string | null + } + error?: string +} + +export interface IdentityCenterListAccountAssignmentsResponse extends ToolResponse { + output: { + assignments: Array<{ + accountId: string + permissionSetArn: string + principalType: string + principalId: string + }> + nextToken: string | null + count: number + } + error?: string +} + +export interface IdentityCenterListGroupsResponse extends ToolResponse { + output: { + groups: Array<{ + groupId: string + displayName: string | null + description: string | null + externalIds: Array<{ issuer: string; id: string }> + }> + nextToken: string | null + count: number + } + error?: string +} + +export interface IdentityCenterGetGroupResponse extends ToolResponse { + output: { + groupId: string + displayName: string | null + description: string | null + } + error?: string +} + +export interface IdentityCenterDescribeAccountResponse extends ToolResponse { + output: { + id: string + arn: string + name: string + email: string + status: string + joinedTimestamp: string | null + } + error?: string +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 8ed66ad1626..b365143a00c 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1193,12 +1193,29 @@ import { iamDetachUserPolicyTool, iamGetRoleTool, iamGetUserTool, + iamListAttachedRolePoliciesTool, + iamListAttachedUserPoliciesTool, iamListGroupsTool, iamListPoliciesTool, iamListRolesTool, iamListUsersTool, iamRemoveUserFromGroupTool, + iamSimulatePrincipalPolicyTool, } from '@/tools/iam' +import { + identityCenterCheckAssignmentDeletionStatusTool, + identityCenterCheckAssignmentStatusTool, + identityCenterCreateAccountAssignmentTool, + identityCenterDeleteAccountAssignmentTool, + identityCenterDescribeAccountTool, + identityCenterGetGroupTool, + identityCenterGetUserTool, + identityCenterListAccountAssignmentsTool, + identityCenterListAccountsTool, + identityCenterListGroupsTool, + identityCenterListInstancesTool, + identityCenterListPermissionSetsTool, +} from '@/tools/identity_center' import { incidentioActionsListTool, incidentioActionsShowTool, @@ -2254,6 +2271,17 @@ import { servicenowReadRecordTool, servicenowUpdateRecordTool, } from '@/tools/servicenow' +import { + sesCreateTemplateTool, + sesDeleteTemplateTool, + sesGetAccountTool, + sesGetTemplateTool, + sesListIdentitiesTool, + sesListTemplatesTool, + sesSendBulkEmailTool, + sesSendEmailTool, + sesSendTemplatedEmailTool, +} from '@/tools/ses' import { sftpDeleteTool, sftpDownloadTool, @@ -4573,6 +4601,15 @@ export const tools: Record = { secrets_manager_create_secret: secretsManagerCreateSecretTool, secrets_manager_update_secret: secretsManagerUpdateSecretTool, secrets_manager_delete_secret: secretsManagerDeleteSecretTool, + ses_send_email: sesSendEmailTool, + ses_send_templated_email: sesSendTemplatedEmailTool, + ses_send_bulk_email: sesSendBulkEmailTool, + ses_list_identities: sesListIdentitiesTool, + ses_get_account: sesGetAccountTool, + ses_create_template: sesCreateTemplateTool, + ses_get_template: sesGetTemplateTool, + ses_list_templates: sesListTemplatesTool, + ses_delete_template: sesDeleteTemplateTool, telegram_message: telegramMessageTool, telegram_delete_message: telegramDeleteMessageTool, telegram_send_audio: telegramSendAudioTool, @@ -5010,6 +5047,21 @@ export const tools: Record = { iam_list_groups: iamListGroupsTool, iam_add_user_to_group: iamAddUserToGroupTool, iam_remove_user_from_group: iamRemoveUserFromGroupTool, + iam_list_attached_role_policies: iamListAttachedRolePoliciesTool, + iam_list_attached_user_policies: iamListAttachedUserPoliciesTool, + iam_simulate_principal_policy: iamSimulatePrincipalPolicyTool, + identity_center_list_instances: identityCenterListInstancesTool, + identity_center_list_accounts: identityCenterListAccountsTool, + identity_center_describe_account: identityCenterDescribeAccountTool, + identity_center_list_permission_sets: identityCenterListPermissionSetsTool, + identity_center_get_user: identityCenterGetUserTool, + identity_center_get_group: identityCenterGetGroupTool, + identity_center_list_groups: identityCenterListGroupsTool, + identity_center_create_account_assignment: identityCenterCreateAccountAssignmentTool, + identity_center_delete_account_assignment: identityCenterDeleteAccountAssignmentTool, + identity_center_check_assignment_status: identityCenterCheckAssignmentStatusTool, + identity_center_check_assignment_deletion_status: identityCenterCheckAssignmentDeletionStatusTool, + identity_center_list_account_assignments: identityCenterListAccountAssignmentsTool, incidentio_incidents_list: incidentioIncidentsListTool, incidentio_incidents_create: incidentioIncidentsCreateTool, incidentio_incidents_show: incidentioIncidentsShowTool, diff --git a/apps/sim/tools/ses/create_template.ts b/apps/sim/tools/ses/create_template.ts new file mode 100644 index 00000000000..29974396ef9 --- /dev/null +++ b/apps/sim/tools/ses/create_template.ts @@ -0,0 +1,88 @@ +import type { SESCreateTemplateParams, SESCreateTemplateResponse } from '@/tools/ses/types' +import type { ToolConfig } from '@/tools/types' + +export const createTemplateTool: ToolConfig = { + id: 'ses_create_template', + name: 'SES Create Template', + description: 'Create a new SES email template for use with templated email sending', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + templateName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique name for the email template', + }, + subjectPart: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Subject line template (supports {{variable}} substitution)', + }, + textPart: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Plain text version of the template body', + }, + htmlPart: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'HTML version of the template body', + }, + }, + + request: { + url: '/api/tools/ses/create-template', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + templateName: params.templateName, + subjectPart: params.subjectPart, + textPart: params.textPart, + htmlPart: params.htmlPart, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to create template') + } + + return { + success: true, + output: { + message: data.message ?? 'Template created successfully', + }, + } + }, + + outputs: { + message: { type: 'string', description: 'Confirmation message for the created template' }, + }, +} diff --git a/apps/sim/tools/ses/delete_template.ts b/apps/sim/tools/ses/delete_template.ts new file mode 100644 index 00000000000..837c88188e6 --- /dev/null +++ b/apps/sim/tools/ses/delete_template.ts @@ -0,0 +1,67 @@ +import type { SESDeleteTemplateParams, SESDeleteTemplateResponse } from '@/tools/ses/types' +import type { ToolConfig } from '@/tools/types' + +export const deleteTemplateTool: ToolConfig = { + id: 'ses_delete_template', + name: 'SES Delete Template', + description: 'Delete an existing SES email template', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + templateName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the template to delete', + }, + }, + + request: { + url: '/api/tools/ses/delete-template', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + templateName: params.templateName, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to delete template') + } + + return { + success: true, + output: { + message: data.message ?? 'Template deleted successfully', + }, + } + }, + + outputs: { + message: { type: 'string', description: 'Confirmation message for the deleted template' }, + }, +} diff --git a/apps/sim/tools/ses/get_account.ts b/apps/sim/tools/ses/get_account.ts new file mode 100644 index 00000000000..c470fd527a6 --- /dev/null +++ b/apps/sim/tools/ses/get_account.ts @@ -0,0 +1,69 @@ +import type { SESGetAccountParams, SESGetAccountResponse } from '@/tools/ses/types' +import type { ToolConfig } from '@/tools/types' + +export const getAccountTool: ToolConfig = { + id: 'ses_get_account', + name: 'SES Get Account', + description: 'Get SES account sending quota and status information', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + }, + + request: { + url: '/api/tools/ses/get-account', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to get account information') + } + + return { + success: true, + output: { + sendingEnabled: data.sendingEnabled ?? false, + max24HourSend: data.max24HourSend ?? 0, + maxSendRate: data.maxSendRate ?? 0, + sentLast24Hours: data.sentLast24Hours ?? 0, + }, + } + }, + + outputs: { + sendingEnabled: { + type: 'boolean', + description: 'Whether email sending is enabled for the account', + }, + max24HourSend: { type: 'number', description: 'Maximum emails allowed per 24-hour period' }, + maxSendRate: { type: 'number', description: 'Maximum emails allowed per second' }, + sentLast24Hours: { type: 'number', description: 'Number of emails sent in the last 24 hours' }, + }, +} diff --git a/apps/sim/tools/ses/get_template.ts b/apps/sim/tools/ses/get_template.ts new file mode 100644 index 00000000000..7b01ab07d6a --- /dev/null +++ b/apps/sim/tools/ses/get_template.ts @@ -0,0 +1,81 @@ +import type { SESGetTemplateParams, SESGetTemplateResponse } from '@/tools/ses/types' +import type { ToolConfig } from '@/tools/types' + +export const getTemplateTool: ToolConfig = { + id: 'ses_get_template', + name: 'SES Get Template', + description: 'Retrieve the content and details of an SES email template', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + templateName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the template to retrieve', + }, + }, + + request: { + url: '/api/tools/ses/get-template', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + templateName: params.templateName, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to get template') + } + + return { + success: true, + output: { + templateName: data.templateName ?? '', + subjectPart: data.subjectPart ?? '', + textPart: data.textPart ?? null, + htmlPart: data.htmlPart ?? null, + }, + } + }, + + outputs: { + templateName: { type: 'string', description: 'Name of the template' }, + subjectPart: { type: 'string', description: 'Subject line of the template' }, + textPart: { + type: 'string', + description: 'Plain text body of the template', + optional: true, + }, + htmlPart: { + type: 'string', + description: 'HTML body of the template', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ses/index.ts b/apps/sim/tools/ses/index.ts new file mode 100644 index 00000000000..be2aa6ccd98 --- /dev/null +++ b/apps/sim/tools/ses/index.ts @@ -0,0 +1,21 @@ +import { createTemplateTool } from './create_template' +import { deleteTemplateTool } from './delete_template' +import { getAccountTool } from './get_account' +import { getTemplateTool } from './get_template' +import { listIdentitiesTool } from './list_identities' +import { listTemplatesTool } from './list_templates' +import { sendBulkEmailTool } from './send_bulk_email' +import { sendEmailTool } from './send_email' +import { sendTemplatedEmailTool } from './send_templated_email' + +export const sesSendEmailTool = sendEmailTool +export const sesSendTemplatedEmailTool = sendTemplatedEmailTool +export const sesSendBulkEmailTool = sendBulkEmailTool +export const sesListIdentitiesTool = listIdentitiesTool +export const sesGetAccountTool = getAccountTool +export const sesCreateTemplateTool = createTemplateTool +export const sesGetTemplateTool = getTemplateTool +export const sesListTemplatesTool = listTemplatesTool +export const sesDeleteTemplateTool = deleteTemplateTool + +export * from './types' diff --git a/apps/sim/tools/ses/list_identities.ts b/apps/sim/tools/ses/list_identities.ts new file mode 100644 index 00000000000..638acb85c61 --- /dev/null +++ b/apps/sim/tools/ses/list_identities.ts @@ -0,0 +1,87 @@ +import type { SESListIdentitiesParams, SESListIdentitiesResponse } from '@/tools/ses/types' +import type { ToolConfig } from '@/tools/types' + +export const listIdentitiesTool: ToolConfig = { + id: 'ses_list_identities', + name: 'SES List Identities', + description: + 'List all verified email identities (email addresses and domains) in your SES account', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of identities to return (1-1000)', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous list response', + }, + }, + + request: { + url: '/api/tools/ses/list-identities', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + pageSize: params.pageSize, + nextToken: params.nextToken, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to list identities') + } + + return { + success: true, + output: { + identities: data.identities ?? [], + nextToken: data.nextToken ?? null, + count: data.count ?? 0, + }, + } + }, + + outputs: { + identities: { + type: 'array', + description: + 'List of email identities with name, type, sending status, and verification status', + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page of results', + optional: true, + }, + count: { type: 'number', description: 'Number of identities returned' }, + }, +} diff --git a/apps/sim/tools/ses/list_templates.ts b/apps/sim/tools/ses/list_templates.ts new file mode 100644 index 00000000000..da60ad92863 --- /dev/null +++ b/apps/sim/tools/ses/list_templates.ts @@ -0,0 +1,85 @@ +import type { SESListTemplatesParams, SESListTemplatesResponse } from '@/tools/ses/types' +import type { ToolConfig } from '@/tools/types' + +export const listTemplatesTool: ToolConfig = { + id: 'ses_list_templates', + name: 'SES List Templates', + description: 'List all SES email templates in your account', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of templates to return', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous list response', + }, + }, + + request: { + url: '/api/tools/ses/list-templates', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + pageSize: params.pageSize, + nextToken: params.nextToken, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to list templates') + } + + return { + success: true, + output: { + templates: data.templates ?? [], + nextToken: data.nextToken ?? null, + count: data.count ?? 0, + }, + } + }, + + outputs: { + templates: { + type: 'array', + description: 'List of email templates with name and creation timestamp', + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page of results', + optional: true, + }, + count: { type: 'number', description: 'Number of templates returned' }, + }, +} diff --git a/apps/sim/tools/ses/send_bulk_email.ts b/apps/sim/tools/ses/send_bulk_email.ts new file mode 100644 index 00000000000..beec97c0ea7 --- /dev/null +++ b/apps/sim/tools/ses/send_bulk_email.ts @@ -0,0 +1,103 @@ +import type { SESSendBulkEmailParams, SESSendBulkEmailResponse } from '@/tools/ses/types' +import type { ToolConfig } from '@/tools/types' + +export const sendBulkEmailTool: ToolConfig = { + id: 'ses_send_bulk_email', + name: 'SES Send Bulk Email', + description: 'Send emails to multiple recipients using an SES template with per-recipient data', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + fromAddress: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Verified sender email address', + }, + templateName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the SES email template to use', + }, + destinations: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'JSON array of destination objects with toAddresses (string[]) and templateData (JSON string)', + }, + defaultTemplateData: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Default JSON template data used when a destination does not specify its own', + }, + configurationSetName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'SES configuration set name for tracking', + }, + }, + + request: { + url: '/api/tools/ses/send-bulk-email', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + fromAddress: params.fromAddress, + templateName: params.templateName, + destinations: params.destinations, + defaultTemplateData: params.defaultTemplateData, + configurationSetName: params.configurationSetName, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to send bulk email') + } + + return { + success: true, + output: { + results: data.results ?? [], + successCount: data.successCount ?? 0, + failureCount: data.failureCount ?? 0, + }, + } + }, + + outputs: { + results: { + type: 'array', + description: 'Per-destination send results with status and messageId', + }, + successCount: { type: 'number', description: 'Number of successfully sent emails' }, + failureCount: { type: 'number', description: 'Number of failed email sends' }, + }, +} diff --git a/apps/sim/tools/ses/send_email.ts b/apps/sim/tools/ses/send_email.ts new file mode 100644 index 00000000000..4858a07af76 --- /dev/null +++ b/apps/sim/tools/ses/send_email.ts @@ -0,0 +1,123 @@ +import type { SESSendEmailParams, SESSendEmailResponse } from '@/tools/ses/types' +import type { ToolConfig } from '@/tools/types' + +export const sendEmailTool: ToolConfig = { + id: 'ses_send_email', + name: 'SES Send Email', + description: 'Send an email via AWS SES using simple or HTML content', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + fromAddress: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Verified sender email address', + }, + toAddresses: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Comma-separated list of recipient email addresses', + }, + subject: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Email subject line', + }, + bodyText: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Plain text email body', + }, + bodyHtml: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'HTML email body', + }, + ccAddresses: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of CC email addresses', + }, + bccAddresses: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of BCC email addresses', + }, + replyToAddresses: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of reply-to email addresses', + }, + configurationSetName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'SES configuration set name for tracking', + }, + }, + + request: { + url: '/api/tools/ses/send-email', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + fromAddress: params.fromAddress, + toAddresses: params.toAddresses, + subject: params.subject, + bodyText: params.bodyText, + bodyHtml: params.bodyHtml, + ccAddresses: params.ccAddresses, + bccAddresses: params.bccAddresses, + replyToAddresses: params.replyToAddresses, + configurationSetName: params.configurationSetName, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to send email') + } + + return { + success: true, + output: { + messageId: data.messageId ?? '', + }, + } + }, + + outputs: { + messageId: { type: 'string', description: 'SES message ID for the sent email' }, + }, +} diff --git a/apps/sim/tools/ses/send_templated_email.ts b/apps/sim/tools/ses/send_templated_email.ts new file mode 100644 index 00000000000..4b6635b8978 --- /dev/null +++ b/apps/sim/tools/ses/send_templated_email.ts @@ -0,0 +1,112 @@ +import type { SESSendTemplatedEmailParams, SESSendTemplatedEmailResponse } from '@/tools/ses/types' +import type { ToolConfig } from '@/tools/types' + +export const sendTemplatedEmailTool: ToolConfig< + SESSendTemplatedEmailParams, + SESSendTemplatedEmailResponse +> = { + id: 'ses_send_templated_email', + name: 'SES Send Templated Email', + description: 'Send an email using an SES email template with dynamic template data', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + fromAddress: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Verified sender email address', + }, + toAddresses: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Comma-separated list of recipient email addresses', + }, + templateName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the SES email template to use', + }, + templateData: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'JSON string of key-value pairs for template variable substitution', + }, + ccAddresses: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of CC email addresses', + }, + bccAddresses: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of BCC email addresses', + }, + configurationSetName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'SES configuration set name for tracking', + }, + }, + + request: { + url: '/api/tools/ses/send-templated-email', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + fromAddress: params.fromAddress, + toAddresses: params.toAddresses, + templateName: params.templateName, + templateData: params.templateData, + ccAddresses: params.ccAddresses, + bccAddresses: params.bccAddresses, + configurationSetName: params.configurationSetName, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to send templated email') + } + + return { + success: true, + output: { + messageId: data.messageId ?? '', + }, + } + }, + + outputs: { + messageId: { type: 'string', description: 'SES message ID for the sent email' }, + }, +} diff --git a/apps/sim/tools/ses/types.ts b/apps/sim/tools/ses/types.ts new file mode 100644 index 00000000000..f0f4b1981cd --- /dev/null +++ b/apps/sim/tools/ses/types.ts @@ -0,0 +1,146 @@ +import type { ToolResponse } from '@/tools/types' + +export interface SESConnectionConfig { + region: string + accessKeyId: string + secretAccessKey: string +} + +export interface SESSendEmailParams extends SESConnectionConfig { + fromAddress: string + toAddresses: string + subject: string + bodyText?: string | null + bodyHtml?: string | null + ccAddresses?: string | null + bccAddresses?: string | null + replyToAddresses?: string | null + configurationSetName?: string | null +} + +export interface SESSendTemplatedEmailParams extends SESConnectionConfig { + fromAddress: string + toAddresses: string + templateName: string + templateData: string + ccAddresses?: string | null + bccAddresses?: string | null + configurationSetName?: string | null +} + +export interface SESSendBulkEmailParams extends SESConnectionConfig { + fromAddress: string + templateName: string + destinations: string + defaultTemplateData?: string | null + configurationSetName?: string | null +} + +export interface SESListIdentitiesParams extends SESConnectionConfig { + pageSize?: number | null + nextToken?: string | null +} + +export interface SESGetAccountParams extends SESConnectionConfig {} + +export interface SESCreateTemplateParams extends SESConnectionConfig { + templateName: string + subjectPart: string + textPart?: string | null + htmlPart?: string | null +} + +export interface SESGetTemplateParams extends SESConnectionConfig { + templateName: string +} + +export interface SESListTemplatesParams extends SESConnectionConfig { + pageSize?: number | null + nextToken?: string | null +} + +export interface SESDeleteTemplateParams extends SESConnectionConfig { + templateName: string +} + +export interface SESSendEmailResponse extends ToolResponse { + output: { + messageId: string + } +} + +export interface SESSendTemplatedEmailResponse extends ToolResponse { + output: { + messageId: string + } +} + +export interface SESSendBulkEmailResponse extends ToolResponse { + output: { + results: Array<{ + messageId: string | null + status: string + error: string | null + }> + successCount: number + failureCount: number + } +} + +export interface SESListIdentitiesResponse extends ToolResponse { + output: { + identities: Array<{ + identityName: string + identityType: string + sendingEnabled: boolean + verificationStatus: string + }> + nextToken: string | null + count: number + } +} + +export interface SESGetAccountResponse extends ToolResponse { + output: { + sendingEnabled: boolean + max24HourSend: number + maxSendRate: number + sentLast24Hours: number + } +} + +export interface SESCreateTemplateResponse extends ToolResponse { + output: { + message: string + } +} + +export interface SESGetTemplateResponse extends ToolResponse { + output: { + templateName: string + subjectPart: string + textPart: string | null + htmlPart: string | null + } +} + +export interface SESListTemplatesResponse extends ToolResponse { + output: { + templates: Array<{ + templateName: string + createdTimestamp: string | null + }> + nextToken: string | null + count: number + } +} + +export interface SESDeleteTemplateResponse extends ToolResponse { + output: { + message: string + } +} + +export interface SESBaseResponse extends ToolResponse { + output: { message: string } +} diff --git a/apps/sim/tools/sts/assume_role.ts b/apps/sim/tools/sts/assume_role.ts index 838a911595f..2e20e89af31 100644 --- a/apps/sim/tools/sts/assume_role.ts +++ b/apps/sim/tools/sts/assume_role.ts @@ -44,6 +44,12 @@ export const assumeRoleTool: ToolConfig=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-utF5qjjbuJQuU9VdCkWl7L87sr93cApsrD+uxGfUnlafX8iyEzJrb7EZnufjThURZVTOtelRMXrblWxpefElUg=="], + + "@aws-sdk/client-identitystore/@smithy/config-resolver": ["@smithy/config-resolver@4.4.16", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-GFlGPNLZKrGfqWpqVb31z7hvYCA9ZscfX1buYnvvMGcRYsQQnhH+4uN6mWWflcD5jB4OXP/LBrdpukEdjl41tg=="], + + "@aws-sdk/client-identitystore/@smithy/core": ["@smithy/core@3.23.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-E7GVCgsQttzfujEZb6Qep005wWf4xiL4x06apFEtzQMWYBPggZh/0cnOxPficw5cuK/YjjkehKoIN4YUaSh0UQ=="], + + "@aws-sdk/client-identitystore/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw=="], + + "@aws-sdk/client-identitystore/@smithy/hash-node": ["@smithy/hash-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g=="], + + "@aws-sdk/client-identitystore/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw=="], + + "@aws-sdk/client-identitystore/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.14", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw=="], + + "@aws-sdk/client-identitystore/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.30", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/middleware-serde": "^4.2.18", "@smithy/node-config-provider": "^4.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-qS2XqhKeXmdZ4nEQ4cOxIczSP/Y91wPAHYuRwmWDCh975B7/57uxsm5d6sisnUThn2u2FwzMdJNM7AbO1YPsPg=="], + + "@aws-sdk/client-identitystore/@smithy/middleware-retry": ["@smithy/middleware-retry@4.5.3", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/service-error-classification": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-TE8dJNi6JuxzGSxMCVd3i9IEWDndCl3bmluLsBNDWok8olgj65OfkndMhl9SZ7m14c+C5SQn/PcUmrDl57rSFw=="], + + "@aws-sdk/client-identitystore/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.18", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-M6CSgnp3v4tYz9ynj2JHbA60woBZcGqEwNjTKjBsNHPV26R1ZX52+0wW8WsZU18q45jD0tw2wL22S17Ze9LpEw=="], + + "@aws-sdk/client-identitystore/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA=="], + + "@aws-sdk/client-identitystore/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.14", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg=="], + + "@aws-sdk/client-identitystore/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.3", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-lc5jFL++x17sPhIwMWJ3YOnqmSjw/2Po6VLDlUIXvxVWRuJwRXnJ4jOBBLB0cfI5BB5ehIl02Fxr1PDvk/kxDw=="], + + "@aws-sdk/client-identitystore/@smithy/protocol-http": ["@smithy/protocol-http@5.3.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ=="], + + "@aws-sdk/client-identitystore/@smithy/smithy-client": ["@smithy/smithy-client@4.12.11", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-stack": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.23", "tslib": "^2.6.2" } }, "sha512-wzz/Wa1CH/Tlhxh0s4DQPEcXSxSVfJ59AZcUh9Gu0c6JTlKuwGf4o/3P2TExv0VbtPFt8odIBG+eQGK2+vTECg=="], + + "@aws-sdk/client-identitystore/@smithy/types": ["@smithy/types@4.14.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg=="], + + "@aws-sdk/client-identitystore/@smithy/url-parser": ["@smithy/url-parser@4.2.14", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ=="], + + "@aws-sdk/client-identitystore/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.47", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-zlIuXai3/SHjQUQ8y3g/woLvrH573SK2wNjcDaHu5e9VOcC0JwM1MI0Sq0GZJyN3BwSUneIhpjZ18nsiz5AtQw=="], + + "@aws-sdk/client-identitystore/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.52", "", { "dependencies": { "@smithy/config-resolver": "^4.4.16", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-cQBz8g68Vnw1W2meXlkb3D/hXJU+Taiyj9P8qLJtjREEV9/Td65xi4A/H1sRQ8EIgX5qbZbvdYPKygKLholZ3w=="], + + "@aws-sdk/client-identitystore/@smithy/util-endpoints": ["@smithy/util-endpoints@3.4.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-wMxNDZJrgS5mQV9oxCs4TWl5767VMgOfqfZ3JHyCkMtGC2ykW9iPqMvFur695Otcc5yxLG8OKO/80tsQBxrhXg=="], + + "@aws-sdk/client-identitystore/@smithy/util-middleware": ["@smithy/util-middleware@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw=="], + + "@aws-sdk/client-identitystore/@smithy/util-retry": ["@smithy/util-retry@4.3.2", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2+KTsJEwTi63NUv4uR9IQ+IFT1yu6Rf6JuoBK2WKaaJ/TRvOiOVGcXAsEqX/TQN2thR9yII21kPUJq1UV/WI2A=="], + + "@aws-sdk/client-organizations/@aws-sdk/core": ["@aws-sdk/core@3.974.1", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.18", "@smithy/core": "^3.23.15", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-gy/gffKz0zaHDaqRiLCdIvgHmaAL/HXuAtMcBP7euYSFx4BsbsdlfmUBJag+Gqe62z6/XuloKyQyaiH+kS3Vrg=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.32", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.27", "@aws-sdk/credential-provider-http": "^3.972.29", "@aws-sdk/credential-provider-ini": "^3.972.31", "@aws-sdk/credential-provider-process": "^3.972.27", "@aws-sdk/credential-provider-sso": "^3.972.31", "@aws-sdk/credential-provider-web-identity": "^3.972.31", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-9aj0x9hGYUondBZSD0XkksAdHhOKttFw4BWpLCeggeg40qSJxGrAP++g0GCm0VqWc1WtC/NRFiAVzPCy56vmog=="], + + "@aws-sdk/client-organizations/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg=="], + + "@aws-sdk/client-organizations/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ=="], + + "@aws-sdk/client-organizations/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ=="], + + "@aws-sdk/client-organizations/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@smithy/core": "^3.23.15", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-retry": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-L+hXN2HDomlIsWSHW5DVD7ppccCeRnlHXZ5uHG34ePTjF5bm0I1fmrJLbUGiW97xRXWryit5cjdP4Sx2FwiGog=="], + + "@aws-sdk/client-organizations/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.12", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/config-resolver": "^4.4.16", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-QQI43Mxd53nBij0pm8HXC+t4IOC6gnhhZfzxE0OATQyO6QfPV4e+aTIRRuAJKA6Nig/cR8eLwPryqYTX9ZrjAQ=="], + + "@aws-sdk/client-organizations/@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], + + "@aws-sdk/client-organizations/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-endpoints": "^3.4.1", "tslib": "^2.6.2" } }, "sha512-ty4LQxN1QC+YhUP28NfEgZDEGXkyqOQy+BDriBozqHsrYO4JMgiPhfizqOGF7P+euBTZ5Ez6SKlLAMCLo8tzmw=="], + + "@aws-sdk/client-organizations/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g=="], + + "@aws-sdk/client-organizations/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.17", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/types": "^3.973.8", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-utF5qjjbuJQuU9VdCkWl7L87sr93cApsrD+uxGfUnlafX8iyEzJrb7EZnufjThURZVTOtelRMXrblWxpefElUg=="], + + "@aws-sdk/client-organizations/@smithy/config-resolver": ["@smithy/config-resolver@4.4.16", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-GFlGPNLZKrGfqWpqVb31z7hvYCA9ZscfX1buYnvvMGcRYsQQnhH+4uN6mWWflcD5jB4OXP/LBrdpukEdjl41tg=="], + + "@aws-sdk/client-organizations/@smithy/core": ["@smithy/core@3.23.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-E7GVCgsQttzfujEZb6Qep005wWf4xiL4x06apFEtzQMWYBPggZh/0cnOxPficw5cuK/YjjkehKoIN4YUaSh0UQ=="], + + "@aws-sdk/client-organizations/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw=="], + + "@aws-sdk/client-organizations/@smithy/hash-node": ["@smithy/hash-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g=="], + + "@aws-sdk/client-organizations/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw=="], + + "@aws-sdk/client-organizations/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.14", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw=="], + + "@aws-sdk/client-organizations/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.30", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/middleware-serde": "^4.2.18", "@smithy/node-config-provider": "^4.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-qS2XqhKeXmdZ4nEQ4cOxIczSP/Y91wPAHYuRwmWDCh975B7/57uxsm5d6sisnUThn2u2FwzMdJNM7AbO1YPsPg=="], + + "@aws-sdk/client-organizations/@smithy/middleware-retry": ["@smithy/middleware-retry@4.5.3", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/service-error-classification": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-TE8dJNi6JuxzGSxMCVd3i9IEWDndCl3bmluLsBNDWok8olgj65OfkndMhl9SZ7m14c+C5SQn/PcUmrDl57rSFw=="], + + "@aws-sdk/client-organizations/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.18", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-M6CSgnp3v4tYz9ynj2JHbA60woBZcGqEwNjTKjBsNHPV26R1ZX52+0wW8WsZU18q45jD0tw2wL22S17Ze9LpEw=="], + + "@aws-sdk/client-organizations/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA=="], + + "@aws-sdk/client-organizations/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.14", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg=="], + + "@aws-sdk/client-organizations/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.3", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-lc5jFL++x17sPhIwMWJ3YOnqmSjw/2Po6VLDlUIXvxVWRuJwRXnJ4jOBBLB0cfI5BB5ehIl02Fxr1PDvk/kxDw=="], + + "@aws-sdk/client-organizations/@smithy/protocol-http": ["@smithy/protocol-http@5.3.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ=="], + + "@aws-sdk/client-organizations/@smithy/smithy-client": ["@smithy/smithy-client@4.12.11", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-stack": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.23", "tslib": "^2.6.2" } }, "sha512-wzz/Wa1CH/Tlhxh0s4DQPEcXSxSVfJ59AZcUh9Gu0c6JTlKuwGf4o/3P2TExv0VbtPFt8odIBG+eQGK2+vTECg=="], + + "@aws-sdk/client-organizations/@smithy/types": ["@smithy/types@4.14.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg=="], + + "@aws-sdk/client-organizations/@smithy/url-parser": ["@smithy/url-parser@4.2.14", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ=="], + + "@aws-sdk/client-organizations/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.47", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-zlIuXai3/SHjQUQ8y3g/woLvrH573SK2wNjcDaHu5e9VOcC0JwM1MI0Sq0GZJyN3BwSUneIhpjZ18nsiz5AtQw=="], + + "@aws-sdk/client-organizations/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.52", "", { "dependencies": { "@smithy/config-resolver": "^4.4.16", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-cQBz8g68Vnw1W2meXlkb3D/hXJU+Taiyj9P8qLJtjREEV9/Td65xi4A/H1sRQ8EIgX5qbZbvdYPKygKLholZ3w=="], + + "@aws-sdk/client-organizations/@smithy/util-endpoints": ["@smithy/util-endpoints@3.4.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-wMxNDZJrgS5mQV9oxCs4TWl5767VMgOfqfZ3JHyCkMtGC2ykW9iPqMvFur695Otcc5yxLG8OKO/80tsQBxrhXg=="], + + "@aws-sdk/client-organizations/@smithy/util-middleware": ["@smithy/util-middleware@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw=="], + + "@aws-sdk/client-organizations/@smithy/util-retry": ["@smithy/util-retry@4.3.2", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2+KTsJEwTi63NUv4uR9IQ+IFT1yu6Rf6JuoBK2WKaaJ/TRvOiOVGcXAsEqX/TQN2thR9yII21kPUJq1UV/WI2A=="], + "@aws-sdk/client-rds-data/@aws-sdk/core": ["@aws-sdk/core@3.940.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA=="], "@aws-sdk/client-rds-data/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.940.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.940.0", "@aws-sdk/credential-provider-http": "3.940.0", "@aws-sdk/credential-provider-ini": "3.940.0", "@aws-sdk/credential-provider-process": "3.940.0", "@aws-sdk/credential-provider-sso": "3.940.0", "@aws-sdk/credential-provider-web-identity": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g=="], @@ -4306,35 +4444,71 @@ "@aws-sdk/client-secrets-manager/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.940.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A=="], - "@aws-sdk/client-sesv2/@aws-sdk/core": ["@aws-sdk/core@3.973.24", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.15", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-vvf82RYQu2GidWAuQq+uIzaPz9V0gSCXVqdVzRosgl5rXcspXOpSD3wFreGGW6AYymPr97Z69kjVnLePBxloDw=="], + "@aws-sdk/client-sesv2/@aws-sdk/core": ["@aws-sdk/core@3.940.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.940.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.940.0", "@aws-sdk/credential-provider-http": "3.940.0", "@aws-sdk/credential-provider-ini": "3.940.0", "@aws-sdk/credential-provider-process": "3.940.0", "@aws-sdk/credential-provider-sso": "3.940.0", "@aws-sdk/credential-provider-web-identity": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws/lambda-invoke-store": "^0.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@smithy/core": "^3.18.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.940.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], + + "@aws-sdk/client-sesv2/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.25", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.22", "@aws-sdk/credential-provider-http": "^3.972.24", "@aws-sdk/credential-provider-ini": "^3.972.24", "@aws-sdk/credential-provider-process": "^3.972.22", "@aws-sdk/credential-provider-sso": "^3.972.24", "@aws-sdk/credential-provider-web-identity": "^3.972.24", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-m7dR0Dsva2P+VUpL+VkC0WwiDby5pgmWXkRVDB5rlwv0jXJrQJf7YMtCoM8Wjk0H9jPeCYOxOXXcIgp/qp5Alg=="], + "@aws-sdk/client-sesv2/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw=="], - "@aws-sdk/client-sesv2/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA=="], + "@aws-sdk/client-sesv2/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.940.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A=="], - "@aws-sdk/client-sesv2/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.25", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-QxiMPofvOt8SwSynTOmuZfvvPM1S9QfkESBxB22NMHTRXCJhR5BygLl8IXfC4jELiisQgwsgUby21GtXfX3f/g=="], + "@aws-sdk/client-sesv2/@smithy/config-resolver": ["@smithy/config-resolver@4.4.16", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-GFlGPNLZKrGfqWpqVb31z7hvYCA9ZscfX1buYnvvMGcRYsQQnhH+4uN6mWWflcD5jB4OXP/LBrdpukEdjl41tg=="], - "@aws-sdk/client-sesv2/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-eQ+dFU05ZRC/lC2XpYlYSPlXtX3VT8sn5toxN2Fv7EXlMoA2p9V7vUBKqHunfD4TRLpxUq8Y8Ol/nCqiv327Ng=="], + "@aws-sdk/client-sesv2/@smithy/core": ["@smithy/core@3.23.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-E7GVCgsQttzfujEZb6Qep005wWf4xiL4x06apFEtzQMWYBPggZh/0cnOxPficw5cuK/YjjkehKoIN4YUaSh0UQ=="], - "@aws-sdk/client-sesv2/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.11", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.25", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-1qdXbXo2s5MMLpUvw00284LsbhtlQ4ul7Zzdn5n+7p4WVgCMLqhxImpHIrjSoc72E/fyc4Wq8dLtUld2Gsh+lA=="], + "@aws-sdk/client-sesv2/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw=="], - "@aws-sdk/client-sesv2/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + "@aws-sdk/client-sesv2/@smithy/hash-node": ["@smithy/hash-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g=="], - "@aws-sdk/client-sesv2/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="], + "@aws-sdk/client-sesv2/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw=="], - "@aws-sdk/client-sesv2/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.44", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA=="], + "@aws-sdk/client-sesv2/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.14", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw=="], - "@aws-sdk/client-sesv2/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="], + "@aws-sdk/client-sesv2/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.30", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/middleware-serde": "^4.2.18", "@smithy/node-config-provider": "^4.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-qS2XqhKeXmdZ4nEQ4cOxIczSP/Y91wPAHYuRwmWDCh975B7/57uxsm5d6sisnUThn2u2FwzMdJNM7AbO1YPsPg=="], - "@aws-sdk/client-sesv2/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="], + "@aws-sdk/client-sesv2/@smithy/middleware-retry": ["@smithy/middleware-retry@4.5.3", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/service-error-classification": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-TE8dJNi6JuxzGSxMCVd3i9IEWDndCl3bmluLsBNDWok8olgj65OfkndMhl9SZ7m14c+C5SQn/PcUmrDl57rSFw=="], - "@aws-sdk/client-sesv2/@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="], + "@aws-sdk/client-sesv2/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.18", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-M6CSgnp3v4tYz9ynj2JHbA60woBZcGqEwNjTKjBsNHPV26R1ZX52+0wW8WsZU18q45jD0tw2wL22S17Ze9LpEw=="], - "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.43", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ=="], + "@aws-sdk/client-sesv2/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA=="], - "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.47", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ=="], + "@aws-sdk/client-sesv2/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.14", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg=="], - "@aws-sdk/client-sesv2/@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="], + "@aws-sdk/client-sesv2/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.3", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-lc5jFL++x17sPhIwMWJ3YOnqmSjw/2Po6VLDlUIXvxVWRuJwRXnJ4jOBBLB0cfI5BB5ehIl02Fxr1PDvk/kxDw=="], + + "@aws-sdk/client-sesv2/@smithy/protocol-http": ["@smithy/protocol-http@5.3.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ=="], + + "@aws-sdk/client-sesv2/@smithy/smithy-client": ["@smithy/smithy-client@4.12.11", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-stack": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.23", "tslib": "^2.6.2" } }, "sha512-wzz/Wa1CH/Tlhxh0s4DQPEcXSxSVfJ59AZcUh9Gu0c6JTlKuwGf4o/3P2TExv0VbtPFt8odIBG+eQGK2+vTECg=="], + + "@aws-sdk/client-sesv2/@smithy/types": ["@smithy/types@4.14.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg=="], + + "@aws-sdk/client-sesv2/@smithy/url-parser": ["@smithy/url-parser@4.2.14", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ=="], + + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.47", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-zlIuXai3/SHjQUQ8y3g/woLvrH573SK2wNjcDaHu5e9VOcC0JwM1MI0Sq0GZJyN3BwSUneIhpjZ18nsiz5AtQw=="], + + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.52", "", { "dependencies": { "@smithy/config-resolver": "^4.4.16", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-cQBz8g68Vnw1W2meXlkb3D/hXJU+Taiyj9P8qLJtjREEV9/Td65xi4A/H1sRQ8EIgX5qbZbvdYPKygKLholZ3w=="], + + "@aws-sdk/client-sesv2/@smithy/util-endpoints": ["@smithy/util-endpoints@3.4.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-wMxNDZJrgS5mQV9oxCs4TWl5767VMgOfqfZ3JHyCkMtGC2ykW9iPqMvFur695Otcc5yxLG8OKO/80tsQBxrhXg=="], + + "@aws-sdk/client-sesv2/@smithy/util-middleware": ["@smithy/util-middleware@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw=="], + + "@aws-sdk/client-sesv2/@smithy/util-retry": ["@smithy/util-retry@4.3.2", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2+KTsJEwTi63NUv4uR9IQ+IFT1yu6Rf6JuoBK2WKaaJ/TRvOiOVGcXAsEqX/TQN2thR9yII21kPUJq1UV/WI2A=="], "@aws-sdk/client-sqs/@aws-sdk/core": ["@aws-sdk/core@3.947.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.7", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Khq4zHhuAkvCFuFbgcy3GrZTzfSX7ZIjIcW1zRDxXRLZKRtuhnZdonqTUfaWi5K42/4OmxkYNpsO7X7trQOeHw=="], @@ -4414,6 +4588,70 @@ "@aws-sdk/client-sso/@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="], + "@aws-sdk/client-sso-admin/@aws-sdk/core": ["@aws-sdk/core@3.974.1", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.18", "@smithy/core": "^3.23.15", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-gy/gffKz0zaHDaqRiLCdIvgHmaAL/HXuAtMcBP7euYSFx4BsbsdlfmUBJag+Gqe62z6/XuloKyQyaiH+kS3Vrg=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.32", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.27", "@aws-sdk/credential-provider-http": "^3.972.29", "@aws-sdk/credential-provider-ini": "^3.972.31", "@aws-sdk/credential-provider-process": "^3.972.27", "@aws-sdk/credential-provider-sso": "^3.972.31", "@aws-sdk/credential-provider-web-identity": "^3.972.31", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-9aj0x9hGYUondBZSD0XkksAdHhOKttFw4BWpLCeggeg40qSJxGrAP++g0GCm0VqWc1WtC/NRFiAVzPCy56vmog=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@smithy/core": "^3.23.15", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-retry": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-L+hXN2HDomlIsWSHW5DVD7ppccCeRnlHXZ5uHG34ePTjF5bm0I1fmrJLbUGiW97xRXWryit5cjdP4Sx2FwiGog=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.12", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/config-resolver": "^4.4.16", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-QQI43Mxd53nBij0pm8HXC+t4IOC6gnhhZfzxE0OATQyO6QfPV4e+aTIRRuAJKA6Nig/cR8eLwPryqYTX9ZrjAQ=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-endpoints": "^3.4.1", "tslib": "^2.6.2" } }, "sha512-ty4LQxN1QC+YhUP28NfEgZDEGXkyqOQy+BDriBozqHsrYO4JMgiPhfizqOGF7P+euBTZ5Ez6SKlLAMCLo8tzmw=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.17", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/types": "^3.973.8", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-utF5qjjbuJQuU9VdCkWl7L87sr93cApsrD+uxGfUnlafX8iyEzJrb7EZnufjThURZVTOtelRMXrblWxpefElUg=="], + + "@aws-sdk/client-sso-admin/@smithy/config-resolver": ["@smithy/config-resolver@4.4.16", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-GFlGPNLZKrGfqWpqVb31z7hvYCA9ZscfX1buYnvvMGcRYsQQnhH+4uN6mWWflcD5jB4OXP/LBrdpukEdjl41tg=="], + + "@aws-sdk/client-sso-admin/@smithy/core": ["@smithy/core@3.23.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-E7GVCgsQttzfujEZb6Qep005wWf4xiL4x06apFEtzQMWYBPggZh/0cnOxPficw5cuK/YjjkehKoIN4YUaSh0UQ=="], + + "@aws-sdk/client-sso-admin/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw=="], + + "@aws-sdk/client-sso-admin/@smithy/hash-node": ["@smithy/hash-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g=="], + + "@aws-sdk/client-sso-admin/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw=="], + + "@aws-sdk/client-sso-admin/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.14", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw=="], + + "@aws-sdk/client-sso-admin/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.30", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/middleware-serde": "^4.2.18", "@smithy/node-config-provider": "^4.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-qS2XqhKeXmdZ4nEQ4cOxIczSP/Y91wPAHYuRwmWDCh975B7/57uxsm5d6sisnUThn2u2FwzMdJNM7AbO1YPsPg=="], + + "@aws-sdk/client-sso-admin/@smithy/middleware-retry": ["@smithy/middleware-retry@4.5.3", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/service-error-classification": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-TE8dJNi6JuxzGSxMCVd3i9IEWDndCl3bmluLsBNDWok8olgj65OfkndMhl9SZ7m14c+C5SQn/PcUmrDl57rSFw=="], + + "@aws-sdk/client-sso-admin/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.18", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-M6CSgnp3v4tYz9ynj2JHbA60woBZcGqEwNjTKjBsNHPV26R1ZX52+0wW8WsZU18q45jD0tw2wL22S17Ze9LpEw=="], + + "@aws-sdk/client-sso-admin/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA=="], + + "@aws-sdk/client-sso-admin/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.14", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg=="], + + "@aws-sdk/client-sso-admin/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.3", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-lc5jFL++x17sPhIwMWJ3YOnqmSjw/2Po6VLDlUIXvxVWRuJwRXnJ4jOBBLB0cfI5BB5ehIl02Fxr1PDvk/kxDw=="], + + "@aws-sdk/client-sso-admin/@smithy/protocol-http": ["@smithy/protocol-http@5.3.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ=="], + + "@aws-sdk/client-sso-admin/@smithy/smithy-client": ["@smithy/smithy-client@4.12.11", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-stack": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.23", "tslib": "^2.6.2" } }, "sha512-wzz/Wa1CH/Tlhxh0s4DQPEcXSxSVfJ59AZcUh9Gu0c6JTlKuwGf4o/3P2TExv0VbtPFt8odIBG+eQGK2+vTECg=="], + + "@aws-sdk/client-sso-admin/@smithy/types": ["@smithy/types@4.14.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg=="], + + "@aws-sdk/client-sso-admin/@smithy/url-parser": ["@smithy/url-parser@4.2.14", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ=="], + + "@aws-sdk/client-sso-admin/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.47", "", { "dependencies": { "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-zlIuXai3/SHjQUQ8y3g/woLvrH573SK2wNjcDaHu5e9VOcC0JwM1MI0Sq0GZJyN3BwSUneIhpjZ18nsiz5AtQw=="], + + "@aws-sdk/client-sso-admin/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.52", "", { "dependencies": { "@smithy/config-resolver": "^4.4.16", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-cQBz8g68Vnw1W2meXlkb3D/hXJU+Taiyj9P8qLJtjREEV9/Td65xi4A/H1sRQ8EIgX5qbZbvdYPKygKLholZ3w=="], + + "@aws-sdk/client-sso-admin/@smithy/util-endpoints": ["@smithy/util-endpoints@3.4.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-wMxNDZJrgS5mQV9oxCs4TWl5767VMgOfqfZ3JHyCkMtGC2ykW9iPqMvFur695Otcc5yxLG8OKO/80tsQBxrhXg=="], + + "@aws-sdk/client-sso-admin/@smithy/util-middleware": ["@smithy/util-middleware@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw=="], + + "@aws-sdk/client-sso-admin/@smithy/util-retry": ["@smithy/util-retry@4.3.2", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2+KTsJEwTi63NUv4uR9IQ+IFT1yu6Rf6JuoBK2WKaaJ/TRvOiOVGcXAsEqX/TQN2thR9yII21kPUJq1UV/WI2A=="], + "@aws-sdk/client-sts/@aws-sdk/core": ["@aws-sdk/core@3.973.27", "", { "dependencies": { "@aws-sdk/types": "^3.973.7", "@aws-sdk/xml-builder": "^3.972.17", "@smithy/core": "^3.23.14", "@smithy/node-config-provider": "^4.3.13", "@smithy/property-provider": "^4.2.13", "@smithy/protocol-http": "^5.3.13", "@smithy/signature-v4": "^5.3.13", "@smithy/smithy-client": "^4.12.9", "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.13", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A=="], "@aws-sdk/client-sts/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.30", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.25", "@aws-sdk/credential-provider-http": "^3.972.27", "@aws-sdk/credential-provider-ini": "^3.972.29", "@aws-sdk/credential-provider-process": "^3.972.25", "@aws-sdk/credential-provider-sso": "^3.972.29", "@aws-sdk/credential-provider-web-identity": "^3.972.29", "@aws-sdk/types": "^3.973.7", "@smithy/credential-provider-imds": "^4.2.13", "@smithy/property-provider": "^4.2.13", "@smithy/shared-ini-file-loader": "^4.4.8", "@smithy/types": "^4.14.0", "tslib": "^2.6.2" } }, "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw=="], @@ -4818,6 +5056,8 @@ "@types/node-fetch/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], + "@types/nodemailer/@aws-sdk/client-sesv2": ["@aws-sdk/client-sesv2@3.1015.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.24", "@aws-sdk/credential-provider-node": "^3.972.25", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.25", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/signature-v4-multi-region": "^3.996.12", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.11", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FoIU3k4Z3ccEEgZFRwLHa84erkMk25r17q7UfsNLrpP/ef3EzNYss+QM15imeS8ekbH8+P/5T6/5/9sUO3L6kA=="], + "@types/nodemailer/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], "@types/papaparse/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], @@ -5272,6 +5512,106 @@ "@aws-sdk/client-iam/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.13", "", { "dependencies": { "@smithy/types": "^4.14.0" } }, "sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw=="], + "@aws-sdk/client-identitystore/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.18", "", { "dependencies": { "@smithy/types": "^4.14.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-BMDNVG1ETXRhl1tnisQiYBef3RShJ1kfZA7x7afivTFMLirfHNTb6U71K569HNXhSXbQZsweHvSDZ6euBw8hPA=="], + + "@aws-sdk/client-identitystore/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-identitystore/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.14", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.27", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xfUt2CUZDC+Tf16A6roD1b4pk/nrXdkoLY3TEhv198AXDtBo5xUJP1zd0e8SmuKLN4PpIBX96OizZbmMlcI6oQ=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.29", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.23", "tslib": "^2.6.2" } }, "sha512-hjNeYb6oLyHgMihra83ie0J/T2y9om3cy1qC90h9DRgvYXEoN4BCFf8bHguZjKhXunnv7YkmZRuYL5Mkk77eCA=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/credential-provider-env": "^3.972.27", "@aws-sdk/credential-provider-http": "^3.972.29", "@aws-sdk/credential-provider-login": "^3.972.31", "@aws-sdk/credential-provider-process": "^3.972.27", "@aws-sdk/credential-provider-sso": "^3.972.31", "@aws-sdk/credential-provider-web-identity": "^3.972.31", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-PuQ7e8WYzAPpzvFcajxf8c0LqSzakVHVlKw8M0oubk8Kf347YOCCqT1seQrHs5AdZuIh2RD9LX4O+Xa5ImEBfQ=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.27", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1CZvfb1WzudWWIFAVQkd1OI/T1RxPcSvNWzNsb2BMBVsBJzBtB8dV5f2nymHVU4UqwxipdVt/DAbgdDRf33JDg=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/token-providers": "3.1032.0", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-x8Mx18S48XMl9bEEpYwmXDTvjWGPIfDadReN37Lc099/DUrlL4Zs9T9rwwggo6DkKS1aev6v+MTUx7JTa87TZQ=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-zfuNMIkGfjYsHis9qytYf74Bcmq6Ji9Xwf4w53baRCI/b2otTwZv3SW1uRiJ5Di7999QzRGhHZ96+eUeo3gSOA=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@aws-sdk/client-identitystore/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-identitystore/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="], + + "@aws-sdk/client-identitystore/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@aws-sdk/client-identitystore/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-vVimoUnGxlx4eLLQbZImdOZFOe+Zh+5ACntv8VxZuGP72LdWu5GV3oEmCahSEReBgRJoWjypFkrehSj7BWx1HQ=="], + + "@aws-sdk/client-identitystore/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-identitystore/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@aws-sdk/client-identitystore/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="], + + "@aws-sdk/client-identitystore/@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-identitystore/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw=="], + + "@aws-sdk/client-identitystore/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-identitystore/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg=="], + + "@aws-sdk/client-identitystore/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-identitystore/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-vVimoUnGxlx4eLLQbZImdOZFOe+Zh+5ACntv8VxZuGP72LdWu5GV3oEmCahSEReBgRJoWjypFkrehSj7BWx1HQ=="], + + "@aws-sdk/client-organizations/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.18", "", { "dependencies": { "@smithy/types": "^4.14.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-BMDNVG1ETXRhl1tnisQiYBef3RShJ1kfZA7x7afivTFMLirfHNTb6U71K569HNXhSXbQZsweHvSDZ6euBw8hPA=="], + + "@aws-sdk/client-organizations/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-organizations/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.14", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.27", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xfUt2CUZDC+Tf16A6roD1b4pk/nrXdkoLY3TEhv198AXDtBo5xUJP1zd0e8SmuKLN4PpIBX96OizZbmMlcI6oQ=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.29", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.23", "tslib": "^2.6.2" } }, "sha512-hjNeYb6oLyHgMihra83ie0J/T2y9om3cy1qC90h9DRgvYXEoN4BCFf8bHguZjKhXunnv7YkmZRuYL5Mkk77eCA=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/credential-provider-env": "^3.972.27", "@aws-sdk/credential-provider-http": "^3.972.29", "@aws-sdk/credential-provider-login": "^3.972.31", "@aws-sdk/credential-provider-process": "^3.972.27", "@aws-sdk/credential-provider-sso": "^3.972.31", "@aws-sdk/credential-provider-web-identity": "^3.972.31", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-PuQ7e8WYzAPpzvFcajxf8c0LqSzakVHVlKw8M0oubk8Kf347YOCCqT1seQrHs5AdZuIh2RD9LX4O+Xa5ImEBfQ=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.27", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1CZvfb1WzudWWIFAVQkd1OI/T1RxPcSvNWzNsb2BMBVsBJzBtB8dV5f2nymHVU4UqwxipdVt/DAbgdDRf33JDg=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/token-providers": "3.1032.0", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-x8Mx18S48XMl9bEEpYwmXDTvjWGPIfDadReN37Lc099/DUrlL4Zs9T9rwwggo6DkKS1aev6v+MTUx7JTa87TZQ=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-zfuNMIkGfjYsHis9qytYf74Bcmq6Ji9Xwf4w53baRCI/b2otTwZv3SW1uRiJ5Di7999QzRGhHZ96+eUeo3gSOA=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@aws-sdk/client-organizations/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-organizations/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="], + + "@aws-sdk/client-organizations/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@aws-sdk/client-organizations/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-vVimoUnGxlx4eLLQbZImdOZFOe+Zh+5ACntv8VxZuGP72LdWu5GV3oEmCahSEReBgRJoWjypFkrehSj7BWx1HQ=="], + + "@aws-sdk/client-organizations/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-organizations/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@aws-sdk/client-organizations/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="], + + "@aws-sdk/client-organizations/@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-organizations/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw=="], + + "@aws-sdk/client-organizations/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-organizations/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg=="], + + "@aws-sdk/client-organizations/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-organizations/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-vVimoUnGxlx4eLLQbZImdOZFOe+Zh+5ACntv8VxZuGP72LdWu5GV3oEmCahSEReBgRJoWjypFkrehSj7BWx1HQ=="], + "@aws-sdk/client-rds-data/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], "@aws-sdk/client-rds-data/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw=="], @@ -5320,19 +5660,99 @@ "@aws-sdk/client-secrets-manager/@aws-sdk/middleware-user-agent/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], - "@aws-sdk/client-sesv2/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.15", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA=="], + "@aws-sdk/client-sesv2/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/credential-provider-env": "3.940.0", "@aws-sdk/credential-provider-http": "3.940.0", "@aws-sdk/credential-provider-login": "3.940.0", "@aws-sdk/credential-provider-process": "3.940.0", "@aws-sdk/credential-provider-sso": "3.940.0", "@aws-sdk/credential-provider-web-identity": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-gn7PJQEzb/cnInNFTOaDoCN/hOKqMejNmLof1W5VW95Qk0TPO52lH8R4RmJPnRrwFMswOWswTOpR1roKNLIrcw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.940.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.940.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/token-providers": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-9QLTIkDJHHaYL0nyymO41H8g3ui1yz6Y3GmAN1gYQa6plXisuFBnGAbmKVj7zNvjWaOKdF0dV3dd3AFKEDoJ/w=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-host-header/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-host-header/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-logger/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-recursion-detection/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-recursion-detection/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-user-agent/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-user-agent/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-user-agent/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-sesv2/@aws-sdk/region-config-resolver/@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-cXp0VTDWT76p3hyK5D51yIKEfpf6/zsUvMfaB8CkyqadJxMQ8SbEeVroregmDlZbtG31wkj9ei0WnftmieggLg=="], + "@aws-sdk/client-sesv2/@aws-sdk/region-config-resolver/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-h694K7+tRuepSRJr09wTvQfaEnjzsKZ5s7fbESrVds02GT/QzViJ94/HCNwM7bUfFxqpPXHxulZfL6Cou0dwPg=="], + "@aws-sdk/client-sesv2/@aws-sdk/region-config-resolver/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/credential-provider-env": "^3.972.22", "@aws-sdk/credential-provider-http": "^3.972.24", "@aws-sdk/credential-provider-login": "^3.972.24", "@aws-sdk/credential-provider-process": "^3.972.22", "@aws-sdk/credential-provider-sso": "^3.972.24", "@aws-sdk/credential-provider-web-identity": "^3.972.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-O46fFmv0RDFWiWEA9/e6oW92BnsyAXuEgTTasxHligjn2RCr9L/DK773m/NoFaL3ZdNAUz8WxgxunleMnHAkeQ=="], + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/core": "^3.18.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Os32s8/4gTZjBk5BtoS/cuTILaj+K72d0dVG7TCJX/fC4598cxwLDmf1AEHEpER5oL3K//yETjvFaz0V8oO5Xw=="], + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.3.14", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/token-providers": "3.1015.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-PaFv7snEfypU2yXkpvfyWgddEbDLtgVe51wdZlinhc2doubBjUzJZZpgwuF2Jenl1FBydMhNpMjD6SBUM3qdSA=="], + "@aws-sdk/client-sesv2/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-J6H4R1nvr3uBTqD/EeIPAskrBtET4WFfNhpFySr2xW7bVZOXpQfPjrLSIx65jcNjBmLXzWq8QFLdVoGxiGG/SA=="], + "@aws-sdk/client-sesv2/@aws-sdk/util-endpoints/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-sesv2/@aws-sdk/util-endpoints/@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/util-endpoints/@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="], + + "@aws-sdk/client-sesv2/@aws-sdk/util-user-agent-browser/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-sesv2/@aws-sdk/util-user-agent-node/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/util-user-agent-node/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-sesv2/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-sesv2/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="], + + "@aws-sdk/client-sesv2/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@aws-sdk/client-sesv2/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-vVimoUnGxlx4eLLQbZImdOZFOe+Zh+5ACntv8VxZuGP72LdWu5GV3oEmCahSEReBgRJoWjypFkrehSj7BWx1HQ=="], + + "@aws-sdk/client-sesv2/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-sesv2/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@aws-sdk/client-sesv2/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="], + + "@aws-sdk/client-sesv2/@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-sesv2/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw=="], + + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg=="], + + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-sesv2/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-vVimoUnGxlx4eLLQbZImdOZFOe+Zh+5ACntv8VxZuGP72LdWu5GV3oEmCahSEReBgRJoWjypFkrehSj7BWx1HQ=="], "@aws-sdk/client-sqs/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], @@ -5348,6 +5768,56 @@ "@aws-sdk/client-sqs/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.947.0", "", { "dependencies": { "@aws-sdk/core": "3.947.0", "@aws-sdk/nested-clients": "3.947.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-gokm/e/YHiHLrZgLq4j8tNAn8RJDPbIcglFRKgy08q8DmAqHQ8MXAKW3eS0QjAuRXU9mcMmUo1NrX6FRNBCCPw=="], + "@aws-sdk/client-sso-admin/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.18", "", { "dependencies": { "@smithy/types": "^4.14.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-BMDNVG1ETXRhl1tnisQiYBef3RShJ1kfZA7x7afivTFMLirfHNTb6U71K569HNXhSXbQZsweHvSDZ6euBw8hPA=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.14", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.27", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xfUt2CUZDC+Tf16A6roD1b4pk/nrXdkoLY3TEhv198AXDtBo5xUJP1zd0e8SmuKLN4PpIBX96OizZbmMlcI6oQ=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.29", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/util-stream": "^4.5.23", "tslib": "^2.6.2" } }, "sha512-hjNeYb6oLyHgMihra83ie0J/T2y9om3cy1qC90h9DRgvYXEoN4BCFf8bHguZjKhXunnv7YkmZRuYL5Mkk77eCA=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/credential-provider-env": "^3.972.27", "@aws-sdk/credential-provider-http": "^3.972.29", "@aws-sdk/credential-provider-login": "^3.972.31", "@aws-sdk/credential-provider-process": "^3.972.27", "@aws-sdk/credential-provider-sso": "^3.972.31", "@aws-sdk/credential-provider-web-identity": "^3.972.31", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-PuQ7e8WYzAPpzvFcajxf8c0LqSzakVHVlKw8M0oubk8Kf347YOCCqT1seQrHs5AdZuIh2RD9LX4O+Xa5ImEBfQ=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.27", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1CZvfb1WzudWWIFAVQkd1OI/T1RxPcSvNWzNsb2BMBVsBJzBtB8dV5f2nymHVU4UqwxipdVt/DAbgdDRf33JDg=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/token-providers": "3.1032.0", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-x8Mx18S48XMl9bEEpYwmXDTvjWGPIfDadReN37Lc099/DUrlL4Zs9T9rwwggo6DkKS1aev6v+MTUx7JTa87TZQ=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-zfuNMIkGfjYsHis9qytYf74Bcmq6Ji9Xwf4w53baRCI/b2otTwZv3SW1uRiJ5Di7999QzRGhHZ96+eUeo3gSOA=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@aws-sdk/client-sso-admin/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-sso-admin/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="], + + "@aws-sdk/client-sso-admin/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@aws-sdk/client-sso-admin/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-vVimoUnGxlx4eLLQbZImdOZFOe+Zh+5ACntv8VxZuGP72LdWu5GV3oEmCahSEReBgRJoWjypFkrehSj7BWx1HQ=="], + + "@aws-sdk/client-sso-admin/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-sso-admin/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], + + "@aws-sdk/client-sso-admin/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A=="], + + "@aws-sdk/client-sso-admin/@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-sso-admin/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw=="], + + "@aws-sdk/client-sso-admin/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-sso-admin/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg=="], + + "@aws-sdk/client-sso-admin/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ=="], + + "@aws-sdk/client-sso-admin/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-vVimoUnGxlx4eLLQbZImdOZFOe+Zh+5ACntv8VxZuGP72LdWu5GV3oEmCahSEReBgRJoWjypFkrehSj7BWx1HQ=="], + "@aws-sdk/client-sso/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], "@aws-sdk/client-sts/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.17", "", { "dependencies": { "@smithy/types": "^4.14.0", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg=="], @@ -5560,6 +6030,36 @@ "@types/node-fetch/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/core": ["@aws-sdk/core@3.973.24", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.15", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-vvf82RYQu2GidWAuQq+uIzaPz9V0gSCXVqdVzRosgl5rXcspXOpSD3wFreGGW6AYymPr97Z69kjVnLePBxloDw=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.25", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.22", "@aws-sdk/credential-provider-http": "^3.972.24", "@aws-sdk/credential-provider-ini": "^3.972.24", "@aws-sdk/credential-provider-process": "^3.972.22", "@aws-sdk/credential-provider-sso": "^3.972.24", "@aws-sdk/credential-provider-web-identity": "^3.972.24", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-m7dR0Dsva2P+VUpL+VkC0WwiDby5pgmWXkRVDB5rlwv0jXJrQJf7YMtCoM8Wjk0H9jPeCYOxOXXcIgp/qp5Alg=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.25", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-QxiMPofvOt8SwSynTOmuZfvvPM1S9QfkESBxB22NMHTRXCJhR5BygLl8IXfC4jELiisQgwsgUby21GtXfX3f/g=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-eQ+dFU05ZRC/lC2XpYlYSPlXtX3VT8sn5toxN2Fv7EXlMoA2p9V7vUBKqHunfD4TRLpxUq8Y8Ol/nCqiv327Ng=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.11", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.25", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-1qdXbXo2s5MMLpUvw00284LsbhtlQ4ul7Zzdn5n+7p4WVgCMLqhxImpHIrjSoc72E/fyc4Wq8dLtUld2Gsh+lA=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.44", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.43", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.47", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="], + "@types/nodemailer/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], "@types/papaparse/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], @@ -5974,6 +6474,34 @@ "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.19", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.27", "@aws-sdk/middleware-host-header": "^3.972.9", "@aws-sdk/middleware-logger": "^3.972.9", "@aws-sdk/middleware-recursion-detection": "^3.972.10", "@aws-sdk/middleware-user-agent": "^3.972.29", "@aws-sdk/region-config-resolver": "^3.972.11", "@aws-sdk/types": "^3.973.7", "@aws-sdk/util-endpoints": "^3.996.6", "@aws-sdk/util-user-agent-browser": "^3.972.9", "@aws-sdk/util-user-agent-node": "^3.973.15", "@smithy/config-resolver": "^4.4.14", "@smithy/core": "^3.23.14", "@smithy/fetch-http-handler": "^5.3.16", "@smithy/hash-node": "^4.2.13", "@smithy/invalid-dependency": "^4.2.13", "@smithy/middleware-content-length": "^4.2.13", "@smithy/middleware-endpoint": "^4.4.29", "@smithy/middleware-retry": "^4.5.0", "@smithy/middleware-serde": "^4.2.17", "@smithy/middleware-stack": "^4.2.13", "@smithy/node-config-provider": "^4.3.13", "@smithy/node-http-handler": "^4.5.2", "@smithy/protocol-http": "^5.3.13", "@smithy/smithy-client": "^4.12.9", "@smithy/types": "^4.14.0", "@smithy/url-parser": "^4.2.13", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.45", "@smithy/util-defaults-mode-node": "^4.2.49", "@smithy/util-endpoints": "^3.3.4", "@smithy/util-middleware": "^4.2.13", "@smithy/util-retry": "^4.3.0", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q=="], + "@aws-sdk/client-identitystore/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-bBmWDmtSpmLOZR6a0kmowBcVL1hiL8Vlap/RXeMpFd7JbWl87YcwqL6T9LH/0oBVEZXu1dUZAtojgSuZgMO5xw=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.21", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Me3d/ua2lb2G0bQfFmvCeQQp3+nN6GSPqMxDmi/IQlQ8CrlpQ5C0JJHpz2AnOUkEFI0lBNrAL3Vnt29l44ndkA=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.21", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Me3d/ua2lb2G0bQfFmvCeQQp3+nN6GSPqMxDmi/IQlQ8CrlpQ5C0JJHpz2AnOUkEFI0lBNrAL3Vnt29l44ndkA=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1032.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-n+PU8Z+gll7p3wDrH+Wo6fkt8sPrVnq30YYM6Ryga95oJlEneNMEbDHj0iqjMX3V7gaGdJo/hJWyPo4lscP+mA=="], + + "@aws-sdk/client-identitystore/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.21", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Me3d/ua2lb2G0bQfFmvCeQQp3+nN6GSPqMxDmi/IQlQ8CrlpQ5C0JJHpz2AnOUkEFI0lBNrAL3Vnt29l44ndkA=="], + + "@aws-sdk/client-organizations/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-bBmWDmtSpmLOZR6a0kmowBcVL1hiL8Vlap/RXeMpFd7JbWl87YcwqL6T9LH/0oBVEZXu1dUZAtojgSuZgMO5xw=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.21", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Me3d/ua2lb2G0bQfFmvCeQQp3+nN6GSPqMxDmi/IQlQ8CrlpQ5C0JJHpz2AnOUkEFI0lBNrAL3Vnt29l44ndkA=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.21", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Me3d/ua2lb2G0bQfFmvCeQQp3+nN6GSPqMxDmi/IQlQ8CrlpQ5C0JJHpz2AnOUkEFI0lBNrAL3Vnt29l44ndkA=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1032.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-n+PU8Z+gll7p3wDrH+Wo6fkt8sPrVnq30YYM6Ryga95oJlEneNMEbDHj0iqjMX3V7gaGdJo/hJWyPo4lscP+mA=="], + + "@aws-sdk/client-organizations/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.21", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Me3d/ua2lb2G0bQfFmvCeQQp3+nN6GSPqMxDmi/IQlQ8CrlpQ5C0JJHpz2AnOUkEFI0lBNrAL3Vnt29l44ndkA=="], + "@aws-sdk/client-rds-data/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], "@aws-sdk/client-rds-data/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg=="], @@ -6000,17 +6528,37 @@ "@aws-sdk/client-secrets-manager/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg=="], - "@aws-sdk/client-sesv2/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], + "@aws-sdk/client-sesv2/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/core/@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-sIk8oa6AzDoUhxsR11svZESqvzGuXesw62Rl2oW6wguZx8i9cdGCvkFg+h5K7iucUZP8wyWibUbJMc+J66cu5g=="], + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.14", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.24", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.25", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.11", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fSESKvh1VbfjtV3QMnRkCPZWkUbQof6T/DOpiLp33yP2wA+rbwwnZeG3XT3Ekljgw2I8X4XaQPnw+zSR8yxJ5Q=="], + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.14", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.24", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.25", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.11", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fSESKvh1VbfjtV3QMnRkCPZWkUbQof6T/DOpiLp33yP2wA+rbwwnZeG3XT3Ekljgw2I8X4XaQPnw+zSR8yxJ5Q=="], + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1015.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-3OSD4y110nisRhHzFOjoEeHU4GQL4KpzkX9PxzWaiZe0Yg2+thZKM0Pn9DjYwezH5JYfh/K++xK/SE0IHGrmCQ=="], + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg=="], - "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.14", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.24", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.25", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.11", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fSESKvh1VbfjtV3QMnRkCPZWkUbQof6T/DOpiLp33yP2wA+rbwwnZeG3XT3Ekljgw2I8X4XaQPnw+zSR8yxJ5Q=="], + "@aws-sdk/client-sesv2/@aws-sdk/middleware-user-agent/@smithy/core/@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-user-agent/@smithy/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/region-config-resolver/@smithy/config-resolver/@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="], + + "@aws-sdk/client-sesv2/@aws-sdk/region-config-resolver/@smithy/config-resolver/@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-sesv2/@aws-sdk/util-endpoints/@smithy/util-endpoints/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="], "@aws-sdk/client-sqs/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], @@ -6024,6 +6572,20 @@ "@aws-sdk/client-sqs/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.947.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DjRJEYNnHUTu9kGPPQDTSXquwSEd6myKR4ssI4FaYLFhdT3ldWpj73yYt807H3tdmhS7vPmdVqchSJnjurUQAw=="], + "@aws-sdk/client-sso-admin/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/util-stream@4.5.23", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.5.3", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-bBmWDmtSpmLOZR6a0kmowBcVL1hiL8Vlap/RXeMpFd7JbWl87YcwqL6T9LH/0oBVEZXu1dUZAtojgSuZgMO5xw=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.21", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Me3d/ua2lb2G0bQfFmvCeQQp3+nN6GSPqMxDmi/IQlQ8CrlpQ5C0JJHpz2AnOUkEFI0lBNrAL3Vnt29l44ndkA=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.21", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Me3d/ua2lb2G0bQfFmvCeQQp3+nN6GSPqMxDmi/IQlQ8CrlpQ5C0JJHpz2AnOUkEFI0lBNrAL3Vnt29l44ndkA=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1032.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-n+PU8Z+gll7p3wDrH+Wo6fkt8sPrVnq30YYM6Ryga95oJlEneNMEbDHj0iqjMX3V7gaGdJo/hJWyPo4lscP+mA=="], + + "@aws-sdk/client-sso-admin/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.21", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Me3d/ua2lb2G0bQfFmvCeQQp3+nN6GSPqMxDmi/IQlQ8CrlpQ5C0JJHpz2AnOUkEFI0lBNrAL3Vnt29l44ndkA=="], + "@aws-sdk/client-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], "@aws-sdk/client-sts/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], @@ -6082,6 +6644,20 @@ "@trigger.dev/core/socket.io/engine.io/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="], + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.15", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-cXp0VTDWT76p3hyK5D51yIKEfpf6/zsUvMfaB8CkyqadJxMQ8SbEeVroregmDlZbtG31wkj9ei0WnftmieggLg=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-h694K7+tRuepSRJr09wTvQfaEnjzsKZ5s7fbESrVds02GT/QzViJ94/HCNwM7bUfFxqpPXHxulZfL6Cou0dwPg=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/credential-provider-env": "^3.972.22", "@aws-sdk/credential-provider-http": "^3.972.24", "@aws-sdk/credential-provider-login": "^3.972.24", "@aws-sdk/credential-provider-process": "^3.972.22", "@aws-sdk/credential-provider-sso": "^3.972.24", "@aws-sdk/credential-provider-web-identity": "^3.972.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-O46fFmv0RDFWiWEA9/e6oW92BnsyAXuEgTTasxHligjn2RCr9L/DK773m/NoFaL3ZdNAUz8WxgxunleMnHAkeQ=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Os32s8/4gTZjBk5BtoS/cuTILaj+K72d0dVG7TCJX/fC4598cxwLDmf1AEHEpER5oL3K//yETjvFaz0V8oO5Xw=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/token-providers": "3.1015.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-PaFv7snEfypU2yXkpvfyWgddEbDLtgVe51wdZlinhc2doubBjUzJZZpgwuF2Jenl1FBydMhNpMjD6SBUM3qdSA=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-J6H4R1nvr3uBTqD/EeIPAskrBtET4WFfNhpFySr2xW7bVZOXpQfPjrLSIx65jcNjBmLXzWq8QFLdVoGxiGG/SA=="], + "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "fetch-cookie/tough-cookie/tldts/tldts-core": ["tldts-core@7.0.27", "", {}, "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg=="], @@ -6158,6 +6734,18 @@ "@aws-sdk/client-secrets-manager/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="], + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], + "@aws-sdk/client-sqs/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.947.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.947.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.947.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.947.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.7", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.14", "@smithy/middleware-retry": "^4.4.14", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.10", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.13", "@smithy/util-defaults-mode-node": "^4.2.16", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DjRJEYNnHUTu9kGPPQDTSXquwSEd6myKR4ssI4FaYLFhdT3ldWpj73yYt807H3tdmhS7vPmdVqchSJnjurUQAw=="], "@aws-sdk/middleware-flexible-checksums/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="], @@ -6170,6 +6758,18 @@ "@trigger.dev/core/socket.io/engine.io/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-sIk8oa6AzDoUhxsR11svZESqvzGuXesw62Rl2oW6wguZx8i9cdGCvkFg+h5K7iucUZP8wyWibUbJMc+J66cu5g=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.14", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.24", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.25", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.11", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fSESKvh1VbfjtV3QMnRkCPZWkUbQof6T/DOpiLp33yP2wA+rbwwnZeG3XT3Ekljgw2I8X4XaQPnw+zSR8yxJ5Q=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.14", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.24", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.25", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.11", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fSESKvh1VbfjtV3QMnRkCPZWkUbQof6T/DOpiLp33yP2wA+rbwwnZeG3XT3Ekljgw2I8X4XaQPnw+zSR8yxJ5Q=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1015.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.24", "@aws-sdk/nested-clients": "^3.996.14", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-3OSD4y110nisRhHzFOjoEeHU4GQL4KpzkX9PxzWaiZe0Yg2+thZKM0Pn9DjYwezH5JYfh/K++xK/SE0IHGrmCQ=="], + + "@types/nodemailer/@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.14", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.24", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.25", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.11", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fSESKvh1VbfjtV3QMnRkCPZWkUbQof6T/DOpiLp33yP2wA+rbwwnZeG3XT3Ekljgw2I8X4XaQPnw+zSR8yxJ5Q=="], + "lint-staged/listr2/cli-truncate/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "lint-staged/listr2/log-update/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], @@ -6198,6 +6798,18 @@ "@aws-sdk/client-secrets-manager/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="], + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/core/@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="], + "lint-staged/listr2/cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "lint-staged/listr2/log-update/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], From 1c66e25e5557ed0b17ea1a0c7eb367a5e0b58363 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 21 Apr 2026 12:29:45 -0700 Subject: [PATCH 02/12] docs(ses): add manual intro section to SES docs --- apps/docs/content/docs/en/tools/ses.mdx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/docs/content/docs/en/tools/ses.mdx b/apps/docs/content/docs/en/tools/ses.mdx index 05eade719bc..a612727754d 100644 --- a/apps/docs/content/docs/en/tools/ses.mdx +++ b/apps/docs/content/docs/en/tools/ses.mdx @@ -10,6 +10,21 @@ import { BlockInfoCard } from "@/components/ui/block-info-card" color="linear-gradient(45deg, #BD0816 0%, #FF5252 100%)" /> +{/* MANUAL-CONTENT-START:intro */} +[Amazon Simple Email Service (SES)](https://aws.amazon.com/ses/) is a cloud-based email sending service designed for high-volume, transactional, and marketing email delivery. It provides a cost-effective, scalable way to send email without managing your own mail server infrastructure. + +With AWS SES, you can: + +- **Send simple emails**: Deliver one-off emails with plain text or HTML body content to individual recipients +- **Send templated emails**: Use pre-defined templates with variable substitution (e.g., `{{name}}`, `{{link}}`) for personalized emails at scale +- **Send bulk emails**: Deliver templated emails to large lists of recipients in a single API call, with per-destination data overrides +- **Manage email templates**: Create, retrieve, list, and delete reusable email templates for transactional and marketing campaigns +- **Monitor account health**: Retrieve your account's sending quota, send rate, and whether sending is currently enabled + +In Sim, the AWS SES integration is designed for workflows that need reliable, programmatic email delivery — from access request notifications and approval alerts to bulk outreach and automated reporting. It pairs naturally with the IAM Identity Center integration for TEAM (Temporary Elevated Access Management) workflows, where email notifications are sent when access is provisioned, approved, or revoked. +{/* MANUAL-CONTENT-END */} + + ## Usage Instructions Integrate AWS SES v2 into the workflow. Send simple, templated, and bulk emails. Manage email templates and retrieve account sending quota and verified identity information. From e4de0bcc9b29630aa5ca73105180f63b9c375bc5 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 21 Apr 2026 12:39:24 -0700 Subject: [PATCH 03/12] fix(dynamodb): add legacy fallbacks in params for subblock migration compatibility Workflows saved with the old shared IDs (key, filterExpression, etc.) that migrate to get-scoped slots via subblock-migrations still work correctly on update/delete/scan/put operations via fallback lookups in tools.config.params. --- apps/sim/blocks/blocks/dynamodb.ts | 70 ++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/apps/sim/blocks/blocks/dynamodb.ts b/apps/sim/blocks/blocks/dynamodb.ts index 6153286ad4f..714f9908f59 100644 --- a/apps/sim/blocks/blocks/dynamodb.ts +++ b/apps/sim/blocks/blocks/dynamodb.ts @@ -596,14 +596,20 @@ Return ONLY the expression - no explanations.`, secretAccessKey: params.secretAccessKey, } - // Table name (introspect uses introspectTableName, all others use tableName) + // Table name (introspect uses introspectTableName, all others use tableName). + // Legacy blocks stored both operations under 'tableName'; fall back to tableName + // for introspect if introspectTableName is not present (migration grace period). if (op === 'introspect') { - if (params.introspectTableName) result.tableName = params.introspectTableName + const tbl = params.introspectTableName || params.tableName + if (tbl) result.tableName = tbl } else { result.tableName = params.tableName } - // Operation-specific params — map unique subBlock IDs back to tool param names + // Operation-specific params — map unique subBlock IDs back to tool param names. + // Where a fallback to the legacy migrated ID is present (e.g. params.getKey as + // fallback for update/delete), it covers blocks saved before the subblock rename + // whose migration entry routed the shared old ID to the query/get slot. if (op === 'get') { const key = parseJson(params.getKey, 'key') if (key !== undefined) result.key = key @@ -615,11 +621,20 @@ Return ONLY the expression - no explanations.`, if (op === 'put') { const item = parseJson(params.item, 'item') if (item !== undefined) result.item = item - if (params.putConditionExpression) - result.conditionExpression = params.putConditionExpression - const names = parseJson(params.putExpressionAttributeNames, 'expressionAttributeNames') + // conditionExpression: fall back to updateConditionExpression (legacy migration target) + const condExpr = params.putConditionExpression || params.updateConditionExpression + if (condExpr) result.conditionExpression = condExpr + // expressionAttributeNames: fall back to queryExpressionAttributeNames (legacy migration target) + const names = parseJson( + params.putExpressionAttributeNames || params.queryExpressionAttributeNames, + 'expressionAttributeNames' + ) if (names !== undefined) result.expressionAttributeNames = names - const values = parseJson(params.putExpressionAttributeValues, 'expressionAttributeValues') + // expressionAttributeValues: fall back to queryExpressionAttributeValues (legacy migration target) + const values = parseJson( + params.putExpressionAttributeValues || params.queryExpressionAttributeValues, + 'expressionAttributeValues' + ) if (values !== undefined) result.expressionAttributeValues = values } @@ -644,28 +659,43 @@ Return ONLY the expression - no explanations.`, } if (op === 'scan') { - if (params.scanFilterExpression) result.filterExpression = params.scanFilterExpression + // filterExpression: fall back to queryFilterExpression (legacy migration target for 'filterExpression') + const filterExpr = params.scanFilterExpression || params.queryFilterExpression + if (filterExpr) result.filterExpression = filterExpr if (params.projectionExpression) result.projectionExpression = params.projectionExpression - const names = parseJson(params.scanExpressionAttributeNames, 'expressionAttributeNames') + // expressionAttributeNames: fall back to queryExpressionAttributeNames (legacy migration target) + const names = parseJson( + params.scanExpressionAttributeNames || params.queryExpressionAttributeNames, + 'expressionAttributeNames' + ) if (names !== undefined) result.expressionAttributeNames = names + // expressionAttributeValues: fall back to queryExpressionAttributeValues (legacy migration target) const values = parseJson( - params.scanExpressionAttributeValues, + params.scanExpressionAttributeValues || params.queryExpressionAttributeValues, 'expressionAttributeValues' ) if (values !== undefined) result.expressionAttributeValues = values - if (params.scanLimit) result.limit = Number.parseInt(String(params.scanLimit), 10) + // limit: fall back to queryLimit (legacy migration target for 'limit') + const lim = params.scanLimit || params.queryLimit + if (lim) result.limit = Number.parseInt(String(lim), 10) const esk = parseJson(params.scanExclusiveStartKey, 'exclusiveStartKey') if (esk !== undefined) result.exclusiveStartKey = esk } if (op === 'update') { - const key = parseJson(params.updateKey, 'key') + // key: fall back to getKey (legacy migration target for shared 'key' subblock) + const key = parseJson(params.updateKey || params.getKey, 'key') if (key !== undefined) result.key = key if (params.updateExpression) result.updateExpression = params.updateExpression - const names = parseJson(params.updateExpressionAttributeNames, 'expressionAttributeNames') + // expressionAttributeNames: fall back to queryExpressionAttributeNames (legacy migration target) + const names = parseJson( + params.updateExpressionAttributeNames || params.queryExpressionAttributeNames, + 'expressionAttributeNames' + ) if (names !== undefined) result.expressionAttributeNames = names + // expressionAttributeValues: fall back to queryExpressionAttributeValues (legacy migration target) const values = parseJson( - params.updateExpressionAttributeValues, + params.updateExpressionAttributeValues || params.queryExpressionAttributeValues, 'expressionAttributeValues' ) if (values !== undefined) result.expressionAttributeValues = values @@ -674,14 +704,20 @@ Return ONLY the expression - no explanations.`, } if (op === 'delete') { - const key = parseJson(params.deleteKey, 'key') + // key: fall back to getKey (legacy migration target for shared 'key' subblock) + const key = parseJson(params.deleteKey || params.getKey, 'key') if (key !== undefined) result.key = key if (params.deleteConditionExpression) result.conditionExpression = params.deleteConditionExpression - const names = parseJson(params.deleteExpressionAttributeNames, 'expressionAttributeNames') + // expressionAttributeNames: fall back to queryExpressionAttributeNames (legacy migration target) + const names = parseJson( + params.deleteExpressionAttributeNames || params.queryExpressionAttributeNames, + 'expressionAttributeNames' + ) if (names !== undefined) result.expressionAttributeNames = names + // expressionAttributeValues: fall back to queryExpressionAttributeValues (legacy migration target) const values = parseJson( - params.deleteExpressionAttributeValues, + params.deleteExpressionAttributeValues || params.queryExpressionAttributeValues, 'expressionAttributeValues' ) if (values !== undefined) result.expressionAttributeValues = values From 4bc4518dd8edc6b08f7365fa19e189ba1e818a72 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 21 Apr 2026 12:20:09 -0700 Subject: [PATCH 04/12] feat(contact): add contact page, migrate help/demo forms to useMutation (#4242) * feat(contact): add contact page, migrate help/demo forms to useMutation * improvement(contact): address greptile review feedback - Map contact topic to help email type for accurate confirmation emails - Drop Zod schema details from 400 response on public /api/contact - Wire aria-describedby + aria-invalid in LandingField for both forms - Reset helpMutation on modal reopen to match demo-request pattern * improvement(landing): extract shared LandingField component --- .../(landing)/components/contact/consts.ts | 82 ++++ .../components/contact/contact-form.tsx | 239 ++++++++++ .../demo-request/demo-request-modal.tsx | 200 ++++---- .../(landing)/components/footer/footer.tsx | 1 + .../components/forms/landing-field.tsx | 34 ++ apps/sim/app/(landing)/contact/page.tsx | 114 +++++ apps/sim/app/api/contact/route.ts | 129 +++++ .../components/help-modal/help-modal.tsx | 448 +++++++----------- apps/sim/lib/posthog/events.ts | 4 + 9 files changed, 857 insertions(+), 394 deletions(-) create mode 100644 apps/sim/app/(landing)/components/contact/consts.ts create mode 100644 apps/sim/app/(landing)/components/contact/contact-form.tsx create mode 100644 apps/sim/app/(landing)/components/forms/landing-field.tsx create mode 100644 apps/sim/app/(landing)/contact/page.tsx create mode 100644 apps/sim/app/api/contact/route.ts diff --git a/apps/sim/app/(landing)/components/contact/consts.ts b/apps/sim/app/(landing)/components/contact/consts.ts new file mode 100644 index 00000000000..242d06ecaf0 --- /dev/null +++ b/apps/sim/app/(landing)/components/contact/consts.ts @@ -0,0 +1,82 @@ +import { z } from 'zod' +import { NO_EMAIL_HEADER_CONTROL_CHARS_REGEX } from '@/lib/messaging/email/utils' +import { quickValidateEmail } from '@/lib/messaging/email/validation' + +export const CONTACT_TOPIC_VALUES = [ + 'general', + 'support', + 'integration', + 'feature_request', + 'sales', + 'partnership', + 'billing', + 'other', +] as const + +export const CONTACT_TOPIC_OPTIONS = [ + { value: 'general', label: 'General question' }, + { value: 'support', label: 'Technical support' }, + { value: 'integration', label: 'Integration request' }, + { value: 'feature_request', label: 'Feature request' }, + { value: 'sales', label: 'Sales & pricing' }, + { value: 'partnership', label: 'Partnership' }, + { value: 'billing', label: 'Billing' }, + { value: 'other', label: 'Other' }, +] as const + +export const contactRequestSchema = z.object({ + name: z + .string() + .trim() + .min(1, 'Name is required') + .max(120, 'Name must be 120 characters or less') + .regex(NO_EMAIL_HEADER_CONTROL_CHARS_REGEX, 'Invalid characters'), + email: z + .string() + .trim() + .min(1, 'Email is required') + .max(320) + .transform((value) => value.toLowerCase()) + .refine((value) => quickValidateEmail(value).isValid, 'Enter a valid email'), + company: z + .string() + .trim() + .max(120, 'Company must be 120 characters or less') + .optional() + .transform((value) => (value && value.length > 0 ? value : undefined)), + topic: z.enum(CONTACT_TOPIC_VALUES, { + errorMap: () => ({ message: 'Please select a topic' }), + }), + subject: z + .string() + .trim() + .min(1, 'Subject is required') + .max(200, 'Subject must be 200 characters or less') + .regex(NO_EMAIL_HEADER_CONTROL_CHARS_REGEX, 'Invalid characters'), + message: z + .string() + .trim() + .min(1, 'Message is required') + .max(5000, 'Message must be 5,000 characters or less'), +}) + +export type ContactRequestPayload = z.infer + +export function getContactTopicLabel(value: ContactRequestPayload['topic']): string { + return CONTACT_TOPIC_OPTIONS.find((option) => option.value === value)?.label ?? value +} + +export type HelpEmailType = 'bug' | 'feedback' | 'feature_request' | 'other' + +export function mapContactTopicToHelpType(topic: ContactRequestPayload['topic']): HelpEmailType { + switch (topic) { + case 'feature_request': + return 'feature_request' + case 'support': + return 'bug' + case 'integration': + return 'feedback' + default: + return 'other' + } +} diff --git a/apps/sim/app/(landing)/components/contact/contact-form.tsx b/apps/sim/app/(landing)/components/contact/contact-form.tsx new file mode 100644 index 00000000000..4cde2b3da7a --- /dev/null +++ b/apps/sim/app/(landing)/components/contact/contact-form.tsx @@ -0,0 +1,239 @@ +'use client' + +import { useState } from 'react' +import { useMutation } from '@tanstack/react-query' +import { Combobox, Input, Textarea } from '@/components/emcn' +import { Check } from '@/components/emcn/icons' +import { cn } from '@/lib/core/utils/cn' +import { captureClientEvent } from '@/lib/posthog/client' +import { + CONTACT_TOPIC_OPTIONS, + type ContactRequestPayload, + contactRequestSchema, +} from '@/app/(landing)/components/contact/consts' +import { LandingField } from '@/app/(landing)/components/forms/landing-field' + +type ContactField = keyof ContactRequestPayload +type ContactErrors = Partial> + +interface ContactFormState { + name: string + email: string + company: string + topic: ContactRequestPayload['topic'] | '' + subject: string + message: string +} + +const INITIAL_FORM_STATE: ContactFormState = { + name: '', + email: '', + company: '', + topic: '', + subject: '', + message: '', +} + +const COMBOBOX_TOPICS = [...CONTACT_TOPIC_OPTIONS] + +const LANDING_INPUT = + 'h-[36px] rounded-[5px] border border-[var(--border-1)] bg-[var(--surface-5)] px-3 font-[430] font-season text-[14px] text-[var(--text-primary)] outline-none transition-colors placeholder:text-[var(--text-muted)]' + +async function submitContactRequest(payload: ContactRequestPayload) { + const response = await fetch('/api/contact', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }) + + const result = (await response.json().catch(() => null)) as { + error?: string + message?: string + } | null + + if (!response.ok) { + throw new Error(result?.error || 'Failed to send message') + } + + return result +} + +export function ContactForm() { + const [form, setForm] = useState(INITIAL_FORM_STATE) + const [errors, setErrors] = useState({}) + const [submitSuccess, setSubmitSuccess] = useState(false) + + const contactMutation = useMutation({ + mutationFn: submitContactRequest, + onSuccess: (_data, variables) => { + captureClientEvent('landing_contact_submitted', { topic: variables.topic }) + setForm(INITIAL_FORM_STATE) + setErrors({}) + setSubmitSuccess(true) + }, + }) + + function updateField( + field: TField, + value: ContactFormState[TField] + ) { + setForm((prev) => ({ ...prev, [field]: value })) + setErrors((prev) => { + if (!prev[field as ContactField]) { + return prev + } + const nextErrors = { ...prev } + delete nextErrors[field as ContactField] + return nextErrors + }) + if (contactMutation.isError) { + contactMutation.reset() + } + } + + function handleSubmit(event: React.FormEvent) { + event.preventDefault() + if (contactMutation.isPending) return + + const parsed = contactRequestSchema.safeParse({ + ...form, + company: form.company || undefined, + }) + + if (!parsed.success) { + const fieldErrors = parsed.error.flatten().fieldErrors + setErrors({ + name: fieldErrors.name?.[0], + email: fieldErrors.email?.[0], + company: fieldErrors.company?.[0], + topic: fieldErrors.topic?.[0], + subject: fieldErrors.subject?.[0], + message: fieldErrors.message?.[0], + }) + return + } + + contactMutation.mutate(parsed.data) + } + + const submitError = contactMutation.isError + ? contactMutation.error instanceof Error + ? contactMutation.error.message + : 'Failed to send message. Please try again.' + : null + + if (submitSuccess) { + return ( +
+
+ +
+

+ Message received +

+

+ Thanks for reaching out. We've sent a confirmation to your inbox and will get back to you + shortly. +

+ +
+ ) + } + + return ( +
+
+ + updateField('name', event.target.value)} + placeholder='Your name' + className={LANDING_INPUT} + /> + + + updateField('email', event.target.value)} + placeholder='you@company.com' + className={LANDING_INPUT} + /> + +
+ +
+ + updateField('company', event.target.value)} + placeholder='Company name' + className={LANDING_INPUT} + /> + + + updateField('topic', value as ContactRequestPayload['topic'])} + placeholder='Select a topic' + editable={false} + filterOptions={false} + className='h-[36px] rounded-[5px] px-3 font-[430] font-season text-[14px]' + /> + +
+ + + updateField('subject', event.target.value)} + placeholder='How can we help?' + className={LANDING_INPUT} + /> + + + +