Skip to content

Commit 593e0dc

Browse files
committed
Also add sig
1 parent bd6db9c commit 593e0dc

File tree

5 files changed

+281
-30
lines changed

5 files changed

+281
-30
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Released: 2025-10-16.
1010

1111
- [x] Fix `smart_sig` CLI to always override embedded credentials with the env-provided auth key while preserving other auth fields.
1212
- [x] Switch `smart_sig` CLI to return Smart CDN URLs via `getSignedSmartCDNUrl`, making it suitable as the cross-SDK reference implementation.
13+
- [x] Add `sig` CLI command for generating request signatures, including an `--algorithm` flag for choosing the HMAC digest.
1314
- [x] Ensure the CLI entrypoint executes when invoked through npm/yarn bin shims by resolving symlinks before bootstrapping.
1415
- [x] Harden validation and tests around CLI invocation paths.
1516
- [x] Add `smart_sig` CLI command (usable via `npx transloadit smart_sig`) for signing params fed through stdin.

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,15 @@ TRANSLOADIT_KEY=... TRANSLOADIT_SECRET=... \
396396

397397
You can also use `TRANSLOADIT_AUTH_KEY`/`TRANSLOADIT_AUTH_SECRET` as aliases for the environment variables.
398398

399+
#### CLI sig
400+
401+
Sign assembly params from the command line. The CLI reads a JSON object from stdin (or falls back to an empty object), injects credentials from `TRANSLOADIT_KEY`/`TRANSLOADIT_SECRET`, and prints the payload returned by `calcSignature()`. Use `--algorithm` to pick a specific hashing algorithm; it defaults to `sha384`.
402+
403+
```sh
404+
TRANSLOADIT_KEY=... TRANSLOADIT_SECRET=... \
405+
printf '{"auth":{"expires":"2025-01-02T00:00:00Z"}}' | npx transloadit sig --algorithm sha256
406+
```
407+
399408
#### getSignedSmartCDNUrl(params)
400409

401410
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:

src/Transloadit.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,9 +708,12 @@ export class Transloadit {
708708
})
709709
}
710710

711-
calcSignature(params: OptionalAuthParams): { signature: string; params: string } {
711+
calcSignature(
712+
params: OptionalAuthParams,
713+
algorithm?: string,
714+
): { signature: string; params: string } {
712715
const jsonParams = this._prepareParams(params)
713-
const signature = this._calcSignature(jsonParams)
716+
const signature = this._calcSignature(jsonParams, algorithm)
714717

715718
return { signature, params: jsonParams }
716719
}

src/cli.ts

Lines changed: 144 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,26 @@ import path from 'node:path'
55
import process from 'node:process'
66
import { fileURLToPath } from 'node:url'
77
import { type ZodIssue, z } from 'zod'
8+
import {
9+
assemblyAuthInstructionsSchema,
10+
assemblyInstructionsSchema,
11+
} from './alphalib/types/template.ts'
12+
import type { OptionalAuthParams } from './apiTypes.ts'
813
import { Transloadit } from './Transloadit.ts'
914

1015
type UrlParamPrimitive = string | number | boolean
1116
type UrlParamArray = UrlParamPrimitive[]
1217
type NormalizedUrlParams = Record<string, UrlParamPrimitive | UrlParamArray>
1318

19+
interface RunSigOptions {
20+
providedInput?: string
21+
algorithm?: string
22+
}
23+
24+
interface RunSmartSigOptions {
25+
providedInput?: string
26+
}
27+
1428
const smartCdnParamsSchema = z
1529
.object({
1630
workspace: z.string().min(1, 'workspace is required'),
@@ -21,6 +35,11 @@ const smartCdnParamsSchema = z
2135
})
2236
.passthrough()
2337

38+
const cliSignatureParamsSchema = assemblyInstructionsSchema
39+
.extend({ auth: assemblyAuthInstructionsSchema.partial().optional() })
40+
.partial()
41+
.passthrough()
42+
2443
export async function readStdin(): Promise<string> {
2544
if (process.stdin.isTTY) return ''
2645

@@ -75,19 +94,85 @@ function normalizeUrlParams(params?: Record<string, unknown>): NormalizedUrlPara
7594
return normalized
7695
}
7796

78-
export async function runSmartSig(providedInput?: string): Promise<void> {
97+
function ensureCredentials(): { authKey: string; authSecret: string } | null {
7998
const authKey = process.env.TRANSLOADIT_KEY || process.env.TRANSLOADIT_AUTH_KEY
8099
const authSecret = process.env.TRANSLOADIT_SECRET || process.env.TRANSLOADIT_AUTH_SECRET
81100

82101
if (!authKey || !authSecret) {
83102
fail(
84103
'Missing credentials. Please set TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables.',
85104
)
86-
return
105+
return null
87106
}
88107

108+
return { authKey, authSecret }
109+
}
110+
111+
export async function runSig(options: RunSigOptions = {}): Promise<void> {
112+
const credentials = ensureCredentials()
113+
if (credentials == null) return
114+
const { authKey, authSecret } = credentials
115+
const { providedInput, algorithm } = options
116+
89117
const rawInput = providedInput ?? (await readStdin())
90118
const input = rawInput.trim()
119+
let params: Record<string, unknown>
120+
121+
if (input === '') {
122+
params = { auth: { key: authKey } }
123+
} else {
124+
let parsed: unknown
125+
try {
126+
parsed = JSON.parse(input)
127+
} catch (error) {
128+
fail(`Failed to parse JSON from stdin: ${(error as Error).message}`)
129+
return
130+
}
131+
132+
if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
133+
fail('Invalid params provided via stdin. Expected a JSON object.')
134+
return
135+
}
136+
137+
const parsedResult = cliSignatureParamsSchema.safeParse(parsed)
138+
if (!parsedResult.success) {
139+
fail(`Invalid params: ${formatIssues(parsedResult.error.issues)}`)
140+
return
141+
}
142+
143+
const parsedParams = parsedResult.data as Record<string, unknown>
144+
const existingAuth =
145+
typeof parsedParams.auth === 'object' &&
146+
parsedParams.auth != null &&
147+
!Array.isArray(parsedParams.auth)
148+
? (parsedParams.auth as Record<string, unknown>)
149+
: {}
150+
151+
params = {
152+
...parsedParams,
153+
auth: {
154+
...existingAuth,
155+
key: authKey,
156+
},
157+
}
158+
}
159+
160+
const client = new Transloadit({ authKey, authSecret })
161+
try {
162+
const signature = client.calcSignature(params as OptionalAuthParams, algorithm)
163+
process.stdout.write(`${JSON.stringify(signature)}\n`)
164+
} catch (error) {
165+
fail(`Failed to generate signature: ${(error as Error).message}`)
166+
}
167+
}
168+
169+
export async function runSmartSig(options: RunSmartSigOptions = {}): Promise<void> {
170+
const credentials = ensureCredentials()
171+
if (credentials == null) return
172+
const { authKey, authSecret } = credentials
173+
174+
const rawInput = options.providedInput ?? (await readStdin())
175+
const input = rawInput.trim()
91176
if (input === '') {
92177
fail(
93178
'Missing params provided via stdin. Expected a JSON object with workspace, template, input, and optional Smart CDN parameters.',
@@ -115,7 +200,6 @@ export async function runSmartSig(providedInput?: string): Promise<void> {
115200
}
116201

117202
const { workspace, template, input: inputFieldRaw, url_params, expire_at_ms } = parsedResult.data
118-
119203
const urlParams = normalizeUrlParams(url_params as Record<string, unknown> | undefined)
120204

121205
let expiresAt: number | undefined
@@ -133,37 +217,83 @@ export async function runSmartSig(providedInput?: string): Promise<void> {
133217
const inputField = typeof inputFieldRaw === 'string' ? inputFieldRaw : String(inputFieldRaw)
134218

135219
const client = new Transloadit({ authKey, authSecret })
136-
const signedUrl = client.getSignedSmartCDNUrl({
137-
workspace,
138-
template,
139-
input: inputField,
140-
urlParams,
141-
expiresAt,
142-
})
143-
process.stdout.write(`${signedUrl}\n`)
220+
try {
221+
const signedUrl = client.getSignedSmartCDNUrl({
222+
workspace,
223+
template,
224+
input: inputField,
225+
urlParams,
226+
expiresAt,
227+
})
228+
process.stdout.write(`${signedUrl}\n`)
229+
} catch (error) {
230+
fail(`Failed to generate Smart CDN URL: ${(error as Error).message}`)
231+
}
232+
}
233+
234+
function parseSigArguments(args: string[]): { algorithm?: string } {
235+
let algorithm: string | undefined
236+
let index = 0
237+
while (index < args.length) {
238+
const arg = args[index]
239+
if (arg === '--algorithm' || arg === '-a') {
240+
const next = args[index + 1]
241+
if (next == null || next.startsWith('-')) {
242+
throw new Error('Missing value for --algorithm option')
243+
}
244+
algorithm = next
245+
index += 2
246+
continue
247+
}
248+
if (arg.startsWith('--algorithm=')) {
249+
const [, value] = arg.split('=', 2)
250+
if (value === undefined || value === '') {
251+
throw new Error('Missing value for --algorithm option')
252+
}
253+
algorithm = value
254+
index += 1
255+
continue
256+
}
257+
throw new Error(`Unknown option: ${arg}`)
258+
}
259+
260+
return { algorithm }
144261
}
145262

146263
export async function main(args = process.argv.slice(2)): Promise<void> {
147-
const [command] = args
264+
const [command, ...commandArgs] = args
148265

149266
switch (command) {
150267
case 'smart_sig': {
151268
await runSmartSig()
152269
break
153270
}
154271

272+
case 'sig': {
273+
try {
274+
const { algorithm } = parseSigArguments(commandArgs)
275+
await runSig({ algorithm })
276+
} catch (error) {
277+
fail((error as Error).message)
278+
}
279+
break
280+
}
281+
155282
case '-h':
156283
case '--help':
157284
case undefined: {
158285
process.stdout.write(
159286
[
160287
'Usage:',
161288
' npx transloadit smart_sig Read Smart CDN params JSON from stdin and output a signed URL.',
289+
' npx transloadit sig [--algorithm <name>] Read params JSON from stdin and output signed payload JSON.',
162290
'',
163291
'Required JSON fields:',
164-
' workspace, template, input',
292+
' smart_sig: workspace, template, input',
293+
' sig: none (object is optional)',
165294
'Optional JSON fields:',
166-
' expire_at_ms, url_params',
295+
' smart_sig: expire_at_ms, url_params',
296+
' sig: auth.expires and any supported assembly params',
167297
'',
168298
'Environment variables:',
169299
' TRANSLOADIT_KEY, TRANSLOADIT_SECRET',

0 commit comments

Comments
 (0)