Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: organization access tokens #6493

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open

Conversation

n1ru4l
Copy link
Contributor

@n1ru4l n1ru4l commented Feb 10, 2025

Background

Adds organization access tokens which will supersede target access tokens.

Organization access tokens allow selecting scopes and permissions, allowing granular resource-based access control.

CONSOLE-950

Description

This PR introduces:

  • Create and delete organization access tokens using GraphQL Mutations
  • List available organization access tokens using the GraphQL Query
  • Authentication and authorization on the GraphQL API using the organization access tokens

Other notes:

It adds the dependency bentocache, simplifying the code we have to write for multi-layer access token caching in memory and redis.

The bento cache implementation will be re-used within the usage reporting service (follow-up PR), so organization access tokens will not require the tokens service. This makes it possible to remove the service once we sunset target access tokens (or migrate them over), which reduces the overall number of services we need to run Hive.

Checklist

  • Input validation
  • Authentication management
  • Session management
  • Access control
  • Cryptographic practices
  • Error handling and logging

Copy link
Contributor

coderabbitai bot commented Feb 10, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Introduced organization access tokens, enabling administrators to create, delete, and paginate tokens with customizable permissions and resource assignments.
    • New GraphQL mutations and queries now allow for streamlined token management.
    • Audit logs now capture token creation and deletion events for enhanced tracking.
  • Enhancements

    • Improved authentication, security, and caching mechanisms to provide a more robust and efficient access control experience.
    • Backend infrastructure updates ensure reliable data management and monitoring.

Walkthrough

This pull request introduces a comprehensive system for managing organization access tokens. It adds integration tests covering token creation, validation, permission checks, resource access, and pagination. New GraphQL mutations and queries for creating and deleting tokens are defined alongside corresponding schema types. Enhancements include new authentication strategies, caching mechanisms, and permission group updates. Additionally, the changes extend the database schema through a new migration and adjust dependency configurations and export mappings across multiple modules.

Changes

File(s) Change Summary
integration-tests/tests/api/organization-access-tokens.spec.ts Added integration tests for organization access tokens covering success cases, validation errors, permission checks, resource queries, and pagination.
package.json, packages/services/api/package.json Patched dependency update: added new bentocache patch and dev dependency without affecting existing entries.
packages/migrations/src/run-pg-migrations.ts, packages/migrations/src/actions/2025.02.20T00-00-00.organization-access-tokens.ts Added an import for the organization access tokens migration and introduced a new migration to create the organization_access_tokens table.
packages/services/api/src/modules/auth/... (authz.ts, organization-access-token-strategy.ts, supertokens-strategy.ts, target-access-token-strategy.ts, module.graphql.mappers.ts) Updated permission handling: added new organization access token strategy classes, removed legacy role translation methods, and refined token validation logic.
packages/services/api/src/modules/organization/... Introduced new providers, resolvers, and schema types for organization access tokens; added resource assignments management; updated permission groups and member permissions; and defined GraphQL mutations/queries for token creation, deletion, and pagination.
packages/services/api/src/modules/schema/providers/schema-manager.ts, packages/services/api/src/modules/shared/providers/id-translator.ts Modified error handling and permission check in schema manager; added a method to resolve organization references and updated the target reference signature.
packages/services/server/src/index.ts, packages/services/storage/src/db/types.ts Replaced the old organization member strategy with the new organization access token strategy and cache; added a new database interface for organization access tokens.
patches/bentocache.patch Added new export mappings for Redis and memory drivers to enhance module export structure.

Sequence Diagram(s)

sequenceDiagram
  participant C as Client
  participant G as GraphQL Resolver (createOrganizationAccessToken)
  participant P as OrganizationAccessTokens Provider
  participant DB as Database/Cache
  participant Key as OrganizationAccessKey

  C->>G: Submit createOrganizationAccessToken mutation
  G->>P: Call create(input)
  P->>Key: Generate & encode access token
  Key-->>P: Return token data (token, hash, firstCharacters)
  P->>DB: Insert token into DB and cache
  P-->>G: Return token details & private access key
  G-->>C: Respond with success result
Loading
sequenceDiagram
  participant C as Client
  participant S as Server (AccessTokenStrategy)
  participant Cache as OrganizationAccessTokensCache
  participant Key as OrganizationAccessKey

  C->>S: Send request with Authorization: Bearer <token>
  S->>Key: Decode access token
  Key-->>S: Return token information
  S->>Cache: Retrieve token details
  Cache-->>S: Return cached token data
  S->>Key: Verify token using secret/hash
  Key-->>S: Return verification result
  S-->>C: Create OrganizationAccessTokenSession and authorize request
Loading

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • CONSOLE-950: Authentication required, not authenticated - You need to authenticate to access this operation.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

github-actions bot commented Feb 10, 2025

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
hive 5.1.2-alpha-20250220020252-f4a867979b48ce9d19bb78f29d91eb228266a85d npm ↗︎ unpkg ↗︎

Copy link
Contributor

github-actions bot commented Feb 10, 2025

🐋 This PR was built and pushed to the following Docker images:

Targets: build

Platforms: linux/arm64

Image Tag: f4a867979b48ce9d19bb78f29d91eb228266a85d

Docker Bake metadata
{
"app": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/api/health",
          "build-arg:IMAGE_DESCRIPTION": "The app of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/app",
          "build-arg:PORT": "3000",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/app",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/ta0xg72j79wcwhbn1p4c04zi5",
  "containerimage.config.digest": "sha256:fb71e93ed9a61e993b750f43707db3170a133a93f97ea61be182c4c6b1813b2c",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:e1993b45cd7c379f0391b5633a70cfb1172103880ab83d4652f374207ffebe86",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:e1993b45cd7c379f0391b5633a70cfb1172103880ab83d4652f374207ffebe86",
  "image.name": "ghcr.io/graphql-hive/app:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/app:wip_organization_access_tokens-arm64"
},
"buildx.build.warnings": [
  {
    "vertex": "sha256:73f4634cfb6f1aaecc9d43d56ea6bf8d37d64473465220d50abdde8d23bb6c8f",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDEwKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 10
        },
        "end": {
          "line": 10
        }
      }
    ]
  },
  {
    "vertex": "sha256:73f4634cfb6f1aaecc9d43d56ea6bf8d37d64473465220d50abdde8d23bb6c8f",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9USVRMRScgKGxpbmUgMTUp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 15
        },
        "end": {
          "line": 15
        }
      }
    ]
  },
  {
    "vertex": "sha256:69623f5b6071510acf48fc9b4870c75adf444170291e260537870687f4899bff",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIwKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 20
        },
        "end": {
          "line": 20
        }
      }
    ]
  },
  {
    "vertex": "sha256:69623f5b6071510acf48fc9b4870c75adf444170291e260537870687f4899bff",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAyMSk=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 21
        },
        "end": {
          "line": 21
        }
      }
    ]
  },
  {
    "vertex": "sha256:69623f5b6071510acf48fc9b4870c75adf444170291e260537870687f4899bff",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIyKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 22
        },
        "end": {
          "line": 22
        }
      }
    ]
  },
  {
    "vertex": "sha256:69623f5b6071510acf48fc9b4870c75adf444170291e260537870687f4899bff",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIxKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 21
        },
        "end": {
          "line": 21
        }
      }
    ]
  },
  {
    "vertex": "sha256:73f4634cfb6f1aaecc9d43d56ea6bf8d37d64473465220d50abdde8d23bb6c8f",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDExKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 11
        },
        "end": {
          "line": 11
        }
      }
    ]
  },
  {
    "vertex": "sha256:73f4634cfb6f1aaecc9d43d56ea6bf8d37d64473465220d50abdde8d23bb6c8f",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDEyKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:69623f5b6071510acf48fc9b4870c75adf444170291e260537870687f4899bff",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9USVRMRScgKGxpbmUgMTIp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:69623f5b6071510acf48fc9b4870c75adf444170291e260537870687f4899bff",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9ERVNDUklQVElPTicgKGxpbmUgMTQp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 14
        },
        "end": {
          "line": 14
        }
      }
    ]
  },
  {
    "vertex": "sha256:73f4634cfb6f1aaecc9d43d56ea6bf8d37d64473465220d50abdde8d23bb6c8f",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAxMik=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:73f4634cfb6f1aaecc9d43d56ea6bf8d37d64473465220d50abdde8d23bb6c8f",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9ERVNDUklQVElPTicgKGxpbmUgMTcp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 17
        },
        "end": {
          "line": 17
        }
      }
    ]
  },
  {
    "vertex": "sha256:69623f5b6071510acf48fc9b4870c75adf444170291e260537870687f4899bff",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAxMyk=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 13
        },
        "end": {
          "line": 13
        }
      }
    ]
  },
  {
    "vertex": "sha256:69623f5b6071510acf48fc9b4870c75adf444170291e260537870687f4899bff",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRQT1JUJyAobGluZSAyMik=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 22
        },
        "end": {
          "line": 22
        }
      }
    ]
  }
],
"composition-federation-2": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "Federation 2 Composition Service for GraphQL Hive.",
          "build-arg:IMAGE_TITLE": "graphql-hive/composition-federation-2",
          "build-arg:PORT": "3069",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/external-composition",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/4pybh7k108r8elbbhd61aujby",
  "containerimage.config.digest": "sha256:59dca4b002d3f743798bf8e9b72e95e7eed672d55a38c5c0a1dca71f9fc9db4f",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:77e3f25a878b52b436451b11ebf457e13278da965e099f24bb90922205be143d",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:77e3f25a878b52b436451b11ebf457e13278da965e099f24bb90922205be143d",
  "image.name": "ghcr.io/graphql-hive/composition-federation-2:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/composition-federation-2:wip_organization_access_tokens-arm64"
},
"emails": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The emails service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/emails",
          "build-arg:PORT": "3006",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/emails",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/dgcmt997k79251sm3duaab33u",
  "containerimage.config.digest": "sha256:e721a40fa5ee76b849158f8aedde429e5af83ee167703fc6670c6b207802f8da",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:903fb0dd10693aa0867ecd3bb6ef60cd2e06044064eb8385d796d90b640f9475",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:903fb0dd10693aa0867ecd3bb6ef60cd2e06044064eb8385d796d90b640f9475",
  "image.name": "ghcr.io/graphql-hive/emails:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/emails:wip_organization_access_tokens-arm64"
},
"policy": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The policy service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/policy",
          "build-arg:PORT": "3012",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/policy",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/latvds7ew9nlvpn48p60qwinl",
  "containerimage.config.digest": "sha256:86a96598b0475a79326cb111c68f7f9cfa7c5d566c571ed519ae39ea73af371e",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:16b6e4d519c212a4545da66b76bf18c9fbb1713ae2f5009e9e7be2e043563143",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:16b6e4d519c212a4545da66b76bf18c9fbb1713ae2f5009e9e7be2e043563143",
  "image.name": "ghcr.io/graphql-hive/policy:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/policy:wip_organization_access_tokens-arm64"
},
"rate-limit": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The rate limit service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/rate-limit",
          "build-arg:PORT": "3009",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/rate-limit",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/mkc1k7lg80aao17wqmkze3x9p",
  "containerimage.config.digest": "sha256:47b8ac6196fe7f895496598cae94ae401c98ca53a464b49b05ea4ba2935c1fc7",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:90b0c1edc2233e70f19c6ed23c23ea8e01b31b0452d9533e73476a59b23599b4",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:90b0c1edc2233e70f19c6ed23c23ea8e01b31b0452d9533e73476a59b23599b4",
  "image.name": "ghcr.io/graphql-hive/rate-limit:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/rate-limit:wip_organization_access_tokens-arm64"
},
"schema": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The schema service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/schema",
          "build-arg:PORT": "3002",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/schema",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/rm2u7jr3nmcrenxiw8oyos56i",
  "containerimage.config.digest": "sha256:245672cda6658202905317dc4a9fcf2c2408f4beb8278adf3c07a9f1e0fda095",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:53c85946d57890d1eda2c7ad7cf343830357078bccb8e21608049f61965eba27",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:53c85946d57890d1eda2c7ad7cf343830357078bccb8e21608049f61965eba27",
  "image.name": "ghcr.io/graphql-hive/schema:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/schema:wip_organization_access_tokens-arm64"
},
"server": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The server service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/server",
          "build-arg:PORT": "3001",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/server",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/v9w0ggb3e7ym303d4o6ly245m",
  "containerimage.config.digest": "sha256:9d2cdb8cb4c5847289e80a1291df76ad460c594f7a3f44912eaf61a8cc88c239",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:f48e7ddfe60ee9e1bb7de2e7753ada523624da8475d44b59b27539abe89dc20e",
    "size": 2076,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:f48e7ddfe60ee9e1bb7de2e7753ada523624da8475d44b59b27539abe89dc20e",
  "image.name": "ghcr.io/graphql-hive/server:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/server:wip_organization_access_tokens-arm64"
},
"storage": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "migrations.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:IMAGE_DESCRIPTION": "The migrations service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/storage",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/brryou4ejm26o0a4fo8x1hzgk",
  "containerimage.config.digest": "sha256:52dfe154c8340eddfa5dd57d9f5afaccd787b45b922a72446444e58d232eeb23",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:8b797f16efda260fa5c42ba159201f18d5452a0e1d7fe3c699f99de5fce63144",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:8b797f16efda260fa5c42ba159201f18d5452a0e1d7fe3c699f99de5fce63144",
  "image.name": "ghcr.io/graphql-hive/storage:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/storage:wip_organization_access_tokens-arm64"
},
"stripe-billing": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The stripe billing service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/stripe-billing",
          "build-arg:PORT": "3010",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/stripe-billing",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/yavg919kq7zakx5sjoe2x4an0",
  "containerimage.config.digest": "sha256:6e0a4291a2adf9a2ca423b4bf837911785d8b834e3add3136ec4f34e2e747f2c",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:ae6c07b9ab43bf60ebeeee61ecb4da91c7a5747e6cfa64edf8b21ac38322efc4",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:ae6c07b9ab43bf60ebeeee61ecb4da91c7a5747e6cfa64edf8b21ac38322efc4",
  "image.name": "ghcr.io/graphql-hive/stripe-billing:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/stripe-billing:wip_organization_access_tokens-arm64"
},
"tokens": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The tokens service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/tokens",
          "build-arg:PORT": "3003",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/tokens",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/64dc2safq12vmjotytwmkj64l",
  "containerimage.config.digest": "sha256:c64f6ec373ee5d29567715bfb95a84346cf81ce6f8d20b414eae82317b555b6b",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:e59438a1770865f7065f829fcc864faae6f72e0a9ccb79a9d874e51c2f477370",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:e59438a1770865f7065f829fcc864faae6f72e0a9ccb79a9d874e51c2f477370",
  "image.name": "ghcr.io/graphql-hive/tokens:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/tokens:wip_organization_access_tokens-arm64"
},
"usage": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The usage ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage",
          "build-arg:PORT": "3006",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/lqzmu3luz64lajgzow1uauaio",
  "containerimage.config.digest": "sha256:09df3545e46d9a382af617c8ccb72f1719d878580e3323253a6059f09819f822",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:61899441e77124a3d56d3d880393708d916250125529b19362af7d3f938cceca",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:61899441e77124a3d56d3d880393708d916250125529b19362af7d3f938cceca",
  "image.name": "ghcr.io/graphql-hive/usage:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/usage:wip_organization_access_tokens-arm64"
},
"usage-estimator": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The usage estimator service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage-estimator",
          "build-arg:PORT": "3008",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage-estimator",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/8gr56u8pdvkhu4mul6rl35uhx",
  "containerimage.config.digest": "sha256:25b4ed03eea7150855843b86e1d91ad0d286ec9780b526f95be4fa84694a9d1b",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:375f07e62ad6acffcfe0d64279a92c59abd993378501ff0a0f0081cf8a7b44c2",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:375f07e62ad6acffcfe0d64279a92c59abd993378501ff0a0f0081cf8a7b44c2",
  "image.name": "ghcr.io/graphql-hive/usage-estimator:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/usage-estimator:wip_organization_access_tokens-arm64"
},
"usage-ingestor": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The usage ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage-ingestor",
          "build-arg:PORT": "3007",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage-ingestor",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/1tq6e30ibjhgabc9zg8w4bjuh",
  "containerimage.config.digest": "sha256:49f37ed8ccbef3fe227f945870f3abee3e160ca63fb99f431f322e679a62d1be",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:e5d33f3f7a920bcb228916f8d740fd4af6bc38e7a0a4c7a3913cbba119229e35",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:e5d33f3f7a920bcb228916f8d740fd4af6bc38e7a0a4c7a3913cbba119229e35",
  "image.name": "ghcr.io/graphql-hive/usage-ingestor:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/usage-ingestor:wip_organization_access_tokens-arm64"
},
"webhooks": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The webhooks ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/webhooks",
          "build-arg:PORT": "3005",
          "build-arg:RELEASE": "f4a867979b48ce9d19bb78f29d91eb228266a85d",
          "build-arg:SERVICE_DIR_NAME": "@hive/webhooks",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:dockerfile": "bhwj0xqalehqqy0sk2kye9jul",
          "local-sessionid:shared": "bhwj0xqalehqqy0sk2kye9jul"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-e4a2d429-f302-4e32-8da9-c41ee3e54d76/builder-e4a2d429-f302-4e32-8da9-c41ee3e54d760/oselp9hy25k6a2dwcjl8isd1g",
  "containerimage.config.digest": "sha256:7d81828c15fcc9dcd946ce810420ede0ea4e646a2e48e351b485a88cdbf851d3",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:31eb814a5e6b4810da57d9238e18cbb07f32b037217df520b99faf658ee97da2",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:31eb814a5e6b4810da57d9238e18cbb07f32b037217df520b99faf658ee97da2",
  "image.name": "ghcr.io/graphql-hive/webhooks:f4a867979b48ce9d19bb78f29d91eb228266a85d-arm64,ghcr.io/graphql-hive/webhooks:wip_organization_access_tokens-arm64"
}
}

@n1ru4l n1ru4l force-pushed the wip-organization-access-tokens branch from d1be7ea to 66165f3 Compare February 12, 2025 10:39
@n1ru4l n1ru4l force-pushed the wip-organization-access-tokens branch from f258b53 to 58fc5bf Compare February 12, 2025 11:46
Copy link
Contributor Author

@n1ru4l n1ru4l Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic here is shared between organization members and organization access tokens, so I moved it form the organization members to this new shared file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost all changes here are related to moving the resource assignment logic to a new standalone file providers/resource-assignments.ts

Comment on lines 40 to +43
/**
* Resolved resource groups, used for runtime permission checks.
* Resolved policy statements
*/
resolvedResources: ResolvedResourceAssignments;
authorizationPolicyStatements: AuthorizationPolicyStatement[];
Copy link
Contributor Author

@n1ru4l n1ru4l Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is not related to moving resource assignment logic to a different file.

I reduced the complexity here a bit. Before we created the ResolvedResourceAssignments object which we transformed to AuthorizationPolicyStatement within auth/lib/supertokens-strategy.ts.

Now we already do that here (lazily using a getter function).

@n1ru4l
Copy link
Contributor Author

n1ru4l commented Feb 12, 2025

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Feb 12, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🔭 Outside diff range comments (1)
packages/services/api/src/modules/organization/lib/organization-member-permissions.ts (1)

124-127: ⚠️ Potential issue

Fix incorrect permission description.

The description for 'project:delete' permission is incorrect. It currently states "Member can access the specified projects" which should describe the deletion capability instead.

Apply this diff to fix the description:

-        description: 'Member can access the specified projects.',
+        description: 'Member can delete the specified projects.',
🧹 Nitpick comments (18)
packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts (1)

43-99: Consider caching token verification
You have a // TODO: we should probably cache this verification. Implementing a short-lived cache for token verification results can reduce repeated cryptographic operations and improve performance, especially if multiple requests use the same token.

packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (2)

25-37: Consider expanding allowed characters
TitleInputModel uses [ a-zA-Z0-9_-]+. If you anticipate titles with international characters or user-defined naming, consider relaxing or extending this regex. Otherwise, this is fine for a strictly limited input set.


38-68: Validate 'createdAt' more strictly
Currently 'createdAt' is validated as a simple string. For stronger guarantees, consider z.string().datetime() or a refinement to ensure it matches a predictable format (e.g., ISO 8601).

packages/services/api/src/modules/organization/providers/organization-members.ts (2)

40-43: Doc comment accurately explains the new field
Providing context for authorizationPolicyStatements clarifies how resolved policies fit into membership. Consider elaborating on whether it’s stored in memory only or persisted elsewhere.


133-145: On-demand resource resolution
This getter dynamically computes policy statements. If performance becomes an issue, consider memoizing the result within the same session or membership object.

packages/services/api/src/modules/organization/module.graphql.ts (2)

51-82: Consider using an enum for permissions.

Currently, permissions: [String!]! is free-form. Using an enum instead would help prevent undefined permission strings and provide stricter validation.


107-108: Harmonize error details.

For consistency with other error types that include detailed fields, consider adding an optional details property if additional debugging info is needed.

packages/services/api/src/modules/organization/providers/resource-assignments.ts (2)

88-183: Ensure logging and monitoring around database lookups.

The constructor and class scaffolding look fine. However, multiple calls to storage.findProjectsByIds can become performance hotspots. Monitor or cache as needed.


479-513: Ensure stable resource identifier format.

The toResourceIdentifier method is well structured. Keep an eye on future expansions in resource hierarchy, as it may affect the HRN scheme.

packages/services/api/src/modules/organization/lib/permissions.ts (1)

3-10: Well-defined PermissionRecord.

Fields like dependsOn and warning provide flexibility for advanced permission handling. Just confirm that isReadyOnly is spelled correctly / intended as “read-only.”

packages/services/api/src/modules/organization/lib/organization-access-key.ts (2)

61-75: Enhance secret generation robustness.

While the current implementation is secure, consider using a more robust secret generation approach.

Apply this diff to enhance secret generation:

 export async function create(id: string) {
-  const secret = Crypto.createHash('sha256')
-    .update(Crypto.randomBytes(20).toString())
-    .digest('hex');
+  const secret = Crypto.randomBytes(32).toString('base64url');
 
   const hash = await bcrypt.hash(secret, await bcrypt.genSalt());
   const privateAccessToken = encode(id, secret);
   const firstCharacters = privateAccessToken.substr(0, 10);
 
   return {
     privateAccessToken,
     hash,
     firstCharacters,
   };
 }

21-21: Improve error message specificity.

The current error message "Invalid access token" could be more specific about what exactly is invalid.

Apply this diff to provide more specific error messages:

-const decodeError = { type: 'error' as const, reason: 'Invalid access token.' };
+function createError(reason: string) {
+  return { type: 'error' as const, reason };
+}

 export function decode(
   accessToken: string,
 ): { type: 'error'; reason: string } | { type: 'ok'; accessKey: DecodedAccessKey } {
   if (!accessToken.startsWith(keyPrefix)) {
-    return decodeError;
+    return createError('Invalid token prefix. Expected "hvo1/".');
   }
 
   accessToken = accessToken.slice(keyPrefix.length);
 
   let str: string;
 
   try {
     str = globalThis.atob(accessToken);
   } catch (error) {
-    return decodeError;
+    return createError('Invalid base64 encoding.');
   }
 
   const parts = str.split(':');
 
   if (parts.length > 2) {
-    return decodeError;
+    return createError('Invalid token format. Too many segments.');
   }
 
   const id = parts.at(0);
   const privateKey = parts.at(1);
 
   if (id && privateKey) {
     return { type: 'ok', accessKey: { id, privateKey } } as const;
   }
 
-  return decodeError;
+  return createError('Invalid token format. Missing required segments.');
 }

Also applies to: 28-59

packages/services/api/src/modules/organization/providers/organization-access-tokens-cache.ts (1)

29-43: Consider making cache configuration more configurable.

The cache configuration values (maxItems, TTL, grace period) are hardcoded. Consider making these configurable through environment variables or configuration files.

Apply this diff to make the configuration more flexible:

+const CACHE_CONFIG = {
+  maxItems: process.env.CACHE_MAX_ITEMS ? parseInt(process.env.CACHE_MAX_ITEMS, 10) : 10_000,
+  ttl: process.env.CACHE_TTL || '5min',
+  grace: process.env.CACHE_GRACE || '24h',
+  prefix: 'bentocache:organization-access-tokens',
+};

   constructor(
     @Inject(REDIS_INSTANCE) redis: Redis,
     @Inject(PG_POOL_CONFIG) pool: DatabasePool,
     logger: Logger,
   ) {
     this.findById = findById({ pool, logger });
     this.cache = new BentoCache({
       default: 'organizationAccessTokens',
       stores: {
         organizationAccessTokens: bentostore()
           .useL1Layer(
             memoryDriver({
-              maxItems: 10_000,
-              prefix: 'bentocache:organization-access-tokens',
+              maxItems: CACHE_CONFIG.maxItems,
+              prefix: CACHE_CONFIG.prefix,
             }),
           )
           .useL2Layer(
             redisDriver({
               connection: redis,
-              prefix: 'bentocache:organization-access-tokens'
+              prefix: CACHE_CONFIG.prefix
             }),
           ),
       },
     });
   }
integration-tests/tests/api/organization-access-tokens.spec.ts (3)

51-81: Consider adding more assertions for the success case.

While the test verifies the basic success case, consider adding assertions for:

  • The format of the generated privateAccessKey
  • The format of the generated id
  • The timestamp format and range of createdAt

94-95: Consider using a more descriptive test title.

The title "a access token" could be more descriptive, e.g., "Test Access Token" or "Integration Test Token".

Also applies to: 126-127


177-223: Consider testing additional permission combinations.

The test only verifies organization:describe and project:describe permissions. Consider testing:

  • Different permission combinations
  • Edge cases with minimal and maximal permissions
  • Invalid permission strings
packages/services/storage/src/db/types.ts (1)

159-169: Consider adding JSDoc comments for the interface.

Adding documentation for the interface and its fields would improve code maintainability.

+/**
+ * Represents an organization access token in the database.
+ */
 export interface organization_access_tokens {
+  /** The unique identifier of the token */
   id: string;
+  /** The organization this token belongs to */
   organization_id: string;
+  /** When the token was created */
   created_at: Date;
+  /** User-provided title for the token */
   title: string;
+  /** User-provided description for the token */
   description: string;
+  /** List of permissions granted to this token */
   permissions: Array<string>;
+  /** Resources this token has access to */
   assigned_resources: any | null;
+  /** Hash of the token for validation */
   hash: string | null;
+  /** First few characters of the token for display */
   first_characters: string | null;
 }
packages/services/api/src/modules/auth/lib/authz.ts (1)

371-371: Consider documenting the purpose of usage:report permission.

The new usage:report permission's purpose and scope should be documented for clarity.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b01750 and 58fc5bf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (33)
  • integration-tests/tests/api/organization-access-tokens.spec.ts (1 hunks)
  • package.json (1 hunks)
  • packages/migrations/src/actions/2025.01.30T00-02-03.organization-access-tokens.ts (1 hunks)
  • packages/migrations/src/run-pg-migrations.ts (1 hunks)
  • packages/services/api/package.json (1 hunks)
  • packages/services/api/src/modules/auth/lib/authz.ts (2 hunks)
  • packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts (1 hunks)
  • packages/services/api/src/modules/auth/lib/supertokens-strategy.ts (2 hunks)
  • packages/services/api/src/modules/auth/lib/target-access-token-strategy.ts (0 hunks)
  • packages/services/api/src/modules/auth/module.graphql.mappers.ts (1 hunks)
  • packages/services/api/src/modules/organization/index.ts (2 hunks)
  • packages/services/api/src/modules/organization/lib/organization-access-key.ts (1 hunks)
  • packages/services/api/src/modules/organization/lib/organization-access-token-permissions.ts (1 hunks)
  • packages/services/api/src/modules/organization/lib/organization-member-permissions.ts (4 hunks)
  • packages/services/api/src/modules/organization/lib/permissions.ts (1 hunks)
  • packages/services/api/src/modules/organization/module.graphql.mappers.ts (2 hunks)
  • packages/services/api/src/modules/organization/module.graphql.ts (2 hunks)
  • packages/services/api/src/modules/organization/providers/organization-access-tokens-cache.ts (1 hunks)
  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (1 hunks)
  • packages/services/api/src/modules/organization/providers/organization-manager.ts (3 hunks)
  • packages/services/api/src/modules/organization/providers/organization-members.ts (3 hunks)
  • packages/services/api/src/modules/organization/providers/resource-assignments.spec.ts (1 hunks)
  • packages/services/api/src/modules/organization/providers/resource-assignments.ts (1 hunks)
  • packages/services/api/src/modules/organization/resolvers/Member.ts (2 hunks)
  • packages/services/api/src/modules/organization/resolvers/Mutation/createOrganizationAccessToken.ts (1 hunks)
  • packages/services/api/src/modules/organization/resolvers/Mutation/deleteOrganizationAccessToken.ts (1 hunks)
  • packages/services/api/src/modules/organization/resolvers/Organization.ts (3 hunks)
  • packages/services/api/src/modules/organization/resolvers/OrganizationAccessToken.ts (1 hunks)
  • packages/services/api/src/modules/schema/providers/schema-manager.ts (2 hunks)
  • packages/services/api/src/modules/shared/providers/id-translator.ts (2 hunks)
  • packages/services/server/src/index.ts (2 hunks)
  • packages/services/storage/src/db/types.ts (2 hunks)
  • patches/bentocache.patch (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/services/api/src/modules/auth/lib/target-access-token-strategy.ts
✅ Files skipped from review due to trivial changes (2)
  • package.json
  • packages/services/api/src/modules/organization/providers/resource-assignments.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/package.json
  • packages/services/api/src/modules/organization/resolvers/OrganizationAccessToken.ts
  • packages/services/storage/src/db/types.ts
  • packages/services/api/src/modules/organization/resolvers/Mutation/deleteOrganizationAccessToken.ts
  • packages/services/api/src/modules/organization/providers/organization-manager.ts
  • packages/services/api/src/modules/organization/providers/organization-access-tokens-cache.ts
  • packages/services/api/src/modules/auth/lib/authz.ts
  • packages/services/api/src/modules/organization/resolvers/Member.ts
  • packages/services/api/src/modules/organization/module.graphql.mappers.ts
  • packages/services/api/src/modules/schema/providers/schema-manager.ts
  • packages/services/api/src/modules/auth/module.graphql.mappers.ts
  • packages/services/api/src/modules/organization/resolvers/Mutation/createOrganizationAccessToken.ts
  • packages/services/api/src/modules/organization/lib/organization-access-token-permissions.ts
  • packages/services/api/src/modules/organization/resolvers/Organization.ts
  • packages/services/api/src/modules/organization/index.ts
  • packages/services/api/src/modules/auth/lib/supertokens-strategy.ts
  • packages/services/api/src/modules/organization/lib/organization-member-permissions.ts
  • packages/services/api/src/modules/organization/lib/permissions.ts
  • packages/services/api/src/modules/organization/lib/organization-access-key.ts
  • packages/services/api/src/modules/organization/providers/organization-members.ts
  • packages/services/server/src/index.ts
  • packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts
  • packages/services/api/src/modules/shared/providers/id-translator.ts
  • packages/services/api/src/modules/organization/providers/resource-assignments.ts
  • packages/services/api/src/modules/organization/module.graphql.ts
  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts
`integration-tests/**`: integration tests for ensuring featu...

integration-tests/**: integration tests for ensuring features and important flows are running. Usually, changes to the packages/ directory will affect this. In ideal flow, each PR should include integration tests.

  • integration-tests/tests/api/organization-access-tokens.spec.ts
🔇 Additional comments (48)
packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts (4)

1-5: No issues with initial imports
These imports are straightforward and appropriate for the module’s functionality.


7-30: Session extension logic looks solid
OrganizationAccessTokenSession nicely extends Session. The overridden loadPolicyStatementsForOrganization method returns statically assigned policies, which is fine as long as the session remains short-lived or policy changes are infrequent.


32-36: Class definition is coherent
OrganizationAccessTokenStrategy extends AuthNStrategy with a clear type parameter. Storing logger and cache as private fields is good practice.


37-41: Constructor usage
Accepting deps as a single object is a neat approach for dependency injection. Creates a well-scoped logger child for this strategy.

packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (3)

93-174: Creation flow appears robust
The create method handles input validation (title, description), checks permissions, and stores the new token. It returns a structured success or error object. The approach is consistent and modular. Consider wrapping everything in a transaction if you plan on adding more DB actions in a single operation later.


176-202: Properly checks permissions before deleting
The delete method ensures the record exists, verifies permissions, removes the entry, and purges from cache. This is a good pattern for consistent resource cleanup.


209-251: Straightforward retrieval with logging
findById correctly validates the ID as UUID, retrieves the token, parses it through the Zod model, and logs relevant details. Returning null if nothing is found is clear and easy to handle.

packages/services/api/src/modules/organization/providers/organization-members.ts (2)

4-6: Imports are well chosen
Adding type Organization and AuthorizationPolicyStatement helps clarify usage without over-importing.


10-15: Sound modular approach
Using the resource-assignments module promotes better separation of concerns. This should simplify maintenance and future enhancements.

packages/services/api/src/modules/organization/module.graphql.ts (5)

38-44: Consider validating input arguments.

The new mutations appear correct overall, but ensure proper input validation for security and consistency.


46-49: Verify one-of directive usage.

Confirm that @oneOf is recognized and validated in your GraphQL setup to ensure that exactly one field is used.


85-92: Looks good.

The OrganizationAccessToken type is consistent with the creation input.


94-105: Straightforward deletion schema.

The input and result types for deleting an organization access token appear clearly defined.


318-321: Confirm resolver implementation.

Ensure that your resolver or data loader populates availableOrganizationPermissionGroups correctly. Consider handling edge cases when no permissions exist.

packages/services/api/src/modules/organization/providers/resource-assignments.ts (6)

1-37: Validate domain logic for assignment modes.

Your Zod schemas for wildcard vs. granular modes look well-structured. Ensure consistent handling throughout your codebase where these modes are interpreted.


38-87: Check for performance in nested loops.

Union-based assignments (e.g., AssignedServicesModel) are quite flexible. Bear in mind that nested transformations can be costly if used frequently. Consider caching or batching in higher call volumes.


185-324: Comprehensive resource filtering.

The logic to filter out non-existent or non-org-bound Projects/Targets is essential for preventing privilege escalation. Great job. Ensure thorough testing of edge cases.


327-329: Small utility isSome.

The isSome function is concise and effective. No issues here.


365-473: Very clear resource resolution.

resolveResourceAssignment correctly handles wildcard expansions. Validate that front-end or other callers handle the varying data structures (arrays vs. single object).


515-563: Check for missing policies.

translateResolvedResourcesToAuthorizationPolicyStatements allows for dynamic policy generation. Verify that each permission group is tested to avoid overlooked actions.

packages/services/api/src/modules/organization/lib/permissions.ts (1)

12-16: PermissionGroup structure is clear.

The grouping logic is solid. Ensure the UI or relevant clients properly render nested permissions if needed.

patches/bentocache.patch (1)

9-10: LGTM! The additional export mappings resolve module resolution conflicts.

The new export mappings provide alternative paths for accessing the Redis and memory drivers, which aligns with the existing exports and resolves the ESM <-> TS module resolution conflict.

packages/services/api/src/modules/organization/module.graphql.mappers.ts (1)

6-6: LGTM! The new access token mapper follows established patterns.

The addition of OrganizationAccessTokenMapper is consistent with the existing mapper types and aligns with the PR's objective of introducing organization access tokens.

Also applies to: 17-17

packages/migrations/src/actions/2025.01.30T00-02-03.organization-access-tokens.ts (1)

18-22: LGTM! Index optimization looks good.

The composite index on (organization_id, created_at DESC, id DESC) is well-designed for:

  1. Filtering by organization
  2. Sorting by creation date
  3. Ensuring deterministic ordering with id
packages/services/api/src/modules/organization/index.ts (1)

16-23: Verify provider dependency order.

The order of providers in the array matters for dependency injection. Ensure that providers are listed in the correct dependency order, with base providers (those with no dependencies) first, followed by providers that depend on them.

Run this script to analyze provider dependencies:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check provider dependencies by searching for injector.get() calls

echo "Analyzing provider dependencies..."

# Search for dependencies in OrganizationAccessTokens
echo -e "\nDependencies of OrganizationAccessTokens:"
ast-grep --pattern 'injector.get($_)' packages/services/api/src/modules/organization/providers/organization-access-tokens.ts

# Search for dependencies in OrganizationAccessTokensCache
echo -e "\nDependencies of OrganizationAccessTokensCache:"
ast-grep --pattern 'injector.get($_)' packages/services/api/src/modules/organization/providers/organization-access-tokens-cache.ts

# Search for dependencies in ResourceAssignments
echo -e "\nDependencies of ResourceAssignments:"
ast-grep --pattern 'injector.get($_)' packages/services/api/src/modules/organization/providers/resource-assignments.ts

Length of output: 756


Provider Dependency Order Verified
Based on the dependency analysis, none of the providers—specifically OrganizationAccessTokens, OrganizationAccessTokensCache, and ResourceAssignments—contain any injector.get(…) calls that indicate inter-provider dependencies. This suggests that there is no imposed ordering required for dependency injection in this module, and the current order is acceptable.

packages/services/api/src/modules/organization/resolvers/Mutation/createOrganizationAccessToken.ts (2)

15-23: LGTM! Success case is well-handled.

The success case properly structures the response with the created token and private access key.


25-31: LGTM! Error case is well-structured.

The error case properly includes both a message and details for better error handling by clients.

packages/services/api/src/modules/organization/resolvers/Member.ts (1)

3-3: LGTM! Good refactoring towards granular resource-based access control.

The changes align well with the PR's objective of implementing more granular resource-based access control. The modification to use ResourceAssignments and pass specific properties instead of the entire member object is a good practice.

Also applies to: 38-43

packages/services/api/src/modules/organization/lib/organization-access-token-permissions.ts (1)

65-67: LGTM! Good use of Set for unique permissions.

Using Set with flatMap is an elegant way to ensure unique permission IDs across all groups.

packages/services/api/src/modules/organization/lib/organization-access-key.ts (1)

1-79: LGTM! Secure implementation with proper error handling.

The implementation uses appropriate cryptographic functions, proper error handling, and secure token generation. The use of bcrypt for hashing is a good security practice.

packages/services/api/src/modules/organization/providers/organization-access-tokens-cache.ts (1)

46-68: LGTM! Well-implemented caching with proper TTL and grace period.

The implementation uses a two-layer caching strategy with proper TTL and grace period. The methods are concise and focused.

packages/services/api/src/modules/organization/resolvers/Organization.ts (2)

2-3: LGTM! Clean import refactoring.

The imports have been cleanly split into separate modules for organization access token permissions and member permissions, improving code organization.


12-12: LGTM! Well-structured permission group resolver.

The new resolver availableOrganizationPermissionGroups follows the same pattern as availableMemberPermissionGroups, maintaining consistency in the codebase.

Also applies to: 187-192

packages/services/api/src/modules/auth/lib/supertokens-strategy.ts (1)

93-93: LGTM! Simplified policy statement loading.

The change reduces complexity by directly using the policy statements from the assigned role, making the code more maintainable.

packages/services/api/src/modules/shared/providers/id-translator.ts (1)

160-210: LGTM! Well-implemented organization reference resolver.

The new resolveOrganizationReference method:

  • Follows consistent error handling patterns
  • Includes comprehensive debug logging
  • Handles both selector and ID-based references
packages/services/api/src/modules/organization/lib/organization-member-permissions.ts (1)

269-270: LGTM! New permissions for organization access tokens.

The addition of 'accessToken:modify' and 'usage:report' permissions aligns with the PR objectives of introducing organization access tokens.

integration-tests/tests/api/organization-access-tokens.spec.ts (2)

6-49: LGTM! Well-structured GraphQL operations.

The GraphQL operations are well-defined with appropriate fields for both mutation and query operations.


225-273: LGTM! Thorough negative test case.

The test effectively validates that granular resource access control works as expected.

packages/migrations/src/run-pg-migrations.ts (1)

161-161: LGTM! Migration properly added to the sequence.

The new migration is correctly added at the end of the migrations list.

packages/services/api/src/modules/auth/lib/authz.ts (2)

352-352: LGTM! New permission aligns with feature scope.

The accessToken:modify permission is appropriately added to the organization level permissions.


386-392: Verify the impact of permission changes.

The changes to permissions might affect existing roles and access patterns.

❌ Verification inconclusive

🏁 Script executed:

#!/bin/bash
# Description: Check for existing usage of removed/modified permissions

# Check for usage of schema:loadFromRegistry permission
rg -l "schema:loadFromRegistry"

# Check for existing roles that might be affected
ast-grep --pattern 'permissions: [$_]'

Length of output: 72


Permission Impact Review – Manual Verification Recommended

The automated checks did not reveal any usage of the schema:loadFromRegistry permission or references within role definitions. However, since no output was returned from the search command, it’s advisable to perform a manual review to ensure that no dynamic or indirect usages have been overlooked.

packages/services/api/package.json (1)

47-47: Verify the bentocache version and security advisories.

The new dependency bentocache is pinned to version 1.1.0. Let's verify this version is secure and up-to-date.

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Description: Check for security advisories and latest versions of the `bentocache` library.

# Check NPM for latest versions
curl -s https://registry.npmjs.org/bentocache | jq '.["dist-tags"].latest'

# Check for security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NPM, package: "bentocache") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 428


Bentocache version 1.1.0 is verified as secure.

  • The NPM registry confirms that 1.1.0 is the latest version.
  • No security advisories were found in the GitHub security query.
packages/services/server/src/index.ts (2)

58-61: LGTM! New imports for organization access token support.

The imports are correctly organized and follow the module structure.


421-425: LGTM! New organization access token strategy implementation.

The strategy is correctly configured with logger and cache dependencies.

packages/services/api/src/modules/organization/providers/organization-manager.ts (3)

23-23: LGTM! New import for resource assignments.

The import is correctly placed with other organization-related imports.


48-48: LGTM! New resource assignments dependency.

The dependency is correctly injected and follows the established pattern.


1007-1010: LGTM! Updated resource assignment transformation.

The implementation correctly uses the new resourceAssignments service.

packages/services/api/src/modules/schema/providers/schema-manager.ts (1)

999-1010: LGTM! Permission change from schema:loadFromRegistry to project:describe.

The permission change aligns with the previous feedback and is safe as the permission is not exposed externally.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (1)

38-42: ⚠️ Potential issue

Fix mismatch between nullable description and non-nullable DB column.

The DescriptionInputModel is marked as .nullable(), but the corresponding description DB column is declared as NOT NULL. This mismatch will cause database errors.

-  .nullable();
🧹 Nitpick comments (3)
packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (2)

31-36: Consider making title validation more restrictive.

The title regex allows spaces at the start and end. Consider using .regex(/^[a-zA-Z0-9][a-zA-Z0-9_\- ]*[a-zA-Z0-9]$/) to ensure titles start and end with alphanumeric characters.

-  .regex(/^[ a-zA-Z0-9_-]+$/, `Can only contain letters, numbers, " ", '_', and '-'.`)
+  .regex(/^[a-zA-Z0-9][a-zA-Z0-9_\- ]*[a-zA-Z0-9]$/, `Must start and end with alphanumeric characters and can contain letters, numbers, spaces, '_', and '-'.`)

193-227: Consider implementing soft delete for better audit trails.

Instead of hard deleting the token, consider implementing soft delete by adding a deleted_at timestamp column. This would preserve the audit trail and make it easier to track token lifecycle.

-    await this.pool.query(sql`
-      DELETE
-      FROM
-        "organization_access_tokens"
-      WHERE
-        "id" = ${args.organizationAccessTokenId}
-    `);
+    await this.pool.query(sql`
+      UPDATE "organization_access_tokens"
+      SET
+        "deleted_at" = NOW()
+      WHERE
+        "id" = ${args.organizationAccessTokenId}
+        AND "deleted_at" IS NULL
+    `);
packages/services/api/src/modules/organization/lib/organization-access-key.ts (1)

74-88: Optimize the key generation process.

The current implementation applies SHA-256 before bcrypt, which is unnecessary and might reduce entropy. Consider using the random bytes directly with bcrypt.

 export async function create(id: string) {
-  const secret = Crypto.createHash('sha256')
-    .update(Crypto.randomBytes(20).toString())
-    .digest('hex');
+  const secret = Crypto.randomBytes(32).toString('base64');
 
   const hash = await bcrypt.hash(secret, await bcrypt.genSalt());
   const privateAccessToken = encode(id, secret);
   const firstCharacters = privateAccessToken.substr(0, 10);
 
   return {
     privateAccessToken,
     hash,
     firstCharacters,
   };
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fae1421 and 766102f.

📒 Files selected for processing (5)
  • packages/migrations/src/actions/2025.02.20T00-00-00.organization-access-tokens.ts (1 hunks)
  • packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts (1 hunks)
  • packages/services/api/src/modules/organization/lib/organization-access-key.ts (1 hunks)
  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (1 hunks)
  • packages/services/storage/src/db/types.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/services/storage/src/db/types.ts
  • packages/migrations/src/actions/2025.02.20T00-00-00.organization-access-tokens.ts
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts
  • packages/services/api/src/modules/organization/lib/organization-access-key.ts
  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: SonarCloud
🔇 Additional comments (7)
packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (4)

77-98: LGTM! Well-structured class with proper dependency injection.

The class follows best practices with proper dependency injection, scoping, and logging setup.


100-191: LGTM! Robust implementation with proper validation and error handling.

The create method implements comprehensive input validation, proper error handling, and follows security best practices with permission checks and audit logging.


229-302: LGTM! Well-implemented cursor-based pagination.

The pagination implementation follows best practices with proper cursor handling, limit enforcement, and permission checks.


305-363: LGTM! Well-documented helper function with proper logging.

The findById implementation includes comprehensive logging and proper error handling. The SQL fields are well-structured with proper aliasing.

packages/services/api/src/modules/organization/lib/organization-access-key.ts (2)

12-17: Well-structured type definition and versioned key prefix!

The DecodedAccessKey type and key prefix are well-documented, making the code maintainable and future-proof. The versioned prefix (hvo1) is a good practice for token identification and potential future migrations.

Also applies to: 19-28


90-96: LGTM! Secure implementation of token verification.

The verify function correctly uses bcrypt's compare function for secure token verification.

packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts (1)

330-341: Consider adding more metadata fields for better audit tracking.

Based on the previous discussion, consider adding these fields to both event types for better audit trail:

  • assignedPermissions: Track what permissions were granted/removed
  • specifiedResources: Track which resources were accessible
   z.object({
     eventType: z.literal('ORGANIZATION_ACCESS_TOKEN_CREATED'),
     metadata: z.object({
       organizationAccessTokenId: z.string().uuid(),
+      assignedPermissions: z.array(z.string()),
+      specifiedResources: z.array(z.string()),
     }),
   }),
   z.object({
     eventType: z.literal('ORGANIZATION_ACCESS_TOKEN_DELETED'),
     metadata: z.object({
       organizationAccessTokenId: z.string().uuid(),
+      assignedPermissions: z.array(z.string()),
+      specifiedResources: z.array(z.string()),
     }),
   }),

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (7)
packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (2)

31-42: Consider adding more descriptive validation messages.

The regex pattern /^[ a-zA-Z0-9_-]+$/ could be more user-friendly with a clearer error message explaining the allowed characters.

-  .regex(/^[ a-zA-Z0-9_-]+$/, `Can only contain letters, numbers, " ", '_', and '-'.`)
+  .regex(/^[ a-zA-Z0-9_-]+$/, `Title can only contain alphanumeric characters, spaces, underscores, and hyphens.`)

149-151: Consider implementing soft delete.

As discussed in past review comments, implementing soft delete would be beneficial for:

  1. Audit trail purposes
  2. Recovering from accidental deletions
  3. Maintaining historical records of sensitive information
-    await this.pool.query(sql`
-      DELETE
-      FROM
-        "organization_access_tokens"
-      WHERE
-        "id" = ${args.organizationAccessTokenId}
-    `);
+    await this.pool.query(sql`
+      UPDATE "organization_access_tokens"
+      SET
+        "deleted_at" = NOW(),
+        "deleted_by" = ${this.session.user.id}
+      WHERE
+        "id" = ${args.organizationAccessTokenId}
+        AND "deleted_at" IS NULL
+    `);

Also applies to: 210-216

packages/services/api/src/modules/organization/providers/resource-assignments.ts (2)

475-477: Enhance error message in casesExhausted function.

The error message could be more descriptive by including the unhandled value.

-function casesExhausted(_value: never): never {
-  throw new Error('Not all cases were handled.');
+function casesExhausted(value: never): never {
+  throw new Error(`Unhandled case: ${JSON.stringify(value)}`);

389-473: Consider adding memoization for resource resolution.

The resolveResourceAssignment function performs complex transformations that could benefit from memoization, especially when called multiple times with the same arguments.

+const memoizedResolveResourceAssignment = new Map<string, ResolvedResourceAssignments>();
+
 export function resolveResourceAssignment(args: {
   organizationId: string;
   projects: ResourceAssignmentGroup;
 }): ResolvedResourceAssignments {
+  const key = `${args.organizationId}:${JSON.stringify(args.projects)}`;
+  const cached = memoizedResolveResourceAssignment.get(key);
+  if (cached) {
+    return cached;
+  }
+
   // ... existing implementation ...
+  const result = {
+    organization: organizationAssignment,
+    project: projectAssignments,
+    target: targetAssignments,
+    service: serviceAssignments,
+    appDeployment: appDeploymentAssignments,
+  };
+
+  memoizedResolveResourceAssignment.set(key, result);
+  return result;
packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts (3)

43-47: Simplify method signature and return type.

The method can be improved:

  1. Remove the unused parameter since it's not needed
  2. Simplify return type to just Array<AuthorizationPolicyStatement> since it's not using Promise
-  protected loadPolicyStatementsForOrganization(
-    _: string,
-  ): Promise<Array<AuthorizationPolicyStatement>> | Array<AuthorizationPolicyStatement> {
+  protected loadPolicyStatementsForOrganization(): Array<AuthorizationPolicyStatement> {
     return this.policies;
   }

67-73: Simplify header extraction logic.

The current header extraction can be simplified using direct access since FastifyRequest headers are case-insensitive.

-    for (const headerName in args.req.headers) {
-      if (headerName.toLowerCase() !== 'authorization') {
-        continue;
-      }
-      const values = args.req.headers[headerName];
-      value = (Array.isArray(values) ? values.at(0) : values) ?? null;
-    }
+    const authHeader = args.req.headers.authorization;
+    value = Array.isArray(authHeader) ? authHeader[0] : authHeader;

61-119:

❓ Verification inconclusive

Consider adding rate limiting and token expiration.

The token validation logic could benefit from:

  1. Rate limiting to prevent brute force attempts
  2. Token expiration checks
  3. Audit logging for failed validation attempts

Let's verify if the PR includes token expiration in the database schema:


🏁 Script executed:

#!/bin/bash
# Search for expiration or validity period in the migration file
rg -i "expir|valid" "packages/migrations/src/actions/2025.02.20T00-00-00.organization-access-tokens.ts"

Length of output: 103


Attention: Token Expiration, Rate Limiting, and Audit Logging Verification Needed

After verifying the migration file, there’s no indication that token expiration is enforced at the database/schema level. Similarly, the current token parsing logic doesn’t include mechanisms for rate limiting or audit logging of failed attempts. Please verify if these features are planned for a future PR or if they should be added now to enhance security:

  • The migration file for organization access tokens doesn’t show any expiration or validity-related fields.
  • There is no rate limiting in place to mitigate brute force attempts.
  • Failed validation attempts aren’t currently audited.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 766102f and 8c38327.

📒 Files selected for processing (5)
  • packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts (2 hunks)
  • packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts (1 hunks)
  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (1 hunks)
  • packages/services/api/src/modules/organization/providers/organization-members.ts (5 hunks)
  • packages/services/api/src/modules/organization/providers/resource-assignments.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts
  • packages/services/api/src/modules/organization/providers/resource-assignments.ts
  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts
  • packages/services/api/src/modules/organization/providers/organization-members.ts
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: SonarCloud
🔇 Additional comments (2)
packages/services/api/src/modules/organization/providers/organization-members.ts (1)

137-148: Efficient implementation of authorization policy statements.

The use of a getter function for authorizationPolicyStatements is a good optimization as it:

  1. Lazily computes the statements only when needed
  2. Avoids unnecessary computations when querying organization members via GraphQL API
packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts (1)

21-23: LGTM! Secure hash implementation.

The token hashing implementation uses SHA-256 from Node's crypto module, which is appropriate for this use case.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/migrations/src/actions/2025.02.20T00-00-00.organization-access-tokens.ts (1)

13-13: Consider adding a check constraint for permissions array

The permissions column allows any text array values. Consider adding a check constraint to ensure only valid permissions are stored.

Apply this diff to add the constraint:

- , "permissions" text[] NOT NULL
+ , "permissions" text[] NOT NULL CHECK (array_length("permissions", 1) > 0)
packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (2)

31-36: Consider adding Unicode support to title regex

The current regex /^[ a-zA-Z0-9_-]+$/ only allows ASCII characters. Consider supporting Unicode characters for internationalization.

Apply this diff to support Unicode:

-  .regex(/^[ a-zA-Z0-9_-]+$/, `Can only contain letters, numbers, " ", '_', and '-'.`)
+  .regex(/^[\p{L}\p{N}\s_-]+$/u, `Can only contain letters, numbers, spaces, '_', and '-'.`)

198-233: Consider implementing soft delete

The current delete implementation uses a deleted_at timestamp but doesn't store additional metadata about the deletion. Consider storing who performed the deletion for audit purposes.

Apply this diff to store deletion metadata:

   await this.pool.query(sql`
     UPDATE
       "organization_access_tokens"
     SET
-      "deleted_at" = now()
+      "deleted_at" = now(),
+      "deleted_by" = ${this.session.user.id}
     WHERE
       "id" = ${args.organizationAccessTokenId}
   `);

Note: This requires adding a deleted_by column to the migration.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c38327 and c428deb.

📒 Files selected for processing (3)
  • packages/migrations/src/actions/2025.02.20T00-00-00.organization-access-tokens.ts (1 hunks)
  • packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts (2 hunks)
  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: SonarCloud
🔇 Additional comments (6)
packages/migrations/src/actions/2025.02.20T00-00-00.organization-access-tokens.ts (2)

10-11: Align schema with nullable description

The description column is marked as NOT NULL, but based on the provider implementation in organization-access-tokens.ts, the description is nullable. This mismatch will cause database errors when null descriptions are provided.

Apply this diff to fix the schema:

- , "description" text NOT NULL
+ , "description" text

19-23: LGTM! Efficient index design

The index is well-designed for the following reasons:

  • Covers the most common query pattern (by organization_id)
  • Includes ordering columns (created_at DESC, id DESC)
  • Filtered to exclude deleted records
packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (4)

247-247: Add validation for negative first values

The current implementation silently converts negative values to 20. Consider throwing an error for negative values.

Apply this diff:

-const limit = args.first ? (args.first > 0 ? Math.min(args.first, 20) : 20) : 20;
+if (args.first !== null && args.first < 0) {
+  throw new Error('The "first" argument must be a non-negative number.');
+}
+const limit = args.first ? Math.min(args.first, 20) : 20;

149-151: LGTM! Secure token generation

The implementation uses crypto.randomUUID() for ID generation and a separate function for access key creation, following security best practices.


131-135: LGTM! Proper authorization checks

The implementation correctly checks for the 'accessToken:modify' permission before allowing token creation or deletion.

Also applies to: 204-208


253-279: LGTM! Efficient pagination implementation

The implementation uses cursor-based pagination with proper ordering and limit handling. The SQL query is optimized to work with the index created in the migration.

@n1ru4l n1ru4l force-pushed the wip-organization-access-tokens branch from c428deb to b98959d Compare February 13, 2025 16:01
if (args.reference.bySelector) {
const organizationId = await this.translateOrganizationId(args.reference.bySelector).catch(
error => {
this.logger.debug(error);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not as part of this PR, but I think we should standardize our logs to include a unique code so that we can easily track log lines back to their source.
I think as they are now, if i saw these lines in a log that I wouldn't know what went wrong.

);

if (!isHashMatch) {
this.logger.debug('Provided private key does not match hash.');
Copy link
Collaborator

@jdolle jdolle Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of returning null, do we want to capture this case to return more specific error messages?

Copy link
Contributor Author

@n1ru4l n1ru4l Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think we do not really want to tell a potential brute-forcer that he correctly guessed the access key id, but not the private key part?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. I picked a condition at random to add this comment to. But more generically for any of these null cases -- would it be worth providing info to the user? E.g. Malformed token if it doesnt start with Bearer?

I suspect still the answer is no, but wanted to make sure i wasnt missing something.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/services/api/src/modules/organization/providers/resource-assignments.ts (1)

225-226: 🛠️ Refactor suggestion

Add service name validation.
A similar issue was flagged in a past review. Defining a ServiceNameModel (similar to AppDeploymentNameModel) would help ensure that only valid service names are stored.

You could define it like so:

+import { z } from 'zod';

+export const ServiceNameModel = z.string().regex(
+  /^[a-zA-Z0-9_\-]+$/,
+  'Service name must only contain letters, numbers, underscores, or hyphens.'
+);

Then apply safeParse or equivalent validation when parsing service names here:

- targetRecord.services.services?.map(record => ({
-   type: 'service',
-   serviceName: record?.serviceName,
- }))
+ targetRecord.services.services
+   ?.map(record => {
+     const parsed = ServiceNameModel.safeParse(record?.serviceName);
+     if (!parsed.success) {
+       this.logger.debug('Omitted invalid service name.');
+       return null;
+     }
+     return {
+       type: 'service',
+       serviceName: parsed.data,
+     };
+   })
+   .filter(Boolean)
🧹 Nitpick comments (4)
packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (1)

103-196: Consider wrapping database operations in a transaction.

The create operation performs multiple steps (insert token, add to cache, record audit log). Wrapping these in a transaction would ensure atomicity and prevent partial operations in case of failures.

-    const result = await this.pool.maybeOne<unknown>(sql`
+    const result = await this.pool.transaction(async tx => {
+      const result = await tx.maybeOne<unknown>(sql`
         INSERT INTO "organization_access_tokens" (
           "id"
           , "organization_id"
           , "title"
           , "description"
           , "permissions"
           , "assigned_resources"
           , "hash"
           , "first_characters"
         )
         VALUES (
           ${id}
           , ${organizationId}
           , ${titleResult.data}
           , ${descriptionResult.data}
           , ${sql.array(permissions, 'text')}
           , ${sql.jsonb(assignedResources)}
           , ${accessKey.hash}
           , ${accessKey.firstCharacters}
         )
         RETURNING
           ${organizationAccessTokenFields}
-    `);
+      `);

+      const organizationAccessToken = OrganizationAccessTokenModel.parse(result);
+      await this.cache.add(organizationAccessToken);
+      await this.auditLogs.record({
+        organizationId,
+        eventType: 'ORGANIZATION_ACCESS_TOKEN_CREATED',
+        metadata: {
+          organizationAccessTokenId: organizationAccessToken.id,
+          permissions: organizationAccessToken.permissions,
+          assignedResources: organizationAccessToken.assignedResources,
+        },
+      });
+      
+      return organizationAccessToken;
+    });
packages/services/api/src/modules/organization/providers/resource-assignments.ts (2)

161-161: Fix the spelling error.
There's a typo "belogn" in the comment. It should be "belong."


179-179: Correct the typographical error.
Replace "a;ll" with "all" in the comment.

packages/services/api/src/modules/organization/providers/organization-members.ts (1)

139-139: Fix the spelling mistake in the comment.
Change "mambers" to "members."

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 346f2d2 and 2385154.

📒 Files selected for processing (4)
  • packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts (2 hunks)
  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (1 hunks)
  • packages/services/api/src/modules/organization/providers/organization-members.ts (5 hunks)
  • packages/services/api/src/modules/organization/providers/resource-assignments.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts
  • packages/services/api/src/modules/organization/providers/organization-members.ts
  • packages/services/api/src/modules/organization/providers/resource-assignments.ts
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (5)
packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (5)

38-42: Fix the mismatch between nullable description and DB column.

The DescriptionInputModel is marked as .nullable(), but based on the OrganizationAccessTokenModel and SQL insert statement, the description column appears to be non-nullable.


80-101: Well-structured class setup with proper dependency injection!

The class follows best practices with:

  • Proper scope definition
  • Dependency injection
  • Contextual logging setup

198-232: Clean implementation of hard deletion with audit logging!

The implementation aligns with the team's decision to use hard deletion while maintaining an audit trail through the audit logs service.


234-307: Robust implementation of cursor-based pagination!

The implementation correctly handles:

  • Cursor-based navigation using both created_at and id
  • Page size limits
  • Edge cases with proper page info

314-368: Well-implemented helper function with proper logging!

The findById implementation includes:

  • Comprehensive debug logging
  • UUID validation
  • Clear SQL field definitions

@n1ru4l n1ru4l force-pushed the wip-organization-access-tokens branch from 2385154 to e9959eb Compare February 19, 2025 16:10
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
packages/services/api/src/modules/organization/lib/resource-assignment-model.ts (1)

10-35: Consider adding validation for service and app names.

While the models define the structure correctly, they could benefit from additional validation rules.

Apply this diff to add validation:

-const ServiceAssignmentModel = z.object({ type: z.literal('service'), serviceName: z.string() });
+const ServiceAssignmentModel = z.object({
+  type: z.literal('service'),
+  serviceName: z.string()
+    .min(1, 'Service name cannot be empty')
+    .max(100, 'Service name too long')
+    .regex(/^[a-zA-Z0-9-_]+$/, 'Service name can only contain letters, numbers, hyphens and underscores')
+});

-const AppDeploymentAssignmentModel = z.object({
-  type: z.literal('appDeployment'),
-  appName: z.string(),
-});
+const AppDeploymentAssignmentModel = z.object({
+  type: z.literal('appDeployment'),
+  appName: z.string()
+    .min(1, 'App name cannot be empty')
+    .max(100, 'App name too long')
+    .regex(/^[a-zA-Z0-9-_]+$/, 'App name can only contain letters, numbers, hyphens and underscores')
+});
packages/services/api/src/modules/organization/providers/organization-members.ts (1)

30-32: Consider adding a custom error message for the transform.

The transform function could benefit from a more descriptive error message when the transformation fails.

-  assignedResources: ResourceAssignmentModel.nullable().transform(
-    value => value ?? { mode: '*' as const, projects: [] },
-  ),
+  assignedResources: ResourceAssignmentModel.nullable().transform(
+    value => value ?? { mode: '*' as const, projects: [] },
+    { description: 'Failed to transform assigned resources to default value' }
+  ),
packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (1)

31-42: Strengthen input validation for title and description.

The current regex for title allows spaces at the start/end despite using trim().

-  .regex(/^[ a-zA-Z0-9_-]+$/, `Can only contain letters, numbers, " ", '_', and '-'.`)
+  .regex(/^[a-zA-Z0-9][a-zA-Z0-9 _-]*[a-zA-Z0-9]$/, 'Must start and end with alphanumeric character and contain only letters, numbers, spaces, underscores, and hyphens.')
packages/services/api/src/modules/organization/providers/resource-assignments.ts (1)

400-402: Consider adding error details to casesExhausted function.

The error message could be more descriptive by including the unhandled value.

-function casesExhausted(_value: never): never {
-  throw new Error('Not all cases were handled.');
+function casesExhausted(value: never): never {
+  throw new Error(`Not all cases were handled. Unhandled value: ${JSON.stringify(value)}`);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2385154 and e9959eb.

📒 Files selected for processing (5)
  • packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts (2 hunks)
  • packages/services/api/src/modules/organization/lib/resource-assignment-model.ts (1 hunks)
  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (1 hunks)
  • packages/services/api/src/modules/organization/providers/organization-members.ts (5 hunks)
  • packages/services/api/src/modules/organization/providers/resource-assignments.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/organization/providers/resource-assignments.ts
  • packages/services/api/src/modules/organization/providers/organization-members.ts
  • packages/services/api/src/modules/organization/lib/resource-assignment-model.ts
  • packages/services/api/src/modules/organization/providers/organization-access-tokens.ts
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: test / db-migrations
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (7)
packages/services/api/src/modules/organization/lib/resource-assignment-model.ts (2)

3-8: LGTM! Clear and concise mode definitions.

The assignment mode models are well-defined using Zod literals, with a clear separation between wildcard and granular modes.


63-81: Well-documented resource assignment model.

The documentation clearly explains the purpose and behavior of the resource assignment model. The exported types are properly derived from the Zod schemas.

packages/services/api/src/modules/organization/providers/organization-members.ts (2)

139-150: LGTM! Robust permission boundary checks.

The code effectively prevents breaking permission boundaries through suspicious input by verifying organization and project relationships.


246-246: Improve limit validation.

The current implementation silently converts negative values to 20.

packages/services/api/src/modules/organization/providers/organization-access-tokens.ts (1)

210-216: Consider implementing soft delete.

Hard deleting access tokens might make it difficult to audit or track historical usage.

packages/services/api/src/modules/organization/providers/resource-assignments.ts (2)

225-229: Add service name validation.

The code lacks service name validation, which could lead to security issues.


440-488: LGTM! Well-structured policy statement generation.

The function effectively translates resource assignments into authorization policy statements, with clear separation of concerns for different resource types.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/services/api/src/create.ts (1)

169-169: Consider using a more specific type for prometheus configuration.

Instead of using Record<string, unknown>, consider defining a specific interface for the Prometheus configuration to improve type safety and documentation.

-  prometheus: null | Record<string, unknown>;
+  prometheus: null | {
+    // Add specific configuration properties here
+    enabled?: boolean;
+    prefix?: string;
+    // ... other Prometheus-specific options
+  };
packages/services/server/src/index.ts (2)

411-439: Consider gradual migration strategy for token types.

The authentication strategies array shows both old and new token types coexisting. This is good for a gradual migration, but we should:

  1. Add a deprecation notice for TargetAccessTokenStrategy
  2. Plan for eventual removal of target tokens
  3. Monitor usage patterns of both token types

423-430:

❓ Verification inconclusive

Verify caching implementation for token validation.

The new OrganizationAccessTokenStrategy is properly integrated with caching mechanisms. However, we should ensure the caching implementation is resilient to high load.

Additionally, consider adding metrics for cache hit/miss rates to monitor performance:

 new OrganizationAccessTokenStrategy({
   logger,
   organizationAccessTokensCache: registry.injector.get(OrganizationAccessTokensCache),
   organizationAccessTokenValidationCache: registry.injector.get(
     OrganizationAccessTokenValidationCache,
   ),
+  metrics: {
+    cacheHits: new prometheus.Counter({
+      name: 'organization_access_token_cache_hits_total',
+      help: 'Total number of organization access token cache hits'
+    }),
+    cacheMisses: new prometheus.Counter({
+      name: 'organization_access_token_cache_misses_total',
+      help: 'Total number of organization access token cache misses'
+    })
+  }
 }),

🏁 Script executed:

#!/bin/bash
# Search for cache implementation details
ast-grep --pattern $'class OrganizationAccessTokensCache {
  $$$
}'

ast-grep --pattern $'class OrganizationAccessTokenValidationCache {
  $$$
}'

Length of output: 10756


Caching Mechanism Verification & Metrics Recommendation

The integration of OrganizationAccessTokenStrategy with its caching mechanisms is confirmed. The implementations in:

  • packages/services/api/src/modules/organization/providers/organization-access-tokens-cache.ts
  • packages/services/api/src/modules/auth/providers/organization-access-token-validation-cache.ts

are leveraging the BentoCache setup with Prometheus plugins, which provides a baseline for cache monitoring. However, please verify under high load that these caching layers remain resilient. It is also recommended to add explicit metrics counters for cache hit/miss tracking to facilitate performance monitoring. For example:

 new OrganizationAccessTokenStrategy({
   logger,
   organizationAccessTokensCache: registry.injector.get(OrganizationAccessTokensCache),
   organizationAccessTokenValidationCache: registry.injector.get(
     OrganizationAccessTokenValidationCache,
   ),
+  metrics: {
+    cacheHits: new prometheus.Counter({
+      name: 'organization_access_token_cache_hits_total',
+      help: 'Total number of organization access token cache hits',
+    }),
+    cacheMisses: new prometheus.Counter({
+      name: 'organization_access_token_cache_misses_total',
+      help: 'Total number of organization access token cache misses',
+    }),
+  }
 }),

Please review these suggestions and ensure the caching behavior under load is well validated.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9959eb and b429d06.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • packages/services/api/package.json (2 hunks)
  • packages/services/api/src/create.ts (4 hunks)
  • packages/services/api/src/modules/auth/index.ts (2 hunks)
  • packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts (1 hunks)
  • packages/services/api/src/modules/auth/providers/organization-access-token-validation-cache.ts (1 hunks)
  • packages/services/api/src/modules/organization/providers/organization-access-tokens-cache.ts (1 hunks)
  • packages/services/api/src/modules/shared/providers/prometheus-config.ts (1 hunks)
  • packages/services/server/src/index.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/services/api/package.json
  • packages/services/api/src/modules/organization/providers/organization-access-tokens-cache.ts
  • packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/shared/providers/prometheus-config.ts
  • packages/services/api/src/modules/auth/index.ts
  • packages/services/api/src/modules/auth/providers/organization-access-token-validation-cache.ts
  • packages/services/api/src/create.ts
  • packages/services/server/src/index.ts
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (7)
packages/services/api/src/modules/shared/providers/prometheus-config.ts (1)

1-12: LGTM! Well-structured configuration class.

The implementation follows best practices with proper dependency injection setup and encapsulation.

packages/services/api/src/modules/auth/providers/organization-access-token-validation-cache.ts (2)

17-40: LGTM! Well-implemented caching with monitoring.

The implementation effectively uses bentocache with:

  • Conditional Prometheus monitoring integration
  • Memory-based caching for performance
  • Clean method forwarding pattern

28-34:

❓ Verification inconclusive

Verify the memory cache limit.

The memory cache is configured with a 10,000 item limit. Please confirm if this limit is sufficient for your expected load and token volume.


🏁 Script executed:

#!/bin/bash
# Check the current number of organization access tokens to validate the cache size
rg -A 1 "INSERT INTO organization_access_tokens" packages/migrations/src/actions/

Length of output: 83


Manual Verification Needed: Confirm Memory Cache Limit for Organization Access Tokens

The cached configuration sets a 10,000 item limit, but our automated check using the provided script returned no output. Please manually verify whether this limit is adequate for your expected load and token volume (e.g., by reviewing production metrics or load test results).

  • File: packages/services/api/src/modules/auth/providers/organization-access-token-validation-cache.ts (Lines 28-34)
  • Action: Confirm that a 10,000 item limit sufficiently supports your environment.
packages/services/api/src/modules/auth/index.ts (1)

5-5: LGTM! Clean provider integration.

The OrganizationAccessTokenValidationCache is properly integrated into the auth module's provider list.

Also applies to: 24-24

packages/services/api/src/create.ts (1)

329-334: LGTM! Clean Prometheus configuration integration.

The PrometheusConfig provider is properly integrated into the registry with correct factory setup.

packages/services/server/src/index.ts (2)

58-62: LGTM! New imports align with PR objectives.

The added imports support the new organization access token feature, which is consistent with the PR's goal of replacing target access tokens.


408-408: LGTM! Prometheus configuration added.

The prometheus configuration is properly integrated into the registry.

@n1ru4l n1ru4l force-pushed the wip-organization-access-tokens branch from b429d06 to f4a8679 Compare February 20, 2025 02:01
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/services/api/src/modules/auth/providers/organization-access-token-validation-cache.ts (1)

38-39: Consider adding type safety to the getOrSetForever method.

The method uses typeof this.cache.getOrSetForever for typing, which works but could be more explicit.

Consider using explicit type parameters:

-  getOrSetForever: typeof this.cache.getOrSetForever = (...args) =>
+  getOrSetForever = <T>(...args: Parameters<typeof this.cache.getOrSetForever<T>>) =>
     this.cache.getOrSetForever<T>(...args);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b429d06 and f4a8679.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • packages/services/api/package.json (2 hunks)
  • packages/services/api/src/create.ts (4 hunks)
  • packages/services/api/src/modules/auth/index.ts (2 hunks)
  • packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts (1 hunks)
  • packages/services/api/src/modules/auth/providers/organization-access-token-validation-cache.ts (1 hunks)
  • packages/services/api/src/modules/organization/providers/organization-access-tokens-cache.ts (1 hunks)
  • packages/services/api/src/modules/shared/providers/prometheus-config.ts (1 hunks)
  • packages/services/server/src/index.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/services/api/src/modules/shared/providers/prometheus-config.ts
  • packages/services/api/src/modules/auth/index.ts
  • packages/services/api/package.json
  • packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts
  • packages/services/api/src/modules/organization/providers/organization-access-tokens-cache.ts
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/auth/providers/organization-access-token-validation-cache.ts
  • packages/services/api/src/create.ts
  • packages/services/server/src/index.ts
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: Mend Security Check
🔇 Additional comments (7)
packages/services/api/src/modules/auth/providers/organization-access-token-validation-cache.ts (3)

1-5: LGTM! Dependencies are correctly imported.

The imports are well-organized and include necessary dependencies for caching, monitoring, and dependency injection.


10-13: LGTM! Class is correctly configured as a singleton.

The @Injectable decorator is properly configured with:

  • scope: Scope.Singleton to ensure a single instance is shared.
  • global: true to make it available across modules.

17-36: LGTM! Cache configuration is well-structured.

The cache configuration is robust with:

  • Unique store name and prefix to prevent collisions.
  • Memory driver with a reasonable max items limit (10,000).
  • Conditional Prometheus integration for monitoring.
packages/services/api/src/create.ts (2)

57-62: LGTM! Imports are correctly organized.

The imports are well-structured and include the necessary components for the new organization access token functionality.


408-408: LGTM! Prometheus configuration is correctly passed.

The prometheus configuration is properly integrated into the registry creation.

packages/services/server/src/index.ts (2)

58-62: LGTM! Required imports for organization access token functionality.

The imports correctly include all necessary components for the new authentication strategy.


423-431: LGTM! Organization access token strategy is properly configured.

The new authentication strategy is correctly integrated with:

  • Required dependencies injected from the registry.
  • Proper logger configuration.

This registers authentication and authorization to the GraphQL API using the new organization access tokens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants