or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-objects.mdbucket-operations.mdclient-setup.mdindex.mdnotifications.mdobject-operations.mdpresigned-operations.mdtypes-and-errors.md

presigned-operations.mddocs/

0

# Presigned Operations

1

2

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

3

4

## Presigned URLs

5

6

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

7

8

### Generic Presigned URL

9

10

```javascript { .api }

11

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

12

13

// Parameters

14

method: string // HTTP method ('GET', 'PUT', 'POST', 'DELETE')

15

bucketName: string // Bucket name

16

objectName: string // Object name/key

17

expires?: number // Expiry time in seconds (default: 604800 = 7 days)

18

reqParams?: PreSignRequestParams // Request parameters and headers

19

requestDate?: Date // Request date (default: current time)

20

21

// Returns: Promise<string> - Presigned URL

22

```

23

24

#### PreSignRequestParams Interface

25

26

```typescript { .api }

27

interface PreSignRequestParams {

28

// Query parameters to include in URL

29

[key: string]: string | undefined

30

31

// Common parameters

32

'response-content-type'?: string // Override Content-Type in response

33

'response-content-language'?: string // Override Content-Language in response

34

'response-expires'?: string // Override Expires in response

35

'response-cache-control'?: string // Override Cache-Control in response

36

'response-content-disposition'?: string // Override Content-Disposition in response

37

'response-content-encoding'?: string // Override Content-Encoding in response

38

39

// Version selection

40

'versionId'?: string // Specific version ID

41

}

42

```

43

44

### Presigned GET URL

45

46

```javascript { .api }

47

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

48

49

// Parameters

50

bucketName: string // Bucket name

51

objectName: string // Object name/key

52

expires?: number // Expiry time in seconds (max: 604800)

53

respHeaders?: PreSignRequestParams // Response headers to override

54

requestDate?: Date // Request date (default: current time)

55

56

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

57

```

58

59

### Presigned PUT URL

60

61

```javascript { .api }

62

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

63

64

// Parameters

65

bucketName: string // Bucket name

66

objectName: string // Object name/key

67

expires?: number // Expiry time in seconds (max: 604800)

68

69

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

70

```

71

72

### Presigned URL Examples

73

74

```javascript { .api }

75

// Generate presigned GET URL (1 hour expiry)

76

const downloadUrl = await client.presignedGetObject('my-bucket', 'photo.jpg', 3600)

77

console.log('Download URL:', downloadUrl)

78

// Client can now download directly: fetch(downloadUrl)

79

80

// Generate presigned PUT URL (30 minutes expiry)

81

const uploadUrl = await client.presignedPutObject('my-bucket', 'upload.pdf', 1800)

82

console.log('Upload URL:', uploadUrl)

83

// Client can now upload: fetch(uploadUrl, { method: 'PUT', body: file })

84

85

// Presigned GET with custom response headers

86

const customUrl = await client.presignedGetObject('my-bucket', 'document.pdf', 3600, {

87

'response-content-type': 'application/octet-stream',

88

'response-content-disposition': 'attachment; filename="custom-name.pdf"'

89

})

90

// Downloaded file will have custom headers

91

92

// Presigned URL for specific version

93

const versionUrl = await client.presignedGetObject('versioned-bucket', 'file.txt', 3600, {

94

'versionId': 'version-123'

95

})

96

97

// Generic presigned URL for DELETE operation

98

const deleteUrl = await client.presignedUrl('DELETE', 'my-bucket', 'temp-file.txt', 600)

99

// Client can delete: fetch(deleteUrl, { method: 'DELETE' })

100

101

// Presigned URL with custom request date

102

const customDateUrl = await client.presignedUrl(

103

'GET',

104

'my-bucket',

105

'file.txt',

106

3600,

107

{},

108

new Date('2023-12-25T00:00:00Z') // Custom signing date

109

)

110

```

111

112

## POST Policy

113

114

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

115

116

### PostPolicy Class

117

118

```typescript { .api }

119

import { PostPolicy } from 'minio'

120

121

class PostPolicy {

122

// Properties

123

policy: {

124

conditions: (string | number)[][] // Policy conditions array

125

expiration?: string // Policy expiration timestamp

126

}

127

formData: Record<string, string> // Form data for POST request

128

129

// Methods

130

setExpires(date: Date): void

131

setKey(objectName: string): void

132

setKeyStartsWith(prefix: string): void

133

setBucket(bucketName: string): void

134

setContentType(type: string): void

135

setContentTypeStartsWith(prefix: string): void

136

setContentDisposition(value: string): void

137

setContentLengthRange(min: number, max: number): void

138

setUserMetaData(metaData: ObjectMetaData): void

139

}

140

```

141

142

### Create New POST Policy

143

144

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

145

146

```javascript { .api }

147

const postPolicy = client.newPostPolicy()

148

149

// Returns: PostPolicy instance

150

```

151

152

#### Examples

153

154

```javascript

155

// Create new post policy

156

const postPolicy = client.newPostPolicy()

157

158

// Set policy conditions

159

postPolicy.setBucket('my-bucket')

160

postPolicy.setKey('uploads/file.jpg')

161

postPolicy.setExpires(new Date(Date.now() + 24 * 60 * 60 * 1000)) // 24 hours

162

postPolicy.setContentType('image/jpeg')

163

postPolicy.setContentLengthRange(1024, 5 * 1024 * 1024) // 1KB to 5MB

164

165

// Generate presigned policy

166

const presignedPostPolicy = await client.presignedPostPolicy(postPolicy)

167

168

// Alternative: Using PostPolicy constructor directly

169

import { PostPolicy } from 'minio'

170

const altPolicy = new PostPolicy()

171

altPolicy.setBucket('my-bucket')

172

// ... set conditions

173

```

174

175

### Generate Presigned POST Policy

176

177

```javascript { .api }

178

const result = await client.presignedPostPolicy(postPolicy)

179

180

// Parameters

181

postPolicy: PostPolicy // POST policy configuration

182

183

// Returns: Promise<PostPolicyResult>

184

```

185

186

#### PostPolicyResult Interface

187

188

```typescript { .api }

189

interface PostPolicyResult {

190

postURL: string // URL to POST to

191

formData: Record<string, string> // Form fields to include in POST

192

}

193

```

194

195

### POST Policy Examples

196

197

```javascript { .api }

198

import { PostPolicy } from 'minio'

199

200

// Basic POST policy for browser uploads

201

const policy = new PostPolicy()

202

203

// Set policy expiration (required)

204

const expires = new Date()

205

expires.setMinutes(expires.getMinutes() + 10) // 10 minutes from now

206

policy.setExpires(expires)

207

208

// Set bucket and object constraints

209

policy.setBucket('upload-bucket')

210

policy.setKey('user-uploads/photo.jpg') // Exact key

211

// OR

212

policy.setKeyStartsWith('user-uploads/') // Key prefix

213

214

// Set content type constraints

215

policy.setContentType('image/jpeg') // Exact type

216

// OR

217

policy.setContentTypeStartsWith('image/') // Type prefix

218

219

// Set file size limits (1KB to 10MB)

220

policy.setContentLengthRange(1024, 10 * 1024 * 1024)

221

222

// Add custom metadata

223

policy.setUserMetaData({

224

'uploaded-by': 'web-client',

225

'upload-session': 'abc123'

226

})

227

228

// Generate presigned POST policy

229

const { postURL, formData } = await client.presignedPostPolicy(policy)

230

231

console.log('POST URL:', postURL)

232

console.log('Form data:', formData)

233

234

// Advanced POST policy with multiple conditions

235

const advancedPolicy = new PostPolicy()

236

237

// Set expiration (1 hour)

238

const expiry = new Date()

239

expiry.setHours(expiry.getHours() + 1)

240

advancedPolicy.setExpires(expiry)

241

242

// Multiple conditions

243

advancedPolicy.setBucket('secure-uploads')

244

advancedPolicy.setKeyStartsWith('documents/') // Only allow documents/ prefix

245

advancedPolicy.setContentTypeStartsWith('application/') // Only application/* types

246

advancedPolicy.setContentLengthRange(100, 50 * 1024 * 1024) // 100B to 50MB

247

advancedPolicy.setContentDisposition('attachment') // Force download

248

249

// Custom metadata for tracking

250

advancedPolicy.setUserMetaData({

251

'department': 'legal',

252

'classification': 'internal',

253

'retention-years': '7'

254

})

255

256

const advancedResult = await client.presignedPostPolicy(advancedPolicy)

257

```

258

259

### Browser POST Upload Example

260

261

```html

262

<!-- HTML form for browser upload using POST policy -->

263

<!DOCTYPE html>

264

<html>

265

<head>

266

<title>Direct S3 Upload</title>

267

</head>

268

<body>

269

<form id="upload-form" method="POST" enctype="multipart/form-data">

270

<!-- Form fields from formData -->

271

<input type="hidden" name="key" value="">

272

<input type="hidden" name="policy" value="">

273

<input type="hidden" name="x-amz-algorithm" value="">

274

<input type="hidden" name="x-amz-credential" value="">

275

<input type="hidden" name="x-amz-date" value="">

276

<input type="hidden" name="x-amz-signature" value="">

277

278

<!-- File input -->

279

<input type="file" name="file" required>

280

<button type="submit">Upload</button>

281

</form>

282

283

<script>

284

// JavaScript to populate form and handle upload

285

async function setupUpload() {

286

// Get presigned POST policy from your backend

287

const response = await fetch('/api/presigned-post-policy', {

288

method: 'POST',

289

headers: { 'Content-Type': 'application/json' },

290

body: JSON.stringify({

291

bucket: 'upload-bucket',

292

keyPrefix: 'user-uploads/',

293

maxSize: 10 * 1024 * 1024

294

})

295

})

296

297

const { postURL, formData } = await response.json()

298

299

// Set form action to POST URL

300

const form = document.getElementById('upload-form')

301

form.action = postURL

302

303

// Populate hidden fields

304

Object.entries(formData).forEach(([name, value]) => {

305

const input = form.querySelector(`input[name="${name}"]`)

306

if (input) input.value = value

307

})

308

309

// Handle form submission

310

form.addEventListener('submit', async (e) => {

311

e.preventDefault()

312

313

const formData = new FormData(form)

314

const file = formData.get('file')

315

316

if (!file) {

317

alert('Please select a file')

318

return

319

}

320

321

try {

322

const uploadResponse = await fetch(postURL, {

323

method: 'POST',

324

body: formData

325

})

326

327

if (uploadResponse.ok) {

328

alert('Upload successful!')

329

} else {

330

alert('Upload failed: ' + uploadResponse.statusText)

331

}

332

} catch (error) {

333

alert('Upload error: ' + error.message)

334

}

335

})

336

}

337

338

setupUpload()

339

</script>

340

</body>

341

</html>

342

```

343

344

## Advanced Presigned Operations

345

346

### Presigned URL with Custom Headers

347

348

```javascript { .api }

349

// Presigned PUT URL that enforces specific headers

350

const putUrl = await client.presignedUrl('PUT', 'my-bucket', 'strict-upload.pdf', 3600, {

351

// These parameters will be included in the signature

352

'x-amz-server-side-encryption': 'AES256',

353

'x-amz-meta-uploader': 'api-service'

354

})

355

356

// Client must include these exact headers when making the PUT request

357

// fetch(putUrl, {

358

// method: 'PUT',

359

// body: file,

360

// headers: {

361

// 'x-amz-server-side-encryption': 'AES256',

362

// 'x-amz-meta-uploader': 'api-service'

363

// }

364

// })

365

```

366

367

### Batch Presigned URL Generation

368

369

```javascript { .api }

370

// Generate multiple presigned URLs efficiently

371

async function generateBatchUrls(bucketName, objectNames, expires = 3600) {

372

const urls = await Promise.all(

373

objectNames.map(objectName =>

374

client.presignedGetObject(bucketName, objectName, expires)

375

)

376

)

377

378

return objectNames.reduce((acc, objectName, index) => {

379

acc[objectName] = urls[index]

380

return acc

381

}, {})

382

}

383

384

const objectNames = ['file1.pdf', 'file2.jpg', 'file3.txt']

385

const urlMap = await generateBatchUrls('my-bucket', objectNames)

386

387

// Usage

388

Object.entries(urlMap).forEach(([objectName, url]) => {

389

console.log(`${objectName}: ${url}`)

390

})

391

```

392

393

### Time-Limited Batch Operations

394

395

```javascript { .api }

396

// Create time-limited upload slots for multiple files

397

async function createUploadSlots(bucketName, fileNames, validMinutes = 30) {

398

const policy = new PostPolicy()

399

400

// Set expiration

401

const expires = new Date()

402

expires.setMinutes(expires.getMinutes() + validMinutes)

403

policy.setExpires(expires)

404

405

// Configure bucket and key prefix

406

policy.setBucket(bucketName)

407

policy.setKeyStartsWith('batch-uploads/')

408

409

// Allow various content types

410

policy.setContentLengthRange(1, 100 * 1024 * 1024) // 1B to 100MB

411

412

// Generate URLs for each file

413

const uploadSlots = []

414

415

for (const fileName of fileNames) {

416

// Create separate policy for each file for specific key

417

const filePolicy = new PostPolicy()

418

filePolicy.setExpires(expires)

419

filePolicy.setBucket(bucketName)

420

filePolicy.setKey(`batch-uploads/${Date.now()}-${fileName}`)

421

filePolicy.setContentLengthRange(1, 100 * 1024 * 1024)

422

423

const result = await client.presignedPostPolicy(filePolicy)

424

uploadSlots.push({

425

fileName,

426

...result

427

})

428

}

429

430

return uploadSlots

431

}

432

433

const files = ['document1.pdf', 'image1.jpg', 'data1.csv']

434

const uploadSlots = await createUploadSlots('batch-bucket', files, 15)

435

436

uploadSlots.forEach(slot => {

437

console.log(`Upload slot for ${slot.fileName}:`)

438

console.log(` URL: ${slot.postURL}`)

439

console.log(` Form data:`, slot.formData)

440

})

441

```

442

443

## Security Considerations

444

445

### URL Expiration Limits

446

447

```javascript { .api }

448

import { PRESIGN_EXPIRY_DAYS_MAX } from 'minio'

449

450

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

451

452

// This will throw an error

453

try {

454

await client.presignedGetObject('bucket', 'object', PRESIGN_EXPIRY_DAYS_MAX + 1)

455

} catch (error) {

456

console.error('Expiry too long:', error.message)

457

}

458

459

// Safe expiry times

460

const oneHour = 3600

461

const oneDay = 24 * 3600

462

const oneWeek = 7 * 24 * 3600 // Maximum allowed

463

464

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

465

```

466

467

### POST Policy Validation

468

469

```javascript { .api }

470

// Strict POST policy with multiple security constraints

471

const securePolicy = new PostPolicy()

472

473

// Short expiration for security

474

const shortExpiry = new Date()

475

shortExpiry.setMinutes(shortExpiry.getMinutes() + 5) // 5 minutes only

476

securePolicy.setExpires(shortExpiry)

477

478

// Restrict to specific bucket and path

479

securePolicy.setBucket('secure-uploads')

480

securePolicy.setKeyStartsWith('verified-users/')

481

482

// Strict content type validation

483

securePolicy.setContentType('image/jpeg') // Only JPEG images

484

485

// Strict size limits

486

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

487

488

// Add security metadata

489

securePolicy.setUserMetaData({

490

'security-scan': 'pending',

491

'upload-source': 'verified-client'

492

})

493

494

const secureResult = await client.presignedPostPolicy(securePolicy)

495

```

496

497

## Error Handling

498

499

```javascript { .api }

500

import {

501

S3Error,

502

ExpiresParamError,

503

InvalidArgumentError

504

} from 'minio'

505

506

try {

507

// Invalid expiry (too long)

508

await client.presignedGetObject('bucket', 'object', 10 * 24 * 3600) // 10 days

509

} catch (error) {

510

if (error instanceof ExpiresParamError) {

511

console.error('Invalid expiry time:', error.message)

512

}

513

}

514

515

try {

516

// Invalid bucket name

517

await client.presignedPutObject('invalid..bucket', 'object.txt')

518

} catch (error) {

519

if (error instanceof InvalidArgumentError) {

520

console.error('Invalid bucket name:', error.message)

521

}

522

}

523

524

// Handle POST policy errors

525

try {

526

const policy = new PostPolicy()

527

// Missing required expiration

528

await client.presignedPostPolicy(policy)

529

} catch (error) {

530

console.error('Policy error:', error.message)

531

}

532

```

533

534

## Best Practices

535

536

### 1. Security

537

- Use shortest reasonable expiration times

538

- Validate client requests match presigned parameters

539

- Monitor presigned URL usage and abuse

540

- Consider IP restrictions for sensitive operations

541

542

### 2. Performance

543

- Cache presigned URLs when appropriate

544

- Generate URLs in batches for efficiency

545

- Use POST policies for browser uploads

546

- Consider CDN integration for download URLs

547

548

### 3. User Experience

549

- Provide clear upload progress feedback

550

- Handle upload failures gracefully

551

- Validate files on client side before upload

552

- Use appropriate content-disposition headers

553

554

### 4. Monitoring

555

- Log presigned URL generation and usage

556

- Monitor for unusual access patterns

557

- Track upload success/failure rates

558

- Set up alerts for policy violations

559

560

### 5. Integration

561

- Separate presigned URL generation from client logic

562

- Use backend services to generate URLs securely

563

- Implement proper CORS for browser uploads

564

- Handle different file types appropriately

565

566

---

567

568

**Next:** [Notifications](./notifications.md) - Learn about event notification system and polling