This project is a fully serverless, token-based file upload system built on AWS. It allows clients to securely upload sensitive documents via a unique, anonymous link, using presigned S3 URLs. Ideal for healthtech, fintech, legal tech, and other privacy-sensitive applications.
✅ Anonymous, secure document uploads
✅ One-time tokens mapped to client IDs
✅ Mobile-friendly — no login required
✅ Files go directly to S3 via presigned URLs
✅ Upload metadata (file name, type, timestamp) is logged
✅ GDPR-compliant: HTTPS-only, encrypted S3 storage, and no PII in URLs
- AWS Lambda – Serverless compute
- API Gateway – REST endpoints
- S3 – Encrypted document storage
- DynamoDB – Token and upload log store
- Python 3.11
- Serverless Framework
secure-upload/
├── src/
│ ├── generate_token.py # Generate upload tokens
│ ├── get_presigned_url.py # Get signed S3 upload URLs
│ └── confirm_upload.py # Log file metadata after upload
├── template.yml # Serverless deployment config
├── requirements.txt # Python dependencies
└── README.md
- Node.js & npm
- Python 3.11+
- AWS CLI configured (
aws configure) - Serverless Framework installed:
npm install -g serverlessPlease note that the name of S3 bucket must be unique, otherwise the deployment of the stack (template) will fail.
-
Clone the repository:
git clone https://github.com/your-username/s3-secure-upload-v1.git cd s3-secure-upload-v1 -
Install Python dependencies:
pip install -r requirements.txt
-
Deploy with Serverless:
sam build sam deploy --guided
- Stack Name:
sam-s3-secure-upload - AWS Region: e.g.
us-west-2 - Confirm changes before deploy [y/N]: N
- Allow SAM CLI IAM role creation [Y/n]: Y
- Disable rollback [y/N]: N
- GenerateTokenFunction has no authentication. Is this okay? [y/N]: y
- PresignedURLFunction has no authentication. Is this okay? [y/N]: y
- ConfirmUploadFunction has no authentication. Is this okay? [y/N]: y
- Save arguments to configuration file [Y/n]: Y
- SAM configuration file [samconfig.toml]: samconfig.toml
- SAM configuration environment [default]: default
curl -X POST https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/generate-token \
-H "Content-Type: application/json" \
-d '{"client_id": "ABC123"}'
Response:
{
"token": "224DB687",
"upload_url": "/upload-url/224DB687",
"expires_at": "2025-08-12T15:25:46.203808"
}
s3-secure-uploadPresignedURL Policy (executionRole)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"s3:PutObject"
],
"Resource": [
"arn:aws:dynamodb:af-south-1:XXXXXXXXXXXX:table/SecureUploadTokens",
"arn:aws:dynamodb:us-west-2:XXXXXXXXXXXX:table/SecureUploadLogs"
"arn:aws:s3:::secure-upload-bucket-kbm/*"
]
}
]
}
From the response in step 1. Replace the token in the endpoint below
curl https://xxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/upload-url/{token}?ext={file_extention}
Response:
(
"upload_url": `Presigned URL`
"filename": "e8006b5b87914dfca7f89706eb03bb72.txt",
"key": "uploads/e8006b5b87914dfca7f89706eb03bb72.txt",
"content_type": "application/octet-stream"
}
Using the Presigned URL from the response of the previous GET request, go to a browser, paste the URL
curl -X PUT -H "Content-Type: application/octet-stream" -T e8006b5b87914dfca7f89706eb03bb72.txt `Presigned URL`
curl -X POST https://xxxxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/upload/confirm/224DB687 \
-H "Content-Type: application/json" \
-d '{"filename": "e8006b5b87914dfca7f89706eb03bb72.txt", "content_type": "application/txt"}'
Response:
{"message": "Upload confirmed"}
- ✅ AES256 encryption for all S3 uploads
- ✅ Tokens expire after 3 days
- ✅ HTTPS-only deployment
- ✅ IAM roles enforce least privilege
- ✅ No personal info in file paths or URLs
- Optional email notification (SES)
- Expiring upload tokens (Dynamo TTL)
- Admin dashboard for reviewing uploads
This project is open-source and available under the MIT License.
---