Skip to content

Commit 64e4a42

Browse files
committed
Add proper queryParam handling
1 parent 3cfc047 commit 64e4a42

File tree

4 files changed

+63
-35
lines changed

4 files changed

+63
-35
lines changed

README.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,39 @@
11
# Azure-SAS-token
22

3-
Generate Azure SAS tokens on the edge with cloudflare workers and this library.
3+
Generate Azure SAS tokens on the edge with cloudflare workers and this library.
4+
This library has zero depdendencies.
45

5-
Zero depdendencies.
6+
We used it in our [sepa-form-recogizer](https://neurocode.io/blog/sepa-payment-recognizer?s=github) API
67

8+
## Install
9+
10+
Use either npm or yarn
11+
```
12+
npm i @neurocode.io/azure-sas-token
13+
yarn add @neurocode.io/azure-sas-token
14+
```
715

816
## How to use the library
917

1018
```ts
11-
import createBlobSas from './index'
19+
import { createBlobSas } from '@neurocode.io/azure-sas-token'
1220

1321
const expireInMin = 5
1422

1523
const { blobSasUrl } = await createBlobSas({
16-
accountKey: 'asd',
17-
accountName: '1132',
18-
blobName: '123.txt',
24+
accountKey: 'yourStorageAccountKey',
25+
accountName: 'yourAccountName',
26+
containerName: 'youStorageContainerName',
27+
blobName: 'someBlob.txt',
1928
permissions: 'rw',
20-
containerName: 'container',
2129
expiresOn: new Date(new Date().valueOf() + expireInMin * 60 * 1000)
2230
})
31+
2332
```
33+
34+
## Current implementation
35+
36+
- [x] Blob SAS token
37+
- [ ] Container SAS token
38+
- [ ] Account SAS token
39+

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "@neurocode.io/azure-sas-token",
33
"version": "1.0.1",
44
"description": "Cloudflare worker app to issue limited access to Azure Storage resources using shared access signatures (SAS)",
5+
"keywords": ["azure-sas-token", "cloudflare-azure-sas", "cloudflare-workers"],
56
"main": "dist/index.js",
67
"module": "dist/index.js",
78
"source": "src/index.ts",

src/index.ts

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
type SASinput = {
32
accountKey: string
43
accountName: string
@@ -23,11 +22,12 @@ const truncatedISO8061Date = (date: Date) => {
2322
return dateString.substring(0, dateString.length - 5) + 'Z'
2423
}
2524

26-
const computeHMACSHA256 = async (stringToSign: string, accountKey: string) => {
25+
const computeHMACSHA256 = async (stringToSign: string, accountKey: string): Promise<string> => {
2726
const enc = new TextEncoder()
2827
const signatureUTF8 = enc.encode(stringToSign)
2928
const key = await crypto.subtle.importKey(
3029
'raw',
30+
//@ts-ignore
3131
Buffer.from(accountKey, 'base64'),
3232
{
3333
name: 'HMAC',
@@ -41,6 +41,7 @@ const computeHMACSHA256 = async (stringToSign: string, accountKey: string) => {
4141

4242
const digest = await crypto.subtle.sign('HMAC', key, signatureUTF8)
4343

44+
//@ts-ignore
4445
return Buffer.from(digest).toString('base64')
4546
}
4647

@@ -57,39 +58,51 @@ const getSASqueryParams = async (input: SASinput) => {
5758
const version = '2018-11-09'
5859
const signedSnapshotTime = undefined
5960

60-
const stringToSign = [
61-
input.permissions ? input.permissions : '',
62-
input.startsOn ? truncatedISO8061Date(input.startsOn) : '',
63-
truncatedISO8061Date(input.expiresOn),
64-
getCanonicalName(input.accountName, input.containerName, input.blobName),
65-
input.identifier ? input.identifier : '',
66-
input.ipRange ? input.ipRange : '',
67-
input.protocol ? input.protocol : '',
68-
version,
69-
resource,
70-
signedSnapshotTime,
71-
input.cacheControl ? input.cacheControl : '',
72-
input.contentDisposition ? input.contentDisposition : '',
73-
input.contentEncoding ? input.contentEncoding : '',
74-
input.contentLanguage ? input.contentLanguage : '',
75-
input.contentType ? input.contentType : '',
76-
].join('\n')
61+
let queryParams = {
62+
sp: input.permissions ?? '',
63+
st: input.startsOn ? truncatedISO8061Date(input.startsOn) : '',
64+
se: truncatedISO8061Date(input.expiresOn),
65+
name: getCanonicalName(input.accountName, input.containerName, input.blobName),
66+
si: input.identifier ?? '',
67+
sip: input.ipRange ?? '',
68+
spr: input.protocol ?? '',
69+
sv: version,
70+
sr: resource,
71+
ne: signedSnapshotTime,
72+
rscc: input.cacheControl ?? '',
73+
rscd: input.contentDisposition ?? '',
74+
rsce: input.contentEncoding ?? '',
75+
rscl: input.contentLanguage ?? '',
76+
rsct: input.contentType ?? '',
77+
}
78+
79+
const stringToSign = Object.values(queryParams).join('\n')
7780

7881
const signature = await computeHMACSHA256(stringToSign, input.accountKey)
82+
const { name, ne, ...rest } = queryParams
7983

80-
return `sv=${version}&spr=https&se=${encodeURIComponent(
81-
truncatedISO8061Date(input.expiresOn)
82-
)}&sr=b&sp=rw&sig=${encodeURIComponent(signature)}`
84+
//@ts-ignore
85+
queryParams.sig = signature
86+
87+
return Object.keys({ ...rest, ...{ sig: signature } })
88+
.map((key) => {
89+
if (queryParams[key] === '') return
90+
91+
return `${key}=${encodeURIComponent(queryParams[key])}`
92+
})
93+
.join('&')
8394
}
8495

85-
export default async (input: SASinput) => {
86-
const url = [input.containerName, input.blobName].filter(el => el).join('/')
96+
const createBlobSas = async (input: SASinput) => {
97+
const url = [input.containerName, input.blobName].filter((el) => el).join('/')
8798
const storageUri = new URL(url, `https://${input.accountName}.blob.core.windows.net`)
8899
const queryParams = await getSASqueryParams(input)
89100

90101
storageUri.search = queryParams
91102

92103
return {
93-
blobSasUrl: url.toString(),
104+
blobSasUrl: storageUri.toString(),
94105
}
95106
}
107+
108+
export { createBlobSas }

tsconfig.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22
{
33
"compilerOptions": {
44
"target": "es2019",
5-
"lib": ["esnext", "WebWorker"],
5+
"lib": ["WebWorker", "ESNext"],
66
"moduleResolution": "node",
77
"esModuleInterop": true,
8-
"strict": true,
98
"sourceMap": true,
109
"noUnusedLocals": true,
1110
"noUnusedParameters": true,
12-
"noImplicitReturns": true,
1311
"noFallthroughCasesInSwitch": true,
1412
"forceConsistentCasingInFileNames": true,
1513
"removeComments": true,

0 commit comments

Comments
 (0)