-
Notifications
You must be signed in to change notification settings - Fork 110
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
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis 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
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
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
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
🐋 This PR was built and pushed to the following Docker images: Targets: Platforms: Image Tag: 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"
}
} |
d1be7ea
to
66165f3
Compare
f258b53
to
58fc5bf
Compare
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
/** | ||
* Resolved resource groups, used for runtime permission checks. | ||
* Resolved policy statements | ||
*/ | ||
resolvedResources: ResolvedResourceAssignments; | ||
authorizationPolicyStatements: AuthorizationPolicyStatement[]; |
There was a problem hiding this comment.
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).
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this 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 issueFix 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, considerz.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 forauthorizationPolicyStatements
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
andwarning
provide flexibility for advanced permission handling. Just confirm thatisReadyOnly
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
andproject: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 ofusage: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
⛔ 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.
Thecdn-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 thepackages/
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 extendsSession
. The overriddenloadPolicyStatementsForOrganization
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
extendsAuthNStrategy
with a clear type parameter. Storinglogger
andcache
as private fields is good practice.
37-41
: Constructor usage
Acceptingdeps
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
Thecreate
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
Thedelete
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. Returningnull
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
Addingtype Organization
andAuthorizationPolicyStatement
helps clarify usage without over-importing.
10-15
: Sound modular approach
Using theresource-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:
- Filtering by organization
- Sorting by creation date
- 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.tsLength of output: 756
Provider Dependency Order Verified
Based on the dependency analysis, none of the providers—specifically OrganizationAccessTokens, OrganizationAccessTokensCache, and ResourceAssignments—contain anyinjector.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 asavailableMemberPermissionGroups
, 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 fromschema:loadFromRegistry
toproject:describe
.The permission change aligns with the previous feedback and is safe as the permission is not exposed externally.
...es/services/api/src/modules/organization/resolvers/Mutation/deleteOrganizationAccessToken.ts
Show resolved
Hide resolved
packages/migrations/src/actions/2025.01.30T00-02-03.organization-access-tokens.ts
Outdated
Show resolved
Hide resolved
packages/services/api/src/modules/organization/resolvers/OrganizationAccessToken.ts
Show resolved
Hide resolved
...es/services/api/src/modules/organization/resolvers/Mutation/createOrganizationAccessToken.ts
Show resolved
Hide resolved
packages/services/api/src/modules/organization/lib/organization-access-token-permissions.ts
Show resolved
Hide resolved
There was a problem hiding this 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 issueFix mismatch between nullable description and non-nullable DB column.
The
DescriptionInputModel
is marked as.nullable()
, but the correspondingdescription
DB column is declared asNOT 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
📒 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.
Thecdn-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/removedspecifiedResources
: Track which resources were accessiblez.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()), }), }),
packages/services/api/src/modules/organization/lib/organization-access-key.ts
Show resolved
Hide resolved
There was a problem hiding this 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:
- Audit trail purposes
- Recovering from accidental deletions
- 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 incasesExhausted
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:
- Remove the unused parameter since it's not needed
- 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:
- Rate limiting to prevent brute force attempts
- Token expiration checks
- 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
📒 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.
Thecdn-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:
- Lazily computes the statements only when needed
- 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.
packages/services/api/src/modules/organization/providers/organization-access-tokens.ts
Show resolved
Hide resolved
packages/services/api/src/modules/organization/providers/resource-assignments.ts
Show resolved
Hide resolved
packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts
Outdated
Show resolved
Hide resolved
packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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 arrayThe
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 regexThe 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 deleteThe 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
📒 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.
Thecdn-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 descriptionThe
description
column is marked asNOT NULL
, but based on the provider implementation inorganization-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 designThe 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 negativefirst
valuesThe 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 generationThe implementation uses
crypto.randomUUID()
for ID generation and a separate function for access key creation, following security best practices.
131-135
: LGTM! Proper authorization checksThe implementation correctly checks for the 'accessToken:modify' permission before allowing token creation or deletion.
Also applies to: 204-208
253-279
: LGTM! Efficient pagination implementationThe 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.
c428deb
to
b98959d
Compare
packages/services/api/src/modules/organization/providers/organization-access-tokens.ts
Show resolved
Hide resolved
if (args.reference.bySelector) { | ||
const organizationId = await this.translateOrganizationId(args.reference.bySelector).catch( | ||
error => { | ||
this.logger.debug(error); |
There was a problem hiding this comment.
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.
packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts
Show resolved
Hide resolved
); | ||
|
||
if (!isHashMatch) { | ||
this.logger.debug('Provided private key does not match hash.'); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
packages/services/api/src/modules/organization/lib/permissions.ts
Outdated
Show resolved
Hide resolved
packages/services/api/src/modules/organization/providers/organization-access-tokens-cache.ts
Show resolved
Hide resolved
There was a problem hiding this 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 suggestionAdd service name validation.
A similar issue was flagged in a past review. Defining aServiceNameModel
(similar toAppDeploymentNameModel
) 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
📒 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.
Thecdn-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 theOrganizationAccessTokenModel
and SQL insert statement, thedescription
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
andid
- 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
2385154
to
e9959eb
Compare
There was a problem hiding this 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
📒 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.
Thecdn-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.
There was a problem hiding this 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:
- Add a deprecation notice for
TargetAccessTokenStrategy
- Plan for eventual removal of target tokens
- 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
⛔ 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.
Thecdn-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.
b429d06
to
f4a8679
Compare
There was a problem hiding this 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 thegetOrSetForever
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
⛔ 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.
Thecdn-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.
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:
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 thetokens
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