Skip to content

Commit

Permalink
Merge pull request #468 from uploadcare/feat/secure-signature-generat…
Browse files Browse the repository at this point in the history
…or-api

feat(signed-uploads): update `generateSecureSignature` signature - BREAKING CHANGE without major bump
  • Loading branch information
nd0ut authored Mar 10, 2023
2 parents 65a00c6 + 97f7ebb commit f09297b
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 23 deletions.
16 changes: 12 additions & 4 deletions packages/signed-uploads/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,23 @@ npm install @uploadcare/signed-uploads
import { generateSecureSignature } from '@uploadcare/signed-uploads'

// by the expiration timestamp in milliseconds since the epoch
const signature = generateSecureSignature('YOUR_SECRET_KEY', {
expire: Date.now() + 60 * 30 * 1000 // 30 minutes
const { secureSignature, secureExpire } = generateSecureSignature('YOUR_SECRET_KEY', {
expire: Date.now() + 60 * 30 * 1000 // expire in 30 minutes
})

// by the expiration date
const { secureSignature, secureExpire } = generateSecureSignature('YOUR_SECRET_KEY', {
expire: new Date("2099-01-01") // expire on 2099-01-01
})

// by the lifetime in milliseconds
const signature = generateSecureSignature('YOUR_SECRET_KEY', {
lifetime: 60 * 30 * 1000 // 30 minutes
const { secureSignature, secureExpire } = generateSecureSignature('YOUR_SECRET_KEY', {
lifetime: 60 * 30 * 1000 // expire in 30 minutes
})
```

A pair of `secureSignature` and `secureExpire` (string with a unixtime in seconds) can be passed directly to the [corresponding options][upload-client-secure-options] of `@uploadcare/upload-client`.

## Security issues

If you think you ran into something in Uploadcare libraries that might have
Expand All @@ -74,3 +81,4 @@ request at [hello@uploadcare.com][uc-email-hello].
[badge-build]: https://github.com/uploadcare/uploadcare-js-api-clients/actions/workflows/checks.yml/badge.svg
[build-url]: https://github.com/uploadcare/uploadcare-js-api-clients/actions/workflows/checks.yml
[uc-docs-signed-uploads]: https://uploadcare.com/docs/security/secure-uploads/#signed-uploads?utm_source=github&utm_campaign=uploadcare-js-api-clients
[upload-client-secure-options]: https://github.com/uploadcare/uploadcare-js-api-clients/blob/master/packages/upload-client/README.md#securesignature-string
38 changes: 29 additions & 9 deletions packages/signed-uploads/src/generateSecureSignature.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,43 @@ const FIXTURE_DATE = new Date(1678359840000)
const FIXTURE_LIFETIME = 60 * 30 * 1000 // 30 minutes
const EXPECTED_SIGNATURE =
'93b69d086a487fbdfc36172b96a5d7f5afa7cb209e43e5f25890bc037e638584'
const EXPECTED_EXPIRE = '1678361640'

describe('generateSecureSignature', () => {
beforeAll(() => {
jest.useFakeTimers().setSystemTime(FIXTURE_DATE)
})

it('should return signature by `expire` as number', () => {
const signature = generateSecureSignature(FIXTURE_SECRET, {
expire: Date.now() + FIXTURE_LIFETIME
})
expect(signature).toBe(EXPECTED_SIGNATURE)
it('should return signature and expire by `expire` as number', () => {
const { secureSignature, secureExpire } = generateSecureSignature(
FIXTURE_SECRET,
{
expire: Date.now() + FIXTURE_LIFETIME
}
)
expect(secureSignature).toBe(EXPECTED_SIGNATURE)
expect(secureExpire).toBe(EXPECTED_EXPIRE)
})

it('should return signature and expire by `expire` as Date', () => {
const { secureSignature, secureExpire } = generateSecureSignature(
FIXTURE_SECRET,
{
expire: new Date(FIXTURE_DATE.getTime() + FIXTURE_LIFETIME)
}
)
expect(secureSignature).toBe(EXPECTED_SIGNATURE)
expect(secureExpire).toBe(EXPECTED_EXPIRE)
})

it('should return signature by `lifetime`', () => {
const signature = generateSecureSignature(FIXTURE_SECRET, {
lifetime: FIXTURE_LIFETIME
})
expect(signature).toBe(EXPECTED_SIGNATURE)
const { secureSignature, secureExpire } = generateSecureSignature(
FIXTURE_SECRET,
{
lifetime: FIXTURE_LIFETIME
}
)
expect(secureSignature).toBe(EXPECTED_SIGNATURE)
expect(secureExpire).toBe(EXPECTED_EXPIRE)
})
})
24 changes: 14 additions & 10 deletions packages/signed-uploads/src/generateSecureSignature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@ export type GenerateSecureSignatureOptions =
| {
/**
* The expiration timestamp of the signature in milliseconds since the
* epoch
* epoch or just Date object.
*/
expire: number
expire: number | Date
}
| {
/** The lifetime of the signature in milliseconds */
lifetime: number
}

const msToUnixTimestamp = (ms: number) => Math.floor(ms / 1000)
const msToUnixTimestamp = (ms: number) => Math.floor(ms / 1000).toString()
const getSecureExpire = (options: GenerateSecureSignatureOptions) => {
if ('expire' in options) {
return msToUnixTimestamp(new Date(options.expire).getTime())
}

return msToUnixTimestamp(Date.now() + options.lifetime)
}

/**
* Generate a secure signature for signing the upload request to Uploadcare.
Expand All @@ -26,12 +33,9 @@ export const generateSecureSignature = (
secret: string,
options: GenerateSecureSignatureOptions
) => {
const expire =
'expire' in options
? msToUnixTimestamp(new Date(options.expire).getTime())
: msToUnixTimestamp(Date.now() + options.lifetime)

const hmac = createHmac('sha256', secret)
hmac.update(expire.toString())
return hmac.digest('hex')
const secureExpire = getSecureExpire(options)
hmac.update(secureExpire)
const secureSignature = hmac.digest('hex')
return { secureSignature, secureExpire }
}

0 comments on commit f09297b

Please sign in to comment.