fix: add route aliases for security notification settings to resolve 404 errors

This commit is contained in:
GitHub Actions
2026-02-11 07:03:20 +00:00
parent 9ef8a1ce21
commit aefebe9372
4 changed files with 542 additions and 0 deletions
+414
View File
@@ -1,4 +1,418 @@
---
post_title: Discord Notification Payload Fix Plan
author1: "Charon Team"
post_slug: discord-notification-payload-fix-plan
categories:
- notifications
tags:
- discord
- webhooks
- payloads
- bugfix
summary: "Plan to fix Discord test notifications by aligning templates with
required payload fields and updating tests."
post_date: "2026-02-11"
---
## Discord Notification Payload Fix Plan
Last updated: 2026-02-11
## 1) Introduction
Discord test notifications fail with the error: "discord payload requires
'content' or 'embeds' field". The fix requires aligning default notification
templates with Discord (and Slack) webhook requirements across backend and
frontend, while preserving custom templates and existing webhook behavior for
other providers.
**Objectives**
- Ensure Discord test notifications succeed with default templates.
- Keep template rendering consistent between backend and frontend.
- Preserve validation for service-specific payload requirements.
- Provide clear, testable behavior for previews and test sends.
## 2) Research Findings (Root Cause)
### 2.1 Request Flow and Failure Point
1) UI "Test" button in Notifications page sends POST to
`/api/v1/notifications/providers/test`.
2) Backend handler `NotificationProviderHandler.Test()` calls
`NotificationService.TestProvider()`.
3) `TestProvider()` uses `sendJSONPayload()` for JSON-template providers.
4) `sendJSONPayload()` renders built-in minimal/detailed templates when
`provider.Template` is `minimal` or `detailed` and `Config` is empty.
5) Discord validation in `sendJSONPayload()` rejects payloads missing
`content` or `embeds`, returning the error seen by users.
### 2.2 Where the Payload Goes Missing
**Backend templates:**
- Minimal template uses `message/title/time/event` keys and omits
`content` or `embeds`.
- Detailed template uses the same keys plus host/service data.
- Validation for Discord requires `content` or `embeds`, so default templates
fail.
**Frontend defaults:**
- The Notifications form defaults to `template: "minimal"` and uses
prefilled template JSON with `message/title/time/event` only.
- This reinforces the backend default template and causes test sends to fail
for Discord (and Slack, which requires `text` or `blocks`).
### 2.3 Evidence (File Trace)
- Default template selection and Discord/Slack validation live in
[backend/internal/services/notification_service.go](backend/internal/services/notification_service.go#L170-L272)
- Provider default template and `Template` field live in
[backend/internal/models/notification_provider.go](backend/internal/models/notification_provider.go#L10-L47)
- Test endpoint that triggers the failure is in
[backend/internal/api/handlers/notification_provider_handler.go](backend/internal/api/handlers/notification_provider_handler.go#L100-L140)
- Frontend template buttons and defaults are in
[frontend/src/pages/Notifications.tsx](frontend/src/pages/Notifications.tsx#L24-L235)
**Root cause:** built-in minimal/detailed templates do not include required
Discord fields (`content` or `embeds`). The frontend defaults to those templates
and the backend enforces strict validation, so tests fail even when the webhook
URL is valid.
### 2.3 Security Notification Settings 404 Regression (New)
**Symptom:** `GET /api/v1/notifications/settings/security` returns `404`.
**Findings:**
- Backend routes register security notification settings at
`GET/PUT /api/v1/security/notifications/settings`.
- Frontend API calls (and tests) use
`GET/PUT /api/v1/notifications/settings/security`.
- This path mismatch causes the 404 and is a regression relative to prior
behavior where the frontend path was valid.
**Root cause:** route path mismatch between frontend API client and backend
route registration. The handler exists, but the frontend calls a different
endpoint path.
**Missing component:** route registration (alias) for
`/api/v1/notifications/settings/security` or an updated frontend path to match
`/api/v1/security/notifications/settings`.
### 2.4 Comprehensive Notification Path Audit (2026-02-11)
**Scope:** backend notification routes in `backend/internal/api/routes/` and
frontend notification API calls in `frontend/src/api/`.
**Audit summary:**
- **Mismatches found:** 1
- **Pattern:** All notification endpoints use `/api/v1/notifications/*` except
security notification settings, which are registered under
`/api/v1/security/notifications/settings` in the backend.
**Path mapping table (backend vs frontend):**
| Backend Route | Frontend Call | Match Status | Endpoint Purpose |
| --- | --- | --- | --- |
| `/api/v1/notifications` (GET) | — | ✅ Match (backend-only) | List notifications |
| `/api/v1/notifications/:id/read` (POST) | — | ✅ Match (backend-only) | Mark a notification read |
| `/api/v1/notifications/read-all` (POST) | — | ✅ Match (backend-only) | Mark all notifications read |
| `/api/v1/notifications/providers` (GET/POST) | `/api/v1/notifications/providers` | ✅ Match | Provider list and create |
| `/api/v1/notifications/providers/:id` (PUT/DELETE) | `/api/v1/notifications/providers/:id` | ✅ Match | Provider update/delete |
| `/api/v1/notifications/providers/test` (POST) | `/api/v1/notifications/providers/test` | ✅ Match | Provider test send |
| `/api/v1/notifications/providers/preview` (POST) | `/api/v1/notifications/providers/preview` | ✅ Match | Provider preview render |
| `/api/v1/notifications/templates` (GET) | `/api/v1/notifications/templates` | ✅ Match | Built-in templates list |
| `/api/v1/notifications/external-templates` (GET/POST) | `/api/v1/notifications/external-templates` | ✅ Match | External template list/create |
| `/api/v1/notifications/external-templates/:id` (PUT/DELETE) | `/api/v1/notifications/external-templates/:id` | ✅ Match | External template update/delete |
| `/api/v1/notifications/external-templates/preview` (POST) | `/api/v1/notifications/external-templates/preview` | ✅ Match | External template preview render |
| `/api/v1/security/notifications/settings` (GET/PUT) | `/api/v1/notifications/settings/security` | ❌ Mismatch | Security notification settings |
**Pattern analysis:**
- The mismatch is isolated to the security notification settings endpoint.
- All other notification-related routes follow a consistent `/api/v1/notifications/*` pattern.
- No evidence of a broader systematic inversion beyond the security settings endpoint.
**Git blame (path establishment):**
- Backend path `/api/v1/security/notifications/settings` registered in
[backend/internal/api/routes/routes.go](backend/internal/api/routes/routes.go#L227-L231),
blamed to commit `3169b0515` (2026-02-09).
- Frontend path `/api/v1/notifications/settings/security` used in
[frontend/src/api/notifications.ts](frontend/src/api/notifications.ts#L188-L203),
blamed to commit `3169b0515` (2026-02-09).
**Fix recommendation:**
- **Preferred:** Add a backend route alias for
`/api/v1/notifications/settings/security` to map to the existing security
settings handler. This preserves backward compatibility and keeps the
broader `/api/v1/notifications/*` namespace consistent for the frontend.
- **Alternative:** Update frontend calls to `/api/v1/security/notifications/settings`
and adjust frontend tests accordingly.
**Impact on original Discord test failure:**
- The Discord test failure is tied to provider payload validation and uses
`/api/v1/notifications/providers/test`; it is **not** caused by the security
settings mismatch. The mismatch only explains the 404 regression for security
settings reads/updates.
## 3) Technical Specifications
### 3.1 Template Catalog (Service-Specific)
Introduce a service-aware template catalog for built-in templates and use it
both for rendering and preview. This ensures Discord and Slack requirements are
met while preserving current behavior for generic webhooks.
**Template mapping (proposed):**
| Provider Type | Minimal Template | Detailed Template |
| --- | --- | --- |
| discord | `content` from Title/Message | `embeds` with title/description/timestamp |
| slack | `text` from Title/Message | `text` from Title/Message (no blocks) |
| gotify | `message` + `title` | `message` + `title` + extras |
| webhook | current minimal | current detailed |
| generic | current minimal | current detailed |
### 3.2 Before/After Payload Structures
**Before (current minimal template):**
```json
{
"message": "{{.Message}}",
"title": "{{.Title}}",
"time": "{{.Time}}",
"event": "{{.EventType}}"
}
```
**After (Discord minimal template):**
```json
{
"content": "{{.Title}} - {{.Message}}",
"username": "Charon"
}
```
**After (Discord detailed template):**
```json
{
"embeds": [
{
"title": "{{.Title}}",
"description": "{{.Message}}",
"timestamp": "{{.Time}}",
"fields": [
{"name": "Event", "value": "{{.EventType}}", "inline": true},
{"name": "Host", "value": "{{.HostName}}", "inline": true}
]
}
]
}
```
**After (Slack minimal template):**
```json
{
"text": "{{.Title}} - {{.Message}}"
}
```
**After (Slack detailed template):**
```json
{
"text": "{{.Title}} - {{.Message}}"
}
```
### 3.3 Validation Rules
- Discord: treat missing or empty `content` and `embeds` as invalid. If both
are missing or empty, return the existing error message.
- Slack: treat missing or empty `text` as invalid. `blocks` are not used in
this plan.
- Gotify: `message` must be present and non-empty.
- Custom templates remain user-defined and are validated by existing rules.
### 3.4 Preview Behavior
Preview (`/api/v1/notifications/providers/preview`) should use the same service-aware
template selection and validation so users see failures before attempting a
real send.
### 3.5 Edge Cases
- Empty `Title` and `Message`: fallback to a safe string (e.g.,
"Charon notification") so `content` or `text` is not empty for Discord or
Slack.
- `Message` only or `Title` only: concatenate non-empty values with " - ".
- Discord detailed: if `embeds` would be empty or missing after rendering,
fallback to a `content` string derived from Title/Message.
- Slack detailed: if `text` renders empty, fallback to the safe string.
- No empty payloads: if a rendered payload would be empty after all fallbacks,
return a validation error and do not send to any webhook.
- Custom templates must remain unchanged; only built-in template selection
becomes service-aware.
## 4) Implementation Plan (Phased)
### Pre-Implementation Gate (Required)
1) Update `requirements.md`, `design.md`, and `tasks.md` for this plan.
2) Add a trace map entry for preview handler/tests and include preview in the
testing strategy.
3) Proceed only after these artifacts are updated and reviewed.
### Phase 1: Playwright Expectations
- Update E2E tests to validate Discord provider test success with the default
minimal template and confirm Slack provider test success.
- Add a negative test to confirm Discord preview/test fails when a custom
template omits both `content` and `embeds`.
**Targets:**
- `tests/settings/notifications.spec.ts`
### Phase 1a: 404 Regression Fix (Security Notification Settings)
- Align the security notification settings endpoint path between frontend and
backend.
- Preferred minimal fix: add a route alias that maps
`/api/v1/notifications/settings/security` to the existing handler for
`/api/v1/security/notifications/settings` to preserve backward
compatibility.
- Alternative fix: update frontend API calls to use
`/api/v1/security/notifications/settings` and update associated frontend
tests.
**Targets:**
- Backend route registration in
[backend/internal/api/routes/routes.go](backend/internal/api/routes/routes.go)
- Frontend client in
[frontend/src/api/notifications.ts](frontend/src/api/notifications.ts)
- Frontend tests:
[frontend/src/api/notifications.test.ts](frontend/src/api/notifications.test.ts)
and [frontend/src/api/__tests__/notifications.test.ts](frontend/src/api/__tests__/notifications.test.ts)
### Phase 2: Backend Template Selection
- Add a template catalog keyed by provider type and template variant.
- Update `sendJSONPayload()` and `RenderTemplate()` to use service-aware
templates for `minimal` and `detailed`.
- Update validation to treat empty strings as missing for required fields and
to enforce non-empty fallbacks.
**Targets:**
- `backend/internal/services/notification_service.go`
- `backend/internal/services/notification_service_test.go`
- `backend/internal/services/notification_service_json_test.go`
### Phase 3: Frontend Template Defaults
- Update the Notifications form to set minimal/detailed templates based on
provider type (Discord vs Slack vs Gotify vs Generic/Webhook).
- Ensure the preview content reflects the new defaults.
**Targets:**
- `frontend/src/pages/Notifications.tsx`
### Phase 4: Integration and Regression Testing
- Run Playwright E2E tests first (notifications suite).
- Run backend unit tests with coverage after E2E passes.
- Run frontend unit tests (Vitest) and type checks.
### Phase 5: Documentation
- Update API or user docs only if the default template behavior is documented
or exposed. Document the Discord/Slack default template expectations if
needed.
## 5) Testing Strategy
### 5.1 Backend Unit Tests (Go)
- Add tests for service-aware minimal/detailed templates:
- Discord minimal produces `content`.
- Discord detailed produces `embeds`.
- Slack minimal produces `text`.
- Slack detailed produces `text` (blocks are not used).
- Add tests for preview path behavior:
- Preview uses service-aware templates.
- Preview enforces fallback rules and rejects empty payloads.
- Update existing tests that assert `message/title` for minimal templates to
account for provider-type differences.
**Targets:**
- `backend/internal/services/notification_service_test.go`
- `backend/internal/services/notification_service_json_test.go`
- `backend/internal/api/handlers/notification_provider_preview_handler_test.go` (new)
### 5.2 Frontend Unit Tests (Vitest)
- Add or update tests to confirm template selection changes with provider type
and that the generated config includes `content` for Discord.
**Targets:**
- `frontend/src/pages/__tests__/Notifications.test.tsx` (new or update
existing if present)
### 5.3 Playwright E2E
- Verify the Discord provider test succeeds with default minimal template.
- Verify Slack provider test succeeds with default minimal template.
- Verify custom template without required fields fails and surfaces the
backend error message.
- Verify provider preview uses service-aware templates and rejects empty
payloads.
**Targets:**
- `tests/settings/notifications.spec.ts`
### 5.4 Manual Testing Steps
1) Create a Discord provider with a real webhook URL.
2) Use the default minimal template and click "Test".
3) Confirm the webhook receives a message (content or embed).
4) Switch to detailed template and click "Test".
5) Confirm the webhook receives an embed payload.
6) Repeat for Slack provider (text only).
## 6) Acceptance Criteria (EARS)
- WHEN a Discord notification provider uses the default minimal template,
THE SYSTEM SHALL send a payload containing `content`.
- WHEN a Discord notification provider uses the default detailed template,
THE SYSTEM SHALL send a payload containing `embeds`.
- WHEN a Slack notification provider uses the default minimal template,
THE SYSTEM SHALL send a payload containing `text`.
- WHEN a custom Discord template omits both `content` and `embeds`,
THE SYSTEM SHALL return a validation error and SHALL NOT send the webhook.
- WHEN a preview request is made for a Discord or Slack provider,
THE SYSTEM SHALL validate the rendered JSON against provider requirements.
- WHEN a preview or test send renders an empty payload after fallback,
THE SYSTEM SHALL return a validation error and SHALL NOT send the webhook.
- WHEN a Discord provider test succeeds, THE SYSTEM SHALL return 200 OK and
THE SYSTEM SHALL record the success without error.
## 7) Files and Components to Touch (Trace Map)
**Backend**
- [backend/internal/services/notification_service.go](backend/internal/services/notification_service.go#L170-L272)
- [backend/internal/services/notification_service_test.go](backend/internal/services/notification_service_test.go)
- [backend/internal/services/notification_service_json_test.go](backend/internal/services/notification_service_json_test.go)
- [backend/internal/api/handlers/notification_provider_preview_handler.go](backend/internal/api/handlers/notification_provider_preview_handler.go) (new)
- [backend/internal/api/handlers/notification_provider_preview_handler_test.go](backend/internal/api/handlers/notification_provider_preview_handler_test.go) (new)
- [backend/internal/api/handlers/notification_provider_handler.go](backend/internal/api/handlers/notification_provider_handler.go#L100-L180)
- [backend/internal/api/handlers/notification_coverage_test.go](backend/internal/api/handlers/notification_coverage_test.go#L340-L610)
**Frontend**
- [frontend/src/pages/Notifications.tsx](frontend/src/pages/Notifications.tsx#L24-L235)
- [tests/settings/notifications.spec.ts](tests/settings/notifications.spec.ts)
## 8) Confidence Score
Confidence: 86%
Rationale: The failure is directly tied to a known validation rule and to
default templates that omit required fields. The changes are isolated to the
notification template selection path and the Notifications UI.---
post_title: Permissions Integrity Plan
author1: "Charon Team"
post_slug: permissions-integrity-plan-non-root