@transloadit/utils 4.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/node.d.ts +37 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +37 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @transloadit/utils
|
|
2
|
+
|
|
3
|
+
Shared runtime helpers used across Transloadit JavaScript SDKs.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @transloadit/utils
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Web / Edge usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { signParams, verifyWebhookSignature } from '@transloadit/utils'
|
|
15
|
+
|
|
16
|
+
const signature = await signParams(paramsString, authSecret)
|
|
17
|
+
const verified = await verifyWebhookSignature({
|
|
18
|
+
rawBody,
|
|
19
|
+
signatureHeader,
|
|
20
|
+
authSecret,
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Node usage
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { signParamsSync, getSignedSmartCdnUrl } from '@transloadit/utils/node'
|
|
28
|
+
|
|
29
|
+
const signature = signParamsSync(paramsString, authSecret)
|
|
30
|
+
const url = getSignedSmartCdnUrl({
|
|
31
|
+
workspace,
|
|
32
|
+
template,
|
|
33
|
+
input,
|
|
34
|
+
authKey,
|
|
35
|
+
authSecret,
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## API
|
|
40
|
+
|
|
41
|
+
- `signParams(paramsString, authSecret, algorithm?)`: WebCrypto-based HMAC signature for params.
|
|
42
|
+
- `verifyWebhookSignature({ rawBody, signatureHeader, authSecret })`: validates webhook signatures.
|
|
43
|
+
- `signParamsSync(paramsString, authSecret, algorithm?)`: Node-only sync signature helper.
|
|
44
|
+
- `getSignedSmartCdnUrl(options)`: Node-only Smart CDN URL signer.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type SignatureAlgorithm = 'sha1' | 'sha256' | 'sha384';
|
|
2
|
+
export declare const signParams: (paramsString: string, authSecret: string, algorithm?: SignatureAlgorithm) => Promise<string>;
|
|
3
|
+
export type VerifyWebhookSignatureOptions = {
|
|
4
|
+
rawBody: string;
|
|
5
|
+
signatureHeader?: string;
|
|
6
|
+
authSecret: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const verifyWebhookSignature: (options: VerifyWebhookSignatureOptions) => Promise<boolean>;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAiD7D,eAAO,MAAM,UAAU,GACrB,cAAc,MAAM,EACpB,YAAY,MAAM,EAClB,YAAW,kBAA6B,KACvC,OAAO,CAAC,MAAM,CAOhB,CAAA;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,SAAS,6BAA6B,KACrC,OAAO,CAAC,OAAO,CAkBjB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const algorithmMap = {
|
|
2
|
+
sha1: 'SHA-1',
|
|
3
|
+
sha256: 'SHA-256',
|
|
4
|
+
sha384: 'SHA-384',
|
|
5
|
+
};
|
|
6
|
+
const isSignatureAlgorithm = (value) => value === 'sha1' || value === 'sha256' || value === 'sha384';
|
|
7
|
+
const getSubtle = () => {
|
|
8
|
+
const subtle = globalThis.crypto?.subtle;
|
|
9
|
+
if (!subtle) {
|
|
10
|
+
throw new Error('Web Crypto is required to sign Transloadit payloads');
|
|
11
|
+
}
|
|
12
|
+
return subtle;
|
|
13
|
+
};
|
|
14
|
+
const hmacHex = async (algorithm, key, data) => {
|
|
15
|
+
const subtle = getSubtle();
|
|
16
|
+
const encoder = new TextEncoder();
|
|
17
|
+
const cryptoKey = await subtle.importKey('raw', encoder.encode(key), { name: 'HMAC', hash: { name: algorithmMap[algorithm] } }, false, ['sign']);
|
|
18
|
+
const signature = await subtle.sign('HMAC', cryptoKey, encoder.encode(data));
|
|
19
|
+
const bytes = new Uint8Array(signature);
|
|
20
|
+
return Array.from(bytes)
|
|
21
|
+
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
22
|
+
.join('');
|
|
23
|
+
};
|
|
24
|
+
const safeCompare = (a, b) => {
|
|
25
|
+
if (a.length !== b.length)
|
|
26
|
+
return false;
|
|
27
|
+
let mismatch = 0;
|
|
28
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
29
|
+
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
30
|
+
}
|
|
31
|
+
return mismatch === 0;
|
|
32
|
+
};
|
|
33
|
+
export const signParams = async (paramsString, authSecret, algorithm = 'sha384') => {
|
|
34
|
+
const normalized = algorithm.toLowerCase();
|
|
35
|
+
if (!isSignatureAlgorithm(normalized)) {
|
|
36
|
+
throw new Error(`Unsupported signature algorithm: ${algorithm}`);
|
|
37
|
+
}
|
|
38
|
+
const signature = await hmacHex(normalized, authSecret, paramsString);
|
|
39
|
+
return `${normalized}:${signature}`;
|
|
40
|
+
};
|
|
41
|
+
export const verifyWebhookSignature = async (options) => {
|
|
42
|
+
if (!options.signatureHeader)
|
|
43
|
+
return false;
|
|
44
|
+
const signatureHeader = options.signatureHeader.trim();
|
|
45
|
+
if (!signatureHeader)
|
|
46
|
+
return false;
|
|
47
|
+
const separatorIndex = signatureHeader.indexOf(':');
|
|
48
|
+
const prefix = separatorIndex === -1 ? 'sha1' : signatureHeader.slice(0, separatorIndex);
|
|
49
|
+
const signature = separatorIndex === -1 ? signatureHeader : signatureHeader.slice(separatorIndex + 1);
|
|
50
|
+
const normalized = prefix.toLowerCase();
|
|
51
|
+
if (!isSignatureAlgorithm(normalized)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const expected = await hmacHex(normalized, options.authSecret, options.rawBody);
|
|
55
|
+
return safeCompare(expected, signature);
|
|
56
|
+
};
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SignatureAlgorithm } from './index.ts';
|
|
2
|
+
export type { SignatureAlgorithm } from './index.ts';
|
|
3
|
+
export type SignatureAlgorithmInput = SignatureAlgorithm | (string & {});
|
|
4
|
+
export type SmartCdnUrlOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Workspace slug.
|
|
7
|
+
*/
|
|
8
|
+
workspace: string;
|
|
9
|
+
/**
|
|
10
|
+
* Template slug or template ID.
|
|
11
|
+
*/
|
|
12
|
+
template: string;
|
|
13
|
+
/**
|
|
14
|
+
* Input value that is provided as `${fields.input}` in the template.
|
|
15
|
+
*/
|
|
16
|
+
input: string;
|
|
17
|
+
/**
|
|
18
|
+
* Additional parameters for the URL query string.
|
|
19
|
+
*/
|
|
20
|
+
urlParams?: Record<string, boolean | number | string | (boolean | number | string)[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Expiration timestamp of the signature in milliseconds since UNIX epoch.
|
|
23
|
+
* Defaults to 1 hour from now.
|
|
24
|
+
*/
|
|
25
|
+
expiresAt?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Transloadit auth key used to sign the URL.
|
|
28
|
+
*/
|
|
29
|
+
authKey: string;
|
|
30
|
+
/**
|
|
31
|
+
* Transloadit auth secret used to sign the URL.
|
|
32
|
+
*/
|
|
33
|
+
authSecret: string;
|
|
34
|
+
};
|
|
35
|
+
export declare const signParamsSync: (paramsString: string, authSecret: string, algorithm?: SignatureAlgorithmInput) => string;
|
|
36
|
+
export declare const getSignedSmartCdnUrl: (opts: SmartCdnUrlOptions) => string;
|
|
37
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,MAAM,MAAM,uBAAuB,GAAG,kBAAkB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAExE,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAA;IACrF;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,cAAc,GACzB,cAAc,MAAM,EACpB,YAAY,MAAM,EAClB,YAAW,uBAAkC,KAC5C,MAKF,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,MAAM,kBAAkB,KAAG,MA8B/D,CAAA"}
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createHmac } from 'node:crypto';
|
|
2
|
+
export const signParamsSync = (paramsString, authSecret, algorithm = 'sha384') => {
|
|
3
|
+
const signature = createHmac(algorithm, authSecret)
|
|
4
|
+
.update(Buffer.from(paramsString, 'utf-8'))
|
|
5
|
+
.digest('hex');
|
|
6
|
+
return `${algorithm}:${signature}`;
|
|
7
|
+
};
|
|
8
|
+
export const getSignedSmartCdnUrl = (opts) => {
|
|
9
|
+
if (opts.workspace == null || opts.workspace === '')
|
|
10
|
+
throw new TypeError('workspace is required');
|
|
11
|
+
if (opts.template == null || opts.template === '')
|
|
12
|
+
throw new TypeError('template is required');
|
|
13
|
+
if (opts.input == null)
|
|
14
|
+
throw new TypeError('input is required');
|
|
15
|
+
const workspaceSlug = encodeURIComponent(opts.workspace);
|
|
16
|
+
const templateSlug = encodeURIComponent(opts.template);
|
|
17
|
+
const inputField = encodeURIComponent(opts.input);
|
|
18
|
+
const expiresAt = opts.expiresAt || Date.now() + 60 * 60 * 1000;
|
|
19
|
+
const queryParams = new URLSearchParams();
|
|
20
|
+
for (const [key, value] of Object.entries(opts.urlParams || {})) {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
for (const val of value) {
|
|
23
|
+
queryParams.append(key, `${val}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
queryParams.append(key, `${value}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
queryParams.set('auth_key', opts.authKey);
|
|
31
|
+
queryParams.set('exp', `${expiresAt}`);
|
|
32
|
+
queryParams.sort();
|
|
33
|
+
const stringToSign = `${workspaceSlug}/${templateSlug}/${inputField}?${queryParams}`;
|
|
34
|
+
const signature = createHmac('sha256', opts.authSecret).update(stringToSign).digest('hex');
|
|
35
|
+
queryParams.set('sig', `sha256:${signature}`);
|
|
36
|
+
return `https://${workspaceSlug}.tlcdn.com/${templateSlug}/${inputField}?${queryParams}`;
|
|
37
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@transloadit/utils",
|
|
3
|
+
"version": "4.1.5",
|
|
4
|
+
"description": "Transloadit shared utilities",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/transloadit/node-sdk",
|
|
10
|
+
"directory": "packages/utils"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./node": {
|
|
24
|
+
"types": "./dist/node.d.ts",
|
|
25
|
+
"default": "./dist/node.js"
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"lint:ts": "../../node_modules/.bin/tsc --build tsconfig.build.json",
|
|
31
|
+
"build": "../../node_modules/.bin/tsc --build tsconfig.build.json",
|
|
32
|
+
"check": "yarn lint:ts",
|
|
33
|
+
"prepack": "yarn build"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"tag": "experimental"
|
|
37
|
+
}
|
|
38
|
+
}
|