Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@Flo4604
Copy link
Member

@Flo4604 Flo4604 commented Jul 15, 2025

What does this PR do?

Fixes #3539
Fixes #3419
Fixes nullable validation issues from openapi spec validator which doesnt support it

If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  • Test A
  • Test B

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

Summary by CodeRabbit

  • New Features

    • Added the ability to update API key roles and permissions directly via the key update endpoint.
    • Introduced support for updating all key fields, including name, metadata, expiration, credits, and rate limits, with three-state (set, clear, leave unchanged) logic.
    • Enhanced OpenAPI schema to reflect new roles and permissions fields and increased name length constraints.
  • Bug Fixes

    • Improved validation to correctly handle nullable fields, reducing false validation errors.
  • Tests

    • Added comprehensive tests for the key update endpoint, covering successful updates, permission checks, workspace isolation, invalid requests, unauthorized access, not found errors, and three-state update logic.
    • Renamed several test functions for consistency and clarity.
  • Chores

    • Registered the new key update route in the API.

@vercel
Copy link

vercel bot commented Jul 15, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
dashboard ⬜️ Ignored (Inspect) Visit Preview Jul 16, 2025 9:08pm
engineering ⬜️ Ignored (Inspect) Visit Preview Jul 16, 2025 9:08pm

@Flo4604 Flo4604 mentioned this pull request Jul 15, 2025
18 tasks
Copy link
Member Author

Flo4604 commented Jul 15, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 15, 2025

📝 Walkthrough

Walkthrough

This change introduces the /v2/keys.updateKey API endpoint, enabling atomic updates to API key properties, permissions, roles, and associated identities. It implements three-state field logic (set, clear, unchanged), adds OpenAPI schema support for permissions and roles, and provides comprehensive integration and validation tests. Supporting SQL queries and generated code for bulk permission/role deletion and conditional key updates are included.

Changes

Files / Group Change Summary
go/apps/api/routes/v2_keys_update_key/handler.go Implements the /v2/keys.updateKey handler with three-state update logic, RBAC enforcement, and audit logging.
go/apps/api/routes/register.go Registers the new update key route handler.
go/apps/api/openapi/openapi.yaml, go/apps/api/openapi/gen.go Adds roles and permissions fields to update key schema; adjusts constraints; updates struct definitions.
go/pkg/db/queries/key_update.sql, ...key_update.sql_generated.go, ...querier_generated.go Adds conditional SQL update for keys and generated Go code.
go/pkg/db/queries/key_permission_delete_all_by_key_id.sql, ...key_permission_delete_all_by_key_id.sql_generated.go Adds SQL and generated code to bulk-delete key permissions.
go/pkg/db/queries/key_role_delete_all_by_key_id.sql, ...key_role_delete_all_by_key_id.sql_generated.go Adds SQL and generated code to bulk-delete key roles.
go/pkg/zen/validation/validator.go Enhances validation to ignore nullable field errors.
go/apps/api/routes/v2_keys_update_key/200_test.go, 400_test.go, 401_test.go, 403_test.go, 404_test.go, three_state_test.go Adds comprehensive tests for success, error, permission, not found, and three-state update logic.
go/apps/api/routes/v2_identities_list_identities/200_test.go Cleans up unused variables in unrelated test.
go/apps/api/routes/v2_keys_create_key/.go, go/apps/api/routes/v2_keys_get_key/.go Renames test functions for consistency; no logic changes.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant APIHandler
    participant DB
    participant AuditLog

    Client->>APIHandler: POST /v2/keys.updateKey (with key update payload)
    APIHandler->>DB: Authenticate root key, fetch target key
    APIHandler->>DB: Verify permissions/roles
    APIHandler->>DB: Begin transaction
    alt Field specified
        APIHandler->>DB: Conditionally update key fields (three-state logic)
    end
    alt ExternalId specified
        APIHandler->>DB: Find or create identity, update key.identity_id
    end
    alt Permissions specified
        APIHandler->>DB: Delete all key permissions
        APIHandler->>DB: Upsert new permissions
    end
    alt Roles specified
        APIHandler->>DB: Delete all key roles
        APIHandler->>DB: Upsert new roles
    end
    APIHandler->>AuditLog: Insert audit log event
    APIHandler->>DB: Commit transaction
    APIHandler-->>Client: 200 OK + updated key info
Loading

Assessment against linked issues

Objective Addressed Explanation
Implement /v2/keys.updateKey endpoint (core, permissions, roles, three-state logic) (#3419)
Create/update key must create/get identityId when externalId is supplied (#3539)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Remove unused variable and adjust loop in unrelated test (go/apps/api/routes/v2_identities_list_identities/200_test.go) This is a minor unrelated test cleanup not required by the linked issues.

Suggested reviewers

  • perkinsjr
  • imeyer
  • chronark

📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f40c3f8 and 436a051.

📒 Files selected for processing (1)
  • go/apps/api/routes/v2_keys_update_key/handler.go (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: Flo4604
PR: unkeyed/unkey#3421
File: go/apps/api/openapi/openapi.yaml:196-200
Timestamp: 2025-07-03T05:58:10.699Z
Learning: In the Unkey codebase, OpenAPI 3.1 is used, which allows sibling keys (such as `description`) alongside `$ref` in schema objects. Do not flag this as an error in future reviews.
Learnt from: MichaelUnkey
PR: unkeyed/unkey#2114
File: apps/api/src/routes/v1_keys_updateKey.error.test.ts:0-0
Timestamp: 2024-09-27T15:20:05.475Z
Learning: In the `v1/keys.updateKey` endpoint, the server validates the refill configuration before checking if the key exists. Therefore, tests can assert validation errors without needing to create the key first.
Learnt from: Flo4604
PR: unkeyed/unkey#2955
File: go/apps/api/routes/v2_identities_create_identity/handler.go:162-202
Timestamp: 2025-03-19T09:25:59.751Z
Learning: In the Unkey codebase, input validation for API endpoints is primarily handled through OpenAPI schema validation, which occurs before requests reach the handler code. For example, in the identities.createIdentity endpoint, minimum values for ratelimit duration and limit are defined in the OpenAPI schema rather than duplicating these checks in the handler.
Learnt from: chronark
PR: unkeyed/unkey#2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In `apps/api/src/routes/v1_keys_updateKey.ts`, the code intentionally handles `externalId` and `ownerId` separately for clarity. The `ownerId` field will be removed in the future, simplifying the code.
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/apps/api/routes/v2_keys_create_key/handler.go:468-581
Timestamp: 2025-07-15T14:47:20.490Z
Learning: In the Unkey codebase, role and permission names are validated at the OpenAPI schema layer with strict regex patterns: role names must match "^[a-zA-Z][a-zA-Z0-9_-]*$" (start with letter, followed by letters/numbers/underscores/hyphens) and permission names must match "^[a-zA-Z0-9_]+$" (letters, numbers, underscores only). This validation occurs during zen.BindBody call before handlers run, preventing malicious or improperly formatted names from reaching auto-creation logic.
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/apps/api/routes/v2_keys_create_key/handler.go:353-466
Timestamp: 2025-07-15T14:25:05.608Z
Learning: In the Unkey codebase, input validation for API endpoints is handled at the OpenAPI schema layer, which validates request fields like permission slugs (pattern: "^[a-zA-Z0-9_]+$", length: 1-100 characters) before requests reach the handler code. This validation occurs during the zen.BindBody call in handlers.
Learnt from: chronark
PR: unkeyed/unkey#3617
File: go/apps/api/openapi/openapi.yaml:3309-3312
Timestamp: 2025-07-16T17:51:57.242Z
Learning: In the Unkey API OpenAPI schema, the permissions query regex for the verifyKey endpoint intentionally allows all whitespace characters (including tabs and newlines) via `\s`. Do not flag this as an error in future reviews.
go/apps/api/routes/v2_keys_update_key/handler.go (15)
Learnt from: Flo4604
PR: unkeyed/unkey#2955
File: go/apps/api/routes/v2_identities_create_identity/handler.go:162-202
Timestamp: 2025-03-19T09:25:59.751Z
Learning: In the Unkey codebase, input validation for API endpoints is primarily handled through OpenAPI schema validation, which occurs before requests reach the handler code. For example, in the identities.createIdentity endpoint, minimum values for ratelimit duration and limit are defined in the OpenAPI schema rather than duplicating these checks in the handler.
Learnt from: chronark
PR: unkeyed/unkey#2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In `apps/api/src/routes/v1_keys_updateKey.ts`, the code intentionally handles `externalId` and `ownerId` separately for clarity. The `ownerId` field will be removed in the future, simplifying the code.
Learnt from: MichaelUnkey
PR: unkeyed/unkey#2114
File: apps/api/src/routes/v1_keys_updateKey.error.test.ts:0-0
Timestamp: 2024-09-27T15:20:05.475Z
Learning: In the `v1/keys.updateKey` endpoint, the server validates the refill configuration before checking if the key exists. Therefore, tests can assert validation errors without needing to create the key first.
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/apps/api/routes/v2_keys_create_key/handler.go:353-466
Timestamp: 2025-07-15T14:25:05.608Z
Learning: In the Unkey codebase, input validation for API endpoints is handled at the OpenAPI schema layer, which validates request fields like permission slugs (pattern: "^[a-zA-Z0-9_]+$", length: 1-100 characters) before requests reach the handler code. This validation occurs during the zen.BindBody call in handlers.
Learnt from: chronark
PR: unkeyed/unkey#2294
File: apps/api/src/pkg/keys/service.ts:268-271
Timestamp: 2024-10-20T07:05:55.471Z
Learning: In `apps/api/src/pkg/keys/service.ts`, `ratelimitAsync` is a table relation, not a column selection. When querying, ensure that table relations are included appropriately, not as columns.
Learnt from: Flo4604
PR: unkeyed/unkey#3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3315
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/general-setup.tsx:40-50
Timestamp: 2025-06-19T13:01:55.338Z
Learning: In the create-key form's GeneralSetup component, the Controller is intentionally bound to "identityId" as the primary field while "externalId" is set explicitly via setValue. The ExternalIdField component has been designed to handle this pattern where it receives identityId as its value prop but manages both identityId and externalId through its onChange callback.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: When querying or updating namespaces in the Unkey dashboard, always scope the operations to the current workspace using `eq(table.workspaceId, ctx.workspace.id)` to prevent cross-workspace access.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3480
File: apps/dashboard/app/new-2/hooks/use-workspace-step.tsx:47-79
Timestamp: 2025-07-09T11:35:51.724Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep invariant checks that throw errors for cases that shouldn't happen in normal operation (like null workspace ID checks), rather than adding graceful error handling code for edge cases that would only occur if someone tampers with the actual flow.
Learnt from: Flo4604
PR: unkeyed/unkey#3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.464Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.
Learnt from: chronark
PR: unkeyed/unkey#3617
File: go/apps/api/openapi/openapi.yaml:3309-3312
Timestamp: 2025-07-16T17:51:57.242Z
Learning: In the Unkey API OpenAPI schema, the permissions query regex for the verifyKey endpoint intentionally allows all whitespace characters (including tabs and newlines) via `\s`. Do not flag this as an error in future reviews.
Learnt from: AkshayBandi027
PR: unkeyed/unkey#2215
File: apps/dashboard/app/(app)/@breadcrumb/authorization/roles/[roleId]/page.tsx:28-29
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In `authorization/roles/[roleId]/update-role.tsx`, the tag `role-${role.id}` is revalidated after updating a role to ensure that the caching mechanism is properly handled for roles.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3324
File: apps/dashboard/app/(app)/authorization/roles/components/table/components/actions/keys-table-action.popover.constants.tsx:17-18
Timestamp: 2025-06-19T11:48:05.070Z
Learning: In the authorization roles refactor, the RoleBasic type uses `roleId` as the property name for the role identifier, not `id`. This is consistent throughout the codebase in apps/dashboard/lib/trpc/routers/authorization/roles/query.ts.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2876
File: apps/dashboard/components/logs/datetime/constants.ts:96-96
Timestamp: 2025-02-06T17:41:47.228Z
Learning: In the Unkey codebase, avoid hardcoding IDs or indices that depend on array positions, as the arrays may be modified in the future. Instead, use methods like `find` with unique identifiers or properties to locate specific items.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Build / Build
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Packages / Test
🔇 Additional comments (7)
go/apps/api/routes/v2_keys_update_key/handler.go (7)

1-33: Well-structured imports and type definitions

The imports are well-organized, and the type aliases for Request/Response improve code readability. The Handler struct follows good dependency injection patterns.


35-44: Correct handler interface implementation

The handler methods correctly implement the expected interface with appropriate HTTP method and path.


46-113: Robust authentication and authorization checks

The implementation correctly validates:

  • Root key authentication
  • Key existence and workspace ownership
  • API deletion status
  • RBAC permissions with proper OR logic for wildcard and specific API access

Error handling uses appropriate fault codes for each scenario.


115-147: Correct three-state update pattern implementation

The transaction setup and three-state field handling for the Name field is implemented correctly, allowing differentiation between:

  • Field not specified (unchanged)
  • Field set to null (clear value)
  • Field set to new value

215-294: Correct field update handling

The implementation properly handles all field updates:

  • Simple boolean for Enabled
  • JSON marshaling for Meta with three-state logic
  • Time conversion for Expires
  • Comprehensive credits/refill validation matching expected business rules

296-303: Standard key update execution

The key update is executed with proper error handling.


544-590: Comprehensive audit logging and clean response

The implementation includes detailed audit logging with all necessary metadata and returns a properly structured response. The transaction handling ensures atomicity of all updates.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

@changeset-bot
Copy link

changeset-bot bot commented Jul 15, 2025

⚠️ No Changeset found

Latest commit: 35fd557

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@Flo4604 Flo4604 marked this pull request as ready for review July 15, 2025 13:32
@github-actions
Copy link
Contributor

github-actions bot commented Jul 15, 2025

Thank you for following the naming conventions for pull request titles! 🙏

@graphite-app
Copy link

graphite-app bot commented Jul 16, 2025

TV gif. Timmy from Shaun the Sheep blinks and extends 2 thumbs up as a lopsided grin emerges on the side of his face. (Added via Giphy)

Copy link
Member Author

Flo4604 commented Jul 16, 2025

mhm will check in a sec

Copy link
Collaborator

no rush, can be tomorrow too 😉

@graphite-app
Copy link

graphite-app bot commented Jul 16, 2025

Graphite Automations

"Post a GIF when PR approved" took an action on this PR • (07/16/25)

1 gif was posted to this PR based on Andreas Thomas's automation.

Copy link
Member Author

Flo4604 commented Jul 16, 2025

➕ Pushed commit: fix: comment

@chronark chronark disabled auto-merge July 17, 2025 07:17
@chronark chronark merged commit 31a8798 into main Jul 17, 2025
16 of 18 checks passed
@chronark chronark deleted the update_key branch July 17, 2025 07:17
@coderabbitai coderabbitai bot mentioned this pull request Jul 17, 2025
18 tasks
@coderabbitai coderabbitai bot mentioned this pull request Oct 28, 2025
18 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create/Update Key must create/get identityId /v2

3 participants