CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-minio

S3 Compatible Cloud Storage client for JavaScript/TypeScript

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

presigned-operations.mddocs/

Presigned Operations

This document covers presigned URL generation and POST policy creation for secure, time-limited access to MinIO/S3 objects without exposing credentials to clients.

Presigned URLs

Presigned URLs allow clients to access objects directly without requiring AWS credentials. These URLs are signed with your credentials and have an expiration time.

Generic Presigned URL

const url = await client.presignedUrl(method, bucketName, objectName, expires?, reqParams?, requestDate?)

// Parameters
method: string                    // HTTP method ('GET', 'PUT', 'POST', 'DELETE')
bucketName: string               // Bucket name
objectName: string               // Object name/key  
expires?: number                 // Expiry time in seconds (default: 604800 = 7 days)
reqParams?: PreSignRequestParams // Request parameters and headers
requestDate?: Date              // Request date (default: current time)

// Returns: Promise<string> - Presigned URL

PreSignRequestParams Interface

interface PreSignRequestParams {
  // Query parameters to include in URL
  [key: string]: string | undefined
  
  // Common parameters
  'response-content-type'?: string          // Override Content-Type in response
  'response-content-language'?: string      // Override Content-Language in response
  'response-expires'?: string               // Override Expires in response
  'response-cache-control'?: string         // Override Cache-Control in response
  'response-content-disposition'?: string   // Override Content-Disposition in response
  'response-content-encoding'?: string      // Override Content-Encoding in response
  
  // Version selection
  'versionId'?: string                      // Specific version ID
}

Presigned GET URL

const url = await client.presignedGetObject(bucketName, objectName, expires?, respHeaders?, requestDate?)

// Parameters  
bucketName: string                    // Bucket name
objectName: string                    // Object name/key
expires?: number                     // Expiry time in seconds (max: 604800)
respHeaders?: PreSignRequestParams   // Response headers to override
requestDate?: Date                  // Request date (default: current time)

// Returns: Promise<string> - Presigned GET URL

Presigned PUT URL

const url = await client.presignedPutObject(bucketName, objectName, expires?)

// Parameters
bucketName: string  // Bucket name  
objectName: string  // Object name/key
expires?: number   // Expiry time in seconds (max: 604800)

// Returns: Promise<string> - Presigned PUT URL

Presigned URL Examples

// Generate presigned GET URL (1 hour expiry)
const downloadUrl = await client.presignedGetObject('my-bucket', 'photo.jpg', 3600)
console.log('Download URL:', downloadUrl)
// Client can now download directly: fetch(downloadUrl)

// Generate presigned PUT URL (30 minutes expiry)  
const uploadUrl = await client.presignedPutObject('my-bucket', 'upload.pdf', 1800)
console.log('Upload URL:', uploadUrl)
// Client can now upload: fetch(uploadUrl, { method: 'PUT', body: file })

// Presigned GET with custom response headers
const customUrl = await client.presignedGetObject('my-bucket', 'document.pdf', 3600, {
  'response-content-type': 'application/octet-stream',
  'response-content-disposition': 'attachment; filename="custom-name.pdf"'
})
// Downloaded file will have custom headers

// Presigned URL for specific version
const versionUrl = await client.presignedGetObject('versioned-bucket', 'file.txt', 3600, {
  'versionId': 'version-123'
})

// Generic presigned URL for DELETE operation
const deleteUrl = await client.presignedUrl('DELETE', 'my-bucket', 'temp-file.txt', 600)
// Client can delete: fetch(deleteUrl, { method: 'DELETE' })

// Presigned URL with custom request date
const customDateUrl = await client.presignedUrl(
  'GET', 
  'my-bucket', 
  'file.txt',
  3600,
  {},
  new Date('2023-12-25T00:00:00Z') // Custom signing date
)

POST Policy

POST policies allow secure, direct browser uploads to S3 with fine-grained control over upload conditions and metadata.

PostPolicy Class

import { PostPolicy } from 'minio'

class PostPolicy {
  // Properties
  policy: {
    conditions: (string | number)[][]  // Policy conditions array
    expiration?: string               // Policy expiration timestamp
  }
  formData: Record<string, string>    // Form data for POST request
  
  // Methods
  setExpires(date: Date): void
  setKey(objectName: string): void
  setKeyStartsWith(prefix: string): void
  setBucket(bucketName: string): void
  setContentType(type: string): void
  setContentTypeStartsWith(prefix: string): void
  setContentDisposition(value: string): void
  setContentLengthRange(min: number, max: number): void
  setUserMetaData(metaData: ObjectMetaData): void
}

Create New POST Policy

Creates a new PostPolicy instance for defining upload conditions and generating presigned POST policies.

const postPolicy = client.newPostPolicy()

// Returns: PostPolicy instance

Examples

// Create new post policy
const postPolicy = client.newPostPolicy()

// Set policy conditions
postPolicy.setBucket('my-bucket')
postPolicy.setKey('uploads/file.jpg')
postPolicy.setExpires(new Date(Date.now() + 24 * 60 * 60 * 1000)) // 24 hours
postPolicy.setContentType('image/jpeg')
postPolicy.setContentLengthRange(1024, 5 * 1024 * 1024) // 1KB to 5MB

// Generate presigned policy
const presignedPostPolicy = await client.presignedPostPolicy(postPolicy)

// Alternative: Using PostPolicy constructor directly
import { PostPolicy } from 'minio'
const altPolicy = new PostPolicy()
altPolicy.setBucket('my-bucket')
// ... set conditions

Generate Presigned POST Policy

const result = await client.presignedPostPolicy(postPolicy)

// Parameters
postPolicy: PostPolicy  // POST policy configuration

// Returns: Promise<PostPolicyResult>

PostPolicyResult Interface

interface PostPolicyResult {
  postURL: string                    // URL to POST to
  formData: Record<string, string>   // Form fields to include in POST
}

POST Policy Examples

import { PostPolicy } from 'minio'

// Basic POST policy for browser uploads
const policy = new PostPolicy()

// Set policy expiration (required)
const expires = new Date()
expires.setMinutes(expires.getMinutes() + 10) // 10 minutes from now
policy.setExpires(expires)

// Set bucket and object constraints  
policy.setBucket('upload-bucket')
policy.setKey('user-uploads/photo.jpg')           // Exact key
// OR
policy.setKeyStartsWith('user-uploads/')          // Key prefix

// Set content type constraints
policy.setContentType('image/jpeg')               // Exact type
// OR  
policy.setContentTypeStartsWith('image/')         // Type prefix

// Set file size limits (1KB to 10MB)
policy.setContentLengthRange(1024, 10 * 1024 * 1024)

// Add custom metadata
policy.setUserMetaData({
  'uploaded-by': 'web-client',
  'upload-session': 'abc123'
})

// Generate presigned POST policy
const { postURL, formData } = await client.presignedPostPolicy(policy)

console.log('POST URL:', postURL)
console.log('Form data:', formData)

// Advanced POST policy with multiple conditions
const advancedPolicy = new PostPolicy()

// Set expiration (1 hour)
const expiry = new Date()
expiry.setHours(expiry.getHours() + 1)
advancedPolicy.setExpires(expiry)

// Multiple conditions
advancedPolicy.setBucket('secure-uploads')
advancedPolicy.setKeyStartsWith('documents/')     // Only allow documents/ prefix
advancedPolicy.setContentTypeStartsWith('application/') // Only application/* types
advancedPolicy.setContentLengthRange(100, 50 * 1024 * 1024) // 100B to 50MB
advancedPolicy.setContentDisposition('attachment') // Force download

// Custom metadata for tracking
advancedPolicy.setUserMetaData({
  'department': 'legal',
  'classification': 'internal',
  'retention-years': '7'
})

const advancedResult = await client.presignedPostPolicy(advancedPolicy)

Browser POST Upload Example

<!-- HTML form for browser upload using POST policy -->
<!DOCTYPE html>
<html>
<head>
    <title>Direct S3 Upload</title>
</head>
<body>
    <form id="upload-form" method="POST" enctype="multipart/form-data">
        <!-- Form fields from formData -->
        <input type="hidden" name="key" value="">
        <input type="hidden" name="policy" value="">
        <input type="hidden" name="x-amz-algorithm" value="">
        <input type="hidden" name="x-amz-credential" value="">
        <input type="hidden" name="x-amz-date" value="">
        <input type="hidden" name="x-amz-signature" value="">
        
        <!-- File input -->
        <input type="file" name="file" required>
        <button type="submit">Upload</button>
    </form>

    <script>
    // JavaScript to populate form and handle upload
    async function setupUpload() {
        // Get presigned POST policy from your backend
        const response = await fetch('/api/presigned-post-policy', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                bucket: 'upload-bucket',
                keyPrefix: 'user-uploads/',
                maxSize: 10 * 1024 * 1024
            })
        })
        
        const { postURL, formData } = await response.json()
        
        // Set form action to POST URL
        const form = document.getElementById('upload-form')
        form.action = postURL
        
        // Populate hidden fields
        Object.entries(formData).forEach(([name, value]) => {
            const input = form.querySelector(`input[name="${name}"]`)
            if (input) input.value = value
        })
        
        // Handle form submission
        form.addEventListener('submit', async (e) => {
            e.preventDefault()
            
            const formData = new FormData(form)
            const file = formData.get('file')
            
            if (!file) {
                alert('Please select a file')
                return
            }
            
            try {
                const uploadResponse = await fetch(postURL, {
                    method: 'POST',
                    body: formData
                })
                
                if (uploadResponse.ok) {
                    alert('Upload successful!')
                } else {
                    alert('Upload failed: ' + uploadResponse.statusText)
                }
            } catch (error) {
                alert('Upload error: ' + error.message)
            }
        })
    }
    
    setupUpload()
    </script>
</body>
</html>

Advanced Presigned Operations

Presigned URL with Custom Headers

// Presigned PUT URL that enforces specific headers
const putUrl = await client.presignedUrl('PUT', 'my-bucket', 'strict-upload.pdf', 3600, {
  // These parameters will be included in the signature
  'x-amz-server-side-encryption': 'AES256',
  'x-amz-meta-uploader': 'api-service'
})

// Client must include these exact headers when making the PUT request
// fetch(putUrl, {
//   method: 'PUT',
//   body: file,
//   headers: {
//     'x-amz-server-side-encryption': 'AES256',
//     'x-amz-meta-uploader': 'api-service'
//   }
// })

Batch Presigned URL Generation

// Generate multiple presigned URLs efficiently
async function generateBatchUrls(bucketName, objectNames, expires = 3600) {
  const urls = await Promise.all(
    objectNames.map(objectName => 
      client.presignedGetObject(bucketName, objectName, expires)
    )
  )
  
  return objectNames.reduce((acc, objectName, index) => {
    acc[objectName] = urls[index]
    return acc
  }, {})
}

const objectNames = ['file1.pdf', 'file2.jpg', 'file3.txt']
const urlMap = await generateBatchUrls('my-bucket', objectNames)

// Usage
Object.entries(urlMap).forEach(([objectName, url]) => {
  console.log(`${objectName}: ${url}`)
})

Time-Limited Batch Operations

// Create time-limited upload slots for multiple files
async function createUploadSlots(bucketName, fileNames, validMinutes = 30) {
  const policy = new PostPolicy()
  
  // Set expiration
  const expires = new Date()
  expires.setMinutes(expires.getMinutes() + validMinutes)
  policy.setExpires(expires)
  
  // Configure bucket and key prefix
  policy.setBucket(bucketName)
  policy.setKeyStartsWith('batch-uploads/')
  
  // Allow various content types
  policy.setContentLengthRange(1, 100 * 1024 * 1024) // 1B to 100MB
  
  // Generate URLs for each file
  const uploadSlots = []
  
  for (const fileName of fileNames) {
    // Create separate policy for each file for specific key
    const filePolicy = new PostPolicy()
    filePolicy.setExpires(expires)
    filePolicy.setBucket(bucketName)
    filePolicy.setKey(`batch-uploads/${Date.now()}-${fileName}`)
    filePolicy.setContentLengthRange(1, 100 * 1024 * 1024)
    
    const result = await client.presignedPostPolicy(filePolicy)
    uploadSlots.push({
      fileName,
      ...result
    })
  }
  
  return uploadSlots
}

const files = ['document1.pdf', 'image1.jpg', 'data1.csv']  
const uploadSlots = await createUploadSlots('batch-bucket', files, 15)

uploadSlots.forEach(slot => {
  console.log(`Upload slot for ${slot.fileName}:`)
  console.log(`  URL: ${slot.postURL}`)
  console.log(`  Form data:`, slot.formData)
})

Security Considerations

URL Expiration Limits

import { PRESIGN_EXPIRY_DAYS_MAX } from 'minio'

console.log('Maximum expiry:', PRESIGN_EXPIRY_DAYS_MAX, 'seconds') // 604800 (7 days)

// This will throw an error
try {
  await client.presignedGetObject('bucket', 'object', PRESIGN_EXPIRY_DAYS_MAX + 1)
} catch (error) {
  console.error('Expiry too long:', error.message)
}

// Safe expiry times
const oneHour = 3600
const oneDay = 24 * 3600
const oneWeek = 7 * 24 * 3600 // Maximum allowed

const url = await client.presignedGetObject('bucket', 'object', oneWeek)

POST Policy Validation

// Strict POST policy with multiple security constraints
const securePolicy = new PostPolicy()

// Short expiration for security
const shortExpiry = new Date()
shortExpiry.setMinutes(shortExpiry.getMinutes() + 5) // 5 minutes only
securePolicy.setExpires(shortExpiry)

// Restrict to specific bucket and path
securePolicy.setBucket('secure-uploads')
securePolicy.setKeyStartsWith('verified-users/') 

// Strict content type validation
securePolicy.setContentType('image/jpeg') // Only JPEG images

// Strict size limits
securePolicy.setContentLengthRange(1024, 2 * 1024 * 1024) // 1KB to 2MB

// Add security metadata
securePolicy.setUserMetaData({
  'security-scan': 'pending',
  'upload-source': 'verified-client'
})

const secureResult = await client.presignedPostPolicy(securePolicy)

Error Handling

import { 
  S3Error, 
  ExpiresParamError, 
  InvalidArgumentError 
} from 'minio'

try {
  // Invalid expiry (too long)
  await client.presignedGetObject('bucket', 'object', 10 * 24 * 3600) // 10 days
} catch (error) {
  if (error instanceof ExpiresParamError) {
    console.error('Invalid expiry time:', error.message)
  }
}

try {
  // Invalid bucket name
  await client.presignedPutObject('invalid..bucket', 'object.txt')
} catch (error) {
  if (error instanceof InvalidArgumentError) {
    console.error('Invalid bucket name:', error.message)
  }
}

// Handle POST policy errors
try {
  const policy = new PostPolicy()
  // Missing required expiration
  await client.presignedPostPolicy(policy)
} catch (error) {
  console.error('Policy error:', error.message)
}

Best Practices

1. Security

  • Use shortest reasonable expiration times
  • Validate client requests match presigned parameters
  • Monitor presigned URL usage and abuse
  • Consider IP restrictions for sensitive operations

2. Performance

  • Cache presigned URLs when appropriate
  • Generate URLs in batches for efficiency
  • Use POST policies for browser uploads
  • Consider CDN integration for download URLs

3. User Experience

  • Provide clear upload progress feedback
  • Handle upload failures gracefully
  • Validate files on client side before upload
  • Use appropriate content-disposition headers

4. Monitoring

  • Log presigned URL generation and usage
  • Monitor for unusual access patterns
  • Track upload success/failure rates
  • Set up alerts for policy violations

5. Integration

  • Separate presigned URL generation from client logic
  • Use backend services to generate URLs securely
  • Implement proper CORS for browser uploads
  • Handle different file types appropriately

Next: Notifications - Learn about event notification system and polling

docs

advanced-objects.md

bucket-operations.md

client-setup.md

index.md

notifications.md

object-operations.md

presigned-operations.md

types-and-errors.md

tile.json