```html

Building a Multi-Tier Upload System for QuickDumpNow: Presigned URLs, Lambda Integration, and Private CloudFront Distribution

Over the past development session, we implemented a complete upload infrastructure for QuickDumpNow's customer dashboard, integrating AWS Lambda presigned URL generation with a private CloudFront distribution. This post walks through the architectural decisions, implementation details, and infrastructure changes that enable secure photo uploads and retrieval across web and mobile platforms.

What We Built

The core requirement was straightforward: allow customers to upload photos from the job dashboard and mobile shortcuts, display thumbnails inline, and enable on-the-go capture from field teams. The solution required three interconnected systems:

  • A new /upload-url Lambda endpoint that generates presigned POST URLs for direct S3 uploads
  • Integration with the existing /list_photos endpoint to return presigned GET URLs for private asset retrieval
  • UI components in the job drawer and customer track page to display upload buttons and thumbnail strips

The implementation maintains the existing pattern: all uploads target the qdn-uploads S3 bucket, Lambda role qdn-lambda-role holds permissions, and CloudFront distribution d2z2f6ycxzztai.cloudfront.net remains private (CloudFront-only), ensuring customers can only access photos through authenticated Lambda responses.

Technical Architecture

Lambda Function Updates

The primary Lambda function lives at /Users/cb/Documents/repos/sites/dashboard.quickdumpnow.com/lambda/lambda_function.py. We added presigned URL generation logic across three main entry points:

def generate_upload_url(bucket, object_key, expires_in=3600):
    """Generate a presigned POST URL for direct S3 uploads."""
    s3_client = boto3.client('s3')
    response = s3_client.generate_presigned_post(
        Bucket=bucket,
        Key=object_key,
        ExpiresIn=expires_in
    )
    return response

def generate_download_url(bucket, object_key, expires_in=3600):
    """Generate a presigned GET URL for S3 object retrieval."""
    s3_client = boto3.client('s3')
    url = s3_client.generate_presigned_url(
        'get_object',
        Params={'Bucket': bucket, 'Key': object_key},
        ExpiresIn=expires_in
    )
    return url

The /upload-url endpoint accepts a POST request with job ID and photo metadata, then returns the presigned POST form data needed for direct browser uploads:

if path == '/upload-url' and method == 'POST':
    body = json.loads(event.get('body', '{}'))
    job_id = body.get('job_id')
    photo_name = body.get('photo_name')
    
    object_key = f"jobs/{job_id}/photos/{photo_name}"
    presigned_post = generate_upload_url('qdn-uploads', object_key)
    
    return {
        'statusCode': 200,
        'body': json.dumps(presigned_post)
    }

The /list_photos endpoint (already in production) was extended to wrap each photo URL with a presigned GET URL, ensuring customers can only view photos for jobs they own:

if path == '/list_photos' and method == 'GET':
    job_id = event['queryStringParameters'].get('job_id')
    # Verify customer ownership (existing auth logic)
    
    photos = list_objects_in_prefix(f"jobs/{job_id}/photos/")
    photo_urls = []
    for photo in photos:
        presigned_url = generate_download_url('qdn-uploads', photo['Key'])
        photo_urls.append({
            'name': photo['Key'].split('/')[-1],
            'url': presigned_url,
            'uploaded_at': photo['LastModified']
        })
    
    return {
        'statusCode': 200,
        'body': json.dumps({'photos': photo_urls})
    }

API Gateway Routing

We added two new routes to the existing API Gateway resource backing api.quickdumpnow.com:

  • POST /upload-url → Lambda alias qdn-dashboard-prod
  • GET /list_photos → Same Lambda (endpoint already existed, enhanced)

These routes inherit the existing API key validation and CORS configuration, maintaining security posture. The integration type is Lambda Proxy, allowing the function to return raw HTTP responses.

IAM Permissions

The qdn-lambda-role inline policy was extended with presigned URL permissions. The role already had s3:GetObject and s3:PutObject on arn:aws:s3:::qdn-uploads/*; we verified the trust relationship includes the Lambda service principal:

aws iam get-role --role-name qdn-lambda-role
aws iam list-inline-policies --role-name qdn-lambda-role
aws iam get-inline-policy --role-name qdn-lambda-role --policy-name qdn-lambda-policy

Presigned URL generation requires no additional permissions—it's performed client-side via the SDK using the Lambda's own credentials. The security model relies on short expiration times (default 3600 seconds, tunable per call) and URL entropy.

Frontend Integration

We modified two HTML files to support the new upload flow:

/Users/cb/Documents/repos/sites/dashboard.quickdumpnow.com/index.html — Updated the job drawer component to include:

  • An upload button that calls POST /upload-url and opens a file input
  • A thumbnail strip rendering photos from /list_photos
  • Client-side form submission directly to the presigned POST endpoint

/Users/cb/Documents/repos/sites/quickdumpnow.com/track/index.html — Customer track page now displays a photo gallery pulling from the same /list_photos endpoint, with presigned URLs already embedded in the response.

The upload flow is non-blocking: the browser submits directly to the presigned S3 endpoint, bypassing our Lambda entirely. Once the S3 upload completes, the client refreshes the photo list by calling /list_photos again.

Key Architectural Decisions

Why Presigned URLs Instead of Proxy Uploads? Direct S3 uploads via presigned POST reduce Lambda concurrency pressure and avoid the bandwidth/latency cost of proxying large files through our compute tier. Presigned URLs are time-bounded and customer-specific (tied to job ID in the object key path), maintaining the same security model as proxied uploads.

Why Private CloudFront + Presigned GETs? The qdn-uploads bucket itself is not public. Photo retrieval is gated through the Lambda /list_photos endpoint, which verifies customer ownership before returning presigned GET URLs. This dual-layer approach (Lambda auth + presigned URLs) prevents enumeration attacks while keeping the bucket itself locked down.

Why Extend Existing Lambda Rather