Skip to content

feat(auth): Integrate @naap/plugin-server-sdk for authentication with NaaP#131

Merged
seanhanca merged 4 commits into
mainfrom
feat/secure-plugins
Feb 20, 2026
Merged

feat(auth): Integrate @naap/plugin-server-sdk for authentication with NaaP#131
seanhanca merged 4 commits into
mainfrom
feat/secure-plugins

Conversation

@eliteprox
Copy link
Copy Markdown
Contributor

@eliteprox eliteprox commented Feb 19, 2026

Summary

This PR fixes a security limitation where plugins trusted the NaaP platform itself rather than the identity of the authenticated user.

Previously, plugins relied on per-plugin shared secrets configured via .env to authenticate requests from NaaP. While this established platform trust, it did not allow plugins to verify which user initiated a request, preventing per-user authorization, auditing, and policy enforcement.

This change forwards the authenticated user bearer token to plugins and introduces validation using the @naap/plugin-server-sdk auth middleware. Plugins can now assert user identity directly from the token claims instead of relying solely on shared secrets.

This shifts the trust model from platform-level trust to user-scoped identity verification while remaining compatible with existing plugin authentication mechanisms.

Changes

  • Added @naap/plugin-server-sdk as a dependency across multiple plugins for unified authentication.
  • Implemented createAuthMiddleware in server setups for capacity-planner, developer-api, marketplace, and publisher plugins, enhancing security with token validation.
  • Updated auth middleware to remove deprecated token handling and streamline user ID extraction from requests.
  • Adjusted package-lock.json to reflect new dependencies and their versions.

Type

  • Feature
  • Bug fix
  • Refactor
  • Documentation
  • CI / Tooling
  • Plugin (new or update)
  • Dependencies

Plugin(s) Affected

Checklist

  • Tests pass locally
  • Lint passes (npm run lint)
  • Build succeeds (npm run build)
  • No new lint warnings introduced
  • Breaking changes documented below
  • Database migration included (if Prisma schema changed)

Breaking Changes

None

Screenshots / Recordings

Summary by CodeRabbit

  • New Features

    • Authentication middleware applied across plugins; /healthz (and static where applicable) remains public.
    • API keys and key-related endpoints are now scoped to the authenticated user.
  • Bug Fixes

    • Endpoints now derive user identity from the authentication context and return 401 when unauthenticated.
    • Token validation centralized via the auth service (local JWT fallbacks removed); auth service failures now surface as errors consistently.

…update dependencies

- Added @naap/plugin-server-sdk as a dependency across multiple plugins for unified authentication.
- Implemented createAuthMiddleware in server setups for capacity-planner, developer-api, marketplace, and publisher plugins, enhancing security with token validation.
- Updated auth middleware to remove deprecated token handling and streamline user ID extraction from requests.
- Adjusted package-lock.json to reflect new dependencies and their versions.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
naap-platform Ready Ready Preview, Comment Feb 20, 2026 0:25am

Request Review

@github-actions github-actions Bot added size/M Medium PR (51-200 lines) scope/packages Shared package changes plugin/capacity-planner Capacity Planner plugin plugin/community Community plugin plugin/marketplace Marketplace plugin plugin/developer-api Developer API plugin plugin/plugin-publisher Plugin Publisher plugin labels Feb 19, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

Consolidates authentication to the centralized auth service by updating the plugin-server-sdk middleware (making secret optional and removing local JWT decoding) and integrating the middleware across multiple backend plugins; user identity is now sourced solely from the middleware-attached req.user.

Changes

Cohort / File(s) Summary
Auth SDK Core
packages/plugin-server-sdk/src/middleware/auth.ts
Made secret optional (deprecated); removed local JWT decode and x-user-id fallback; always return AUTH_SERVICE_ERROR on auth-service failures.
Package Dependency Updates
plugins/capacity-planner/backend/package.json, plugins/developer-api/backend/package.json, plugins/marketplace/backend/package.json, plugins/plugin-publisher/backend/package.json
Added runtime dependency @naap/plugin-server-sdk to multiple plugin backends.
Capacity Planner Auth Integration
plugins/capacity-planner/backend/src/server.ts
Registered createAuthMiddleware (publicPaths: ['/healthz']); commit endpoint now requires authenticated req.user.id and no longer accepts body-provided userId fallback.
Community Auth Updates
plugins/community/backend/src/server.ts
Removed body/query-based userId fallbacks; getUserId/getUserIdFromQuery now use req.user?.id; restricted public routes to only /healthz.
Developer API Auth Implementation
plugins/developer-api/backend/src/server.ts
Registered auth middleware; added getRequestUserId(req) helper; replaced header-based userId extraction with middleware-derived userId across endpoints; enforced user-scoped API key operations and added userId to in-memory keys.
Marketplace & Plugin Publisher Auth Integration
plugins/marketplace/backend/src/server.ts, plugins/plugin-publisher/backend/src/server.ts
Registered createAuthMiddleware with publicPaths: ['/healthz'] (publisher also includes /static) to protect routes and allow public health/static access.

Sequence Diagram

sequenceDiagram
    participant Client
    participant AuthMiddleware
    participant AuthService
    participant ExpressHandler

    Client->>AuthMiddleware: Request (e.g. POST /.../commit)
    AuthMiddleware->>AuthMiddleware: Is path in publicPaths?
    alt path not public
        AuthMiddleware->>AuthService: Validate token (/api/v1/auth/me)
        AuthService-->>AuthMiddleware: Authenticated user
        AuthMiddleware->>AuthMiddleware: Attach user -> req.user
        AuthMiddleware->>ExpressHandler: next() (with req.user)
    else path is public
        AuthMiddleware->>ExpressHandler: next() (no auth)
    end
    ExpressHandler->>ExpressHandler: Use req.user.id for authorization/handlers
    ExpressHandler-->>Client: Response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Suggested labels

scope/backend, size/XL

Suggested reviewers

  • seanhanca
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: integrating @naap/plugin-server-sdk for authentication across plugins to validate NaaP user identity via bearer tokens.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/secure-plugins

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/plugin-server-sdk/src/middleware/auth.ts (1)

202-207: ⚠️ Potential issue | 🟠 Major

Return 503 on auth service outage (not 401).
A failure to reach the auth service is an upstream availability issue, not an authentication failure. The client-side code explicitly clears all stored tokens when it receives a 401 response—returning 401 for AUTH_SERVICE_ERROR will cause valid tokens to be dropped and prevent retry/backoff logic from engaging. Use 503 (or 504) instead.

🔧 Proposed change
-      return res.status(401).json({
+      return res.status(503).json({
         success: false,
         error: { code: 'AUTH_SERVICE_ERROR', message: 'Unable to validate authorization token' },
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/plugin-server-sdk/src/middleware/auth.ts` around lines 202 - 207,
The catch block handling failures to reach the auth service (inside
middleware/auth.ts where authServiceUrl is used) currently returns
res.status(401) which causes clients to clear tokens; change the response status
to 503 (or 504) for the AUTH_SERVICE_ERROR path so upstream availability is
signaled instead of an auth failure—update the res.status(...) call in the catch
block that logs console.error(`[auth] Failed to reach auth service at
${authServiceUrl}:`, err) to return res.status(503). Ensure the JSON body
remains { success: false, error: { code: 'AUTH_SERVICE_ERROR', message: 'Unable
to validate authorization token' } } so clients can detect the service outage.
plugins/community/backend/src/server.ts (1)

221-226: ⚠️ Potential issue | 🟠 Major

Add community read endpoints to publicRoutes to prevent breaking anonymous access.

Restricting publicRoutes to only ['/healthz'] makes /community/posts, /tags, /badges, /leaderboard, /stats, and /search auth-required by the SDK middleware. These endpoints have no auth enforcement in their handlers and are called publicly in the documentation and client code. Either add read endpoints to publicRoutes (following the daydream-video example pattern) or update all clients to provide auth tokens.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/community/backend/src/server.ts` around lines 221 - 226, The
createPluginServer call currently sets publicRoutes to only ['/healthz'], which
forces SDK auth on public community endpoints; update the publicRoutes array in
the createPluginServer invocation (symbol: createPluginServer, property:
publicRoutes) to include the read-only community routes
['/community/posts','/community/tags','/community/badges','/community/leaderboard','/community/stats','/community/search']
(or the exact route paths used by handlers like the
posts/tags/badges/leaderboard/stats/search handlers) so those endpoints remain
accessible anonymously following the daydream-video pattern; ensure route
strings match the handler paths exposed by this plugin.
plugins/developer-api/backend/src/server.ts (1)

389-395: ⚠️ Potential issue | 🟡 Minor

Bug: In-memory usage fallback returns unscoped data.

The in-memory fallback returns counts for all keys in inMemoryApiKeys rather than filtering by the authenticated userId. This is inconsistent with the Prisma path (lines 370-386) which correctly scopes by user.

Proposed fix
     // Fallback
+    const userKeys = inMemoryApiKeys.filter(k => k.userId === userId);
     res.json({
-      totalKeys: inMemoryApiKeys.length,
-      activeKeys: inMemoryApiKeys.filter(k => k.status === 'active').length,
+      totalKeys: userKeys.length,
+      activeKeys: userKeys.filter(k => k.status === 'active').length,
       totalRequests: 0,
       totalCost: '0.0000',
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/developer-api/backend/src/server.ts` around lines 389 - 395, The
in-memory fallback currently aggregates across inMemoryApiKeys for all users;
update the block that returns res.json to first filter inMemoryApiKeys by the
authenticated userId (e.g., const userKeys = inMemoryApiKeys.filter(k =>
k.userId === userId)) and then compute totalKeys, activeKeys, totalRequests and
totalCost from that userKeys array so the response is scoped the same way as the
Prisma branch (referencing inMemoryApiKeys, userId, and the existing res.json
payload).
🧹 Nitpick comments (1)
plugins/capacity-planner/backend/src/server.ts (1)

407-416: Consider using authenticated user for comment author.

The /comments endpoint (lines 407-446) still accepts author from the request body, allowing clients to impersonate any author name. Since authentication is now enforced, consider using req.user for the author identity similar to how the commit endpoint was updated.

Suggested approach
 app.post('/api/v1/capacity-planner/requests/:id/comments', async (req, res) => {
   try {
-    const { author, text } = req.body;
+    const user = (req as any).user;
+    if (!user?.id) {
+      return res.status(401).json({ success: false, error: 'Authentication required' });
+    }
+    const author = req.body.author || user.email || user.id;
+    const { text } = req.body;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/capacity-planner/backend/src/server.ts` around lines 407 - 416, The
comments endpoint app.post('/api/v1/capacity-planner/requests/:id/comments') is
trusting author from req.body which allows impersonation; change it to use the
authenticated user from req.user (like the commit endpoint does) when creating
the prisma.capacityRequestComment record — remove or ignore the body author,
derive author identity from req.user (e.g., req.user.name or req.user.email or
req.user.id depending on your auth shape), validate req.user exists and fall
back to a safe default like 'Anonymous' only if absolutely necessary, and pass
that value into prisma.capacityRequestComment.create instead of the incoming
author field.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/plugin-publisher/backend/src/server.ts`:
- Around line 97-99: The auth middleware registration using createAuthMiddleware
currently only lists '/healthz' as a public path, causing authenticated blocking
of plugin bundle requests; update the publicPaths passed to createAuthMiddleware
in the app.use call (the createAuthMiddleware(...) invocation) to include
'/static' (or a prefix match like '/static/*' if supported) so requests to
/static/* bypass authentication and reach the static file handler and CORS
logic.

---

Outside diff comments:
In `@packages/plugin-server-sdk/src/middleware/auth.ts`:
- Around line 202-207: The catch block handling failures to reach the auth
service (inside middleware/auth.ts where authServiceUrl is used) currently
returns res.status(401) which causes clients to clear tokens; change the
response status to 503 (or 504) for the AUTH_SERVICE_ERROR path so upstream
availability is signaled instead of an auth failure—update the res.status(...)
call in the catch block that logs console.error(`[auth] Failed to reach auth
service at ${authServiceUrl}:`, err) to return res.status(503). Ensure the JSON
body remains { success: false, error: { code: 'AUTH_SERVICE_ERROR', message:
'Unable to validate authorization token' } } so clients can detect the service
outage.

In `@plugins/community/backend/src/server.ts`:
- Around line 221-226: The createPluginServer call currently sets publicRoutes
to only ['/healthz'], which forces SDK auth on public community endpoints;
update the publicRoutes array in the createPluginServer invocation (symbol:
createPluginServer, property: publicRoutes) to include the read-only community
routes
['/community/posts','/community/tags','/community/badges','/community/leaderboard','/community/stats','/community/search']
(or the exact route paths used by handlers like the
posts/tags/badges/leaderboard/stats/search handlers) so those endpoints remain
accessible anonymously following the daydream-video pattern; ensure route
strings match the handler paths exposed by this plugin.

In `@plugins/developer-api/backend/src/server.ts`:
- Around line 389-395: The in-memory fallback currently aggregates across
inMemoryApiKeys for all users; update the block that returns res.json to first
filter inMemoryApiKeys by the authenticated userId (e.g., const userKeys =
inMemoryApiKeys.filter(k => k.userId === userId)) and then compute totalKeys,
activeKeys, totalRequests and totalCost from that userKeys array so the response
is scoped the same way as the Prisma branch (referencing inMemoryApiKeys,
userId, and the existing res.json payload).

---

Nitpick comments:
In `@plugins/capacity-planner/backend/src/server.ts`:
- Around line 407-416: The comments endpoint
app.post('/api/v1/capacity-planner/requests/:id/comments') is trusting author
from req.body which allows impersonation; change it to use the authenticated
user from req.user (like the commit endpoint does) when creating the
prisma.capacityRequestComment record — remove or ignore the body author, derive
author identity from req.user (e.g., req.user.name or req.user.email or
req.user.id depending on your auth shape), validate req.user exists and fall
back to a safe default like 'Anonymous' only if absolutely necessary, and pass
that value into prisma.capacityRequestComment.create instead of the incoming
author field.

Comment thread plugins/plugin-publisher/backend/src/server.ts
…resources

- Updated the createAuthMiddleware configuration to allow access to the "/static" path in addition to the existing "/healthz" path, enhancing the flexibility of the authentication setup.
@eliteprox eliteprox requested a review from seanhanca February 19, 2026 23:58
@eliteprox
Copy link
Copy Markdown
Contributor Author

@seanhanca this was a requirement I began adding into developer-api when I realized it was a broader pattern issue across all plugins. Let me know if you're ok with merging

Copy link
Copy Markdown
Contributor

@seanhanca seanhanca left a comment

Choose a reason for hiding this comment

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

thanks for spotting this. it is great find

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugin/capacity-planner Capacity Planner plugin plugin/community Community plugin plugin/developer-api Developer API plugin plugin/marketplace Marketplace plugin plugin/plugin-publisher Plugin Publisher plugin scope/packages Shared package changes size/M Medium PR (51-200 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants