diff --git a/.github/workflows/client-api.yml b/.github/workflows/client-api.yml index 0e6cef2d6..97514c9f1 100644 --- a/.github/workflows/client-api.yml +++ b/.github/workflows/client-api.yml @@ -10,6 +10,7 @@ on: - '.github/actions/**' - '.github/workflows/client-api.yml' - '.github/workflows/reuse-*.yml' + - 'servers/coprocessor/**' push: branches: - main @@ -22,8 +23,9 @@ on: - '.github/actions/**' - '.github/workflows/client-api.yml' - '.github/workflows/reuse-*.yml' -jobs: + - 'servers/coprocessor/**' +jobs: infrastructure: uses: ./.github/workflows/reuse-infrastructure.yml with: @@ -31,13 +33,26 @@ jobs: stack-output-path: infrastructure/client-api/cdktf.out/stacks/client-api secrets: inherit - api: + coprocessor: uses: ./.github/workflows/reuse-build-and-push-image.yml needs: [infrastructure] + with: + scope: '@server/coprocessor' + docker-repo-name-pattern: clientapi-{0}-coprocessor + app-path: servers/coprocessor + # Do not pass terraform-output because we don't want to redo codedeploy + # and it's conditional on this output + secrets: inherit + + api: + uses: ./.github/workflows/reuse-build-and-push-image.yml + # Require coprocessor dependency because we need the image to + # be built and pushed prior to codedeploy + needs: [infrastructure, coprocessor] with: scope: '@server/client-api' docker-repo-name-pattern: clientapi-{0}-app context: servers/client-api app-path: servers/client-api terraform-output: ${{needs.infrastructure.outputs.terraform-output}} - secrets: inherit \ No newline at end of file + secrets: inherit diff --git a/.github/workflows/reuse-build-and-push-image.yml b/.github/workflows/reuse-build-and-push-image.yml index c90deeef1..3ce612718 100644 --- a/.github/workflows/reuse-build-and-push-image.yml +++ b/.github/workflows/reuse-build-and-push-image.yml @@ -47,6 +47,7 @@ on: terraform-output: description: 'The terraform output which is used to get the ECS_Service and Task Defintion arns for codedeploy' required: false + default: '{"ecs-task-containerName": {"value":""}}' type: string archive-download-name: description: 'If specified, download this archive instead of checkout' @@ -55,117 +56,116 @@ on: default: '' permissions: - contents: read # This is required for actions/checkout - id-token: write # Access the Github JWT for AWS access + contents: read # This is required for actions/checkout + id-token: write # Access the Github JWT for AWS access deployments: write jobs: - # Let's build the image on every pull request just like we would on production - pull-request: - # Only run this job on a pull request event - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: - - name: Checkout - if: inputs.archive-download-name == '' - uses: actions/checkout@v4 - - name: Archive download - if: inputs.archive-download-name != '' - uses: actions/download-artifact@v4 - with: - name: ${{inputs.archive-download-name}} - - name: Build Docker Image - uses: pocket/pocket-monorepo/.github/actions/containerize@main - with: - docker-repo-name: "${{inputs.development-aws-registry }}/${{ format(inputs.docker-repo-name-pattern, 'dev') }}" - app-path: ${{inputs.app-path}} - app-port: ${{inputs.app-port}} - context: ${{inputs.context}} - sentry-project: ${{inputs.sentry-project}} - sentry-org: ${{inputs.sentry-org}} - sentry-token: ${{secrets.SENTRY_BEARER}} - dockerhub-username: ${{secrets.DOCKERHUB_USERNAME}} - dockerhub-token: ${{secrets.DOCKERHUB_TOKEN}} - scope: ${{inputs.scope}} + # Let's build the image on every pull request just like we would on production + pull-request: + # Only run this job on a pull request event + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout + if: inputs.archive-download-name == '' + uses: actions/checkout@v4 + - name: Archive download + if: inputs.archive-download-name != '' + uses: actions/download-artifact@v4 + with: + name: ${{inputs.archive-download-name}} + - name: Build Docker Image + uses: pocket/pocket-monorepo/.github/actions/containerize@main + with: + docker-repo-name: "${{inputs.development-aws-registry }}/${{ format(inputs.docker-repo-name-pattern, 'dev') }}" + app-path: ${{inputs.app-path}} + app-port: ${{inputs.app-port}} + context: ${{inputs.context}} + sentry-project: ${{inputs.sentry-project}} + sentry-org: ${{inputs.sentry-org}} + sentry-token: ${{secrets.SENTRY_BEARER}} + dockerhub-username: ${{secrets.DOCKERHUB_USERNAME}} + dockerhub-token: ${{secrets.DOCKERHUB_TOKEN}} + scope: ${{inputs.scope}} + development: + if: github.ref == 'refs/heads/dev' + runs-on: ubuntu-latest + steps: + - name: Checkout + if: inputs.archive-download-name == '' + uses: actions/checkout@v4 + - name: Archive download + if: inputs.archive-download-name != '' + uses: actions/download-artifact@v4 + with: + name: ${{inputs.archive-download-name}} + # Get the AWS credentials + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: us-east-1 + role-to-assume: arn:aws:iam::410318598490:role/PocketGHARole + - name: Build and Push Development Docker Image + id: dev-docker-build + uses: pocket/pocket-monorepo/.github/actions/containerize@main + with: + docker-repo-name: "${{inputs.development-aws-registry }}/${{ format(inputs.docker-repo-name-pattern, 'dev') }}" + app-path: ${{inputs.app-path}} + app-port: ${{inputs.app-port}} + context: ${{inputs.context}} + sentry-project: ${{inputs.sentry-project}} + sentry-org: ${{inputs.sentry-org}} + sentry-token: ${{secrets.SENTRY_BEARER}} + dockerhub-username: ${{secrets.DOCKERHUB_USERNAME}} + dockerhub-token: ${{secrets.DOCKERHUB_TOKEN}} + scope: ${{inputs.scope}} + push: true + - name: Code Deploy Docker Image + uses: pocket/pocket-monorepo/.github/actions/ecs-codedeploy@main + if: inputs.terraform-output != '' && fromJSON(inputs.terraform-output).ecs-task-containerName.value != '' + with: + docker-image-name: ${{steps.dev-docker-build.outputs.docker-image-name}} + terraform-output: ${{ inputs.terraform-output }} + name: ${{inputs.scope}} - development: - if: github.ref == 'refs/heads/dev' - runs-on: ubuntu-latest - steps: - - name: Checkout - if: inputs.archive-download-name == '' - uses: actions/checkout@v4 - - name: Archive download - if: inputs.archive-download-name != '' - uses: actions/download-artifact@v4 - with: - name: ${{inputs.archive-download-name}} - # Get the AWS credentials - - name: AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-region: us-east-1 - role-to-assume: arn:aws:iam::410318598490:role/PocketGHARole - - name: Build and Push Development Docker Image - id: dev-docker-build - uses: pocket/pocket-monorepo/.github/actions/containerize@main - with: - docker-repo-name: "${{inputs.development-aws-registry }}/${{ format(inputs.docker-repo-name-pattern, 'dev') }}" - app-path: ${{inputs.app-path}} - app-port: ${{inputs.app-port}} - context: ${{inputs.context}} - sentry-project: ${{inputs.sentry-project}} - sentry-org: ${{inputs.sentry-org}} - sentry-token: ${{secrets.SENTRY_BEARER}} - dockerhub-username: ${{secrets.DOCKERHUB_USERNAME}} - dockerhub-token: ${{secrets.DOCKERHUB_TOKEN}} - scope: ${{inputs.scope}} - push: true - - name: Code Deploy Docker Image - uses: pocket/pocket-monorepo/.github/actions/ecs-codedeploy@main - if: fromJSON(inputs.terraform-output).ecs-task-containerName.value != '' - with: - docker-image-name: ${{steps.dev-docker-build.outputs.docker-image-name}} - terraform-output: ${{ inputs.terraform-output }} - name: ${{inputs.scope}} - - production: - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - steps: - - name: Checkout - if: inputs.archive-download-name == '' - uses: actions/checkout@v4 - - name: Archive download - if: inputs.archive-download-name != '' - uses: actions/download-artifact@v4 - with: - name: ${{inputs.archive-download-name}} - - name: AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-region: us-east-1 - role-to-assume: arn:aws:iam::996905175585:role/PocketGHARole - - name: Build and Push Production Docker Image - id: prod-docker-build - uses: pocket/pocket-monorepo/.github/actions/containerize@main - with: - docker-repo-name: "${{inputs.production-aws-registry }}/${{ format(inputs.docker-repo-name-pattern, 'prod') }}" - app-path: ${{inputs.app-path}} - app-port: ${{inputs.app-port}} - context: ${{inputs.context}} - sentry-project: ${{inputs.sentry-project}} - sentry-org: ${{inputs.sentry-org}} - sentry-token: ${{secrets.SENTRY_BEARER}} - dockerhub-username: ${{secrets.DOCKERHUB_USERNAME}} - dockerhub-token: ${{secrets.DOCKERHUB_TOKEN}} - scope: ${{inputs.scope}} - push: true - - name: Code Deploy Docker Image - uses: pocket/pocket-monorepo/.github/actions/ecs-codedeploy@main - if: fromJSON(inputs.terraform-output).ecs-task-containerName.value != '' - with: - docker-image-name: ${{steps.prod-docker-build.outputs.docker-image-name}} - terraform-output: ${{ inputs.terraform-output }} - name: ${{inputs.scope}} \ No newline at end of file + production: + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - name: Checkout + if: inputs.archive-download-name == '' + uses: actions/checkout@v4 + - name: Archive download + if: inputs.archive-download-name != '' + uses: actions/download-artifact@v4 + with: + name: ${{inputs.archive-download-name}} + - name: AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: us-east-1 + role-to-assume: arn:aws:iam::996905175585:role/PocketGHARole + - name: Build and Push Production Docker Image + id: prod-docker-build + uses: pocket/pocket-monorepo/.github/actions/containerize@main + with: + docker-repo-name: "${{inputs.production-aws-registry }}/${{ format(inputs.docker-repo-name-pattern, 'prod') }}" + app-path: ${{inputs.app-path}} + app-port: ${{inputs.app-port}} + context: ${{inputs.context}} + sentry-project: ${{inputs.sentry-project}} + sentry-org: ${{inputs.sentry-org}} + sentry-token: ${{secrets.SENTRY_BEARER}} + dockerhub-username: ${{secrets.DOCKERHUB_USERNAME}} + dockerhub-token: ${{secrets.DOCKERHUB_TOKEN}} + scope: ${{inputs.scope}} + push: true + - name: Code Deploy Docker Image + uses: pocket/pocket-monorepo/.github/actions/ecs-codedeploy@main + if: inputs.terraform-output != '' && fromJSON(inputs.terraform-output).ecs-task-containerName.value != '' + with: + docker-image-name: ${{steps.prod-docker-build.outputs.docker-image-name}} + terraform-output: ${{ inputs.terraform-output }} + name: ${{inputs.scope}} diff --git a/.vscode/launch.json b/.vscode/launch.json index 3e425e522..62aab5e09 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,7 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { "type": "node", "request": "attach", @@ -14,6 +15,16 @@ "localRoot": "${workspaceFolder}/servers/v3-proxy-api/src", "outFiles": ["${workspaceFolder}/servers/v3-proxy-api/dist/**/*.js"] }, + { + "type": "node", + "request": "attach", + "name": "Attach to auth-coprocessor", + "remoteRoot": null, + "port": 9229, + "sourceMaps": true, + "localRoot": "${workspaceFolder}/servers/auth-coprocessor/src", + "outFiles": ["${workspaceFolder}/servers/auth-coprocessor/dist/**/*.js"] + }, { "type": "node", "request": "attach", diff --git a/infrastructure/client-api/src/main.ts b/infrastructure/client-api/src/main.ts index 04dfe57b0..288e36352 100644 --- a/infrastructure/client-api/src/main.ts +++ b/infrastructure/client-api/src/main.ts @@ -238,6 +238,10 @@ class ClientAPI extends TerraformStack { name: 'OTLP_COLLECTOR_URL', value: `${config.tracing.url}`, }, + { + name: 'COPROCESSOR_URL', + value: 'http://localhost:3007', + }, { name: 'REDIS_ENDPOINT', value: cache, @@ -266,11 +270,54 @@ class ClientAPI extends TerraformStack { startPeriod: 0, }, }, + { + name: 'coprocessor', + portMappings: [ + { + hostPort: 3007, + containerPort: 3007, + protocol: 'tcp', + }, + ], + envVars: [ + { + name: 'PORT', + value: '3007', + }, + { + name: 'APP_ENVIRONMENT', + value: config.isProd ? 'production' : 'development', + }, + ], + logGroup: this.createCustomLogGroup('coprocessor'), + logMultilinePattern: '^\\S.+', + secretEnvVars: [ + { + name: 'SENTRY_DSN', + valueFrom: `arn:aws:ssm:${region.name}:${caller.accountId}:parameter/${config.name}/${config.environment}/SENTRY_DSN`, + }, + ], + healthCheck: { + command: [ + 'CMD-SHELL', + 'curl -f http://localhost:3007/health || exit 1', + ], + interval: 15, + retries: 3, + timeout: 5, + startPeriod: 0, + }, + }, ], codeDeploy: { useCodeDeploy: true, useCodePipeline: false, useTerraformBasedCodeDeploy: false, + // Shifts 10 percent of traffic in the first increment. + // The remaining 90 percent is deployed five minutes later. + deploymentConfigName: config.isProd + ? 'CodeDeployDefault.ECSCanary10Percent5Minutes' + : 'CodeDeployDefault.ECSAllAtOnce', generateAppSpec: false, snsNotificationTopicArn: snsTopic.arn, successTerminationWaitTimeInMinutes: 5, @@ -281,6 +328,7 @@ class ClientAPI extends TerraformStack { notifyOnSucceeded: false, }, }, + // This doesn't need to be exposed; expose only client-api exposedContainer: { name: 'app', port: 4001, diff --git a/packages/apollo-utils/package.json b/packages/apollo-utils/package.json index 837fea925..6d14b210d 100644 --- a/packages/apollo-utils/package.json +++ b/packages/apollo-utils/package.json @@ -99,7 +99,7 @@ "@apollo/utils.keyvaluecache": "3.1.0", "@pocket-tools/ts-logger": "workspace:*", "@sentry/node": "8.47.0", - "express": "4.20.0", + "express": "4.21.2", "graphql": "16.9.0", "graphql-tag": "2.12.6", "ioredis": "5.4.1", @@ -129,7 +129,7 @@ "@apollo/subgraph": "2.9.3", "@apollo/utils.keyvadapter": "3.1.0", "@apollo/utils.keyvaluecache": "3.1.0", - "express": "4.20.0", + "express": "4.21.2", "graphql": "16.9.0", "graphql-tag": "2.12.6" }, diff --git a/packages/terraform-modules/src/base/ApplicationECSAlbCodeDeploy.ts b/packages/terraform-modules/src/base/ApplicationECSAlbCodeDeploy.ts index f0802ef44..a566c90fc 100644 --- a/packages/terraform-modules/src/base/ApplicationECSAlbCodeDeploy.ts +++ b/packages/terraform-modules/src/base/ApplicationECSAlbCodeDeploy.ts @@ -25,6 +25,8 @@ export interface ApplicationECSAlbCodeDeployProps targetGroupNames: string[]; tags?: { [key: string]: string }; dependsOn?: TerraformResource[]; + // Docs at Terraform Registry: CodedeployDeploymentGroup#deployment_config_name + deploymentConfigName?: string | undefined; successTerminationWaitTimeInMinutes?: number; notifications?: { notifyOnStarted?: boolean; @@ -66,7 +68,9 @@ export class ApplicationECSAlbCodeDeploy extends Construct { { dependsOn: config.dependsOn, appName: codeDeployApp.name, - deploymentConfigName: 'CodeDeployDefault.ECSAllAtOnce', + deploymentConfigName: + this.config.deploymentConfigName ?? + 'CodeDeployDefault.ECSAllAtOnce', deploymentGroupName: `${this.config.prefix}-ECS`, serviceRoleArn: ecsCodeDeployRole.arn, autoRollbackConfiguration: { diff --git a/packages/terraform-modules/src/base/ApplicationECSService.ts b/packages/terraform-modules/src/base/ApplicationECSService.ts index f52beb52b..808d3b590 100644 --- a/packages/terraform-modules/src/base/ApplicationECSService.ts +++ b/packages/terraform-modules/src/base/ApplicationECSService.ts @@ -64,6 +64,8 @@ export interface ApplicationECSServiceProps extends TerraformMetaArguments { useCodePipeline?: boolean; generateAppSpec?: boolean; successTerminationWaitTimeInMinutes?: number; + // Docs at Terraform Registry: CodedeployDeploymentGroup#deployment_config_name + deploymentConfigName?: string | undefined; codeDeployNotifications?: { notifyOnStarted?: boolean; //defaults to true notifyOnSucceeded?: boolean; //defaults to true @@ -205,6 +207,7 @@ export class ApplicationECSService extends Construct { clusterName: this.config.ecsClusterName, targetGroupNames: targetGroupNames, listenerArn: this.config.albConfig.listenerArn, + deploymentConfigName: this.config.deploymentConfigName, snsNotificationTopicArn: this.config.codeDeploySnsNotificationTopicArn, tags: this.config.tags, diff --git a/packages/terraform-modules/src/pocket/PocketALBApplication.ts b/packages/terraform-modules/src/pocket/PocketALBApplication.ts index 3031f9153..be2b5f59f 100644 --- a/packages/terraform-modules/src/pocket/PocketALBApplication.ts +++ b/packages/terraform-modules/src/pocket/PocketALBApplication.ts @@ -123,6 +123,12 @@ export interface PocketALBApplicationProps extends TerraformMetaArguments { * Option to create a CodeDeploy application. */ useCodeDeploy: boolean; + /** + * How to do the blue/green deployment + * Docs at Terraform Registry: CodedeployDeploymentGroup#deployment_config_name + * See also: https://docs.aws.amazon.com/codedeploy/latest/userguide/deployment-configurations.html + */ + deploymentConfigName?: string | undefined; /** * Option to deploy a new version using terraform, instead of externally */ @@ -644,6 +650,7 @@ export class PocketALBApplication extends Construct { ecsClusterArn: ecsCluster.cluster.arn, ecsClusterName: ecsCluster.cluster.name, useCodeDeploy: this.config.codeDeploy.useCodeDeploy, + deploymentConfigName: this.config.codeDeploy.deploymentConfigName, codeDeployNotifications: this.config.codeDeploy.notifications, useCodePipeline: this.config.codeDeploy.useCodePipeline, useTerraformBasedCodeDeploy: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8dbf00855..f4dfdf497 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,7 +36,7 @@ importers: devDependencies: '@commitlint/cli': specifier: 19.6.0 - version: 19.6.0(@types/node@22.10.5)(typescript@5.8.0-dev.20250107) + version: 19.6.0(@types/node@22.10.5)(typescript@5.8.0-dev.20250114) '@commitlint/config-conventional': specifier: ^19.6.0 version: 19.6.0 @@ -45,7 +45,7 @@ importers: version: link:packages/eslint-config syncpack: specifier: ^13.0.0 - version: 13.0.0(typescript@5.8.0-dev.20250107) + version: 13.0.0(typescript@5.8.0-dev.20250114) tsconfig: specifier: workspace:* version: link:packages/tsconfig @@ -1885,8 +1885,8 @@ importers: specifier: 8.47.0 version: 8.47.0 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 graphql: specifier: 16.9.0 version: 16.9.0 @@ -2746,8 +2746,8 @@ importers: specifier: ^6.5.1 version: 6.5.2 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 express-validator: specifier: ^7.1.0 version: 7.2.1 @@ -2876,8 +2876,8 @@ importers: specifier: ^3.1.1 version: 3.1.1 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 express-validator: specifier: ^7.1.0 version: 7.2.1 @@ -3003,8 +3003,8 @@ importers: specifier: 2.2.2 version: 2.2.2 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 graphql-tag: specifier: 2.12.6 version: 2.12.6(graphql@16.9.0) @@ -3073,6 +3073,43 @@ importers: specifier: 5.7.2 version: 5.7.2 + servers/coprocessor: + dependencies: + express: + specifier: 4.21.2 + version: 4.21.2 + jwt-decode: + specifier: ^4.0.0 + version: 4.0.0 + multer: + specifier: 1.4.5-lts.1 + version: 1.4.5-lts.1 + tslib: + specifier: 2.8.0 + version: 2.8.0 + devDependencies: + '@types/express': + specifier: 4.17.21 + version: 4.17.21 + '@types/multer': + specifier: ^1.4.12 + version: 1.4.12 + '@types/node': + specifier: ^22.8.2 + version: 22.10.5 + nodemon: + specifier: 3.1.7 + version: 3.1.7 + ts-node: + specifier: 10.9.2 + version: 10.9.2(@types/node@22.10.5)(typescript@5.7.2) + tsconfig: + specifier: workspace:* + version: link:../../packages/tsconfig + typescript: + specifier: 5.7.2 + version: 5.7.2 + servers/feature-flags: dependencies: '@apollo/server': @@ -3103,8 +3140,8 @@ importers: specifier: 2.8.5 version: 2.8.5 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 graphql: specifier: 16.9.0 version: 16.9.0 @@ -3230,8 +3267,8 @@ importers: specifier: 2.2.2 version: 2.2.2 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 graphql: specifier: 16.9.0 version: 16.9.0 @@ -3345,8 +3382,8 @@ importers: specifier: 2.2.2 version: 2.2.2 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 express-validator: specifier: ^7.1.0 version: 7.2.1 @@ -3496,8 +3533,8 @@ importers: specifier: 2.2.2 version: 2.2.2 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 graphql: specifier: 16.9.0 version: 16.9.0 @@ -3680,8 +3717,8 @@ importers: specifier: ^3.1.1 version: 3.1.1 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 graphql: specifier: 16.9.0 version: 16.9.0 @@ -3901,8 +3938,8 @@ importers: specifier: 2.8.5 version: 2.8.5 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 express-validator: specifier: ^7.1.0 version: 7.2.1 @@ -4013,8 +4050,8 @@ importers: specifier: 3.24.3 version: 3.24.3 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 express-validator: specifier: ^7.1.0 version: 7.2.1 @@ -4101,8 +4138,8 @@ importers: specifier: 2.8.5 version: 2.8.5 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 graphql: specifier: 16.9.0 version: 16.9.0 @@ -4216,8 +4253,8 @@ importers: specifier: 2.2.2 version: 2.2.2 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 graphql: specifier: 16.9.0 version: 16.9.0 @@ -4340,8 +4377,8 @@ importers: specifier: ^16.7.3 version: 16.7.3 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 express-validator: specifier: ^7.1.0 version: 7.2.1 @@ -4473,8 +4510,8 @@ importers: specifier: 2.8.5 version: 2.8.5 express: - specifier: 4.20.0 - version: 4.20.0 + specifier: 4.21.2 + version: 4.21.2 express-validator: specifier: ^7.1.0 version: 7.2.1 @@ -9136,10 +9173,6 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookie@1.0.2: - resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} - engines: {node: '>=18'} - cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} @@ -9849,10 +9882,6 @@ packages: resolution: {integrity: sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==} engines: {node: '>= 8.0.0'} - express@4.20.0: - resolution: {integrity: sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==} - engines: {node: '>= 0.10.0'} - express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} @@ -10015,10 +10044,6 @@ packages: final-fs@1.6.1: resolution: {integrity: sha512-r5dgz23H8qh1LxKVJK84zet2PhWSWkIOgbLVUd5PlNFAULD/kCDBH9JEMwJt9dpdTnLsSD4rEqS56p2MH7Wbvw==} - finalhandler@1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} - engines: {node: '>= 0.8'} - finalhandler@1.3.1: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} @@ -11385,6 +11410,10 @@ packages: jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} @@ -12644,9 +12673,6 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-to-regexp@0.1.10: - resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} - path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} @@ -12961,10 +12987,6 @@ packages: pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - qs@6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} - engines: {node: '>=0.6'} - qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} @@ -13366,10 +13388,6 @@ packages: resolution: {integrity: sha512-FMW2RvqNr03x+C0WxTyu6sOv21oOjkq5j8tjquWccwa6ScNyGFOGJVpuS1NmTVGBAHS07xnSKotgf2ehQmf9iA==} engines: {node: '>= 0.8.0'} - serve-static@1.16.0: - resolution: {integrity: sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==} - engines: {node: '>= 0.8.0'} - serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} @@ -14192,8 +14210,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.8.0-dev.20250107: - resolution: {integrity: sha512-5S8fQVAfikyl3niFOaTOxfF281giwdT8CLPe1yaZFvzS1p6Az4sTCZsiap9sX6pQ5Kn+7L6SxhjPjFFT5iCVMw==} + typescript@5.8.0-dev.20250114: + resolution: {integrity: sha512-DGtuEPL692JPjTQHFmP810EklYi8ndHgCWt61kRjQfaO25LFpxuB9ZNVs79u15t9JpkeIB6WLKpjY/JiRCzYMw==} engines: {node: '>=14.17'} hasBin: true @@ -16507,11 +16525,11 @@ snapshots: dependencies: commander: 12.1.0 - '@commitlint/cli@19.6.0(@types/node@22.10.5)(typescript@5.8.0-dev.20250107)': + '@commitlint/cli@19.6.0(@types/node@22.10.5)(typescript@5.8.0-dev.20250114)': dependencies: '@commitlint/format': 19.5.0 '@commitlint/lint': 19.6.0 - '@commitlint/load': 19.6.1(@types/node@22.10.5)(typescript@5.8.0-dev.20250107) + '@commitlint/load': 19.6.1(@types/node@22.10.5)(typescript@5.8.0-dev.20250114) '@commitlint/read': 19.5.0 '@commitlint/types': 19.5.0 tinyexec: 0.3.2 @@ -16558,15 +16576,15 @@ snapshots: '@commitlint/rules': 19.6.0 '@commitlint/types': 19.5.0 - '@commitlint/load@19.6.1(@types/node@22.10.5)(typescript@5.8.0-dev.20250107)': + '@commitlint/load@19.6.1(@types/node@22.10.5)(typescript@5.8.0-dev.20250114)': dependencies: '@commitlint/config-validator': 19.5.0 '@commitlint/execute-rule': 19.5.0 '@commitlint/resolve-extends': 19.5.0 '@commitlint/types': 19.5.0 chalk: 5.4.1 - cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250107) - cosmiconfig-typescript-loader: 6.1.0(@types/node@22.10.5)(cosmiconfig@9.0.0(typescript@5.8.0-dev.20250107))(typescript@5.8.0-dev.20250107) + cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250114) + cosmiconfig-typescript-loader: 6.1.0(@types/node@22.10.5)(cosmiconfig@9.0.0(typescript@5.8.0-dev.20250114))(typescript@5.8.0-dev.20250114) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -21551,8 +21569,6 @@ snapshots: cookie@0.7.2: {} - cookie@1.0.2: {} - cookiejar@2.1.4: {} cookies@0.9.1: @@ -21567,12 +21583,12 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig-typescript-loader@6.1.0(@types/node@22.10.5)(cosmiconfig@9.0.0(typescript@5.8.0-dev.20250107))(typescript@5.8.0-dev.20250107): + cosmiconfig-typescript-loader@6.1.0(@types/node@22.10.5)(cosmiconfig@9.0.0(typescript@5.8.0-dev.20250114))(typescript@5.8.0-dev.20250114): dependencies: '@types/node': 22.10.5 - cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250107) + cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250114) jiti: 2.4.2 - typescript: 5.8.0-dev.20250107 + typescript: 5.8.0-dev.20250114 cosmiconfig@8.3.6(typescript@5.7.2): dependencies: @@ -21592,14 +21608,14 @@ snapshots: optionalDependencies: typescript: 5.7.2 - cosmiconfig@9.0.0(typescript@5.8.0-dev.20250107): + cosmiconfig@9.0.0(typescript@5.8.0-dev.20250114): dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.8.0-dev.20250107 + typescript: 5.8.0-dev.20250114 cpu-features@0.0.2: dependencies: @@ -21933,7 +21949,7 @@ snapshots: dependencies: semver: 7.6.3 shelljs: 0.8.5 - typescript: 5.8.0-dev.20250107 + typescript: 5.8.0-dev.20250114 dreamopt@0.8.0: dependencies: @@ -22378,42 +22394,6 @@ snapshots: lodash: 4.17.21 validator: 13.12.0 - express@4.20.0: - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.3 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 1.0.2 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.2.0 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.10 - proxy-addr: 2.0.7 - qs: 6.11.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.0 - serve-static: 1.16.0 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - express@4.21.2: dependencies: accepts: 1.3.8 @@ -22613,18 +22593,6 @@ snapshots: node-fs: 0.1.7 when: 2.0.1 - finalhandler@1.2.0: - dependencies: - debug: 2.6.9 - encodeurl: 1.0.2 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - finalhandler@1.3.1: dependencies: debug: 2.6.9 @@ -24357,6 +24325,8 @@ snapshots: safe-buffer: 5.2.1 optional: true + jwt-decode@4.0.0: {} + keygrip@1.1.0: dependencies: tsscmp: 1.0.6 @@ -25495,8 +25465,6 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - path-to-regexp@0.1.10: {} - path-to-regexp@0.1.12: {} path-to-regexp@0.1.7: {} @@ -25799,10 +25767,6 @@ snapshots: pure-rand@6.1.0: {} - qs@6.11.0: - dependencies: - side-channel: 1.1.0 - qs@6.13.0: dependencies: side-channel: 1.1.0 @@ -26314,15 +26278,6 @@ snapshots: parseurl: 1.3.3 safe-buffer: 5.1.1 - serve-static@1.16.0: - dependencies: - encodeurl: 1.0.2 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.19.0 - transitivePeerDependencies: - - supports-color - serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -26819,13 +26774,13 @@ snapshots: '@pkgr/core': 0.1.1 tslib: 2.8.0 - syncpack@13.0.0(typescript@5.8.0-dev.20250107): + syncpack@13.0.0(typescript@5.8.0-dev.20250114): dependencies: '@effect/schema': 0.71.1(effect@3.6.5) chalk: 5.3.0 chalk-template: 1.1.0 commander: 12.1.0 - cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250107) + cosmiconfig: 9.0.0(typescript@5.8.0-dev.20250114) effect: 3.6.5 enquirer: 2.4.1 fast-check: 3.21.0 @@ -27202,7 +27157,7 @@ snapshots: typescript@5.7.2: {} - typescript@5.8.0-dev.20250107: {} + typescript@5.8.0-dev.20250114: {} ua-parser-js@1.0.40: {} diff --git a/servers/account-data-deleter/package.json b/servers/account-data-deleter/package.json index 922982276..9958ecf88 100644 --- a/servers/account-data-deleter/package.json +++ b/servers/account-data-deleter/package.json @@ -38,7 +38,7 @@ "ajv": "8.17.1", "archiver": "^7.0.1", "csv-stringify": "^6.5.1", - "express": "4.20.0", + "express": "4.21.2", "express-validator": "^7.1.0", "knex": "3.1.0", "lodash": "4.17.21", diff --git a/servers/annotations-api/package.json b/servers/annotations-api/package.json index 3ecba7a10..6d24e7ea2 100644 --- a/servers/annotations-api/package.json +++ b/servers/annotations-api/package.json @@ -41,7 +41,7 @@ "cors": "2.8.5", "dataloader": "2.2.2", "exponential-backoff": "^3.1.1", - "express": "4.20.0", + "express": "4.21.2", "express-validator": "^7.1.0", "graphql": "16.9.0", "graphql-constraint-directive": "5.4.2", diff --git a/servers/braze-content-proxy/package.json b/servers/braze-content-proxy/package.json index 8714645ff..91a47620c 100644 --- a/servers/braze-content-proxy/package.json +++ b/servers/braze-content-proxy/package.json @@ -24,7 +24,7 @@ "@sentry/node": "8.47.0", "cross-fetch": "4.0.0", "dataloader": "2.2.2", - "express": "4.20.0", + "express": "4.21.2", "graphql-tag": "2.12.6", "tslib": "2.8.0" }, diff --git a/servers/client-api/config/router.yaml b/servers/client-api/config/router.yaml index 111eefd54..4af5d2e59 100644 --- a/servers/client-api/config/router.yaml +++ b/servers/client-api/config/router.yaml @@ -293,3 +293,14 @@ apq: redis: urls: ['${env.REDIS_PROTOCOL:-rediss-cluster}://${env.REDIS_ENDPOINT}'] ttl: 24h # optional, by default no expiration +coprocessor: + url: '${env.COPROCESSOR_URL:-http://localhost:3007}' + timeout: 100s # The timeout for all coprocessor requests. Defaults to 1 second (1s) + router: # This coprocessor hooks into the `RouterService` + request: # By including this key, the `RouterService` sends a coprocessor request whenever it first receives a client request. + headers: true # These boolean properties indicate which request data to include in the coprocessor request. All are optional and false by default. + body: true + context: true + sdl: false + path: true + method: false diff --git a/servers/coprocessor/eslint.config.mjs b/servers/coprocessor/eslint.config.mjs new file mode 100644 index 000000000..be4ca205d --- /dev/null +++ b/servers/coprocessor/eslint.config.mjs @@ -0,0 +1,3 @@ +import servers from '@pocket-tools/eslint-config/servers'; +import tseslint from 'typescript-eslint'; +export default tseslint.config(...servers); diff --git a/servers/coprocessor/jest.config.js b/servers/coprocessor/jest.config.js new file mode 100644 index 000000000..f1469541a --- /dev/null +++ b/servers/coprocessor/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/?(*.)+(spec|integration).[jt]s?(x)'], + testPathIgnorePatterns: ['/dist/'], +}; diff --git a/servers/coprocessor/package.json b/servers/coprocessor/package.json new file mode 100644 index 000000000..77fb54471 --- /dev/null +++ b/servers/coprocessor/package.json @@ -0,0 +1,32 @@ +{ + "name": "@server/coprocessor", + "version": "1.0.0", + "description": "", + "keywords": [], + "license": "ISC", + "author": "", + "main": "dist/index.js", + "scripts": { + "build": "rm -rf dist && tsc", + "dev": "npm run build && npm run watch", + "format": "eslint --fix", + "lint": "eslint --fix-dry-run", + "start": "node dist/index.js", + "watch": "tsc -w --preserveWatchOutput & nodemon --inspect --config ../../nodemon.json" + }, + "dependencies": { + "express": "4.21.2", + "jwt-decode": "^4.0.0", + "multer": "1.4.5-lts.1", + "tslib": "2.8.0" + }, + "devDependencies": { + "@types/express": "4.17.21", + "@types/multer": "^1.4.12", + "@types/node": "^22.8.2", + "nodemon": "3.1.7", + "ts-node": "10.9.2", + "tsconfig": "workspace:*", + "typescript": "5.7.2" + } +} diff --git a/servers/coprocessor/src/index.ts b/servers/coprocessor/src/index.ts new file mode 100644 index 000000000..e76dcdd59 --- /dev/null +++ b/servers/coprocessor/src/index.ts @@ -0,0 +1,86 @@ +import express, { json, urlencoded } from 'express'; +import multer from 'multer'; +import { jwtDecode, JwtPayload } from 'jwt-decode'; + +const app = express(); +app.use(json({ limit: '1mb' })); +app.use(urlencoded({ limit: '1mb', extended: true })); +app.use(multer().none()); + +const requestJwt = async ( + payload: any, + params: URLSearchParams, + headers: HeadersInit | undefined, +) => { + const url = 'https://getpocket.com/v3/jwt'; + const paramString = params.toString(); + const fetchUrl = paramString.length > 0 ? `${url}?${paramString}` : url; + try { + const response = await fetch(fetchUrl, { + headers, + mode: 'cors', + }).then((res) => res.json()); + if (response.jwt != null) { + const decoded = jwtDecode( + response.jwt, + ); + payload.context.entries['apollo_authentication::JWT::claims'] = decoded; + // Extract scopes for AuthZ directives + payload.context.entries['apollo_authentication::JWT::claims'].scope = + decoded.roles != null ? decoded.roles.join(' ') : undefined; + } + return payload; + } catch (error) { + console.log(error); + const response = await fetch(fetchUrl, { + headers, + mode: 'cors', + }).then((res) => res.status); + console.log(response); + return payload; + } +}; + +app.get('/health', async (req, res) => { + res.status(200).send('ok'); +}); + +app.post('/', express.json(), async (req, res) => { + const payload = req.body; + // short-circuit if we already have JWT + if (payload.context.entries['apollo_authentication::JWT::claims'] != null) { + res.send(payload); + return; + } + + const paramString = (req.body.path as string).split('?')[1]; + + // TODO: Process form-data and form urlencoded etc. + // short-circuit if no consumer key + const givenParams = paramString + ? new URLSearchParams(paramString) + : undefined; + if (givenParams == null || givenParams?.get('consumer_key') == null) { + res.send(payload); + return; + } + + const params = new URLSearchParams(); + params.append('consumer_key', givenParams!.get('consumer_key')!); + params.append('enable_cors', '1'); + const accessToken = givenParams?.get('access_token'); + if (accessToken != null) { + params.append('access_token', accessToken); + } + const headers = + payload.headers.cookie != null + ? { Cookie: payload.headers.cookie } + : undefined; + + const response = await requestJwt(payload, params, headers); + res.send(response); +}); + +app.listen(3007, () => { + console.log('🚀 Server running at http://localhost:3007'); +}); diff --git a/servers/coprocessor/tsconfig.json b/servers/coprocessor/tsconfig.json new file mode 100644 index 000000000..b8fd9c566 --- /dev/null +++ b/servers/coprocessor/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "tsconfig/graphql.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "sourceRoot": "", + "strict": true, + "resolveJsonModule": true, + "strictBuiltinIteratorReturn": false, + "lib": [ + "es2019", + "es2020.bigint", + "es2020.string", + "es2020.symbol.wellknown", + "dom" + ] + }, + "exclude": ["node_modules/", "dist/"], + "include": ["src/**/*.ts"] +} diff --git a/servers/feature-flags/package.json b/servers/feature-flags/package.json index 8d330f33b..353db3458 100644 --- a/servers/feature-flags/package.json +++ b/servers/feature-flags/package.json @@ -30,7 +30,7 @@ "@sentry/node": "8.47.0", "body-parser": "1.20.3", "cors": "2.8.5", - "express": "4.20.0", + "express": "4.21.2", "graphql": "16.9.0", "graphql-tag": "2.12.6", "jsonwebtoken": "^9.0.0", diff --git a/servers/image-api/package.json b/servers/image-api/package.json index 813f8b4e2..ef706e3d5 100644 --- a/servers/image-api/package.json +++ b/servers/image-api/package.json @@ -36,7 +36,7 @@ "axios": "1.7.7", "axios-retry": "4.5.0", "dataloader": "2.2.2", - "express": "4.20.0", + "express": "4.21.2", "graphql": "16.9.0", "graphql-tag": "2.12.6", "keyv": "4.5.4", diff --git a/servers/list-api/package.json b/servers/list-api/package.json index 77cde688b..84ead2822 100644 --- a/servers/list-api/package.json +++ b/servers/list-api/package.json @@ -39,7 +39,7 @@ "@snowplow/node-tracker": "3.24.3", "@snowplow/tracker-core": "3.24.3", "dataloader": "2.2.2", - "express": "4.20.0", + "express": "4.21.2", "express-validator": "^7.1.0", "graphql": "16.9.0", "graphql-constraint-directive": "5.4.2", diff --git a/servers/notes-api/package.json b/servers/notes-api/package.json index 343937f1c..f73b00090 100644 --- a/servers/notes-api/package.json +++ b/servers/notes-api/package.json @@ -47,7 +47,7 @@ "@sentry/node": "8.47.0", "cors": "2.8.5", "dataloader": "2.2.2", - "express": "4.20.0", + "express": "4.21.2", "graphql": "16.9.0", "graphql-constraint-directive": "5.4.2", "graphql-scalars": "^1.23.0", diff --git a/servers/parser-graphql-wrapper/package.json b/servers/parser-graphql-wrapper/package.json index b9cd9715b..619a3c80c 100644 --- a/servers/parser-graphql-wrapper/package.json +++ b/servers/parser-graphql-wrapper/package.json @@ -41,7 +41,7 @@ "dataloader": "2.2.2", "domino": "2.1.6", "exponential-backoff": "^3.1.1", - "express": "4.20.0", + "express": "4.21.2", "graphql": "16.9.0", "graphql-scalars": "^1.23.0", "graphql-tag": "2.12.6", diff --git a/servers/shareable-lists-api/package.json b/servers/shareable-lists-api/package.json index da8416389..80377644b 100644 --- a/servers/shareable-lists-api/package.json +++ b/servers/shareable-lists-api/package.json @@ -46,7 +46,7 @@ "@prisma/client": "5.22.0", "@sentry/node": "8.47.0", "cors": "2.8.5", - "express": "4.20.0", + "express": "4.21.2", "express-validator": "^7.1.0", "graphql": "16.9.0", "graphql-constraint-directive": "5.4.2", diff --git a/servers/shared-snowplow-consumer/package.json b/servers/shared-snowplow-consumer/package.json index 27c580736..53a8f6a98 100644 --- a/servers/shared-snowplow-consumer/package.json +++ b/servers/shared-snowplow-consumer/package.json @@ -28,7 +28,7 @@ "@sentry/node": "8.47.0", "@snowplow/node-tracker": "3.24.3", "@snowplow/tracker-core": "3.24.3", - "express": "4.20.0", + "express": "4.21.2", "express-validator": "^7.1.0", "tslib": "2.8.0" }, diff --git a/servers/shares-api/package.json b/servers/shares-api/package.json index 429168c28..22a9d4fad 100644 --- a/servers/shares-api/package.json +++ b/servers/shares-api/package.json @@ -37,7 +37,7 @@ "@sentry/node": "8.47.0", "@snowplow/tracker-core": "3.24.3", "cors": "2.8.5", - "express": "4.20.0", + "express": "4.21.2", "graphql": "16.9.0", "graphql-constraint-directive": "5.4.2", "graphql-scalars": "^1.23.0", diff --git a/servers/user-api/package.json b/servers/user-api/package.json index d8ce6913e..33167d05d 100644 --- a/servers/user-api/package.json +++ b/servers/user-api/package.json @@ -29,7 +29,7 @@ "@pocket-tools/ts-logger": "workspace:*", "@sentry/node": "8.47.0", "dataloader": "2.2.2", - "express": "4.20.0", + "express": "4.21.2", "graphql": "16.9.0", "graphql-tag": "2.12.6", "knex": "3.1.0", diff --git a/servers/user-list-search/package.json b/servers/user-list-search/package.json index 64f86cae7..56210762b 100644 --- a/servers/user-list-search/package.json +++ b/servers/user-list-search/package.json @@ -39,7 +39,7 @@ "@smithy/util-retry": "3.0.3", "elastic-builder": "^2.29.0", "elasticsearch": "^16.7.3", - "express": "4.20.0", + "express": "4.21.2", "express-validator": "^7.1.0", "graphql": "16.9.0", "graphql-constraint-directive": "5.4.2", diff --git a/servers/v3-proxy-api/package.json b/servers/v3-proxy-api/package.json index e80e0f8ce..a6ca81a08 100644 --- a/servers/v3-proxy-api/package.json +++ b/servers/v3-proxy-api/package.json @@ -24,7 +24,7 @@ "@sentry/node": "8.47.0", "content-type": "1.0.5", "cors": "2.8.5", - "express": "4.20.0", + "express": "4.21.2", "express-validator": "^7.1.0", "graphql-request": "^6.1.0", "luxon": "3.5.0",