@aws/nx-plugin-mcp 1.0.0-rc.4 → 1.0.0-rc.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/aws-nx-mcp.js +2240 -1030
- package/docs/get_started/building-with-ai.mdx +116 -0
- package/docs/get_started/concepts.mdx +52 -0
- package/docs/get_started/existing-project.mdx +176 -0
- package/docs/get_started/quick-start.mdx +266 -0
- package/docs/get_started/tutorials/contribute-generator.mdx +405 -0
- package/docs/get_started/tutorials/dungeon-game/1.mdx +1205 -0
- package/docs/get_started/tutorials/dungeon-game/2.mdx +237 -0
- package/docs/get_started/tutorials/dungeon-game/3.mdx +76 -0
- package/docs/get_started/tutorials/dungeon-game/4.mdx +162 -0
- package/docs/get_started/tutorials/dungeon-game/overview.mdx +144 -0
- package/docs/get_started/tutorials/dungeon-game/wrap-up.mdx +41 -0
- package/docs/get_started/tutorials/existing-project.mdx +4 -0
- package/docs/guides/agentcore-gateway.mdx +376 -0
- package/docs/guides/connection/agentcore-gateway-gateway.mdx +154 -0
- package/docs/guides/connection/agentcore-gateway-mcp.mdx +134 -0
- package/docs/guides/connection/py-agent-a2a.mdx +47 -15
- package/docs/guides/connection/py-agent-dynamodb.mdx +116 -0
- package/docs/guides/connection/py-agent-gateway.mdx +176 -0
- package/docs/guides/connection/py-agent-mcp.mdx +42 -13
- package/docs/guides/connection/py-agent-rdb.mdx +178 -0
- package/docs/guides/connection/py-fast-api-dynamodb.mdx +56 -0
- package/docs/guides/connection/py-fast-api-rdb.mdx +184 -0
- package/docs/guides/connection/py-mcp-server-dynamodb.mdx +116 -0
- package/docs/guides/connection/py-mcp-server-rdb.mdx +187 -0
- package/docs/guides/connection/react-agui.mdx +4 -4
- package/docs/guides/connection/react-fastapi.mdx +38 -2
- package/docs/guides/connection/react-py-agent.mdx +7 -13
- package/docs/guides/connection/react-smithy.mdx +3 -3
- package/docs/guides/connection/react-trpc.mdx +1 -1
- package/docs/guides/connection/react-ts-agent.mdx +8 -8
- package/docs/guides/connection/smithy-dynamodb.mdx +4 -4
- package/docs/guides/connection/smithy-rdb.mdx +5 -5
- package/docs/guides/connection/trpc-dynamodb.mdx +4 -4
- package/docs/guides/connection/trpc-rdb.mdx +5 -5
- package/docs/guides/connection/ts-agent-a2a.mdx +12 -9
- package/docs/guides/connection/ts-agent-dynamodb.mdx +3 -3
- package/docs/guides/connection/ts-agent-gateway.mdx +141 -0
- package/docs/guides/connection/ts-agent-mcp.mdx +11 -8
- package/docs/guides/connection/ts-agent-rdb.mdx +66 -21
- package/docs/guides/connection/ts-mcp-server-dynamodb.mdx +3 -3
- package/docs/guides/connection/ts-mcp-server-rdb.mdx +66 -16
- package/docs/guides/connection.mdx +104 -5
- package/docs/guides/docker-bundling.mdx +68 -8
- package/docs/guides/fastapi.mdx +244 -4
- package/docs/guides/license.mdx +264 -109
- package/docs/guides/local-development.mdx +87 -0
- package/docs/guides/nx-generator.mdx +7 -2
- package/docs/guides/py-agent.mdx +257 -49
- package/docs/guides/py-dynamodb.mdx +476 -0
- package/docs/guides/py-mcp-server.mdx +61 -2
- package/docs/guides/py-rdb.mdx +254 -0
- package/docs/guides/react-website-auth.mdx +58 -1
- package/docs/guides/react-website.mdx +130 -19
- package/docs/guides/security.mdx +75 -0
- package/docs/guides/terraform-project.mdx +1 -1
- package/docs/guides/trpc.mdx +45 -9
- package/docs/guides/ts-agent.mdx +149 -9
- package/docs/guides/ts-dynamodb.mdx +62 -239
- package/docs/guides/ts-mcp-server.mdx +66 -3
- package/docs/guides/ts-nx-plugin.mdx +1 -1
- package/docs/guides/ts-rdb.mdx +117 -470
- package/docs/guides/ts-smithy-api.mdx +183 -4
- package/docs/guides/typescript-infrastructure.mdx +9 -1
- package/docs/guides/typescript-project.mdx +5 -10
- package/docs/guides/workspace.mdx +2 -2
- package/docs/snippets/agent/architecture.mdx +1 -1
- package/docs/snippets/agent/bedrock-deployment.mdx +4 -0
- package/docs/snippets/agent/runtime-arn.mdx +21 -0
- package/docs/snippets/agent/securing-your-agent.mdx +39 -0
- package/docs/snippets/api/access-logging.mdx +33 -0
- package/docs/snippets/api/type-safe-api-integrations.mdx +31 -0
- package/docs/snippets/api/waf-configuration.mdx +1 -1
- package/docs/snippets/connection/dynamodb-local-development.mdx +2 -2
- package/docs/snippets/connection/py-dynamodb-local-development.mdx +7 -0
- package/docs/snippets/connection/py-lambda-rdb-ssl-requirements.mdx +7 -0
- package/docs/snippets/connection/rdb-api-infrastructure.mdx +50 -18
- package/docs/snippets/dynamodb/deploying-table.mdx +166 -0
- package/docs/snippets/dynamodb/gsi-config.mdx +38 -0
- package/docs/snippets/dynamodb/infrastructure.mdx +33 -0
- package/docs/snippets/dynamodb/local-dev-start.mdx +13 -0
- package/docs/snippets/dynamodb/local-dev-windows.mdx +15 -0
- package/docs/snippets/mcp/architecture.mdx +1 -1
- package/docs/snippets/mcp/bedrock-deployment.mdx +4 -0
- package/docs/snippets/mcp/config.mdx +3 -2
- package/docs/snippets/rdb/architecture.mdx +38 -0
- package/docs/snippets/rdb/cluster-instances.mdx +31 -0
- package/docs/snippets/rdb/deletion-protection.mdx +34 -0
- package/docs/snippets/rdb/deploying.mdx +187 -0
- package/docs/snippets/rdb/encryption-key-rotation.mdx +30 -0
- package/docs/snippets/rdb/engine-version.mdx +63 -0
- package/docs/snippets/rdb/infrastructure.mdx +35 -0
- package/docs/snippets/rdb/logging.mdx +32 -0
- package/docs/snippets/rdb/rds-proxy.mdx +50 -0
- package/docs/snippets/rdb/removal-policy.mdx +57 -0
- package/docs/snippets/rdb/serverless-capacity.mdx +32 -0
- package/docs/snippets/required-prerequisites.mdx +1 -1
- package/docs/snippets/trivy-image-scan.mdx +27 -0
- package/generators.json +101 -2
- package/package.json +1 -1
- package/src/agentcore-gateway/gateway-connection/schema.json +31 -0
- package/src/agentcore-gateway/mcp-connection/schema.json +31 -0
- package/src/agentcore-gateway/schema.json +70 -0
- package/src/connection/schema.json +5 -0
- package/src/infra/app/schema.json +5 -0
- package/src/init/schema.json +35 -0
- package/src/license/schema.json +11 -0
- package/src/preset/schema.json +11 -5
- package/src/py/agent/a2a-connection/schema.json +5 -0
- package/src/py/agent/gateway-connection/schema.json +31 -0
- package/src/py/agent/mcp-connection/schema.json +5 -0
- package/src/py/agent/react-connection/schema.json +5 -0
- package/src/py/agent/schema.json +6 -1
- package/src/py/api/schema.json +5 -0
- package/src/py/dynamodb/agent-connection/schema.json +27 -0
- package/src/py/dynamodb/fast-api-connection/schema.json +23 -0
- package/src/py/dynamodb/mcp-server-connection/schema.json +27 -0
- package/src/py/dynamodb/schema.json +75 -0
- package/src/py/fast-api/react/schema.json +5 -0
- package/src/py/fast-api/schema.json +5 -0
- package/src/py/lambda-function/schema.json +5 -0
- package/src/py/mcp-server/schema.json +5 -0
- package/src/py/project/schema.json +5 -0
- package/src/py/rdb/agent-connection/schema.json +27 -0
- package/src/py/rdb/fast-api-connection/schema.json +23 -0
- package/src/py/rdb/mcp-server-connection/schema.json +27 -0
- package/src/py/rdb/schema.json +77 -0
- package/src/smithy/project/schema.json +5 -0
- package/src/smithy/react-connection/schema.json +5 -0
- package/src/smithy/ts/api/schema.json +5 -0
- package/src/terraform/project/schema.json +5 -0
- package/src/trpc/backend/schema.json +5 -0
- package/src/trpc/react/schema.json +5 -0
- package/src/ts/agent/a2a-connection/schema.json +5 -0
- package/src/ts/agent/gateway-connection/schema.json +31 -0
- package/src/ts/agent/mcp-connection/schema.json +5 -0
- package/src/ts/agent/react-connection/schema.json +5 -0
- package/src/ts/agent/schema.json +5 -0
- package/src/ts/api/schema.json +5 -0
- package/src/ts/astro-docs/schema.json +3 -3
- package/src/ts/docs/schema.json +3 -3
- package/src/ts/dynamodb/agent-connection/schema.json +5 -0
- package/src/ts/dynamodb/mcp-server-connection/schema.json +5 -0
- package/src/ts/dynamodb/schema.json +25 -2
- package/src/ts/dynamodb/smithy-connection/schema.json +5 -0
- package/src/ts/dynamodb/trpc-connection/schema.json +5 -0
- package/src/ts/lambda-function/schema.json +5 -0
- package/src/ts/lib/schema.json +5 -0
- package/src/ts/mcp-server/schema.json +5 -0
- package/src/ts/nx-generator/schema.json +5 -0
- package/src/ts/nx-plugin/schema.json +5 -0
- package/src/ts/rdb/agent-connection/schema.json +5 -0
- package/src/ts/rdb/mcp-server-connection/schema.json +5 -0
- package/src/ts/rdb/schema.json +6 -1
- package/src/ts/rdb/smithy-connection/schema.json +5 -0
- package/src/ts/rdb/trpc-connection/schema.json +5 -0
- package/src/ts/react-website/app/schema.json +11 -6
- package/src/ts/react-website/cognito-auth/schema.json +5 -0
- package/src/ts/react-website/runtime-config/schema.json +5 -0
- package/src/ts/website/app/schema.json +11 -6
- package/src/ts/website/auth/schema.json +5 -0
- /package/docs/snippets/connection/{lambda-rdb-ssl-requirements.mdx → ts-lambda-rdb-ssl-requirements.mdx} +0 -0
- /package/docs/snippets/connection/{mcp-server-rdb-ssl-requirements.mdx → ts-mcp-server-rdb-ssl-requirements.mdx} +0 -0
|
@@ -3,12 +3,36 @@ title: Docker Bundling
|
|
|
3
3
|
description: Build and deploy Docker images for TypeScript and Python projects in an Nx Plugin for AWS workspace.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
import { FileTree, Tabs, TabItem } from '@astrojs/starlight/components';
|
|
6
|
+
import { FileTree, Tabs, TabItem, Code } from '@astrojs/starlight/components';
|
|
7
7
|
import NxCommands from '@components/nx-commands.astro';
|
|
8
8
|
import Link from '@components/link.astro';
|
|
9
9
|
import Infrastructure from '@components/infrastructure.astro';
|
|
10
|
+
import TrivyVersion from '@components/trivy-version.astro';
|
|
11
|
+
import { CONTAINER_VERSIONS } from '../../../../../../packages/nx-plugin/src/utils/versions';
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
export const trivyTarget = `{
|
|
14
|
+
"targets": {
|
|
15
|
+
"trivy": {
|
|
16
|
+
"cache": true,
|
|
17
|
+
"inputs": ["default", "^production"],
|
|
18
|
+
"outputs": ["{workspaceRoot}/dist/{projectRoot}/trivy"],
|
|
19
|
+
"executor": "nx:run-commands",
|
|
20
|
+
"options": {
|
|
21
|
+
"commands": [
|
|
22
|
+
"rimraf dist/packages/my-project/trivy",
|
|
23
|
+
"make-dir dist/packages/my-project/trivy",
|
|
24
|
+
"ncp packages/my-project/.trivyignore dist/packages/my-project/trivy/.trivyignore",
|
|
25
|
+
"docker save -o dist/packages/my-project/trivy/image.tar my-scope-my-project:latest",
|
|
26
|
+
"docker run --rm -v \\"./dist/packages/my-project/trivy\\":/scan public.ecr.aws/aquasecurity/trivy:${CONTAINER_VERSIONS.trivy} image --input /scan/image.tar --ignorefile /scan/.trivyignore --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --no-progress -q"
|
|
27
|
+
],
|
|
28
|
+
"parallel": false
|
|
29
|
+
},
|
|
30
|
+
"dependsOn": ["docker"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}`;
|
|
34
|
+
|
|
35
|
+
Several generators (such as <Link path="/guides/ts-agent">`ts#agent`</Link> and <Link path="/guides/py-agent">`py#agent`</Link>) produce a Docker image that is pushed to Amazon ECR and consumed by AWS infrastructure. This guide describes the pattern they follow so that you can apply it to other use cases — for example, running a <Link path="/guides/fastapi">FastAPI</Link> project on Amazon ECS, or deploying a containerised Express server.
|
|
12
36
|
|
|
13
37
|
:::tip[Docker or Finch]
|
|
14
38
|
The container engine used to build images is chosen at workspace creation time via the `--containerEngine` flag (`docker`, `finch`, or `infer` — the default — which auto-detects what's installed). [Finch](https://runfinch.com/) is an open-source, drop-in alternative to Docker. The selection is recorded in `aws-nx-plugin.config.mts` and applied to every generator that emits container build commands. CDK image asset builds honour the choice via the `CDK_DOCKER` environment variable.
|
|
@@ -253,6 +277,36 @@ This clears the output directory, then copies both the bundle contents and the `
|
|
|
253
277
|
|
|
254
278
|
<NxCommands commands={['docker my-project']} />
|
|
255
279
|
|
|
280
|
+
## Scanning Images with Trivy
|
|
281
|
+
|
|
282
|
+
It's good practice to scan your images for known vulnerabilities. The generators that follow this pattern add a `trivy` target which scans the built image with [Trivy](https://trivy.dev/), running from the [ECR-hosted Trivy image](https://gallery.ecr.aws/aquasecurity/trivy), and fails the build on `HIGH` or `CRITICAL` findings.
|
|
283
|
+
|
|
284
|
+
Add a `trivy` target which `dependsOn` your `docker` target. It saves the built image to a tarball and scans it via a workspace-relative bind mount, so the same command works under both `docker` and `finch`:
|
|
285
|
+
|
|
286
|
+
<Code lang="json" code={trivyTarget} />
|
|
287
|
+
|
|
288
|
+
Then make `build` depend on `trivy` so the scan runs as part of a build:
|
|
289
|
+
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"targets": {
|
|
293
|
+
"build": {
|
|
294
|
+
"dependsOn": ["trivy"]
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
<NxCommands commands={['trivy my-project']} />
|
|
301
|
+
|
|
302
|
+
:::tip[Caching skips unchanged images]
|
|
303
|
+
Because the image is fully determined by your project source, declaring `inputs` (here `default` and `^production`, matching the `bundle` target) lets Nx cache the scan and skip re-scanning an image that hasn't changed. Pin the Trivy image version (e.g. <code>trivy:<TrivyVersion /></code>) so scans are reproducible.
|
|
304
|
+
:::
|
|
305
|
+
|
|
306
|
+
:::note[Suppressing findings]
|
|
307
|
+
Trivy reads a `.trivyignore` file (a list of vulnerability IDs, one per line) from the root of your project. `--ignore-unfixed` skips vulnerabilities with no available fix, so the scan only fails on issues you can action by upgrading tooling in your `Dockerfile`. See the [Trivy filtering documentation](https://trivy.dev/latest/docs/configuration/filtering/#by-finding-ids) for details.
|
|
308
|
+
:::
|
|
309
|
+
|
|
256
310
|
## Infrastructure
|
|
257
311
|
|
|
258
312
|
Wiring the resulting build-context directory to infrastructure as code is the same for both TypeScript and Python — only the path to the build-context directory differs (`dist/packages/my-project/bundle` for TypeScript, `dist/packages/my-project/docker` for Python).
|
|
@@ -318,8 +372,8 @@ Terraform's AWS provider does not have a first-class "build and push a Docker im
|
|
|
318
372
|
|
|
319
373
|
1. The project's `build` target runs `docker build`, producing a local image tagged `my-scope-my-project:latest`.
|
|
320
374
|
2. An `aws_ecr_repository` to hold the image.
|
|
321
|
-
3. A `null_resource` with a `local-exec` provisioner that authenticates to ECR, re-tags the locally-built image, and pushes it.
|
|
322
|
-
4. The downstream resource (e.g. `aws_ecs_task_definition`) references
|
|
375
|
+
3. A `null_resource` with a `local-exec` provisioner that authenticates to ECR, re-tags the locally-built image with its content digest, and pushes it.
|
|
376
|
+
4. The downstream resource (e.g. `aws_ecs_task_definition`) references the image by its immutable, digest-based tag.
|
|
323
377
|
|
|
324
378
|
```d2
|
|
325
379
|
direction: down
|
|
@@ -361,7 +415,7 @@ infra.repo -> ecr
|
|
|
361
415
|
```hcl
|
|
362
416
|
resource "aws_ecr_repository" "repo" {
|
|
363
417
|
name = "my-project-repository"
|
|
364
|
-
image_tag_mutability = "
|
|
418
|
+
image_tag_mutability = "IMMUTABLE"
|
|
365
419
|
force_delete = true
|
|
366
420
|
}
|
|
367
421
|
|
|
@@ -370,24 +424,30 @@ data "external" "docker_digest" {
|
|
|
370
424
|
program = ["sh", "-c", "echo '{\"digest\":\"'$(docker inspect my-scope-my-project:latest --format '{{.Id}}')'\"}'"]
|
|
371
425
|
}
|
|
372
426
|
|
|
427
|
+
locals {
|
|
428
|
+
# Content-based, immutable image tag derived from the local image digest
|
|
429
|
+
image_tag = replace(data.external.docker_digest.result.digest, "sha256:", "")
|
|
430
|
+
}
|
|
431
|
+
|
|
373
432
|
resource "null_resource" "docker_publish" {
|
|
374
433
|
triggers = {
|
|
375
434
|
docker_digest = data.external.docker_digest.result.digest
|
|
376
435
|
repository_url = aws_ecr_repository.repo.repository_url
|
|
436
|
+
image_tag = local.image_tag
|
|
377
437
|
}
|
|
378
438
|
|
|
379
439
|
provisioner "local-exec" {
|
|
380
440
|
command = <<-EOT
|
|
381
441
|
aws ecr get-login-password --region ${data.aws_region.current.id} \
|
|
382
442
|
| docker login --username AWS --password-stdin ${self.triggers.repository_url}
|
|
383
|
-
docker tag my-scope-my-project:latest ${self.triggers.repository_url}
|
|
384
|
-
docker push ${self.triggers.repository_url}
|
|
443
|
+
docker tag my-scope-my-project:latest ${self.triggers.repository_url}:${self.triggers.image_tag}
|
|
444
|
+
docker push ${self.triggers.repository_url}:${self.triggers.image_tag}
|
|
385
445
|
EOT
|
|
386
446
|
}
|
|
387
447
|
}
|
|
388
448
|
```
|
|
389
449
|
|
|
390
|
-
The `data.external.docker_digest` block ensures the `null_resource` re-runs whenever the local image hash changes, triggering a new push on every meaningful code change.
|
|
450
|
+
The `data.external.docker_digest` block ensures the `null_resource` re-runs whenever the local image hash changes, triggering a new push on every meaningful code change. The image is pushed under an immutable, content-based tag derived from its digest, so the ECR repository can use `IMMUTABLE` tag mutability and reject any attempt to overwrite an existing tag.
|
|
391
451
|
|
|
392
452
|
:::note[Running bundle before apply]
|
|
393
453
|
`nx apply <project>` requires the image tag `my-scope-my-project:latest` to already exist locally. Run `nx build my-project` (or `nx docker my-project`) before `nx apply <project>`.
|
package/docs/guides/fastapi.mdx
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: FastAPI
|
|
3
3
|
description: Reference documentation for FastAPI
|
|
4
|
-
generator: py#
|
|
4
|
+
generator: py#api
|
|
5
5
|
when:
|
|
6
6
|
framework: [fastapi]
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
import { FileTree, Tabs, TabItem } from '@astrojs/starlight/components';
|
|
9
|
+
import { FileTree, Tabs, TabItem, CardGrid } from '@astrojs/starlight/components';
|
|
10
|
+
import Astro from '@astrojs/react';
|
|
11
|
+
import ConnectionCard from '@components/connection-card.astro';
|
|
10
12
|
import Link from '@components/link.astro';
|
|
11
13
|
import RunGenerator from '@components/run-generator.astro';
|
|
12
14
|
import GeneratorParameters from '@components/generator-parameters.astro';
|
|
@@ -26,11 +28,11 @@ The FastAPI generator creates a new FastAPI with AWS CDK or Terraform infrastruc
|
|
|
26
28
|
|
|
27
29
|
You can generate a new FastAPI in two ways:
|
|
28
30
|
|
|
29
|
-
<RunGenerator generator="py#
|
|
31
|
+
<RunGenerator generator="py#api" requiredParameters={{ framework: 'fastapi' }} />
|
|
30
32
|
|
|
31
33
|
### Options
|
|
32
34
|
|
|
33
|
-
<GeneratorParameters generator="py#
|
|
35
|
+
<GeneratorParameters generator="py#api" />
|
|
34
36
|
|
|
35
37
|
<Snippet name="api/api-choice-note" />
|
|
36
38
|
|
|
@@ -185,6 +187,216 @@ Unhandled exceptions are caught by the middleware and:
|
|
|
185
187
|
It's recommended to specify response models for your API operations for better code generation if using the `connection` generator. <Link path="guides/connection/react-fastapi#errors">See here for more details</Link>.
|
|
186
188
|
:::
|
|
187
189
|
|
|
190
|
+
### Accessing the Calling User
|
|
191
|
+
|
|
192
|
+
When your API is protected by authentication, your route handlers often need to know who is calling. The generated FastAPI runs inside AWS Lambda via the [Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter), which forwards the API Gateway request context as JSON on the `x-amzn-request-context` header. You can read it from the FastAPI `Request` to extract the caller's identity.
|
|
193
|
+
|
|
194
|
+
As an example, let's add a `/me` endpoint that returns details about the calling user. We'll implement the extraction as a [FastAPI dependency](https://fastapi.tiangolo.com/tutorial/dependencies/) so it can be reused across routes. The shape of the request context — and therefore how you extract the identity — depends on both your selected `auth` method and whether you deployed a REST or HTTP API.
|
|
195
|
+
|
|
196
|
+
<OptionFilter when={{ auth: 'iam' }} description="Identity extraction for IAM-authenticated APIs">
|
|
197
|
+
For `IAM` authentication, we look up the caller in Cognito using the sub extracted from the API Gateway request context. Create `identity.py` alongside `main.py`:
|
|
198
|
+
|
|
199
|
+
<Tabs syncKey="http-rest">
|
|
200
|
+
<TabItem label="REST API" _filter={{ infra: 'rest-lambda' }}>
|
|
201
|
+
```python
|
|
202
|
+
import json
|
|
203
|
+
import os
|
|
204
|
+
from typing import Annotated
|
|
205
|
+
|
|
206
|
+
from boto3 import client
|
|
207
|
+
from fastapi import Depends, HTTPException, Request
|
|
208
|
+
from pydantic import BaseModel
|
|
209
|
+
|
|
210
|
+
cognito = client("cognito-idp")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class Identity(BaseModel):
|
|
214
|
+
sub: str
|
|
215
|
+
username: str
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def get_identity(request: Request) -> Identity:
|
|
219
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
220
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
221
|
+
if not request_context_header:
|
|
222
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
223
|
+
|
|
224
|
+
request_context = json.loads(request_context_header)
|
|
225
|
+
provider = request_context.get("identity", {}).get("cognitoAuthenticationProvider")
|
|
226
|
+
|
|
227
|
+
sub = provider.split(":")[-1] if provider else None
|
|
228
|
+
if not sub:
|
|
229
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
230
|
+
|
|
231
|
+
users = cognito.list_users(
|
|
232
|
+
# Assumes user pool id is configured in lambda environment
|
|
233
|
+
UserPoolId=os.environ["USER_POOL_ID"],
|
|
234
|
+
Limit=1,
|
|
235
|
+
Filter=f'sub="{sub}"',
|
|
236
|
+
).get("Users", [])
|
|
237
|
+
|
|
238
|
+
if len(users) != 1:
|
|
239
|
+
raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
|
|
240
|
+
|
|
241
|
+
return Identity(sub=sub, username=users[0]["Username"])
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
245
|
+
```
|
|
246
|
+
</TabItem>
|
|
247
|
+
<TabItem label="HTTP API" _filter={{ infra: 'http-lambda' }}>
|
|
248
|
+
```python
|
|
249
|
+
import json
|
|
250
|
+
import os
|
|
251
|
+
from typing import Annotated
|
|
252
|
+
|
|
253
|
+
from boto3 import client
|
|
254
|
+
from fastapi import Depends, HTTPException, Request
|
|
255
|
+
from pydantic import BaseModel
|
|
256
|
+
|
|
257
|
+
cognito = client("cognito-idp")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class Identity(BaseModel):
|
|
261
|
+
sub: str
|
|
262
|
+
username: str
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def get_identity(request: Request) -> Identity:
|
|
266
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
267
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
268
|
+
if not request_context_header:
|
|
269
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
270
|
+
|
|
271
|
+
request_context = json.loads(request_context_header)
|
|
272
|
+
amr = (
|
|
273
|
+
request_context.get("authorizer", {})
|
|
274
|
+
.get("iam", {})
|
|
275
|
+
.get("cognitoIdentity", {})
|
|
276
|
+
.get("amr", [])
|
|
277
|
+
)
|
|
278
|
+
sign_in = next((s for s in amr if ":CognitoSignIn:" in s), None)
|
|
279
|
+
sub = sign_in.split(":")[-1] if sign_in else None
|
|
280
|
+
|
|
281
|
+
if not sub:
|
|
282
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
283
|
+
|
|
284
|
+
users = cognito.list_users(
|
|
285
|
+
# Assumes user pool id is configured in lambda environment
|
|
286
|
+
UserPoolId=os.environ["USER_POOL_ID"],
|
|
287
|
+
Limit=1,
|
|
288
|
+
Filter=f'sub="{sub}"',
|
|
289
|
+
).get("Users", [])
|
|
290
|
+
|
|
291
|
+
if len(users) != 1:
|
|
292
|
+
raise HTTPException(status_code=403, detail=f"No user found with subjectId {sub}")
|
|
293
|
+
|
|
294
|
+
return Identity(sub=sub, username=users[0]["Username"])
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
298
|
+
```
|
|
299
|
+
</TabItem>
|
|
300
|
+
</Tabs>
|
|
301
|
+
</OptionFilter>
|
|
302
|
+
|
|
303
|
+
<OptionFilter when={{ auth: 'cognito' }} description="Identity extraction for Cognito-authenticated APIs">
|
|
304
|
+
With `auth: 'cognito'`, the API Gateway Cognito User Pools authorizer verifies the JWT that the caller supplies in the `Authorization` header and places the verified claims on the request context.
|
|
305
|
+
|
|
306
|
+
Create `identity.py` alongside `main.py`:
|
|
307
|
+
|
|
308
|
+
<Tabs syncKey="http-rest">
|
|
309
|
+
<TabItem label="REST API" _filter={{ infra: 'rest-lambda' }}>
|
|
310
|
+
```python
|
|
311
|
+
import json
|
|
312
|
+
from typing import Annotated
|
|
313
|
+
|
|
314
|
+
from fastapi import Depends, HTTPException, Request
|
|
315
|
+
from pydantic import BaseModel
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class Identity(BaseModel):
|
|
319
|
+
sub: str
|
|
320
|
+
username: str
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def get_identity(request: Request) -> Identity:
|
|
324
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
325
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
326
|
+
if not request_context_header:
|
|
327
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
328
|
+
|
|
329
|
+
request_context = json.loads(request_context_header)
|
|
330
|
+
claims = request_context.get("authorizer", {}).get("claims", {})
|
|
331
|
+
|
|
332
|
+
sub = claims.get("sub")
|
|
333
|
+
username = claims.get("username")
|
|
334
|
+
|
|
335
|
+
if not sub or not username:
|
|
336
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
337
|
+
|
|
338
|
+
return Identity(sub=sub, username=username)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
342
|
+
```
|
|
343
|
+
</TabItem>
|
|
344
|
+
<TabItem label="HTTP API" _filter={{ infra: 'http-lambda' }}>
|
|
345
|
+
HTTP APIs use a JWT authorizer which places the verified claims under `authorizer.jwt.claims`:
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
import json
|
|
349
|
+
from typing import Annotated
|
|
350
|
+
|
|
351
|
+
from fastapi import Depends, HTTPException, Request
|
|
352
|
+
from pydantic import BaseModel
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class Identity(BaseModel):
|
|
356
|
+
sub: str
|
|
357
|
+
username: str
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def get_identity(request: Request) -> Identity:
|
|
361
|
+
# The Lambda Web Adapter forwards the API Gateway request context as JSON
|
|
362
|
+
request_context_header = request.headers.get("x-amzn-request-context")
|
|
363
|
+
if not request_context_header:
|
|
364
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
365
|
+
|
|
366
|
+
request_context = json.loads(request_context_header)
|
|
367
|
+
claims = request_context.get("authorizer", {}).get("jwt", {}).get("claims", {})
|
|
368
|
+
|
|
369
|
+
sub = claims.get("sub")
|
|
370
|
+
username = claims.get("username")
|
|
371
|
+
|
|
372
|
+
if not sub or not username:
|
|
373
|
+
raise HTTPException(status_code=403, detail="Unable to determine calling user")
|
|
374
|
+
|
|
375
|
+
return Identity(sub=sub, username=username)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
CurrentUser = Annotated[Identity, Depends(get_identity)]
|
|
379
|
+
```
|
|
380
|
+
</TabItem>
|
|
381
|
+
</Tabs>
|
|
382
|
+
|
|
383
|
+
:::tip[No token verification required]
|
|
384
|
+
You don't need any JWT-verification library here — the API Gateway Cognito User Pools authorizer has already verified the signature, issuer, scopes, and expiry by the time your Lambda runs. If any of those checks fail, API Gateway returns `401 Unauthorized` and your handler is never invoked.
|
|
385
|
+
:::
|
|
386
|
+
</OptionFilter>
|
|
387
|
+
|
|
388
|
+
You can then inject the `CurrentUser` dependency into any route that needs the caller's identity:
|
|
389
|
+
|
|
390
|
+
```python
|
|
391
|
+
from .identity import CurrentUser, Identity
|
|
392
|
+
from .init import app, tracer
|
|
393
|
+
|
|
394
|
+
@app.get("/me")
|
|
395
|
+
@tracer.capture_method
|
|
396
|
+
def me(identity: CurrentUser) -> Identity:
|
|
397
|
+
return identity
|
|
398
|
+
```
|
|
399
|
+
|
|
188
400
|
<OptionFilter when={{ infra: 'rest-lambda' }} description="Streaming — REST API only">
|
|
189
401
|
### Streaming
|
|
190
402
|
|
|
@@ -406,6 +618,12 @@ When using `Custom` auth, your API is protected by a Lambda Authorizer that **de
|
|
|
406
618
|
<Snippet name="api/waf-configuration" parentHeading="WAF" />
|
|
407
619
|
</OptionFilter>
|
|
408
620
|
|
|
621
|
+
<OptionFilter when={{ infra: 'rest-lambda' }} description="Access logging — REST APIs log requests to CloudWatch by default">
|
|
622
|
+
### Access logging
|
|
623
|
+
|
|
624
|
+
<Snippet name="api/access-logging" parentHeading="Access logging" />
|
|
625
|
+
</OptionFilter>
|
|
626
|
+
|
|
409
627
|
### Integrations
|
|
410
628
|
|
|
411
629
|
<Snippet name="api/type-safe-api-integrations" parentHeading="Integrations" />
|
|
@@ -509,3 +727,25 @@ This starts a local FastAPI development server with:
|
|
|
509
727
|
## Invoking your FastAPI
|
|
510
728
|
|
|
511
729
|
To invoke your API from a React website, you can use the <Link path="guides/connection/react-fastapi">`connection` generator</Link>.
|
|
730
|
+
|
|
731
|
+
## Connections
|
|
732
|
+
|
|
733
|
+
Use the <Link path="guides/connection">`connection`</Link> generator to integrate this project with others in your workspace. The following connections involve this project:
|
|
734
|
+
|
|
735
|
+
<CardGrid>
|
|
736
|
+
<ConnectionCard
|
|
737
|
+
title="React to FastAPI"
|
|
738
|
+
description="Call a Python FastAPI from a React website"
|
|
739
|
+
href={`/nx-plugin-for-aws/${Astro.currentLocale || 'en'}/guides/connection/react-fastapi`}
|
|
740
|
+
source="react"
|
|
741
|
+
target="fastapi"
|
|
742
|
+
/>
|
|
743
|
+
<ConnectionCard
|
|
744
|
+
title="FastAPI to Python DynamoDB"
|
|
745
|
+
description="Connect a FastAPI to a DynamoDB table"
|
|
746
|
+
href={`/nx-plugin-for-aws/${Astro.currentLocale || 'en'}/guides/connection/py-fast-api-dynamodb`}
|
|
747
|
+
source="fastapi"
|
|
748
|
+
target="dynamodb"
|
|
749
|
+
targetBadge="python"
|
|
750
|
+
/>
|
|
751
|
+
</CardGrid>
|