cloud-presign
Version:
A unified presigned URL generator for multiple cloud platforms
128 lines (112 loc) • 4.14 kB
text/typescript
import { BlobServiceClient, StorageSharedKeyCredential, BlobSASPermissions, generateBlobSASQueryParameters } from '@azure/storage-blob';
import {
AzureConfig,
HttpMethod,
PresignOptions,
PresignParams,
PresignResponse,
Provider,
} from '../types';
import { Common } from './common';
export class AzureProvider implements Provider {
private blobServiceClient: BlobServiceClient;
private containerName: string;
private accountName: string;
private accountKey: string;
private defaultExpiration: number;
private common = new Common()
constructor(config: AzureConfig, defaultExpiration: number) {
this.containerName = config.container;
this.accountName = config.accountName;
this.accountKey = config.accountKey;
this.defaultExpiration = defaultExpiration;
// Create the BlobServiceClient
if (config.connectionString) {
this.blobServiceClient = BlobServiceClient.fromConnectionString(config.connectionString);
} else {
const credential = new StorageSharedKeyCredential(config.accountName, config.accountKey);
const blobServiceUri = `https://${config.accountName}.blob.core.windows.net`;
this.blobServiceClient = new BlobServiceClient(blobServiceUri, credential);
}
}
async generatePresignedUrl(
params: PresignParams,
options?: PresignOptions
): Promise<PresignResponse> {
const expiration = options?.expiration || this.defaultExpiration;
const method = params.method || HttpMethod.GET;
let fileName = params.key;
let key = params.key;
// Create permissions based on HTTP method
const permissions = new BlobSASPermissions();
switch (method) {
case HttpMethod.GET:
permissions.read = true;
break;
case HttpMethod.PUT:
case HttpMethod.POST:
permissions.create = true;
permissions.write = true;
fileName = await this.common.generateFileUniqueName(params.key);
key = params.prefix ? `${params.prefix}/${fileName}` : fileName;
break;
case HttpMethod.DELETE:
permissions.delete = true;
break;
default:
throw new Error(`Unsupported HTTP method: ${method}`);
}
// Set start and expiry time
const startDate = new Date();
const expiryDate = new Date(startDate);
expiryDate.setSeconds(startDate.getSeconds() + expiration);
// Define SAS parameters
const sasOptions = {
containerName: this.containerName,
blobName: key,
permissions,
startsOn: startDate,
expiresOn: expiryDate,
};
// Create the shared key credential
const sharedKeyCredential = new StorageSharedKeyCredential(
this.accountName,
this.accountKey
);
// Generate the SAS token
const sasToken = generateBlobSASQueryParameters(
sasOptions,
sharedKeyCredential
).toString();
// Construct the full URL
const containerClient = this.blobServiceClient.getContainerClient(this.containerName);
const blobClient = containerClient.getBlobClient(key);
const presignedUrl = `${blobClient.url}?${sasToken}`;
const url = blobClient.url.split('?')[0];
// Calculate expiration date
const expiresAt = expiryDate.toISOString();
// Build response
const response: PresignResponse = {
key,
presignedUrl,
url: params.isPublic ? url : '',
method,
expiresAt,
headers: {},
};
// Add content type header for upload requests
if ((method === HttpMethod.PUT || method === HttpMethod.POST) && params.contentType) {
response.headers['Content-Type'] = params.contentType;
}
return response;
}
async makeFilePublic(key?: string): Promise<void> {
const containerClient = this.blobServiceClient.getContainerClient(this.containerName);
// Check current access level
const properties = await containerClient.getProperties();
if (properties.blobPublicAccess !== 'blob') {
// Set container access level to 'blob' (public read access to blobs)
await containerClient.setAccessPolicy('blob');
}
}
}