Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,33 @@ Calculates a signature for the given `params` JSON object. If the `params` objec

This function returns an object with the key `signature` (containing the calculated signature string) and a key `params`, which contains the stringified version of the passed `params` object (including the set expires and authKey keys).

#### getSignedSmartCDNUrl(params)

Constructs a signed Smart CDN URL, as defined in the [API documentation](https://transloadit.com/docs/topics/signature-authentication/#smart-cdn). `params` must be an object with the following properties:

- `workspace` - Workspace slug (required)
- `template` - Template slug or template ID (required)
- `input` - Input value that is provided as `${fields.input}` in the template (required)
- `urlParams` - Object with additional parameters for the URL query string (optional)
- `expiresAt` - Expiration timestamp of the signature in milliseconds since UNIX epoch. Defaults to 1 hour from now. (optional)

Example:

```js
const client = new Transloadit({ authKey: 'foo_key', authSecret: 'foo_secret' })
const url = client.getSignedSmartCDNUrl({
workspace: 'foo_workspace',
template: 'foo_template',
input: 'foo_input',
urlParams: {
foo: 'bar',
},
})

// url is:
// https://foo_workspace.tlcdn.com/foo_template/foo_input?auth_key=foo_key&exp=1714525200000&foo=bar&sig=sha256:9548915ec70a5f0d05de9497289e792201ceec19a526fe315f4f4fd2e7e377ac
```

### Errors

Errors from Node.js will be passed on and we use [GOT](https://github.com/sindresorhus/got) for HTTP requests and errors from there will also be passed on. When the HTTP response code is not 200, the error will be an `HTTPError`, which is a [got.HTTPError](https://github.com/sindresorhus/got#errors)) with some additional properties:
Expand Down
65 changes: 65 additions & 0 deletions src/Transloadit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,47 @@ export class Transloadit {
return { signature, params: jsonParams }
}

/**
* Construct a signed Smart CDN URL. See https://transloadit.com/docs/topics/signature-authentication/#smart-cdn.
*/
getSignedSmartCDNUrl(opts: SmartCDNUrlOptions): string {
if (opts.workspace == null || opts.workspace === '')
throw new TypeError('workspace is required')
if (opts.template == null || opts.template === '') throw new TypeError('template is required')
if (opts.input == null) throw new TypeError('input is required') // `input` can be an empty string.

const workspaceSlug = encodeURIComponent(opts.workspace)
const templateSlug = encodeURIComponent(opts.template)
const inputField = encodeURIComponent(opts.input)
const expiresAt = opts.expiresAt || Date.now() + 60 * 60 * 1000 // 1 hour

const queryParams = new URLSearchParams()
for (const [key, value] of Object.entries(opts.urlParams || {})) {
if (Array.isArray(value)) {
for (const val of value) {
queryParams.append(key, `${val}`)
}
} else {
queryParams.append(key, `${value}`)
}
}

queryParams.set('auth_key', this._authKey)
queryParams.set('exp', `${expiresAt}`)
// The signature changes depending on the order of the query parameters. We therefore sort them on the client-
// and server-side to ensure that we do not get mismatching signatures if a proxy changes the order of query
// parameters or implementations handle query parameters ordering differently.
queryParams.sort()

const stringToSign = `${workspaceSlug}/${templateSlug}/${inputField}?${queryParams}`
const algorithm = 'sha256'
const signature = createHmac(algorithm, this._authSecret).update(stringToSign).digest('hex')

queryParams.set('sig', `sha256:${signature}`)
const signedUrl = `https://${workspaceSlug}.tlcdn.com/${templateSlug}/${inputField}?${queryParams}`
return signedUrl
}

private _calcSignature(toSign: string, algorithm = 'sha384'): string {
return `${algorithm}:${createHmac(algorithm, this._authSecret)
.update(Buffer.from(toSign, 'utf-8'))
Expand Down Expand Up @@ -960,3 +1001,27 @@ export interface PaginationList<T> {
count: number
items: T[]
}

export interface SmartCDNUrlOptions {
/**
* Workspace slug
*/
workspace: string
/**
* Template slug or template ID
*/
template: string
/**
* Input value that is provided as `${fields.input}` in the template
*/
input: string
/**
* Additional parameters for the URL query string
*/
urlParams?: Record<string, boolean | number | string | (boolean | number | string)[]>
/**
* Expiration timestamp of the signature in milliseconds since UNIX epoch.
* Defaults to 1 hour from now.
*/
expiresAt?: number
}
22 changes: 22 additions & 0 deletions test/unit/test-transloadit-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,26 @@ describe('Transloadit', () => {
)
})
})

describe('getSignedSmartCDNUrl', () => {
it('should return a signed url', () => {
const client = new Transloadit({ authKey: 'foo_key', authSecret: 'foo_secret' })

const url = client.getSignedSmartCDNUrl({
workspace: 'foo_workspace',
template: 'foo_template',
input: 'foo/input',
urlParams: {
foo: 'bar',
aaa: [42, 21], // Should be sorted before `foo`.
empty: '',
},
expiresAt: 1714525200000,
})

expect(url).toBe(
'https://foo_workspace.tlcdn.com/foo_template/foo%2Finput?aaa=42&aaa=21&auth_key=foo_key&empty=&exp=1714525200000&foo=bar&sig=sha256%3A1ab71ef553df3507a9e2cf7beb8f921538bbef49a13a94a22ff49f2f030a5e9e'
)
})
})
})