Skip to content

Conversation

@elibosley
Copy link
Member

@elibosley elibosley commented Aug 27, 2025

Summary by CodeRabbit

  • New Features

    • None.
  • Refactor

    • Streamlined API key retrieval to a single, consistent path, simplifying role updates and improving null-safety.
  • Bug Fixes

    • Improved error handling during API key operations, providing more robust behavior when data is missing or unreadable.
  • Tests

    • Expanded coverage for error scenarios and initialization flows to ensure reliable API key loading and updates.
  • Chores

    • Minor formatting cleanup to maintain code quality.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 27, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Renamed and removed the findByIdWithSecret API across services and tests, consolidating lookups to the async findById. Updated auth role add/remove flows to operate directly on the returned ApiKey, initializing roles when absent, saving via saveApiKey, and enhancing error handling. Adjusted tests to initialize module state and cover error paths.

Changes

Cohort / File(s) Summary
Auth API Key Service (impl)
api/src/unraid-api/auth/api-key.service.ts
Removed public findByIdWithSecret; update path now awaits async findById. Lookup and error behavior unchanged aside from async callsite.
Auth Service (impl)
api/src/unraid-api/auth/auth.service.ts
Replaced usages of findByIdWithSecret with findById; add/remove role now initializes missing roles array, mutates/saves ApiKey directly, and adds context to error handling.
Auth Tests
api/src/unraid-api/auth/auth.service.spec.ts
Updated tests to use findById only; expectations adjusted to operate on returned ApiKey; removed secret-based variants.
API Key Service Tests
api/src/unraid-api/auth/api-key.service.spec.ts
Tests updated to call findById; added error-handling tests for invalid data and file-read failures; async beforeEach with onModuleInit and mocked loadAllFromDisk.
Shared Interface
packages/unraid-shared/src/services/api-key.ts
Removed ApiKeyService.findByIdWithSecret signature; interface now exposes async findById only.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant Auth as AuthService
  participant KeySvc as ApiKeyService
  participant AuthZ as AuthzService
  participant Store as Storage

  rect rgb(240,248,255)
  note over C,Auth: Add role to API key (updated async flow)
  C->>Auth: addRoleToApiKey(apiKeyId, role)
  Auth->>KeySvc: findById(apiKeyId)
  KeySvc->>Store: read api-keys
  Store-->>KeySvc: ApiKey | null
  KeySvc-->>Auth: ApiKey | null
  alt ApiKey found
    Auth->>Auth: ensure roles[], mutate roles
    Auth->>KeySvc: saveApiKey(ApiKey)
    KeySvc->>Store: write api-key
    Store-->>KeySvc: ok
    KeySvc-->>Auth: ok
    Auth->>AuthZ: addRoleForUser(apiKeyId, role)
    AuthZ-->>Auth: ok
    Auth-->>C: result
  else Not found
    Auth-->>C: error "API key not found"
  end
  end
Loading
sequenceDiagram
  autonumber
  participant C as Client
  participant Auth as AuthService
  participant KeySvc as ApiKeyService
  participant AuthZ as AuthzService
  participant Store as Storage

  rect rgb(245,240,255)
  note over C,Auth: Remove role from API key (updated async flow)
  C->>Auth: removeRoleFromApiKey(apiKeyId, role)
  Auth->>KeySvc: findById(apiKeyId)
  KeySvc->>Store: read api-keys
  Store-->>KeySvc: ApiKey | null
  KeySvc-->>Auth: ApiKey | null
  alt ApiKey found
    Auth->>Auth: ensure roles[], filter role
    Auth->>KeySvc: saveApiKey(ApiKey)
    KeySvc->>Store: write api-key
    Store-->>KeySvc: ok
    KeySvc-->>Auth: ok
    Auth->>AuthZ: deleteRoleForUser(apiKeyId, role)
    AuthZ-->>Auth: ok
    Auth-->>C: result
  else Not found
    Auth-->>C: error "API key not found"
  end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fix: remove unused api key calls #1628 — Removes findByIdWithSecret and updates all call sites (api-key service, auth service, shared interface, plugin wrapper, tests), overlapping the same functions and refactors.

Poem

I hop through keys, no secrets to find,
Async breezes whisper, tidy and kind.
Roles line up neatly, saved with care,
Errors well-labeled float in the air.
Thump-thump! I review, then cheerfully say:
One clean hop to a simpler day. 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 41ea19f and 0fd11d0.

📒 Files selected for processing (5)
  • api/src/unraid-api/auth/api-key.service.spec.ts (4 hunks)
  • api/src/unraid-api/auth/api-key.service.ts (1 hunks)
  • api/src/unraid-api/auth/auth.service.spec.ts (2 hunks)
  • api/src/unraid-api/auth/auth.service.ts (2 hunks)
  • packages/unraid-shared/src/services/api-key.ts (0 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/remove-auth-service

🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@claude
Copy link

claude bot commented Aug 27, 2025

Claude finished @elibosley's task —— View job


PR Review - Critical Issues Found

Task: Review ONLY the diff/changeset for HIGH-PRIORITY issues

Todo List:

  • Read and analyze the changed files
  • Check for critical bugs in the diff
  • Look for security issues in changes
  • Identify any breaking changes
  • Check for data loss risks
  • Provide final assessment

🚨 Critical Issue Found in auth.service.ts

CRITICAL BUG - Unnecessary I/O Operations in removeRoleFromApiKey

In removeRoleFromApiKey (lines 228-237), there's a logic flaw:

if (!apiKey.roles) {
    apiKey.roles = [];  // Initialize empty array
}
apiKey.roles = apiKey.roles.filter((r) => r !== role);  // Filter (no-op if role not present)
await this.apiKeyService.saveApiKey(apiKey);            // ⚠️ Always saves, even when no change
await this.authzService.deleteRoleForUser(apiKeyId, role); // ⚠️ Always deletes, even if role wasn't there

Problems:

  1. Unnecessary I/O: If the role doesn't exist, still performs expensive file saves and authz operations
  2. AuthZ Inconsistency: Calling deleteRoleForUser when role was never assigned could cause state issues

Recommended Fix: Check if role exists before filtering, only save/delete if changes were made.

🚨 Breaking Change Identified

PUBLIC API METHOD REMOVAL

  • Removed: findByIdWithSecret(id: string): ApiKey | null from shared interface and implementation
  • Replaced with: await findById(id) (async method)
  • Impact: Any external consumers using the synchronous method will break

✅ Positive Changes Noted

  • Null Safety: Added apiKey.roles null checks in both addRoleToApiKey and removeRoleFromApiKey - prevents runtime crashes
  • Async Consistency: Using async findById in update() method creates consistent API usage

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

🧹 Nitpick comments (3)
api/src/unraid-api/auth/auth.service.spec.ts (1)

233-237: Make saveApiKey expectations less brittle

Use partial/arrayContaining to reduce coupling to object shape and order.

Apply:

-            expect(apiKeyService.saveApiKey).toHaveBeenCalledWith({
-                ...mockApiKeyWithoutRole,
-                roles: [Role.ADMIN, role],
-            });
+            expect(apiKeyService.saveApiKey).toHaveBeenCalledWith(
+                expect.objectContaining({
+                    id: apiKeyId,
+                    roles: expect.arrayContaining([Role.ADMIN, role]),
+                })
+            );
-            expect(apiKeyService.saveApiKey).toHaveBeenCalledWith({
-                ...apiKey,
-                roles: [Role.GUEST],
-            });
+            expect(apiKeyService.saveApiKey).toHaveBeenCalledWith(
+                expect.objectContaining({
+                    id: apiKey.id,
+                    roles: [Role.GUEST],
+                })
+            );

Also applies to: 261-265

api/src/unraid-api/auth/api-key.service.spec.ts (2)

229-260: Duplicate “describe('findById')” block

There are two describe('findById') sections; consolidate to avoid redundancy and future drift.

I can propose a follow-up patch to merge these tests into the first block and keep only unique scenarios (invalid structure/read error).


253-254: Follow testing guideline: avoid asserting exact error messages

Prefer generic .rejects.toThrow() to reduce brittleness.

Apply:

-            await expect(apiKeyService.onModuleInit()).rejects.toThrow('Invalid API key structure');
+            await expect(apiKeyService.onModuleInit()).rejects.toThrow();
-            await expect(apiKeyService.onModuleInit()).rejects.toThrow('Read failed');
+            await expect(apiKeyService.onModuleInit()).rejects.toThrow();

Also applies to: 258-259

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 674323f and 41ea19f.

📒 Files selected for processing (6)
  • api/src/unraid-api/auth/api-key.service.spec.ts (4 hunks)
  • api/src/unraid-api/auth/api-key.service.ts (1 hunks)
  • api/src/unraid-api/auth/auth.service.spec.ts (2 hunks)
  • api/src/unraid-api/auth/auth.service.ts (2 hunks)
  • packages/unraid-api-plugin-connect/src/authn/connect-api-key.service.ts (0 hunks)
  • packages/unraid-shared/src/services/api-key.ts (0 hunks)
💤 Files with no reviewable changes (2)
  • packages/unraid-shared/src/services/api-key.ts
  • packages/unraid-api-plugin-connect/src/authn/connect-api-key.service.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

TypeScript source files must use import specifiers with .js extensions for ESM compatibility

Files:

  • api/src/unraid-api/auth/auth.service.ts
  • api/src/unraid-api/auth/api-key.service.ts
  • api/src/unraid-api/auth/auth.service.spec.ts
  • api/src/unraid-api/auth/api-key.service.spec.ts
api/src/unraid-api/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place new API source files in api/src/unraid-api/ rather than legacy locations

Prefer adding new files to the Nest repo at api/src/unraid-api/ instead of legacy code

Files:

  • api/src/unraid-api/auth/auth.service.ts
  • api/src/unraid-api/auth/api-key.service.ts
  • api/src/unraid-api/auth/auth.service.spec.ts
  • api/src/unraid-api/auth/api-key.service.spec.ts
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid unnecessary or obvious comments; only add comments when needed for clarity

Files:

  • api/src/unraid-api/auth/auth.service.ts
  • api/src/unraid-api/auth/api-key.service.ts
  • api/src/unraid-api/auth/auth.service.spec.ts
  • api/src/unraid-api/auth/api-key.service.spec.ts
api/**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer not to mock simple dependencies in API tests

Files:

  • api/src/unraid-api/auth/auth.service.spec.ts
  • api/src/unraid-api/auth/api-key.service.spec.ts
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{test,spec}.{js,ts,jsx,tsx}: General testing: use .rejects.toThrow() without arguments when asserting thrown errors
General testing: focus on behavior, not exact error message wording
General testing: avoid brittle tests tied to non-essential details (exact messages, log formats)
General testing: use mocks as nouns (objects), not verbs (behavioral overuse)

Files:

  • api/src/unraid-api/auth/auth.service.spec.ts
  • api/src/unraid-api/auth/api-key.service.spec.ts
api/**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

api/**/*.{test,spec}.{js,jsx,ts,tsx}: Use Vitest for tests in the api; do not use Jest
Prefer not to mock simple dependencies in tests
For error testing, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless the message format is the subject under test

Files:

  • api/src/unraid-api/auth/auth.service.spec.ts
  • api/src/unraid-api/auth/api-key.service.spec.ts
🧠 Learnings (8)
📓 Common learnings
Learnt from: mdatelle
PR: unraid/api#942
File: api/src/unraid-api/auth/auth.service.ts:0-0
Timestamp: 2024-11-04T20:44:46.432Z
Learning: When modifying `apiKey.roles` in `removeRoleFromApiKey` and `addRoleToApiKey` within `api/src/unraid-api/auth/auth.service.ts`, concurrency issues are not a concern because the keys are stored in the file system.
Learnt from: mdatelle
PR: unraid/api#942
File: api/src/unraid-api/auth/auth.service.ts:0-0
Timestamp: 2024-11-04T20:41:22.303Z
Learning: In `api/src/unraid-api/auth/auth.service.ts`, the `addRoleToApiKey` function operates on API keys stored as JSON files in a directory, not a database, so concurrency is not a concern for modifying `apiKey.roles`.
📚 Learning: 2024-11-04T20:44:46.432Z
Learnt from: mdatelle
PR: unraid/api#942
File: api/src/unraid-api/auth/auth.service.ts:0-0
Timestamp: 2024-11-04T20:44:46.432Z
Learning: When modifying `apiKey.roles` in `removeRoleFromApiKey` and `addRoleToApiKey` within `api/src/unraid-api/auth/auth.service.ts`, concurrency issues are not a concern because the keys are stored in the file system.

Applied to files:

  • api/src/unraid-api/auth/auth.service.ts
  • api/src/unraid-api/auth/api-key.service.ts
  • api/src/unraid-api/auth/auth.service.spec.ts
  • api/src/unraid-api/auth/api-key.service.spec.ts
📚 Learning: 2024-11-04T20:41:22.303Z
Learnt from: mdatelle
PR: unraid/api#942
File: api/src/unraid-api/auth/auth.service.ts:0-0
Timestamp: 2024-11-04T20:41:22.303Z
Learning: In `api/src/unraid-api/auth/auth.service.ts`, the `addRoleToApiKey` function operates on API keys stored as JSON files in a directory, not a database, so concurrency is not a concern for modifying `apiKey.roles`.

Applied to files:

  • api/src/unraid-api/auth/auth.service.ts
  • api/src/unraid-api/auth/api-key.service.ts
  • api/src/unraid-api/auth/auth.service.spec.ts
  • api/src/unraid-api/auth/api-key.service.spec.ts
📚 Learning: 2024-11-06T20:59:25.809Z
Learnt from: mdatelle
PR: unraid/api#942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:111-113
Timestamp: 2024-11-06T20:59:25.809Z
Learning: In the Unraid API project, error handling for mutations is handled at the service level rather than in the GraphQL resolvers. Specifically, in `api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts`, methods like `removeRoleFromApiKey` rely on service-level error handling.

Applied to files:

  • api/src/unraid-api/auth/auth.service.ts
  • api/src/unraid-api/auth/auth.service.spec.ts
📚 Learning: 2024-11-15T16:22:03.485Z
Learnt from: elibosley
PR: unraid/api#942
File: api/src/unraid-api/auth/api-key.service.ts:176-188
Timestamp: 2024-11-15T16:22:03.485Z
Learning: Atomic writes are not required for the `saveApiKey` method in `ApiKeyService` (`api/src/unraid-api/auth/api-key.service.ts`) unless specifically needed.

Applied to files:

  • api/src/unraid-api/auth/auth.service.ts
  • api/src/unraid-api/auth/api-key.service.ts
  • api/src/unraid-api/auth/auth.service.spec.ts
  • api/src/unraid-api/auth/api-key.service.spec.ts
📚 Learning: 2024-11-06T20:59:06.096Z
Learnt from: mdatelle
PR: unraid/api#942
File: api/src/unraid-api/graph/resolvers/auth/auth.resolver.ts:87-89
Timestamp: 2024-11-06T20:59:06.096Z
Learning: Error handling in mutations like `addRoleForUser` in `auth.resolver.ts` is performed at the service level, so additional error handling in the resolver is unnecessary.

Applied to files:

  • api/src/unraid-api/auth/auth.service.ts
📚 Learning: 2024-11-05T14:49:07.308Z
Learnt from: elibosley
PR: unraid/api#942
File: api/src/unraid-api/auth/api-key.service.ts:62-70
Timestamp: 2024-11-05T14:49:07.308Z
Learning: In `api/src/unraid-api/auth/api-key.service.ts`, when handling read errors in the `findById` method, throw a `GraphQLError` instead of an `InternalServerErrorException`.

Applied to files:

  • api/src/unraid-api/auth/auth.service.ts
  • api/src/unraid-api/auth/api-key.service.ts
  • api/src/unraid-api/auth/api-key.service.spec.ts
📚 Learning: 2025-08-11T15:06:30.877Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-11T15:06:30.877Z
Learning: Applies to api/**/*.{test,spec}.{ts,tsx,js,jsx} : Prefer not to mock simple dependencies in API tests

Applied to files:

  • api/src/unraid-api/auth/auth.service.spec.ts
  • api/src/unraid-api/auth/api-key.service.spec.ts
🧬 Code graph analysis (3)
api/src/unraid-api/auth/auth.service.ts (1)
api/src/unraid-api/graph/resolvers/api-key/api-key.resolver.ts (1)
  • apiKey (28-33)
api/src/unraid-api/auth/api-key.service.ts (1)
api/src/unraid-api/graph/resolvers/api-key/api-key.resolver.ts (1)
  • apiKey (28-33)
api/src/unraid-api/auth/auth.service.spec.ts (1)
api/src/unraid-api/graph/resolvers/api-key/api-key.resolver.ts (1)
  • apiKey (28-33)
🔇 Additional comments (3)
api/src/unraid-api/auth/api-key.service.ts (2)

329-352: LGTM: update() now uses async findById

Switching to await findById aligns with the removal of findByIdWithSecret and keeps behavior consistent.


329-352: No remaining findByIdWithSecret references found

  • Ran ripgrep searches for both exact (findByIdWithSecret) and partial (findByIdWith…) matches across the repository (excluding node_modules) with zero hits.

All clear—no stragglers remain.

api/src/unraid-api/auth/api-key.service.spec.ts (1)

356-378: LGTM: tests await onModuleInit and seed memoryApiKeys

This fixes potential flakiness around service initialization.

@elibosley elibosley force-pushed the fix/remove-auth-service branch from 41ea19f to f7f75e9 Compare August 27, 2025 19:36
Added checks to initialize the roles array for API keys if it is undefined, preventing potential errors when adding or removing roles. This change enhances the robustness of the role management functionality in the AuthService.
@github-actions
Copy link
Contributor

This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:

https://preview.dl.unraid.net/unraid-api/tag/PR1628/dynamix.unraid.net.plg

@elibosley elibosley merged commit 9cd0d6a into main Aug 27, 2025
11 checks passed
@elibosley elibosley deleted the fix/remove-auth-service branch August 27, 2025 19:50
elibosley pushed a commit that referenced this pull request Aug 27, 2025
🤖 I have created a release *beep* *boop*
---


## [4.16.0](v4.15.1...v4.16.0)
(2025-08-27)


### Features

* add `parityCheckStatus` field to `array` query
([#1611](#1611))
([c508366](c508366))
* generated UI API key management + OAuth-like API Key Flows
([#1609](#1609))
([674323f](674323f))


### Bug Fixes

* **connect:** clear `wanport` upon disabling remote access
([#1624](#1624))
([9df6a3f](9df6a3f))
* **connect:** valid LAN FQDN while remote access is enabled
([#1625](#1625))
([aa58888](aa58888))
* correctly parse periods in share names from ini file
([#1629](#1629))
([7d67a40](7d67a40))
* **rc.unraid-api:** remove profile sourcing
([#1622](#1622))
([6947b5d](6947b5d))
* remove unused api key calls
([#1628](#1628))
([9cd0d6a](9cd0d6a))
* retry VMs init for up to 2 min
([#1612](#1612))
([b2e7801](b2e7801))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants