Improve error handling and authentication for uploads and notifications

Refactor PhotoUpload component to fetch session token before polling, enhance error handling in NotificationService and versioningHelpers with `instanceof Error` checks, and add comprehensive validation for request body fields in the create-novu-subscriber Supabase function.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 8d708ff6-09f1-4b67-8edc-de3fcb2349b3
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
This commit is contained in:
pac7
2025-10-07 14:55:35 +00:00
parent f4020969d8
commit 88ed3207c4
5 changed files with 138 additions and 6 deletions

View File

@@ -1,6 +1,9 @@
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { Novu } from "npm:@novu/node@2.0.2";
// TODO: In production, restrict CORS to specific domains
// For now, allowing all origins for development flexibility
// Example production config: 'Access-Control-Allow-Origin': 'https://yourdomain.com'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
@@ -22,7 +25,24 @@ serve(async (req) => {
backendUrl: Deno.env.get('VITE_NOVU_API_URL') || 'https://api.novu.co',
});
const { subscriberId, email, firstName, lastName, phone, avatar, data } = await req.json();
// Parse and validate request body
let requestBody;
try {
requestBody = await req.json();
} catch (parseError) {
return new Response(
JSON.stringify({
success: false,
error: 'Invalid JSON in request body',
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
const { subscriberId, email, firstName, lastName, phone, avatar, data } = requestBody;
// Validate required fields
if (!subscriberId || typeof subscriberId !== 'string' || subscriberId.trim() === '') {
@@ -66,6 +86,106 @@ serve(async (req) => {
);
}
// Validate optional fields if provided
if (firstName !== undefined && firstName !== null && (typeof firstName !== 'string' || firstName.length > 100)) {
return new Response(
JSON.stringify({
success: false,
error: 'firstName must be a string with maximum 100 characters',
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
if (lastName !== undefined && lastName !== null && (typeof lastName !== 'string' || lastName.length > 100)) {
return new Response(
JSON.stringify({
success: false,
error: 'lastName must be a string with maximum 100 characters',
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
if (phone !== undefined && phone !== null) {
if (typeof phone !== 'string') {
return new Response(
JSON.stringify({
success: false,
error: 'phone must be a string',
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
// Validate phone format (basic validation for international numbers)
const phoneRegex = /^\+?[1-9]\d{1,14}$/;
if (!phoneRegex.test(phone.replace(/[\s\-\(\)]/g, ''))) {
return new Response(
JSON.stringify({
success: false,
error: 'Invalid phone format. Please provide a valid international phone number',
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
}
if (avatar !== undefined && avatar !== null && (typeof avatar !== 'string' || !avatar.startsWith('http'))) {
return new Response(
JSON.stringify({
success: false,
error: 'avatar must be a valid URL',
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
// Validate data field if provided
if (data !== undefined && data !== null) {
if (typeof data !== 'object' || Array.isArray(data)) {
return new Response(
JSON.stringify({
success: false,
error: 'data must be a valid object',
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
// Check data size (limit to 10KB serialized)
const dataSize = JSON.stringify(data).length;
if (dataSize > 10240) {
return new Response(
JSON.stringify({
success: false,
error: 'data field is too large (maximum 10KB)',
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
}
console.log('Creating Novu subscriber:', { subscriberId, email, firstName });
const subscriber = await novu.subscribers.identify(subscriberId, {