feat(api): add webhook CRUD API router and tests#393
feat(api): add webhook CRUD API router and tests#393nickmeinhold wants to merge 8 commits intokanbn:mainfrom
Conversation
Add settings page for managing workspace webhooks: - Add webhooks page route and settings navigation link - Add webhook list view with status toggles and action menus - Add create/edit modal with URL validation and event selection - Add delete confirmation dialog - Add WEBHOOKS_ENABLED env flag for feature gating Depends on kanbn#393 (CRUD API router). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MaxwellMergeSlam
left a comment
There was a problem hiding this comment.
MaxwellMergeSlam's Review
Verdict: COMMENT
Summary: Comprehensive CRUD router with solid test coverage, but uses the older assertUserInWorkspace instead of the preferred assertPermission helpers, and the test endpoint has an SSRF surface.
Dutch (Predator): "If it bleeds, we can kill it." — And if it has an SSRF surface, we can exploit it. But let's talk about the good stuff first.
Findings:
-
packages/api/src/routers/webhook.ts: All five endpoints useassertUserInWorkspace(ctx.db, userId, workspace.id, "admin")for authorization. Per the project's CLAUDE.md: "the permissions utils are preferred for new code" — this should useassertPermissionfrompackages/api/src/utils/permissions.tsinstead. The old helper still works, but it's inconsistent with the direction of the codebase. -
packages/api/src/routers/webhook.ts:138-164: Thetestendpoint dynamically imports../utils/webhookto avoid circular dependencies. This works, but it means PR 3 has a runtime dependency on PR 2's code. The PR description says they're independent, but they're not — the test endpoint will crash if PR 3 is merged without PR 2. -
packages/api/src/routers/webhook.ts:33: Input validation uses.min(12)forworkspacePublicId, but publicIds in this codebase are exactly 12 characters. Should this be.length(12)instead? Same forwebhookPublicId. -
packages/api/integration-tests/test-db.ts:13-14: TheTestDbClienttype is cast throughunknown—db as unknown as TestDbClient. This is a pragmatic workaround for PGlite vs node-postgres type mismatch, but it means zero type safety in integration tests. -
packages/api/integration-tests/test-db.ts:33: The migration folder path is../../packages/db/migrations— this is a relative path that depends on the test being run frompackages/api. If the test runner's CWD changes, migrations won't be found. Consider usingpath.resolve(__dirname, ...)for robustness. -
packages/api/vitest.config.ts:11: The alias"@kan/db": resolve(__dirname, "../db/src")maps to a sibling package's source — this bypasses the package's own build output. Fine for testing but worth noting.
The Good:
- OpenAPI metadata on all endpoints — enables automatic API documentation
- Proper ownership validation (checking
webhook.workspaceId !== workspace.id) prevents cross-workspace access - Both unit tests (mocked) and integration tests (PGlite) — defense in depth
- The integration test infrastructure (
createTestDb,seedTestData) is reusable for future test suites - Input validation is thorough: URL format, length limits, enum-constrained events
The Concerns:
assertUserInWorkspacevsassertPermission— tech debt that contradicts project conventions- The test endpoint creates a hidden dependency between PR 3 and PR 2
- Boilerplate repetition — the workspace lookup + auth check is copy-pasted across all 5 endpoints. A middleware or helper could DRY this up, though that's a style preference
Tyler Durden: "The first rule of Fight Club is: you do not talk about Fight Club." — The first rule of this codebase is: you use assertPermission, not assertUserInWorkspace. Read the damn CLAUDE.md.
KelvinBitBrawler
left a comment
There was a problem hiding this comment.
KelvinBitBrawler's Review
Verdict: REQUEST_CHANGES
Summary: You've built a structure, but it leaks entropy like a cracked hull on Titan.
Findings:
-
packages/api/integration-tests/webhook.integration.test.ts:3:import * as webhookRepo from "../../db/src/repository/webhook.repo";- Replicant: "I've seen things you people wouldn't believe." Like relative imports reaching across package boundaries when you explicitly configured an alias.
- Correction: You set up
@kan/dbalias invitest.config.ts. Use it. Don't traverse the file system like a rat in a ventilation shaft.
-
packages/api/src/routers/webhook.ts:79(and others):url: z.string().url().max(2048)- Thermodynamics check: Accessing internal networks via user-supplied URLs causes the system temperature to rise to critical levels.
- Critique:
z.string().url()allowshttp://localhost,http://169.254.169.254, etc. Without SSRF protection (blocking internal IP ranges), this is a security hole wide enough to fly a drop-ship through. At minimum, block localhost/private IPs or acknowledge the risk.
-
packages/api/src/routers/webhook.ts:335:const { sendWebhookToUrl... } = await import("../utils/webhook");- Observation: Dynamic imports inside a handler? Trying to avoid a circular dependency loop? It's uglier than a Xenomorph, but efficient. I'll allow it if it keeps the startup time close to absolute zero.
The Good:
- You actually wrote integration tests with
PGlite. Most developers are too lazy to spin up a proper test environment, preferring to mock reality until it breaks. - The authorization checks
assertUserInWorkspace(..., "admin")are present on every endpoint. Good. Trust no one.
The Concerns:
- Security: Sending the
secretin plain text tocreate/updaterelies entirely on TLS. If that layer fails, your security model enters heat death. - Error Handling:
if (!result) throw ... INTERNAL_SERVER_ERROR. Vague. If the database rejects the insert (e.g. constraint violation), returning a generic 500 is as helpful as a screen door on a submarine. Catch specific errors.
MacReady: "I know I'm human. And if you were all these things, then you'd just attack me right now."
Fix the imports and address the SSRF risk before I break out the flamethrower.
Add the database foundation for workspace webhooks: - Add workspace_webhooks table with migration (webhook_event enum, URL, secret, event subscriptions, active flag) - Add webhook repository with CRUD operations - Add webhooks schema definition with relations - Extend card and list repos to return board/list names for webhook payload context Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove dead webhook_event pgEnum from schema (events column uses text) - Remove CREATE TYPE statement from migration SQL - Fix migration journal timestamp to be chronologically after the notifications migration (idx 25) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add board publicId to getWorkspaceAndCardIdByCardPublicId and getWorkspaceAndListIdByListPublicId return values, needed for correct boardId in webhook payloads. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add index on workspaceId for efficient webhook lookups per workspace - Add JSDoc comment on getActiveByWorkspaceId explaining that it returns secrets for server-side HMAC signing only and must never be exposed via client-facing endpoints Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add tRPC router for managing workspace webhooks: - list, create, update, delete endpoints (admin role required) - test endpoint to send a synthetic payload to a webhook URL - URL validation, event subscription filtering - Unit tests for all router procedures - Integration tests with PGlite test database - Add vitest config and test infrastructure for API package Depends on kanbn#391 (DB schema & repository). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace assertUserInWorkspace with assertPermission("workspace:manage")
per project conventions. The permissions system is the preferred
authorization approach for new code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace relative path imports (../../db/src/...) with the @kan/db alias configured in vitest.config.ts for consistency and robustness. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5ccf9bc to
e3020f0
Compare
Add settings page for managing workspace webhooks: - Add webhooks page route and settings navigation link - Add webhook list view with status toggles and action menus - Add create/edit modal with URL validation and event selection - Add delete confirmation dialog - Add WEBHOOKS_ENABLED env flag for feature gating Depends on kanbn#393 (CRUD API router). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Reviewed all bot feedback on this PR. The flagged integration test import ( |
Cherry-pick router-related changes from b2cc9ac: - Use extracted webhookUrlSchema zod validator in create/update input schemas for consistent SSRF checks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Pushed review feedback changes to this branch (30e616e):
|
Summary
list,create,update,delete, andtestendpointswebhookEventsenumtestendpoint sends a syntheticcard.createdpayload and returns success/error statusDependencies
Depends on #391 (DB schema & repository). Independent of #392 (delivery utility).
PR series
Test plan
🤖 Generated with Claude Code