chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import crypto from 'crypto'
|
||||
|
||||
export function encodeS3PathComponent(pathComponent: string): string {
|
||||
return encodeURIComponent(pathComponent).replace(/%2F/g, '/')
|
||||
}
|
||||
|
||||
export function getSignatureKey(
|
||||
key: string,
|
||||
dateStamp: string,
|
||||
regionName: string,
|
||||
serviceName: string
|
||||
): Buffer {
|
||||
if (!key || typeof key !== 'string') {
|
||||
throw new Error('Invalid key provided to getSignatureKey')
|
||||
}
|
||||
const kDate = crypto.createHmac('sha256', `AWS4${key}`).update(dateStamp).digest()
|
||||
const kRegion = crypto.createHmac('sha256', kDate).update(regionName).digest()
|
||||
const kService = crypto.createHmac('sha256', kRegion).update(serviceName).digest()
|
||||
const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest()
|
||||
return kSigning
|
||||
}
|
||||
|
||||
export function parseS3Uri(
|
||||
s3Uri: string,
|
||||
fallbackRegion?: string
|
||||
): {
|
||||
bucketName: string
|
||||
region: string
|
||||
objectKey: string
|
||||
} {
|
||||
try {
|
||||
const url = new URL(s3Uri)
|
||||
const hostname = url.hostname
|
||||
const normalizedPath = url.pathname.startsWith('/') ? url.pathname.slice(1) : url.pathname
|
||||
|
||||
const virtualHostedDualstackMatch = hostname.match(
|
||||
/^(.+)\.s3\.dualstack\.([^.]+)\.amazonaws\.com(?:\.cn)?$/
|
||||
)
|
||||
const virtualHostedRegionalMatch = hostname.match(
|
||||
/^(.+)\.s3[.-]([^.]+)\.amazonaws\.com(?:\.cn)?$/
|
||||
)
|
||||
const virtualHostedGlobalMatch = hostname.match(/^(.+)\.s3\.amazonaws\.com(?:\.cn)?$/)
|
||||
|
||||
const pathStyleDualstackMatch = hostname.match(
|
||||
/^s3\.dualstack\.([^.]+)\.amazonaws\.com(?:\.cn)?$/
|
||||
)
|
||||
const pathStyleRegionalMatch = hostname.match(/^s3[.-]([^.]+)\.amazonaws\.com(?:\.cn)?$/)
|
||||
const pathStyleGlobalMatch = hostname.match(/^s3\.amazonaws\.com(?:\.cn)?$/)
|
||||
|
||||
const isPathStyleHost = Boolean(
|
||||
pathStyleDualstackMatch || pathStyleRegionalMatch || pathStyleGlobalMatch
|
||||
)
|
||||
|
||||
const firstSlashIndex = normalizedPath.indexOf('/')
|
||||
const pathStyleBucketName =
|
||||
firstSlashIndex === -1 ? normalizedPath : normalizedPath.slice(0, firstSlashIndex)
|
||||
const pathStyleObjectKey =
|
||||
firstSlashIndex === -1 ? '' : normalizedPath.slice(firstSlashIndex + 1)
|
||||
|
||||
const bucketName = isPathStyleHost
|
||||
? pathStyleBucketName
|
||||
: (virtualHostedDualstackMatch?.[1] ??
|
||||
virtualHostedRegionalMatch?.[1] ??
|
||||
virtualHostedGlobalMatch?.[1] ??
|
||||
'')
|
||||
|
||||
const rawObjectKey = isPathStyleHost ? pathStyleObjectKey : normalizedPath
|
||||
const objectKey = (() => {
|
||||
try {
|
||||
return decodeURIComponent(rawObjectKey)
|
||||
} catch {
|
||||
return rawObjectKey
|
||||
}
|
||||
})()
|
||||
|
||||
const normalizedFallbackRegion = fallbackRegion?.trim()
|
||||
const regionFromHost =
|
||||
virtualHostedDualstackMatch?.[2] ??
|
||||
virtualHostedRegionalMatch?.[2] ??
|
||||
pathStyleDualstackMatch?.[1] ??
|
||||
pathStyleRegionalMatch?.[1]
|
||||
const region = regionFromHost || normalizedFallbackRegion || 'us-east-1'
|
||||
|
||||
if (!bucketName || !objectKey) {
|
||||
throw new Error('Invalid S3 URI format')
|
||||
}
|
||||
|
||||
return { bucketName, region, objectKey }
|
||||
} catch (_error) {
|
||||
throw new Error(
|
||||
'Invalid S3 Object URL format. Expected S3 virtual-hosted or path-style URL with object key.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function generatePresignedUrl(params: any, expiresIn = 3600): string {
|
||||
const date = new Date()
|
||||
const amzDate = date.toISOString().replace(/[:-]|\.\d{3}/g, '')
|
||||
const dateStamp = amzDate.slice(0, 8)
|
||||
const encodedPath = encodeS3PathComponent(params.objectKey)
|
||||
|
||||
const _expires = Math.floor(Date.now() / 1000) + expiresIn
|
||||
|
||||
const method = 'GET'
|
||||
const canonicalUri = `/${encodedPath}`
|
||||
const canonicalQueryString = `X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=${encodeURIComponent(`${params.accessKeyId}/${dateStamp}/${params.region}/s3/aws4_request`)}&X-Amz-Date=${amzDate}&X-Amz-Expires=${expiresIn}&X-Amz-SignedHeaders=host`
|
||||
const canonicalHeaders = `host:${params.bucketName}.s3.${params.region}.amazonaws.com\n`
|
||||
const signedHeaders = 'host'
|
||||
const payloadHash = 'UNSIGNED-PAYLOAD'
|
||||
|
||||
const canonicalRequest = `${method}\n${canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`
|
||||
|
||||
const algorithm = 'AWS4-HMAC-SHA256'
|
||||
const credentialScope = `${dateStamp}/${params.region}/s3/aws4_request`
|
||||
const stringToSign = `${algorithm}\n${amzDate}\n${credentialScope}\n${crypto.createHash('sha256').update(canonicalRequest).digest('hex')}`
|
||||
|
||||
const signingKey = getSignatureKey(params.secretAccessKey, dateStamp, params.region, 's3')
|
||||
const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex')
|
||||
|
||||
return `https://${params.bucketName}.s3.${params.region}.amazonaws.com/${encodedPath}?${canonicalQueryString}&X-Amz-Signature=${signature}`
|
||||
}
|
||||
Reference in New Issue
Block a user