Description
The POST /api/notifications/subscribe, GET /api/notifications/preferences/:userId, and DELETE /api/notifications/unsubscribe endpoints accept any non-empty string as userId with no validation that it is a valid Stellar public key. This allows invalid user IDs to persist permanently in the database, breaking downstream functionality that assumes userId is a valid Stellar address.
Steps to Reproduce
# Subscribe with a completely invalid userId
curl -X POST http://localhost:3001/api/notifications/subscribe \
-H "Content-Type: application/json" \
-d '{
"userId": "not-a-stellar-address",
"emailEnabled": true,
"emailAddress": "user@example.com",
"events": ["rebalance"]
}'
# Returns: {"success": true, "message": "Notification preferences saved"}
# The string "not-a-stellar-address" is now permanently in the database
# Can retrieve preferences for the invalid ID
curl http://localhost:3001/api/notifications/preferences/not-a-stellar-address
# Returns: {"success": true, "preferences": {...}}
Root Cause
backend/src/api/routes.ts, the subscribe route (~line 591):
const { userId, emailEnabled, emailAddress, webhookEnabled, webhookUrl, events } = req.body
if (!userId) {
return res.status(400).json({ success: false, error: 'userId is required' })
}
// ↑ Only falsy check — any non-empty string passes
The code validates emailAddress format (valid email regex) and webhookUrl format (http/https pattern), but applies no format validation to userId itself.
Downstream Impact
-
Auto-rebalancer: When queuing notifications for a rebalance event, the service iterates users from notification_preferences. For a row with userId = "not-a-stellar-address", any attempt to look up that user's portfolio on Stellar Horizon returns an error. Depending on error handling, this could halt the entire notification pass.
-
Stellar signature verification: The requireAuth middleware (middleware/auth.ts) verifies that a request is signed by the wallet matching userId. With an invalid Stellar address, Keypair.fromPublicKey(userId) throws a StellarBase.InvalidChecksumError, causing an unhandled exception in the middleware.
-
Database integrity: The notification_preferences table likely has no CHECK constraint on user_id format. Over time, the table accumulates orphaned rows with no corresponding valid Stellar account.
Proposed Fix
Add Stellar address validation before persisting notification preferences:
// backend/src/api/routes.ts — notification/subscribe handler
// Stellar public key: 56 characters, starts with G (mainnet) or T (testnet)
const STELLAR_PUBLIC_KEY_REGEX = /^[GT][A-Z0-9]{54}$/
if (!STELLAR_PUBLIC_KEY_REGEX.test(userId)) {
return res.status(400).json({
success: false,
error: 'userId must be a valid Stellar public key (56 characters, starting with G or T)'
})
}
Apply the same check to GET /api/notifications/preferences/:userId (route parameter) and DELETE /api/notifications/unsubscribe.
Additionally, consider adding a CHECK constraint to the notification_preferences table:
ALTER TABLE notification_preferences
ADD CONSTRAINT valid_stellar_address
CHECK (user_id ~ '^[GT][A-Z0-9]{54}$');
Files Affected
backend/src/api/routes.ts — subscribe, preferences, unsubscribe handlers
backend/src/db/migrations/ — optional: add CHECK constraint
backend/src/api/validation.ts — add stellarAddressSchema to Zod schemas for reuse
Description
The
POST /api/notifications/subscribe,GET /api/notifications/preferences/:userId, andDELETE /api/notifications/unsubscribeendpoints accept any non-empty string asuserIdwith no validation that it is a valid Stellar public key. This allows invalid user IDs to persist permanently in the database, breaking downstream functionality that assumesuserIdis a valid Stellar address.Steps to Reproduce
Root Cause
backend/src/api/routes.ts, the subscribe route (~line 591):The code validates
emailAddressformat (valid email regex) andwebhookUrlformat (http/https pattern), but applies no format validation touserIditself.Downstream Impact
Auto-rebalancer: When queuing notifications for a rebalance event, the service iterates users from
notification_preferences. For a row withuserId = "not-a-stellar-address", any attempt to look up that user's portfolio on Stellar Horizon returns an error. Depending on error handling, this could halt the entire notification pass.Stellar signature verification: The
requireAuthmiddleware (middleware/auth.ts) verifies that a request is signed by the wallet matchinguserId. With an invalid Stellar address,Keypair.fromPublicKey(userId)throws aStellarBase.InvalidChecksumError, causing an unhandled exception in the middleware.Database integrity: The
notification_preferencestable likely has noCHECKconstraint onuser_idformat. Over time, the table accumulates orphaned rows with no corresponding valid Stellar account.Proposed Fix
Add Stellar address validation before persisting notification preferences:
Apply the same check to
GET /api/notifications/preferences/:userId(route parameter) andDELETE /api/notifications/unsubscribe.Additionally, consider adding a
CHECKconstraint to thenotification_preferencestable:Files Affected
backend/src/api/routes.ts— subscribe, preferences, unsubscribe handlersbackend/src/db/migrations/— optional: add CHECK constraintbackend/src/api/validation.ts— addstellarAddressSchemato Zod schemas for reuse