[WEB-5512]feat: workspace api tokens#8463
Conversation
- Added WorkspaceAPITokenEndpoint for managing API tokens within specific workspaces. - Enhanced APITokenSerializer to associate tokens with workspaces and users. - Updated URL routing to include endpoints for workspace API tokens. - Introduced ServiceApiTokenEndpoint for handling service tokens linked to workspaces. - Created base views for API token operations, including create, read, update, and delete functionalities.
…e into feat-workspace-api-tokens
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis pull request introduces workspace-scoped API token management, enabling users to create, list, and delete API tokens within specific workspaces. Changes include backend endpoints for CRUD operations, updated authentication middleware with workspace validation, new frontend components for token management, service layer integration, and internationalization updates across 18+ locales. Changes
Sequence DiagramsequenceDiagram
participant Client as Web Client
participant Server as Django API Server
participant DB as Database
participant Auth as Auth Middleware
Client->>Server: POST /workspaces/{slug}/api-tokens/
Server->>Auth: validate_api_token(token, workspace_slug)
Auth->>DB: resolve workspace by slug
Auth->>DB: verify token.workspace_id matches workspace.id
DB-->>Auth: workspace validation result
Auth-->>Server: return (user, token)
Server->>DB: create APIToken with workspace_id
DB-->>Server: new token created
Server-->>Client: 201 with token data
Client->>Server: GET /workspaces/{slug}/api-tokens/
Server->>Auth: validate_api_token(token, workspace_slug)
Auth->>DB: workspace validation
DB-->>Auth: valid
Auth-->>Server: return (user, token)
Server->>DB: fetch tokens filtered by workspace_id and user
DB-->>Server: token list
Server-->>Client: 200 with tokens
Client->>Server: DELETE /workspaces/{slug}/api-tokens/{id}/
Server->>Auth: validate_api_token(token, workspace_slug)
Auth->>DB: workspace validation
DB-->>Auth: valid
Auth-->>Server: return (user, token)
Server->>DB: delete token by id, workspace_id, and user
DB-->>Server: deleted
Server-->>Client: 204 No Content
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes The changes span multiple subsystems (authentication, rate limiting, API endpoints, frontend components, services, i18n) with distinct logic patterns. While i18n updates are highly repetitive, the backend endpoint implementations require careful review of workspace validation logic, rate throttling integration, and permission checks. Frontend component modifications introduce new service integrations and state management patterns across multiple files. Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. 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. Comment |
|
Linked to Plane Work Item(s) This comment was auto-generated by Plane |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/web/app/routes/core.ts (1)
257-283: Path conflict between new workspace API tokens route and existing redirectYou now have two route entries for the exact same path
:workspaceSlug/settings/api-tokens:
- Workspace settings page route under the settings layout (Lines 257‑283).
- Legacy redirect route sending the same path to
routes/redirects/core/api-tokens.tsx(Lines 395‑397).This duplication is likely to cause one of these behaviors (depending on how @react-router/dev resolves siblings):
- The redirect still fires and makes the new workspace API tokens page unreachable.
- Or the redirect becomes dead/never used, leaving stale behavior in the config.
Given the PR’s goal (workspace‑scoped API tokens), this path should probably resolve directly to the new workspace settings page, and the legacy redirect should either be deleted or updated to point somewhere else.
I recommend cleaning this up so there is a single, unambiguous route for
:workspaceSlug/settings/api-tokens.Proposed cleanup options
Option A — Remove legacy redirect if no longer needed:
- // API tokens redirect: /:workspaceSlug/settings/api-tokens - // → /:workspaceSlug/settings/account/api-tokens - route(":workspaceSlug/settings/api-tokens", "routes/redirects/core/api-tokens.tsx"),Option B — Keep redirect but move it to a different deprecated path (if you still need one), and let the new workspace settings route own
:workspaceSlug/settings/api-tokensexclusively.Also applies to: 395-397
apps/api/plane/app/views/api/base.py (2)
47-50: Add workspace filter to DELETE method for security.The DELETE method is missing the
workspace_id__isnull=Truefilter that was added to the GET methods. This could allow users to delete workspace-scoped tokens through the user token endpoint, bypassing workspace-level permissions.🔎 Proposed fix
def delete(self, request: Request, pk: str) -> Response: - api_token = APIToken.objects.get(user=request.user, pk=pk, is_service=False) + api_token = APIToken.objects.get(user=request.user, pk=pk, is_service=False, workspace_id__isnull=True) api_token.delete() return Response(status=status.HTTP_204_NO_CONTENT)
52-58: Add workspace filter to PATCH method for security.The PATCH method is missing the
workspace_id__isnull=Truefilter that was added to the GET methods. This could allow users to modify workspace-scoped tokens through the user token endpoint, bypassing workspace-level permissions.🔎 Proposed fix
def patch(self, request: Request, pk: str) -> Response: - api_token = APIToken.objects.get(user=request.user, pk=pk) + api_token = APIToken.objects.get(user=request.user, pk=pk, workspace_id__isnull=True) serializer = APITokenSerializer(api_token, data=request.data, partial=True) if serializer.is_valid(): serializer.save()
🧹 Nitpick comments (2)
apps/api/plane/api/rate_limit.py (1)
114-124: Consider extracting duplicated rate-limit header logic.The
allow_requestlogic for computingX-RateLimit-RemainingandX-RateLimit-Resetheaders is repeated acrossApiKeyRateThrottle,ServiceTokenRateThrottle, andWorkspaceTokenRateThrottle. Consider extracting this into a shared mixin or base class to reduce duplication and ensure consistency.apps/api/plane/api/middleware/api_authentication.py (1)
44-46: Consider reducing database writes forlast_usedupdates.Updating
last_usedon every successful authentication adds a write operation per request. For high-traffic endpoints, consider:
- Batching updates asynchronously
- Using a sampling approach (update every N requests)
- Caching with periodic flush
This is a minor performance consideration and may be acceptable depending on traffic patterns.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (48)
apps/api/plane/api/middleware/api_authentication.pyapps/api/plane/api/rate_limit.pyapps/api/plane/api/views/base.pyapps/api/plane/app/serializers/api.pyapps/api/plane/app/urls/api.pyapps/api/plane/app/views/__init__.pyapps/api/plane/app/views/api/__init__.pyapps/api/plane/app/views/api/base.pyapps/api/plane/app/views/api/service.pyapps/api/plane/app/views/api/workspace.pyapps/api/plane/db/migrations/0113_webhook_version.pyapps/api/plane/db/models/api.pyapps/api/plane/db/models/user.pyapps/api/plane/db/models/webhook.pyapps/api/plane/db/models/workspace.pyapps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/api-tokens/page.tsxapps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar.tsxapps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxapps/web/app/routes/core.tsapps/web/core/components/api-token/delete-token-modal.tsxapps/web/core/components/api-token/modal/create-token-modal.tsxapps/web/core/components/api-token/token-list-item.tsxapps/web/core/components/ui/loader/settings/api-token.tsxapps/web/core/constants/fetch-keys.tspackages/constants/src/event-tracker/core.tspackages/constants/src/settings.tspackages/constants/src/workspace.tspackages/i18n/src/locales/cs/translations.tspackages/i18n/src/locales/de/translations.tspackages/i18n/src/locales/en/translations.tspackages/i18n/src/locales/es/translations.tspackages/i18n/src/locales/fr/translations.tspackages/i18n/src/locales/id/translations.tspackages/i18n/src/locales/it/translations.tspackages/i18n/src/locales/ja/translations.tspackages/i18n/src/locales/ko/translations.tspackages/i18n/src/locales/pl/translations.tspackages/i18n/src/locales/pt-BR/translations.tspackages/i18n/src/locales/ro/translations.tspackages/i18n/src/locales/ru/translations.tspackages/i18n/src/locales/sk/translations.tspackages/i18n/src/locales/tr-TR/translations.tspackages/i18n/src/locales/ua/translations.tspackages/i18n/src/locales/vi-VN/translations.tspackages/i18n/src/locales/zh-CN/translations.tspackages/i18n/src/locales/zh-TW/translations.tspackages/services/src/developer/index.tspackages/services/src/developer/workspace-api-token.service.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,mts,cts}
📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)
**/*.{ts,tsx,mts,cts}: Useconsttype parameters for more precise literal inference in TypeScript 5.0+
Use thesatisfiesoperator to validate types without widening them
Leverage inferred type predicates to reduce the need for explicitisreturn types in filter/check functions
UseNoInfer<T>utility to block inference for specific type arguments when they should be determined by other arguments
Utilize narrowing inswitch(true)blocks for control flow analysis (TypeScript 5.3+)
Rely on narrowing from direct boolean comparisons for type guards
Trust preserved narrowing in closures when variables aren't modified after the check (TypeScript 5.4+)
Use constant indices to narrow object/array properties (TypeScript 5.5+)
Use standard ECMAScript decorators (Stage 3) instead of legacyexperimentalDecorators
Useusingdeclarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)
Usewith { type: "json" }for import attributes; avoid deprecatedassertsyntax (TypeScript 5.3/5.8+)
Useimport typeexplicitly when importing types to ensure they are erased during compilation, respectingverbatimModuleSyntaxflag
Use.ts,.mts,.ctsextensions inimport typestatements (TypeScript 5.2+)
Useimport type { Type } from "mod" with { "resolution-mode": "import" }for specific module resolution contexts (TypeScript 5.3+)
Use new iterator methods (map, filter, etc.) if targeting modern environments (TypeScript 5.6+)
Utilize newSetmethods likeunion,intersection, etc., when available (TypeScript 5.5+)
UseObject.groupBy/Map.groupBystandard methods for grouping instead of external libraries (TypeScript 5.4+)
UsePromise.withResolvers()for creating promises with exposed resolve/reject functions (TypeScript 5.7+)
Use copying array methods (toSorted,toSpliced,with) for immutable array operations (TypeScript 5.2+)
Avoid accessing instance fields viasuperin classes (TypeScript 5....
Files:
apps/web/core/constants/fetch-keys.tspackages/services/src/developer/index.tspackages/i18n/src/locales/fr/translations.tspackages/i18n/src/locales/ja/translations.tspackages/i18n/src/locales/ua/translations.tsapps/web/core/components/ui/loader/settings/api-token.tsxpackages/constants/src/workspace.tspackages/constants/src/settings.tsapps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/api-tokens/page.tsxapps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar.tsxpackages/i18n/src/locales/zh-TW/translations.tspackages/i18n/src/locales/ko/translations.tsapps/web/app/routes/core.tsapps/web/core/components/api-token/token-list-item.tsxpackages/i18n/src/locales/vi-VN/translations.tspackages/i18n/src/locales/id/translations.tspackages/i18n/src/locales/it/translations.tspackages/constants/src/event-tracker/core.tspackages/i18n/src/locales/ru/translations.tspackages/services/src/developer/workspace-api-token.service.tspackages/i18n/src/locales/es/translations.tspackages/i18n/src/locales/zh-CN/translations.tspackages/i18n/src/locales/de/translations.tspackages/i18n/src/locales/sk/translations.tsapps/web/core/components/api-token/delete-token-modal.tsxpackages/i18n/src/locales/tr-TR/translations.tsapps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxpackages/i18n/src/locales/ro/translations.tspackages/i18n/src/locales/pl/translations.tspackages/i18n/src/locales/cs/translations.tspackages/i18n/src/locales/pt-BR/translations.tsapps/web/core/components/api-token/modal/create-token-modal.tsxpackages/i18n/src/locales/en/translations.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Enable TypeScript strict mode and ensure all files are fully typed
Files:
apps/web/core/constants/fetch-keys.tspackages/services/src/developer/index.tspackages/i18n/src/locales/fr/translations.tspackages/i18n/src/locales/ja/translations.tspackages/i18n/src/locales/ua/translations.tsapps/web/core/components/ui/loader/settings/api-token.tsxpackages/constants/src/workspace.tspackages/constants/src/settings.tsapps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/api-tokens/page.tsxapps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar.tsxpackages/i18n/src/locales/zh-TW/translations.tspackages/i18n/src/locales/ko/translations.tsapps/web/app/routes/core.tsapps/web/core/components/api-token/token-list-item.tsxpackages/i18n/src/locales/vi-VN/translations.tspackages/i18n/src/locales/id/translations.tspackages/i18n/src/locales/it/translations.tspackages/constants/src/event-tracker/core.tspackages/i18n/src/locales/ru/translations.tspackages/services/src/developer/workspace-api-token.service.tspackages/i18n/src/locales/es/translations.tspackages/i18n/src/locales/zh-CN/translations.tspackages/i18n/src/locales/de/translations.tspackages/i18n/src/locales/sk/translations.tsapps/web/core/components/api-token/delete-token-modal.tsxpackages/i18n/src/locales/tr-TR/translations.tsapps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxpackages/i18n/src/locales/ro/translations.tspackages/i18n/src/locales/pl/translations.tspackages/i18n/src/locales/cs/translations.tspackages/i18n/src/locales/pt-BR/translations.tsapps/web/core/components/api-token/modal/create-token-modal.tsxpackages/i18n/src/locales/en/translations.ts
**/*.{js,jsx,ts,tsx,json,css}
📄 CodeRabbit inference engine (AGENTS.md)
Use Prettier with Tailwind plugin for code formatting, run
pnpm fix:format
Files:
apps/web/core/constants/fetch-keys.tspackages/services/src/developer/index.tspackages/i18n/src/locales/fr/translations.tspackages/i18n/src/locales/ja/translations.tspackages/i18n/src/locales/ua/translations.tsapps/web/core/components/ui/loader/settings/api-token.tsxpackages/constants/src/workspace.tspackages/constants/src/settings.tsapps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/api-tokens/page.tsxapps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar.tsxpackages/i18n/src/locales/zh-TW/translations.tspackages/i18n/src/locales/ko/translations.tsapps/web/app/routes/core.tsapps/web/core/components/api-token/token-list-item.tsxpackages/i18n/src/locales/vi-VN/translations.tspackages/i18n/src/locales/id/translations.tspackages/i18n/src/locales/it/translations.tspackages/constants/src/event-tracker/core.tspackages/i18n/src/locales/ru/translations.tspackages/services/src/developer/workspace-api-token.service.tspackages/i18n/src/locales/es/translations.tspackages/i18n/src/locales/zh-CN/translations.tspackages/i18n/src/locales/de/translations.tspackages/i18n/src/locales/sk/translations.tsapps/web/core/components/api-token/delete-token-modal.tsxpackages/i18n/src/locales/tr-TR/translations.tsapps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxpackages/i18n/src/locales/ro/translations.tspackages/i18n/src/locales/pl/translations.tspackages/i18n/src/locales/cs/translations.tspackages/i18n/src/locales/pt-BR/translations.tsapps/web/core/components/api-token/modal/create-token-modal.tsxpackages/i18n/src/locales/en/translations.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,jsx,ts,tsx}: Use ESLint with shared config across packages, adhering to max warnings limits per package
Use camelCase for variable and function names, PascalCase for components and types
Use try-catch with proper error types and log errors appropriately
Files:
apps/web/core/constants/fetch-keys.tspackages/services/src/developer/index.tspackages/i18n/src/locales/fr/translations.tspackages/i18n/src/locales/ja/translations.tspackages/i18n/src/locales/ua/translations.tsapps/web/core/components/ui/loader/settings/api-token.tsxpackages/constants/src/workspace.tspackages/constants/src/settings.tsapps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/api-tokens/page.tsxapps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar.tsxpackages/i18n/src/locales/zh-TW/translations.tspackages/i18n/src/locales/ko/translations.tsapps/web/app/routes/core.tsapps/web/core/components/api-token/token-list-item.tsxpackages/i18n/src/locales/vi-VN/translations.tspackages/i18n/src/locales/id/translations.tspackages/i18n/src/locales/it/translations.tspackages/constants/src/event-tracker/core.tspackages/i18n/src/locales/ru/translations.tspackages/services/src/developer/workspace-api-token.service.tspackages/i18n/src/locales/es/translations.tspackages/i18n/src/locales/zh-CN/translations.tspackages/i18n/src/locales/de/translations.tspackages/i18n/src/locales/sk/translations.tsapps/web/core/components/api-token/delete-token-modal.tsxpackages/i18n/src/locales/tr-TR/translations.tsapps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxpackages/i18n/src/locales/ro/translations.tspackages/i18n/src/locales/pl/translations.tspackages/i18n/src/locales/cs/translations.tspackages/i18n/src/locales/pt-BR/translations.tsapps/web/core/components/api-token/modal/create-token-modal.tsxpackages/i18n/src/locales/en/translations.ts
🧠 Learnings (12)
📚 Learning: 2025-12-12T15:20:36.542Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-12T15:20:36.542Z
Learning: Applies to **/package.json : Use `workspace:*` for internal packages and `catalog:` for external dependencies in imports
Applied to files:
apps/web/core/constants/fetch-keys.tspackages/services/src/developer/index.ts
📚 Learning: 2025-09-02T08:14:49.260Z
Learnt from: sriramveeraghanta
Repo: makeplane/plane PR: 7697
File: apps/web/app/(all)/[workspaceSlug]/(projects)/header.tsx:12-13
Timestamp: 2025-09-02T08:14:49.260Z
Learning: The star-us-link.tsx file in apps/web/app/(all)/[workspaceSlug]/(projects)/ already has "use client" directive at the top, making it a proper Client Component for hook usage.
Applied to files:
packages/services/src/developer/index.ts
📚 Learning: 2025-10-21T17:22:05.204Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7989
File: apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx:45-46
Timestamp: 2025-10-21T17:22:05.204Z
Learning: In the makeplane/plane repository, the refactor from useParams() to params prop is specifically scoped to page.tsx and layout.tsx files in apps/web/app (Next.js App Router pattern). Other components (hooks, regular client components, utilities) should continue using the useParams() hook as that is the correct pattern for non-route components.
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/api-tokens/page.tsxapps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar.tsxapps/web/core/components/api-token/token-list-item.tsxapps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxapps/web/core/components/api-token/modal/create-token-modal.tsx
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.
Applied to files:
apps/api/plane/api/middleware/api_authentication.py
📚 Learning: 2025-12-23T14:18:32.899Z
Learnt from: dheeru0198
Repo: makeplane/plane PR: 8339
File: apps/api/plane/db/models/api.py:35-35
Timestamp: 2025-12-23T14:18:32.899Z
Learning: Django REST Framework rate limit strings are flexible: only the first character of the time unit matters. Acceptable formats include: "60/s", "60/sec", "60/second" (all equivalent), "60/m", "60/min", "60/minute" (all equivalent), "60/h", "60/hr", "60/hour" (all equivalent), and "60/d", "60/day" (all equivalent). Abbreviations like "min" are valid and do not need to be changed to "minute". Apply this guidance to any Python files in the project that configure DRF throttling rules.
Applied to files:
apps/api/plane/api/middleware/api_authentication.pyapps/api/plane/api/views/base.pyapps/api/plane/app/views/api/__init__.pyapps/api/plane/app/views/__init__.pyapps/api/plane/db/models/workspace.pyapps/api/plane/api/rate_limit.pyapps/api/plane/db/models/api.pyapps/api/plane/db/models/user.pyapps/api/plane/app/views/api/workspace.pyapps/api/plane/app/views/api/base.pyapps/api/plane/app/serializers/api.pyapps/api/plane/db/migrations/0113_webhook_version.pyapps/api/plane/app/views/api/service.pyapps/api/plane/app/urls/api.pyapps/api/plane/db/models/webhook.py
📚 Learning: 2025-10-09T20:42:31.843Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T20:42:31.843Z
Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar.tsxapps/web/core/components/api-token/token-list-item.tsxapps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxapps/web/core/components/api-token/modal/create-token-modal.tsx
📚 Learning: 2025-10-01T15:30:17.605Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7888
File: packages/propel/src/avatar/avatar.stories.tsx:2-3
Timestamp: 2025-10-01T15:30:17.605Z
Learning: In the makeplane/plane repository, avoid suggesting inline type imports (e.g., `import { Avatar, type TAvatarSize }`) due to bundler compatibility issues. Keep type imports and value imports as separate statements.
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar.tsxapps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx
📚 Learning: 2025-05-14T13:16:23.323Z
Learnt from: vamsikrishnamathala
Repo: makeplane/plane PR: 7061
File: web/core/components/workspace-notifications/root.tsx:18-18
Timestamp: 2025-05-14T13:16:23.323Z
Learning: In the Plane project codebase, the path alias `@/plane-web` resolves to the `ce` directory, making imports like `@/plane-web/hooks/use-notification-preview` correctly resolve to files in `web/ce/hooks/`.
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar.tsx
📚 Learning: 2025-12-29T08:58:39.209Z
Learnt from: dheeru0198
Repo: makeplane/plane PR: 8339
File: apps/api/plane/db/migrations/0113_webhook_version.py:7-14
Timestamp: 2025-12-29T08:58:39.209Z
Learning: In the Plane codebase, when adding product tour or onboarding fields via migrations, it's intentional to set existing records to `True` (completed) while having the model default to `False` for new records. This ensures existing users don't see tours they don't need.
Applied to files:
apps/api/plane/db/models/workspace.pyapps/api/plane/db/models/user.pyapps/api/plane/db/migrations/0113_webhook_version.py
📚 Learning: 2025-10-09T22:12:26.424Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T22:12:26.424Z
Learning: When `types/react` is installed in a TypeScript project (which is standard for React + TypeScript codebases), React types (React.FC, React.ReactNode, React.ComponentProps, etc.) are globally available by design. These type annotations can and should be used without explicitly importing the React namespace. This is a TypeScript/DefinitelyTyped feature, not codebase-specific configuration.
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx
📚 Learning: 2025-12-12T15:20:36.542Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-12T15:20:36.542Z
Learning: Applies to packages/shared-state/**/*.{ts,tsx} : Maintain MobX stores in `packages/shared-state` using reactive patterns
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx
📚 Learning: 2025-03-11T19:42:41.769Z
Learnt from: janreges
Repo: makeplane/plane PR: 6743
File: packages/i18n/src/store/index.ts:160-161
Timestamp: 2025-03-11T19:42:41.769Z
Learning: In the Plane project, the file 'packages/i18n/src/store/index.ts' already includes support for Polish language translations with the case "pl".
Applied to files:
packages/i18n/src/locales/pl/translations.tspackages/i18n/src/locales/cs/translations.ts
🧬 Code graph analysis (15)
apps/web/core/constants/fetch-keys.ts (1)
apps/space/core/store/publish/publish.store.ts (1)
workspaceSlug(93-95)
packages/constants/src/settings.ts (1)
packages/constants/src/workspace.ts (1)
WORKSPACE_SETTINGS(74-117)
apps/api/plane/api/middleware/api_authentication.py (3)
apps/api/plane/db/models/api.py (1)
APIToken(19-44)apps/api/plane/db/models/workspace.py (1)
Workspace(125-188)apps/api/plane/tests/conftest.py (1)
api_token(46-53)
apps/api/plane/api/views/base.py (2)
apps/api/plane/api/rate_limit.py (3)
ApiKeyRateThrottle(9-47)ServiceTokenRateThrottle(50-88)WorkspaceTokenRateThrottle(91-126)apps/api/plane/db/models/api.py (1)
APIToken(19-44)
apps/api/plane/app/views/api/__init__.py (3)
apps/api/plane/app/views/api/base.py (1)
ApiTokenEndpoint(16-58)apps/api/plane/app/views/api/service.py (1)
ServiceApiTokenEndpoint(15-37)apps/api/plane/app/views/api/workspace.py (1)
WorkspaceAPITokenEndpoint(18-59)
apps/api/plane/app/views/__init__.py (2)
apps/api/plane/app/views/api/base.py (1)
ApiTokenEndpoint(16-58)apps/api/plane/app/views/api/workspace.py (1)
WorkspaceAPITokenEndpoint(18-59)
packages/services/src/developer/workspace-api-token.service.ts (2)
packages/constants/src/endpoints.ts (1)
API_BASE_URL(1-1)apps/space/core/store/publish/publish.store.ts (1)
workspaceSlug(93-95)
apps/api/plane/api/rate_limit.py (1)
apps/api/plane/db/models/api.py (1)
APIToken(19-44)
apps/web/core/components/api-token/delete-token-modal.tsx (4)
packages/services/src/developer/workspace-api-token.service.ts (1)
WorkspaceAPITokenService(10-73)apps/space/core/store/publish/publish.store.ts (1)
workspaceSlug(93-95)apps/web/core/constants/fetch-keys.ts (2)
WORKSPACE_API_TOKENS_LIST(146-147)API_TOKENS_LIST(145-145)packages/constants/src/event-tracker/core.ts (2)
WORKSPACE_SETTINGS_TRACKER_EVENTS(475-489)PROFILE_SETTINGS_TRACKER_EVENTS(434-448)
apps/api/plane/db/models/user.py (1)
apps/api/plane/utils/exporters/schemas/base.py (1)
BooleanField(97-105)
apps/api/plane/app/views/api/workspace.py (4)
apps/api/plane/app/serializers/api.py (2)
APITokenSerializer(12-23)APITokenReadSerializer(26-36)apps/api/plane/utils/permissions/workspace.py (1)
WorkSpaceAdminPermission(57-67)apps/api/plane/app/views/api/base.py (3)
post(17-35)get(37-45)delete(47-50)apps/api/plane/tests/conftest.py (1)
api_token(46-53)
apps/api/plane/app/views/api/base.py (3)
apps/api/plane/db/models/api.py (1)
APIToken(19-44)apps/api/plane/app/serializers/api.py (1)
APITokenReadSerializer(26-36)apps/api/plane/app/views/api/workspace.py (1)
get(46-54)
apps/api/plane/db/migrations/0113_webhook_version.py (1)
apps/api/plane/db/models/workspace.py (2)
get_default_product_tour(115-122)WorkspaceUserProperties(313-355)
apps/web/core/components/api-token/modal/create-token-modal.tsx (4)
packages/services/src/developer/workspace-api-token.service.ts (1)
WorkspaceAPITokenService(10-73)apps/space/core/store/publish/publish.store.ts (1)
workspaceSlug(93-95)apps/web/core/constants/fetch-keys.ts (2)
WORKSPACE_API_TOKENS_LIST(146-147)API_TOKENS_LIST(145-145)packages/constants/src/event-tracker/core.ts (2)
WORKSPACE_SETTINGS_TRACKER_EVENTS(475-489)PROFILE_SETTINGS_TRACKER_EVENTS(434-448)
apps/api/plane/app/urls/api.py (1)
apps/api/plane/app/views/api/workspace.py (1)
WorkspaceAPITokenEndpoint(18-59)
⏰ 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). (1)
- GitHub Check: Build packages
🔇 Additional comments (62)
packages/i18n/src/locales/id/translations.ts (1)
1723-1723: LGTM! Translation correctly reflects workspace-scoped tokens.The Indonesian translation has been appropriately updated to specify "workspace access token" instead of a generic API token, aligning with the PR's implementation of workspace-scoped API token management.
packages/i18n/src/locales/it/translations.ts (1)
1727-1727: LGTM! Translation accurately reflects workspace-scoped API tokens.The Italian translation correctly updates the label from a generic "Add API token" to the more specific "Add workspace access token", properly reflecting the workspace-scoped nature of the API tokens feature being implemented in this PR.
packages/i18n/src/locales/ko/translations.ts (1)
1706-1706: LGTM! Translation update aligns with workspace API token feature.The Korean translation has been appropriately updated from "API 토큰 추가" (Add API token) to "워크스페이스 액세스 토큰 추가" (Add workspace access token), which accurately reflects the workspace-scoped nature of the new token functionality introduced in this PR.
packages/i18n/src/locales/sk/translations.ts (1)
1714-1714: LGTM! Translation aligns with workspace API tokens feature.The updated Slovak translation correctly reflects the workspace-scoped nature of the API tokens being implemented in this PR. The change from "Pridať API token" (Add API token) to "Pridať token prístupu k pracovnému priestoru" (Add workspace access token) provides better clarity and specificity for users.
packages/i18n/src/locales/pt-BR/translations.ts (1)
1735-1735: LGTM! Translation improvement enhances clarity.The updated translation from "Adicionar token de API" to "Adicionar token de acesso ao espaço de trabalho" is more specific and accurately reflects that these are workspace-scoped tokens rather than generic API tokens. This aligns well with the PR's objective of implementing workspace API token functionality.
packages/i18n/src/locales/vi-VN/translations.ts (1)
1723-1723: LGTM! Translation improves clarity for workspace-scoped tokens.The updated Vietnamese translation now specifies "Thêm token truy cập không gian làm việc" (Add workspace access token) instead of the generic "Thêm token API" (Add API token). This change better reflects the workspace-scoped nature of these tokens and aligns with the PR's objective to implement workspace API token management.
The terminology is consistent with other workspace-related translations throughout this file that use "không gian làm việc" for workspace.
packages/i18n/src/locales/ru/translations.ts (1)
1717-1717: LGTM! Translation accurately reflects workspace-scoped API tokens.The Russian translation has been appropriately updated from "Добавить токен" (Add token) to "Добавить токен доступа к рабочему пространству" (Add workspace access token). This change is grammatically correct, natural in Russian, and aligns perfectly with the PR's objective of implementing workspace-scoped API tokens.
packages/i18n/src/locales/tr-TR/translations.ts (1)
1723-1723: LGTM! Translation correctly updated for workspace-scoped tokens.The Turkish translation accurately reflects the workspace access token terminology introduced in this PR. The grammar and apostrophe usage with "token'ı" follows proper Turkish orthography conventions.
packages/i18n/src/locales/zh-TW/translations.ts (1)
1695-1695: LGTM! Translation update correctly reflects workspace-scoped token terminology.The updated translation from "新增 API 權杖" to "新增工作區存取權杖" (Add Workspace Access Token) accurately reflects the workspace-scoped nature of the API tokens introduced in this PR. The translation is grammatically correct and consistent with other workspace-related terminology used throughout the file.
packages/i18n/src/locales/zh-CN/translations.ts (1)
1692-1694: Translation correctly implements workspace-scoped API tokens across all locales.The update from "添加 API 令牌" to "添加工作区访问令牌" appropriately adds workspace context and is consistently applied across all 19+ locale files, ensuring uniform labeling throughout the application.
packages/i18n/src/locales/pl/translations.ts (1)
1715-1715: LGTM! Translation accurately reflects workspace-scoped API tokens.The updated Polish translation correctly changes from a generic "API token" to "workspace access token," aligning with the PR's workspace-scoped API token implementation. The terminology "obszar roboczy" (workspace) is consistent throughout the file. This change is part of a coordinated translation update across all 18+ language files in the project, confirming the consistency of the effort.
The new translation is notably longer (43 vs 19 characters). Consider verifying that this text displays properly in the UI, especially in buttons or constrained layouts, though the proportional length increase is consistent with the corresponding changes in other languages.
packages/i18n/src/locales/cs/translations.ts (1)
1712-1712: LGTM! Translation updated to reflect workspace-scoped tokens.The Czech translation has been correctly updated from "Add API token" to "Add workspace access token", aligning with the broader workspace API tokens feature introduced in this PR.
apps/api/plane/app/serializers/api.py (1)
1-6: LGTM! Import organization improved.The explicit imports for
django.utils.timezoneandrest_framework.serializersimprove code clarity with no functional changes to the serializers.apps/api/plane/db/models/workspace.py (2)
115-122: LGTM! Product tour defaults correctly configured.The
get_default_product_tour()function appropriately returnsFalsefor all tour flags on new records, while the migration (0113_webhook_version.py) sets existing records toTrueto mark tours as completed for existing users.Based on learnings, this pattern ensures existing users don't see tours they don't need.
338-338: LGTM! Product tour field addition.The
product_tourJSONField correctly uses theget_default_product_tourfunction as its default, enabling per-user tracking of product tour completion across different features.packages/i18n/src/locales/es/translations.ts (1)
1737-1737: LGTM! Spanish translation updated for workspace tokens.The translation has been correctly updated from "Add API token" to "Add workspace access token", maintaining consistency with the workspace API tokens feature across all supported locales.
apps/api/plane/db/models/api.py (1)
35-35: LGTM! Rate limit field addition.The
allowed_rate_limitfield with default"60/min"correctly supports per-token rate limiting for workspace API tokens. The format is valid for Django REST Framework throttling.Based on learnings, DRF rate limit abbreviations like "min" are valid and equivalent to "minute".
packages/i18n/src/locales/fr/translations.ts (1)
1734-1753: Workspace API token label update looks goodThe new wording clearly indicates a workspace-scoped access token and remains consistent with surrounding API token copy.
packages/i18n/src/locales/ja/translations.ts (1)
1711-1731: Japanese workspace token label aligns with new semanticsThe updated
add_tokenstring correctly clarifies this as a workspace access token while remaining consistent with the surrounding API token copy.apps/api/plane/app/urls/api.py (1)
2-30: Workspace API token endpoints are wired consistentlyThe new import and URL patterns cleanly extend the existing token API surface:
workspaces/<str:slug>/api-tokens/andworkspaces/<str:slug>/api-tokens/<uuid:pk>/map toWorkspaceAPITokenEndpoint, matching its(slug, pk?)signature.- Naming (
workspace-api-tokens,workspace-api-tokens-details) follows the existingapi-tokensconventions.One thing to double‑check outside this file:
WorkspaceAPITokenEndpointcurrently exposesPOST/GET/DELETEonly. If you ever need to edit workspace tokens (e.g., label or expiry) similar to user tokens, you’ll also want aPATCHhandler and corresponding expectations on the client.packages/i18n/src/locales/ua/translations.ts (1)
1716-1735: Ukrainian workspace token label matches new conceptThe updated
add_tokentext clearly communicates that this is a workspace access token and stays consistent with the rest of the workspace settings section.apps/api/plane/app/views/__init__.py (1)
164-165: Publicly exportingWorkspaceAPITokenEndpointis consistentRe‑exporting
WorkspaceAPITokenEndpointalongsideApiTokenEndpointandServiceApiTokenEndpointkeeps the views package’s public surface coherent and simplifies imports for URL configuration.apps/api/plane/db/models/webhook.py (1)
41-41: LGTM! Version field addition looks good.The addition of the
versionfield to the Webhook model is straightforward and follows Django model conventions. The default value of "v1" and max_length of 50 are appropriate for version tracking.packages/services/src/developer/index.ts (1)
3-3: LGTM! Service export follows existing patterns.The re-export of the workspace API token service is consistent with the existing service exports in this file and appropriately extends the public API surface.
packages/i18n/src/locales/de/translations.ts (1)
1732-1732: LGTM! Translation update aligns with workspace-scoped tokens.The translation change from "API-Token hinzufügen" to "Workspace-Zugriffstoken hinzufügen" (Add API Token → Add Workspace Access Token) appropriately reflects the workspace-scoped nature of the new token functionality.
apps/api/plane/db/models/user.py (1)
236-237: LGTM! Navigation tour field follows established patterns.The addition of
is_navigation_tour_completedfollows the established pattern for product tour fields in the Profile model. The default ofFalseis appropriate for new users, with existing users being handled via migration.Based on learnings, this aligns with the project's approach to tour/onboarding fields.
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar.tsx (1)
2-2: LGTM! Icon addition is appropriate and consistent.The addition of the
KeyRoundicon for API tokens is:
- Semantically appropriate for representing API tokens/keys
- Consistent with the existing icon mapping pattern
- Properly imported from lucide-react
Also applies to: 28-28
apps/web/core/constants/fetch-keys.ts (1)
146-147: LGTM! Fetch key follows established patterns.The
WORKSPACE_API_TOKENS_LISTfunction follows the same pattern as other workspace-scoped fetch keys in this file, using consistent naming conventions and thetoUpperCase()transformation.apps/api/plane/app/views/api/__init__.py (1)
1-3: LGTM! Clean API surface for token endpoints.The module consolidates token endpoint exports appropriately, following standard Python package patterns and creating a clean public API surface.
apps/api/plane/app/views/api/base.py (1)
37-45: LGTM! GET methods properly filter workspace tokens.The addition of
workspace_id__isnull=Trueto both the list and detail GET methods ensures that workspace-scoped tokens are not exposed through the user API token endpoints. This is a critical security enhancement that properly separates user tokens from workspace tokens.apps/web/core/components/api-token/token-list-item.tsx (2)
25-27: LGTM!The conditional tracker element selection correctly distinguishes between workspace-scoped and profile-scoped contexts. The ternary expression is clear and the tracker constants are appropriately named.
31-36: LGTM!The
workspaceSlugprop is correctly propagated toDeleteApiTokenModal, enabling workspace-scoped token deletion when applicable.packages/constants/src/settings.ts (1)
40-40: LGTM!The
api-tokenssetting is correctly added to theDEVELOPERcategory, consistent with the workspace settings structure.packages/constants/src/workspace.ts (2)
110-116: LGTM!The new
api-tokensworkspace setting follows the established pattern with correct access control (admin-only), consistent href structure, and proper highlight function. The i18n label key aligns with the translation structure.
135-135: LGTM!The
api-tokensentry is correctly appended toWORKSPACE_SETTINGS_LINKS, making it available in workspace settings navigation.apps/web/core/components/ui/loader/settings/api-token.tsx (1)
3-12: LGTM!Good refactor to accept
titleas a prop instead of hardcoding the translation. This makes the loader reusable across different contexts (workspace and profile API tokens pages) while delegating translation responsibility to the caller.apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/api-tokens/page.tsx (4)
26-26: Module-level service instantiation is acceptable here.The
WorkspaceAPITokenServiceis instantiated at module level. This is a common pattern for stateless services and is fine since the service likely doesn't hold request-specific state. Just ensure the service class is designed to be reused across requests.
41-44: LGTM!The SWR conditional fetching pattern is correctly implemented—both the key and fetcher are
nullwhen the user lacks admin permissions, preventing unauthorized API calls while maintaining the hooks' call order.
50-52: LGTM!Good guard clause: returns
NotAuthorizedViewwhen user info is loaded but lacks admin permissions. TheworkspaceUserInfocheck ensures the permission state is determined before rendering the unauthorized view.
79-86: Consider adding a key prop stability check.The token list renders correctly with
token.idas the key. The implementation is clean.apps/api/plane/api/middleware/api_authentication.py (1)
48-49: All workspace-scoped API routes consistently useslugas the URL parameter name, making the middleware's assumption correct. No action needed.Likely an incorrect or invalid review comment.
apps/api/plane/api/views/base.py (1)
63-75: LGTM!The throttle selection logic is well-structured:
- Single database query for the base queryset
- In-memory filtering for service and workspace tokens
- Clear priority: service tokens → workspace tokens → default API key throttle
- Early returns prevent unnecessary checks
Note: While the APIToken model does not enforce mutual exclusivity between
is_service=Trueandworkspace_idat the database level, the current logic handles this correctly by prioritizing service tokens over workspace tokens, so any ambiguous token would be treated as a service token as intended.apps/api/plane/api/rate_limit.py (1)
102-110: Database query on every request may impact performance.The
allow_requestmethod queries the database for the token'sallowed_rate_limiton every request. Consider caching the rate limit (e.g., in Django's cache framework with the API key as the cache key) to reduce database load under high traffic.apps/web/core/components/api-token/modal/create-token-modal.tsx (3)
1-26: LGTM! Imports and service initialization are correct.The workspace-scoped token support is properly set up with appropriate imports, props type extension, and service instantiation.
54-70: LGTM! API call selection and cache mutation are correctly implemented.The conditional logic properly selects the appropriate service and mutation key based on whether the token is workspace-scoped or global.
71-95: LGTM! Event tracking properly differentiates workspace vs. profile contexts.Success and error event tracking correctly uses workspace-specific or profile-specific event names based on the context, ensuring accurate analytics.
packages/services/src/developer/workspace-api-token.service.ts (3)
1-13: LGTM! Class structure and constructor follow established patterns.The service class correctly extends APIService and follows the same initialization pattern as other service classes in the codebase.
21-42: LGTM! CRUD methods are correctly implemented.The list and retrieve methods properly construct workspace-scoped endpoints and handle responses and errors consistently.
51-72: LGTM! Create and destroy methods are correctly implemented.Both methods properly construct workspace-scoped endpoints, handle payloads appropriately, and follow consistent error handling patterns.
apps/web/core/components/api-token/delete-token-modal.tsx (3)
1-26: LGTM! Setup mirrors create-token-modal pattern correctly.Imports, props extension, and service instantiation follow the same pattern established in the create modal, ensuring consistency.
37-56: LGTM! Deletion logic and cache management are correct.The conditional service selection and cache mutation properly handle both workspace-scoped and global token deletion.
57-86: LGTM! Event tracking properly differentiates contexts.Success and error tracking correctly uses workspace-specific or profile-specific events, maintaining consistency with the create modal implementation.
packages/i18n/src/locales/en/translations.ts (2)
1410-1413: LGTM! Translation key renamed for consistency.The key rename from
headingtotitlein account settings maintains the same display text while improving naming consistency.
1573-1578: LGTM! Workspace token translations properly added.The new translation keys clearly differentiate workspace tokens from personal tokens with appropriate labels:
- "Workspace Access Tokens" for heading/title
- "Add Workspace access token" for the action button
packages/constants/src/event-tracker/core.ts (1)
486-508: LGTM! Workspace-scoped event tracking constants properly added.The new PAT events and UI element identifiers correctly distinguish workspace-level token tracking from profile-level tracking, with appropriate prefixing to avoid naming collisions.
apps/api/plane/app/views/api/workspace.py (1)
18-21: LGTM! Permission class correctly restricts access.The WorkSpaceAdminPermission correctly restricts token management to workspace admins and members, which is appropriate for workspace-scoped API tokens.
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx (3)
1-40: LGTM! Component structure and loading state are correct.The component properly uses FC type, mobx observer, and handles the loading state with appropriate translation keys for account-scoped tokens.
46-58: LGTM! Header with button correctly configured.The SettingsHeading properly includes an add-token button with correct event tracking for the profile/account context (no workspaceSlug passed to modal).
59-86: LGTM! Token list and empty state are correctly implemented.The conditional rendering properly shows either the token list or an appropriately configured empty state, with correct event tracking for the account context.
apps/api/plane/db/migrations/0113_webhook_version.py (3)
7-21: LGTM! Product tour default correctly set for existing users.The migration sets existing records to
True(completed) while the model default isFalsefor new records. This is intentional to prevent existing users from seeing tours they don't need.Based on learnings, this pattern is standard in the Plane codebase.
17-21: LGTM! Data migration efficiently populates product tour.The bulk update approach is performant and correctly sets the product tour field for all existing workspace user properties records.
30-52: LGTM! All field additions are correctly configured.The migration adds four fields with appropriate defaults:
webhook.version: versioning support for webhooksprofile.is_navigation_tour_completed: tracking navigation tour completionworkspaceuserproperties.product_tour: feature tour tracking with correct default patternapitoken.allowed_rate_limit: rate limit configuration (format is valid per DRF standards)The RunPython operation runs after field additions, ensuring correct execution order.
Based on learnings, the rate limit format '60/min' is valid for DRF throttling.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
apps/api/plane/app/views/api/workspace.py (1)
23-44: Add error handling for workspace lookup.Line 31 uses
Workspace.objects.get(slug=slug)without error handling. If the workspace doesn't exist, this will raiseDoesNotExistand return a 500 error instead of a proper 404 response.🔎 Recommended fix using get_object_or_404
+from django.shortcuts import get_object_or_404 + class WorkspaceAPITokenEndpoint(BaseAPIView): ... def post(self, request: Request, slug: str) -> Response: label = request.data.get("label", str(uuid4().hex)) description = request.data.get("description", "") expired_at = request.data.get("expired_at", None) # Check the user type user_type = 1 if request.user.is_bot else 0 - workspace = Workspace.objects.get(slug=slug) + workspace = get_object_or_404(Workspace, slug=slug) api_token = APIToken.objects.create( label=label, description=description, user=request.user, user_type=user_type, expired_at=expired_at, workspace=workspace, ) serializer = APITokenSerializer(api_token) return Response(serializer.data, status=status.HTTP_201_CREATED)
🧹 Nitpick comments (1)
apps/api/plane/app/views/api/workspace.py (1)
56-62: Fix variable naming inconsistency.The variable
api_tokens(plural) should beapi_token(singular) when retrieving a single object for clarity.🔎 Proposed refactor
else: try: - api_tokens = APIToken.objects.get(workspace__slug=slug, pk=pk, user=request.user) + api_token = APIToken.objects.get(workspace__slug=slug, pk=pk, user=request.user) except APIToken.DoesNotExist: return Response({"error": "API token does not exist"}, status=status.HTTP_404_NOT_FOUND) - serializer = APITokenReadSerializer(api_tokens) + serializer = APITokenReadSerializer(api_token) return Response(serializer.data, status=status.HTTP_200_OK)
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/api/plane/app/views/api/workspace.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.
Applied to files:
apps/api/plane/app/views/api/workspace.py
📚 Learning: 2025-12-23T14:18:32.899Z
Learnt from: dheeru0198
Repo: makeplane/plane PR: 8339
File: apps/api/plane/db/models/api.py:35-35
Timestamp: 2025-12-23T14:18:32.899Z
Learning: Django REST Framework rate limit strings are flexible: only the first character of the time unit matters. Acceptable formats include: "60/s", "60/sec", "60/second" (all equivalent), "60/m", "60/min", "60/minute" (all equivalent), "60/h", "60/hr", "60/hour" (all equivalent), and "60/d", "60/day" (all equivalent). Abbreviations like "min" are valid and do not need to be changed to "minute". Apply this guidance to any Python files in the project that configure DRF throttling rules.
Applied to files:
apps/api/plane/app/views/api/workspace.py
⏰ 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). (3)
- GitHub Check: check:lint
- GitHub Check: check:types
- GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
apps/api/plane/app/views/api/workspace.py (2)
1-21: LGTM!Imports are well-organized and the permission class appropriately restricts access to workspace administrators.
64-71: LGTM!The DELETE method correctly implements error handling with try-except and returns appropriate status codes (404 for not found, 204 for successful deletion). The filters properly enforce security by checking workspace, user, and service token status.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/web/core/components/api-token/delete-token-modal.tsx (1)
38-66: Clean implementation of workspace-scoped deletion.The conditional logic properly handles both workspace-scoped and global token deletion flows. The SWR cache invalidation correctly uses the workspace-specific key when
workspaceSlugis provided, and error handling is appropriate.💡 Optional: Add workspace slug validation
Consider adding validation to ensure
workspaceSlugis a non-empty string when provided, to prevent malformed API requests:const handleDeletion = async () => { setDeleteLoading(true); + if (workspaceSlug !== undefined && !workspaceSlug.trim()) { + setToast({ + type: TOAST_TYPE.ERROR, + title: t("workspace_settings.settings.api_tokens.delete.error.title"), + message: "Invalid workspace identifier", + }); + setDeleteLoading(false); + return; + } + const apiCall = workspaceSlug ? workspaceApiTokenService.destroy(workspaceSlug, tokenId) : apiTokenService.destroy(tokenId);This is likely unnecessary if validation happens upstream, but adds an extra safety layer.
apps/web/core/components/api-token/modal/create-token-modal.tsx (1)
51-77: Solid implementation of workspace-scoped token creation.The conditional logic properly handles both workspace-scoped and global token creation. The SWR cache mutation correctly adds the new token to the appropriate list, and the automatic secret key download is a nice UX touch.
💡 Optional: Consider workspace slug validation
Similar to the delete modal, you could add validation for the
workspaceSlugparameter:const handleCreateToken = async (data: Partial<IApiToken>) => { + if (workspaceSlug !== undefined && !workspaceSlug.trim()) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Invalid workspace identifier", + }); + return; + } + // make the request to generate the token const apiCall = workspaceSlug ? workspaceApiTokenService.create(workspaceSlug, data) : apiTokenService.create(data);This adds consistency with the delete modal if you decide to implement validation there.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxapps/web/core/components/api-token/delete-token-modal.tsxapps/web/core/components/api-token/modal/create-token-modal.tsxapps/web/core/constants/fetch-keys.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,mts,cts}
📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)
**/*.{ts,tsx,mts,cts}: Useconsttype parameters for more precise literal inference in TypeScript 5.0+
Use thesatisfiesoperator to validate types without widening them
Leverage inferred type predicates to reduce the need for explicitisreturn types in filter/check functions
UseNoInfer<T>utility to block inference for specific type arguments when they should be determined by other arguments
Utilize narrowing inswitch(true)blocks for control flow analysis (TypeScript 5.3+)
Rely on narrowing from direct boolean comparisons for type guards
Trust preserved narrowing in closures when variables aren't modified after the check (TypeScript 5.4+)
Use constant indices to narrow object/array properties (TypeScript 5.5+)
Use standard ECMAScript decorators (Stage 3) instead of legacyexperimentalDecorators
Useusingdeclarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)
Usewith { type: "json" }for import attributes; avoid deprecatedassertsyntax (TypeScript 5.3/5.8+)
Useimport typeexplicitly when importing types to ensure they are erased during compilation, respectingverbatimModuleSyntaxflag
Use.ts,.mts,.ctsextensions inimport typestatements (TypeScript 5.2+)
Useimport type { Type } from "mod" with { "resolution-mode": "import" }for specific module resolution contexts (TypeScript 5.3+)
Use new iterator methods (map, filter, etc.) if targeting modern environments (TypeScript 5.6+)
Utilize newSetmethods likeunion,intersection, etc., when available (TypeScript 5.5+)
UseObject.groupBy/Map.groupBystandard methods for grouping instead of external libraries (TypeScript 5.4+)
UsePromise.withResolvers()for creating promises with exposed resolve/reject functions (TypeScript 5.7+)
Use copying array methods (toSorted,toSpliced,with) for immutable array operations (TypeScript 5.2+)
Avoid accessing instance fields viasuperin classes (TypeScript 5....
Files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxapps/web/core/components/api-token/delete-token-modal.tsxapps/web/core/components/api-token/modal/create-token-modal.tsxapps/web/core/constants/fetch-keys.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Enable TypeScript strict mode and ensure all files are fully typed
Files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxapps/web/core/components/api-token/delete-token-modal.tsxapps/web/core/components/api-token/modal/create-token-modal.tsxapps/web/core/constants/fetch-keys.ts
**/*.{js,jsx,ts,tsx,json,css}
📄 CodeRabbit inference engine (AGENTS.md)
Use Prettier with Tailwind plugin for code formatting, run
pnpm fix:format
Files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxapps/web/core/components/api-token/delete-token-modal.tsxapps/web/core/components/api-token/modal/create-token-modal.tsxapps/web/core/constants/fetch-keys.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,jsx,ts,tsx}: Use ESLint with shared config across packages, adhering to max warnings limits per package
Use camelCase for variable and function names, PascalCase for components and types
Use try-catch with proper error types and log errors appropriately
Files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsxapps/web/core/components/api-token/delete-token-modal.tsxapps/web/core/components/api-token/modal/create-token-modal.tsxapps/web/core/constants/fetch-keys.ts
🧠 Learnings (6)
📚 Learning: 2025-10-09T20:42:31.843Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T20:42:31.843Z
Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx
📚 Learning: 2025-10-09T22:12:26.424Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T22:12:26.424Z
Learning: When `types/react` is installed in a TypeScript project (which is standard for React + TypeScript codebases), React types (React.FC, React.ReactNode, React.ComponentProps, etc.) are globally available by design. These type annotations can and should be used without explicitly importing the React namespace. This is a TypeScript/DefinitelyTyped feature, not codebase-specific configuration.
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx
📚 Learning: 2025-12-12T15:20:36.542Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-12T15:20:36.542Z
Learning: Applies to packages/shared-state/**/*.{ts,tsx} : Maintain MobX stores in `packages/shared-state` using reactive patterns
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx
📚 Learning: 2025-10-21T17:22:05.204Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7989
File: apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx:45-46
Timestamp: 2025-10-21T17:22:05.204Z
Learning: In the makeplane/plane repository, the refactor from useParams() to params prop is specifically scoped to page.tsx and layout.tsx files in apps/web/app (Next.js App Router pattern). Other components (hooks, regular client components, utilities) should continue using the useParams() hook as that is the correct pattern for non-route components.
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx
📚 Learning: 2025-10-01T15:30:17.605Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7888
File: packages/propel/src/avatar/avatar.stories.tsx:2-3
Timestamp: 2025-10-01T15:30:17.605Z
Learning: In the makeplane/plane repository, avoid suggesting inline type imports (e.g., `import { Avatar, type TAvatarSize }`) due to bundler compatibility issues. Keep type imports and value imports as separate statements.
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx
📚 Learning: 2025-12-12T15:20:36.542Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-12T15:20:36.542Z
Learning: Applies to **/package.json : Use `workspace:*` for internal packages and `catalog:` for external dependencies in imports
Applied to files:
apps/web/core/constants/fetch-keys.ts
🧬 Code graph analysis (4)
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx (5)
apps/web/core/components/ui/loader/settings/api-token.tsx (1)
APITokenSettingsLoader(7-28)apps/web/core/components/api-token/modal/create-token-modal.tsx (1)
CreateApiTokenModal(25-93)apps/web/core/components/settings/heading.tsx (1)
SettingsHeading(17-46)apps/web/core/components/api-token/token-list-item.tsx (1)
ApiTokenListItem(18-72)packages/propel/src/empty-state/compact-empty-state.tsx (1)
EmptyStateCompact(8-62)
apps/web/core/components/api-token/delete-token-modal.tsx (3)
packages/services/src/developer/workspace-api-token.service.ts (1)
WorkspaceAPITokenService(10-73)apps/space/core/store/publish/publish.store.ts (1)
workspaceSlug(93-95)apps/web/core/constants/fetch-keys.ts (2)
WORKSPACE_API_TOKENS_LIST(149-150)API_TOKENS_LIST(148-148)
apps/web/core/components/api-token/modal/create-token-modal.tsx (2)
packages/services/src/developer/workspace-api-token.service.ts (1)
WorkspaceAPITokenService(10-73)apps/web/core/constants/fetch-keys.ts (2)
WORKSPACE_API_TOKENS_LIST(149-150)API_TOKENS_LIST(148-148)
apps/web/core/constants/fetch-keys.ts (1)
apps/space/core/store/publish/publish.store.ts (1)
workspaceSlug(93-95)
⏰ 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). (2)
- GitHub Check: Build packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (12)
apps/web/core/constants/fetch-keys.ts (1)
149-150: LGTM! Consistent with established patterns.The new
WORKSPACE_API_TOKENS_LISTfunction follows the same pattern as other workspace-scoped cache keys in this file (e.g.,WORKSPACE_MEMBERS,WORKSPACE_INTEGRATIONS). The implementation is clean and consistent.apps/web/core/components/api-token/delete-token-modal.tsx (2)
6-6: LGTM! Proper imports for workspace-scoped token management.The imports for
WorkspaceAPITokenServiceandWORKSPACE_API_TOKENS_LISTare correctly added to support the workspace-scoped deletion flow.Also applies to: 11-11
17-17: Good extension for workspace scope support.The addition of the optional
workspaceSlugprop and corresponding service instance properly enables workspace-scoped token deletion while maintaining backward compatibility with the existing global token deletion flow.Also applies to: 21-21, 24-24
apps/web/core/components/api-token/modal/create-token-modal.tsx (2)
5-5: LGTM! Consistent imports for workspace token creation.The imports align with the workspace-scoped token management pattern established in the delete modal.
Also applies to: 10-10
18-18: Good consistency with the deletion flow.The props extension and service instantiation mirror the pattern used in
delete-token-modal.tsx, maintaining consistency across the token management features.Also applies to: 23-23, 26-26
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx (7)
2-2: LGTM: Correct type import syntax.The explicit
import typestatement follows TypeScript best practices and coding guidelines for erasable type imports.
21-21: LGTM: Correct observer pattern and export.The component is properly wrapped with MobX observer for reactivity, and the export statement correctly exports the observer-wrapped component.
Also applies to: 81-83
35-37: LGTM: Correct loader usage.The loader component is properly invoked with the title prop, and the translation key is consistent with the account settings context.
44-53: LGTM: Proper SettingsHeading integration.The SettingsHeading component is correctly integrated with appropriate title, description, and button props. The button handler properly opens the token creation modal.
54-59: LGTM: Correct token list rendering.The token list is properly rendered with correct key props. The absence of
workspaceSlugprop is correct since this is an account-level token page.
61-76: LGTM: Proper empty state implementation.The EmptyStateCompact component is correctly configured with appropriate props, translation keys, and action handler. The empty state provides clear guidance for users to create their first token.
1-83: Overall structure is well-implemented.The component follows React and Next.js best practices with proper:
- Data fetching using SWR
- State management for modal interactions
- Conditional rendering for loading and empty states
- Account-scoped token management (correctly omits workspace context)
The only issue identified is the inconsistent translation key at line 48.
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/access-tokens/page.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,mts,cts}
📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)
**/*.{ts,tsx,mts,cts}: Useconsttype parameters for more precise literal inference in TypeScript 5.0+
Use thesatisfiesoperator to validate types without widening them
Leverage inferred type predicates to reduce the need for explicitisreturn types in filter/check functions
UseNoInfer<T>utility to block inference for specific type arguments when they should be determined by other arguments
Utilize narrowing inswitch(true)blocks for control flow analysis (TypeScript 5.3+)
Rely on narrowing from direct boolean comparisons for type guards
Trust preserved narrowing in closures when variables aren't modified after the check (TypeScript 5.4+)
Use constant indices to narrow object/array properties (TypeScript 5.5+)
Use standard ECMAScript decorators (Stage 3) instead of legacyexperimentalDecorators
Useusingdeclarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)
Usewith { type: "json" }for import attributes; avoid deprecatedassertsyntax (TypeScript 5.3/5.8+)
Useimport typeexplicitly when importing types to ensure they are erased during compilation, respectingverbatimModuleSyntaxflag
Use.ts,.mts,.ctsextensions inimport typestatements (TypeScript 5.2+)
Useimport type { Type } from "mod" with { "resolution-mode": "import" }for specific module resolution contexts (TypeScript 5.3+)
Use new iterator methods (map, filter, etc.) if targeting modern environments (TypeScript 5.6+)
Utilize newSetmethods likeunion,intersection, etc., when available (TypeScript 5.5+)
UseObject.groupBy/Map.groupBystandard methods for grouping instead of external libraries (TypeScript 5.4+)
UsePromise.withResolvers()for creating promises with exposed resolve/reject functions (TypeScript 5.7+)
Use copying array methods (toSorted,toSpliced,with) for immutable array operations (TypeScript 5.2+)
Avoid accessing instance fields viasuperin classes (TypeScript 5....
Files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/access-tokens/page.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Enable TypeScript strict mode and ensure all files are fully typed
Files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/access-tokens/page.tsx
**/*.{js,jsx,ts,tsx,json,css}
📄 CodeRabbit inference engine (AGENTS.md)
Use Prettier with Tailwind plugin for code formatting, run
pnpm fix:format
Files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/access-tokens/page.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,jsx,ts,tsx}: Use ESLint with shared config across packages, adhering to max warnings limits per package
Use camelCase for variable and function names, PascalCase for components and types
Use try-catch with proper error types and log errors appropriately
Files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/access-tokens/page.tsx
🧠 Learnings (1)
📚 Learning: 2025-10-21T17:22:05.204Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7989
File: apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx:45-46
Timestamp: 2025-10-21T17:22:05.204Z
Learning: In the makeplane/plane repository, the refactor from useParams() to params prop is specifically scoped to page.tsx and layout.tsx files in apps/web/app (Next.js App Router pattern). Other components (hooks, regular client components, utilities) should continue using the useParams() hook as that is the correct pattern for non-route components.
Applied to files:
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/access-tokens/page.tsx
⏰ 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). (2)
- GitHub Check: Build packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (6)
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/access-tokens/page.tsx (6)
1-22: LGTM!Imports follow best practices. The use of
import typeon line 22 ensures type erasure, and usingparamsfrom props (line 30) aligns with Next.js App Router patterns for page.tsx files.
24-24: LGTM!Module-level service instantiation is appropriate here, as frontend service classes are typically stateless HTTP client wrappers.
44-46: LGTM!Page title logic is defensive and correctly handles the case when workspace data isn't yet loaded.
48-50: LGTM!The authorization guard correctly waits for
workspaceUserInfoto load before showing the not authorized view, preventing a flash of incorrect content during initial render.
52-106: LGTM!The render logic is well-structured with appropriate conditional rendering for different states (loading, token list, empty state). The modal integration and token list rendering are correctly implemented.
Note: The loading state check at line 55 will be improved once error handling is added per the earlier comment.
109-109: LGTM!Default export wrapped with
observeris the correct pattern for this MobX-integrated Next.js page component.
| const { data: tokens } = useSWR( | ||
| canPerformWorkspaceAdminActions ? WORKSPACE_API_TOKENS_LIST(workspaceSlug) : null, | ||
| canPerformWorkspaceAdminActions ? () => workspaceApiTokenService.list(workspaceSlug) : null | ||
| ); |
There was a problem hiding this comment.
Add error handling for API failures.
The SWR hook doesn't destructure the error state, so if the API call fails, users won't see any error feedback. This creates poor UX during network or API failures.
🔎 Proposed fix to add error handling
- const { data: tokens } = useSWR(
+ const { data: tokens, error, isLoading } = useSWR(
canPerformWorkspaceAdminActions ? WORKSPACE_API_TOKENS_LIST(workspaceSlug) : null,
canPerformWorkspaceAdminActions ? () => workspaceApiTokenService.list(workspaceSlug) : null
);Then update the render logic to handle error states:
<SettingsContentWrapper>
<PageHead title={pageTitle} />
- {!tokens ? (
+ {error ? (
+ <div className="flex h-full w-full items-center justify-center">
+ <EmptyStateCompact
+ assetKey="error"
+ title={t("common.error")}
+ description={t("common.error_fetching_data")}
+ />
+ </div>
+ ) : isLoading ? (
<APITokenSettingsLoader title={t("workspace_settings.settings.api_tokens.title")} />
) : (📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { data: tokens } = useSWR( | |
| canPerformWorkspaceAdminActions ? WORKSPACE_API_TOKENS_LIST(workspaceSlug) : null, | |
| canPerformWorkspaceAdminActions ? () => workspaceApiTokenService.list(workspaceSlug) : null | |
| ); | |
| const { data: tokens, error, isLoading } = useSWR( | |
| canPerformWorkspaceAdminActions ? WORKSPACE_API_TOKENS_LIST(workspaceSlug) : null, | |
| canPerformWorkspaceAdminActions ? () => workspaceApiTokenService.list(workspaceSlug) : null | |
| ); |
🤖 Prompt for AI Agents
In
apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/access-tokens/page.tsx
around lines 39-42, the useSWR call currently only reads `data: tokens` and
ignores the `error` (and isLoading) state; update the hook to destructure
`error` (and optionally `isValidating`/`isLoading`) from useSWR, then update the
component render flow to display a user-facing error message or retry UI when
`error` is truthy (and avoid rendering token UI while loading), and also log the
error (console or telemetry) to aid debugging; keep the SWR key conditional on
canPerformWorkspaceAdminActions as-is and ensure the fetcher continues to
throw/return rejections so SWR populates `error`.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
apps/api/plane/api/middleware/api_authentication.py (1)
36-36: Missing exception handling forWorkspace.DoesNotExist.The
Workspace.objects.get(slug=workspace_slug)call will raiseWorkspace.DoesNotExistif the slug is invalid, resulting in an unhandled 500 error instead of a proper authentication failure response.🔎 Proposed fix
- workspace = Workspace.objects.get(slug=workspace_slug) + try: + workspace = Workspace.objects.get(slug=workspace_slug) + except Workspace.DoesNotExist: + raise AuthenticationFailed("Invalid workspace")
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/api/plane/api/middleware/api_authentication.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.
Applied to files:
apps/api/plane/api/middleware/api_authentication.py
📚 Learning: 2025-12-23T14:18:32.899Z
Learnt from: dheeru0198
Repo: makeplane/plane PR: 8339
File: apps/api/plane/db/models/api.py:35-35
Timestamp: 2025-12-23T14:18:32.899Z
Learning: Django REST Framework rate limit strings are flexible: only the first character of the time unit matters. Acceptable formats include: "60/s", "60/sec", "60/second" (all equivalent), "60/m", "60/min", "60/minute" (all equivalent), "60/h", "60/hr", "60/hour" (all equivalent), and "60/d", "60/day" (all equivalent). Abbreviations like "min" are valid and do not need to be changed to "minute". Apply this guidance to any Python files in the project that configure DRF throttling rules.
Applied to files:
apps/api/plane/api/middleware/api_authentication.py
🧬 Code graph analysis (1)
apps/api/plane/api/middleware/api_authentication.py (3)
apps/api/plane/db/models/api.py (1)
APIToken(19-44)apps/api/plane/db/models/workspace.py (1)
Workspace(125-188)apps/api/plane/tests/conftest.py (1)
api_token(46-53)
⏰ 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). (3)
- GitHub Check: check:types
- GitHub Check: check:lint
- GitHub Check: Analyze (javascript)
🔇 Additional comments (4)
apps/api/plane/api/middleware/api_authentication.py (4)
4-4: LGTM! Necessary imports for workspace validation.The imports are correctly added to support extracting the workspace slug from the request path and validating against the Workspace model.
Also applies to: 11-11
26-26: LGTM! Method signature updated appropriately.The addition of the
workspace_slugparameter enables workspace-scoped token validation while maintaining the existing token validation flow.
44-46: LGTM! Tracking token usage enhances security monitoring.Recording
last_usedtimestamps provides valuable audit trails for token activity and helps identify inactive or compromised tokens. The use ofupdate_fieldsoptimizes the database operation.
57-57: LGTM! Workspace slug correctly passed to validation.The workspace slug extracted from the request path is properly passed to the validation method, enabling workspace-scoped token enforcement.
There was a problem hiding this comment.
🧹 Nitpick comments (4)
apps/api/plane/api/views/base.py (1)
67-79: Two DB queries can be reduced to one.Lines 69 and 71 each execute a separate SQL query against the same token. For workspace tokens (the common new case), both queries run. Fetch the token once and branch in Python:
♻️ Proposed fix
if api_key: - api_token = APIToken.objects.filter(token=api_key) - - service_token = api_token.filter(is_service=True).first() - - workspace_token = api_token.filter(workspace_id__isnull=False).first() + api_token = APIToken.objects.filter(token=api_key).only( + "is_service", "workspace_id", "allowed_rate_limit" + ).first() - if service_token: + if api_token and api_token.is_service: throttle_classes.append(ServiceTokenRateThrottle()) return throttle_classes - if workspace_token: + if api_token and api_token.workspace_id: throttle_classes.append(WorkspaceTokenRateThrottle()) return throttle_classes🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api/plane/api/views/base.py` around lines 67 - 79, The code performs two DB queries by calling APIToken.objects.filter(token=api_key) then filtering again for service and workspace tokens; change this to fetch the token once (e.g., api_token = APIToken.objects.filter(token=api_key).first() or a get) and then branch in Python: check api_token.is_service to append ServiceTokenRateThrottle() or check api_token.workspace_id (or truthiness) to append WorkspaceTokenRateThrottle(), handling the None case gracefully and returning throttle_classes as before; update references to service_token and workspace_token to use the single api_token object.apps/api/plane/api/rate_limit.py (2)
106-114: Redundant DB query — token is already fetched inget_throttles().
get_throttles()inapps/api/plane/api/views/base.py(line 67) already queriesAPIToken.objects.filter(token=api_key)to decide which throttle to use. Thenallow_requesthere queries the same token again. This adds an unnecessary DB round-trip on every workspace-token-authenticated request.Consider passing the
allowed_rate_limitto the throttle instance at construction time inget_throttles(), so the throttle doesn't need to re-query.♻️ Suggested approach
In
rate_limit.py, accept an optional rate override at init:class WorkspaceTokenRateThrottle(SimpleRateThrottle): scope = "workspace_token" rate = "60/minute" + def __init__(self, rate_override=None): + if rate_override: + self.rate = rate_override + super().__init__() + def get_cache_key(self, request, view): api_key = request.headers.get("X-Api-Key") if not api_key: return None return f"{self.scope}:{api_key}" def allow_request(self, request, view): - api_key = request.headers.get("X-Api-Key") - - if api_key: - token = APIToken.objects.filter(token=api_key).only("allowed_rate_limit").first() - if token and token.allowed_rate_limit: - self.rate = token.allowed_rate_limit - - self.num_requests, self.duration = self.parse_rate(self.rate) - allowed = super().allow_request(request, view)Then in
views/base.py:if workspace_token: rate = workspace_token.allowed_rate_limit throttle_classes.append(WorkspaceTokenRateThrottle(rate_override=rate)) return throttle_classesThis also removes the need for the
APITokenimport inrate_limit.py.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api/plane/api/rate_limit.py` around lines 106 - 114, The allow_request method in WorkspaceTokenRateThrottle is re-querying APIToken even though get_throttles() already fetched the token; modify the throttle to accept an optional rate_override in its constructor (e.g. WorkspaceTokenRateThrottle(rate_override=...)), set self.rate and call self.parse_rate(rate_override) inside __init__ (or a dedicated initializer) so allow_request no longer queries APIToken or sets self.rate there, and update get_throttles() to pass workspace_token.allowed_rate_limit into WorkspaceTokenRateThrottle; remove the now-unused APIToken import from rate_limit.py.
95-130: Consider extracting the shared rate-limit header logic into a base mixin.All three throttle classes (
ApiKeyRateThrottle,ServiceTokenRateThrottle,WorkspaceTokenRateThrottle) duplicate the sameallow_requestpattern for computingX-RateLimit-RemainingandX-RateLimit-Resetheaders. A shared base class or mixin would eliminate the repetition.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api/plane/api/rate_limit.py` around lines 95 - 130, The three throttle classes (ApiKeyRateThrottle, ServiceTokenRateThrottle, WorkspaceTokenRateThrottle) duplicate the same allow_request logic for computing X-RateLimit-Remaining and X-RateLimit-Reset; extract that shared logic into a new mixin (e.g., RateLimitHeadersMixin) that implements an allow_request which calls super().allow_request(request, view), and on allowed requests computes now = self.timer(), prunes history from self.cache.get(self.key, []), calculates available = self.num_requests - len(history), and sets request.META["X-RateLimit-Remaining"] and request.META["X-RateLimit-Reset"]; then update ApiKeyRateThrottle, ServiceTokenRateThrottle, and WorkspaceTokenRateThrottle to inherit this mixin and remove their duplicated header computation while leaving token-specific rate overrides (e.g., the token lookup and self.rate/self.num_requests/self.duration assignments in WorkspaceTokenRateThrottle) intact so the mixin only handles the common header behavior.apps/api/plane/api/middleware/api_authentication.py (1)
48-50:last_usedwrite on every authenticated request adds DB overhead.
api_token.save(update_fields=["last_used"])executes anUPDATEquery on every single API request. Under high throughput this becomes a write bottleneck and causes row-level contention on theapi_tokenstable.Consider throttling the update (e.g., only update if
last_usedis older than N minutes) or deferring it to an async task.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api/plane/api/middleware/api_authentication.py` around lines 48 - 50, The middleware currently writes api_token.last_used on every request causing DB write contention; change api_authentication.py so the update is throttled or deferred: in the request auth path where api_token is available, compute now = timezone.now() and only call api_token.save(update_fields=["last_used"]) if api_token.last_used is None or now - api_token.last_used > timedelta(minutes=N) (choose N like 5), or instead enqueue an async job (e.g., via your task queue) to persist the last_used timestamp; ensure you reference the same api_token object and handle timezones consistently when comparing to avoid unnecessary updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@apps/api/plane/api/middleware/api_authentication.py`:
- Around line 38-43: The current logic allows workspace-scoped tokens when
workspace_slug is missing and can raise a 500 for an invalid slug; update the
condition around api_token.workspace_id and workspace_slug so that if
api_token.workspace_id is set but workspace_slug is None you immediately raise
AuthenticationFailed (reject workspace-scoped tokens on non-workspace
endpoints), and wrap the Workspace.objects.get(slug=workspace_slug) call in a
try/except catching Workspace.DoesNotExist and converting it to
AuthenticationFailed; keep the existing APIToken.DoesNotExist handling as-is and
reference the symbols api_token, workspace_slug, Workspace.objects.get,
Workspace.DoesNotExist, and AuthenticationFailed when making these changes.
---
Nitpick comments:
In `@apps/api/plane/api/middleware/api_authentication.py`:
- Around line 48-50: The middleware currently writes api_token.last_used on
every request causing DB write contention; change api_authentication.py so the
update is throttled or deferred: in the request auth path where api_token is
available, compute now = timezone.now() and only call
api_token.save(update_fields=["last_used"]) if api_token.last_used is None or
now - api_token.last_used > timedelta(minutes=N) (choose N like 5), or instead
enqueue an async job (e.g., via your task queue) to persist the last_used
timestamp; ensure you reference the same api_token object and handle timezones
consistently when comparing to avoid unnecessary updates.
In `@apps/api/plane/api/rate_limit.py`:
- Around line 106-114: The allow_request method in WorkspaceTokenRateThrottle is
re-querying APIToken even though get_throttles() already fetched the token;
modify the throttle to accept an optional rate_override in its constructor (e.g.
WorkspaceTokenRateThrottle(rate_override=...)), set self.rate and call
self.parse_rate(rate_override) inside __init__ (or a dedicated initializer) so
allow_request no longer queries APIToken or sets self.rate there, and update
get_throttles() to pass workspace_token.allowed_rate_limit into
WorkspaceTokenRateThrottle; remove the now-unused APIToken import from
rate_limit.py.
- Around line 95-130: The three throttle classes (ApiKeyRateThrottle,
ServiceTokenRateThrottle, WorkspaceTokenRateThrottle) duplicate the same
allow_request logic for computing X-RateLimit-Remaining and X-RateLimit-Reset;
extract that shared logic into a new mixin (e.g., RateLimitHeadersMixin) that
implements an allow_request which calls super().allow_request(request, view),
and on allowed requests computes now = self.timer(), prunes history from
self.cache.get(self.key, []), calculates available = self.num_requests -
len(history), and sets request.META["X-RateLimit-Remaining"] and
request.META["X-RateLimit-Reset"]; then update ApiKeyRateThrottle,
ServiceTokenRateThrottle, and WorkspaceTokenRateThrottle to inherit this mixin
and remove their duplicated header computation while leaving token-specific rate
overrides (e.g., the token lookup and self.rate/self.num_requests/self.duration
assignments in WorkspaceTokenRateThrottle) intact so the mixin only handles the
common header behavior.
In `@apps/api/plane/api/views/base.py`:
- Around line 67-79: The code performs two DB queries by calling
APIToken.objects.filter(token=api_key) then filtering again for service and
workspace tokens; change this to fetch the token once (e.g., api_token =
APIToken.objects.filter(token=api_key).first() or a get) and then branch in
Python: check api_token.is_service to append ServiceTokenRateThrottle() or check
api_token.workspace_id (or truthiness) to append WorkspaceTokenRateThrottle(),
handling the None case gracefully and returning throttle_classes as before;
update references to service_token and workspace_token to use the single
api_token object.
Description
Implementation of workspace api tokens.
Type of Change
Summary by CodeRabbit
Release Notes
New Features
UI/UX Improvements