mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 10:31:13 -05:00
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:
@@ -148,16 +148,25 @@ export function PhotoUpload({
|
||||
throw new Error('Direct upload to Cloudflare failed');
|
||||
}
|
||||
|
||||
// Fetch session token once before polling
|
||||
const sessionData = await supabase.auth.getSession();
|
||||
const accessToken = sessionData.data.session?.access_token;
|
||||
|
||||
if (!accessToken) {
|
||||
revokeObjectUrl(previewUrl);
|
||||
throw new Error('Authentication required for upload');
|
||||
}
|
||||
|
||||
const maxAttempts = 60;
|
||||
let attempts = 0;
|
||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || 'https://ydvtmnrszybqnbcqbdcy.supabase.co';
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
try {
|
||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || 'https://ydvtmnrszybqnbcqbdcy.supabase.co';
|
||||
const response = await fetch(`${supabaseUrl}/functions/v1/upload-image?id=${encodeURIComponent(id)}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${(await supabase.auth.getSession()).data.session?.access_token}`,
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -67,7 +67,7 @@ class NotificationService {
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
console.error('Error creating Novu subscriber:', error);
|
||||
return { success: false, error: error.message };
|
||||
return { success: false, error: error instanceof Error ? error.message : 'An unexpected error occurred' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ class NotificationService {
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
console.error('Error updating Novu subscriber:', error);
|
||||
return { success: false, error: error.message };
|
||||
return { success: false, error: error instanceof Error ? error.message : 'An unexpected error occurred' };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ export async function createEntityVersion(params: {
|
||||
console.error('Error creating entity version:', error);
|
||||
toast({
|
||||
title: 'Version Creation Failed',
|
||||
description: error.message,
|
||||
description: error instanceof Error ? error.message : 'An unexpected error occurred',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return null;
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@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',
|
||||
|
||||
Reference in New Issue
Block a user