diff --git a/package-lock.json b/package-lock.json index d161f3f1..b5515ed9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -869,17 +869,17 @@ } }, "node_modules/@azure/core-auth": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.2.tgz", - "integrity": "sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", "license": "MIT", "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-util": "^1.1.0", + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": { @@ -1046,16 +1046,17 @@ } }, "node_modules/@azure/core-util": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.2.tgz", - "integrity": "sha512-l1Qrqhi4x1aekkV+OlcqsJa4AnAkj5p0JV8omgwjaV9OAbP41lvrMvs+CptfetKkeEaGRGSzby7sjPZEX7+kkQ==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", "license": "MIT", "dependencies": { - "@azure/abort-controller": "^2.0.0", + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@azure/core-util/node_modules/@azure/abort-controller": { @@ -2715,16 +2716,6 @@ "resolved": "packages/utils", "link": true }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, "node_modules/@types/caseless": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", @@ -2732,15 +2723,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/connect": { - "version": "3.4.38", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/cookiejar": { "version": "2.1.2", "dev": true, @@ -2754,18 +2736,6 @@ "@types/ms": "*" } }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.41", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, "node_modules/@types/glob": { "version": "8.1.0", "dev": true, @@ -2775,12 +2745,6 @@ "@types/node": "*" } }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@types/lodash": { "version": "4.17.0", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", @@ -2796,12 +2760,6 @@ "@types/lodash": "*" } }, - "node_modules/@types/mime": { - "version": "1.3.5", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@types/minimatch": { "version": "5.1.2", "dev": true, @@ -2835,18 +2793,6 @@ "undici-types": "~6.20.0" } }, - "node_modules/@types/qs": { - "version": "6.9.11", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@types/request": { "version": "2.48.12", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", @@ -2886,33 +2832,6 @@ "@types/node": "*" } }, - "node_modules/@types/send": { - "version": "0.17.4", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.5", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static/node_modules/@types/mime": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@types/set-cookie-parser": { "version": "2.4.10", "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz", @@ -2969,6 +2888,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.3.tgz", + "integrity": "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==", + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@typespec/ts-http-runtime/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "dev": true, @@ -3548,6 +3494,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -3557,6 +3504,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -6193,6 +6141,7 @@ "version": "2.0.0", "license": "MIT", "dependencies": { + "@azure/core-auth": "^1.9.0", "@azure/storage-blob": "^12.24.0", "@tus/utils": "^0.6.0", "debug": "^4.3.4" @@ -6366,4 +6315,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 6ff1b7b3..01d71dd2 100644 --- a/package.json +++ b/package.json @@ -23,4 +23,4 @@ "@changesets/cli": "^2.29.2", "typescript": "^5.8.2" } -} +} \ No newline at end of file diff --git a/packages/azure-store/package.json b/packages/azure-store/package.json index b2adfb64..ee25781e 100644 --- a/packages/azure-store/package.json +++ b/packages/azure-store/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@tus/utils": "^0.6.0", + "@azure/core-auth": "^1.9.0", "@azure/storage-blob": "^12.24.0", "debug": "^4.3.4" }, @@ -35,4 +36,4 @@ "engines": { "node": ">=20.19.0" } -} +} \ No newline at end of file diff --git a/packages/azure-store/src/index.ts b/packages/azure-store/src/index.ts index c80ecd5f..8307dfb0 100644 --- a/packages/azure-store/src/index.ts +++ b/packages/azure-store/src/index.ts @@ -16,13 +16,21 @@ import { type ContainerClient, StorageSharedKeyCredential, } from '@azure/storage-blob' - -type Options = { - cache?: KvStore - account: string - accountKey: string - containerName: string -} +import type {TokenCredential} from '@azure/core-auth' + +type Options = + | { + cache?: KvStore + account: string + containerName: string + accountKey: string + } + | { + cache?: KvStore + account: string + containerName: string + credential: TokenCredential + } const log = debug('tus-node-server:stores:azurestore') @@ -44,23 +52,17 @@ export class AzureStore extends DataStore { if (!options.account) { throw new Error('Azure store must have a account') } - if (!options.accountKey) { - throw new Error('Azure store must have a account key') - } if (!options.containerName) { throw new Error('Azure store must have a container name') } const storageAccountBaseUrl = `https://${options.account}.blob.core.windows.net` - const sharedKeyCredential = new StorageSharedKeyCredential( - options.account, - options.accountKey - ) + const credential = + 'credential' in options + ? options.credential + : new StorageSharedKeyCredential(options.account, options.accountKey) - this.blobServiceClient = new BlobServiceClient( - storageAccountBaseUrl, - sharedKeyCredential - ) + this.blobServiceClient = new BlobServiceClient(storageAccountBaseUrl, credential) this.containerClient = this.blobServiceClient.getContainerClient( options.containerName ) diff --git a/packages/azure-store/src/test/index.ts b/packages/azure-store/src/test/index.ts index b2f19026..c3e1026f 100644 --- a/packages/azure-store/src/test/index.ts +++ b/packages/azure-store/src/test/index.ts @@ -1,6 +1,8 @@ import 'should' +import {strict as assert} from 'node:assert' import path from 'node:path' import {AzureStore} from '@tus/azure-store' +import type {TokenCredential} from '@azure/core-auth' import * as shared from '../../../utils/dist/test/stores.js' const fixturesPath = path.resolve('../', '../', 'test', 'fixtures') @@ -15,18 +17,82 @@ describe('AzureStore', () => { }) beforeEach(function () { - this.datastore = new AzureStore({ - account: process.env.AZURE_ACCOUNT_ID as string, - accountKey: process.env.AZURE_ACCOUNT_KEY as string, - containerName: process.env.AZURE_CONTAINER_NAME as string, - }) + const hasCredentials = + process.env.AZURE_ACCOUNT_ID && + process.env.AZURE_ACCOUNT_KEY && + process.env.AZURE_CONTAINER_NAME + + if (hasCredentials) { + this.datastore = new AzureStore({ + account: process.env.AZURE_ACCOUNT_ID as string, + accountKey: process.env.AZURE_ACCOUNT_KEY as string, + containerName: process.env.AZURE_CONTAINER_NAME as string, + }) + } else { + const mockCredential: TokenCredential = { + getToken: async () => ({ + token: 'mock-token', + expiresOnTimestamp: Date.now() + 3600_000, + }), + } + this.datastore = new AzureStore({ + account: 'testaccount', + containerName: 'testcontainer', + credential: mockCredential, + }) + } }) shared.shouldHaveStoreMethods() - shared.shouldCreateUploads() - // shared.shouldRemoveUploads() // Not implemented yet - // shared.shouldExpireUploads() // Not implemented yet - shared.shouldWriteUploads() - shared.shouldHandleOffset() - shared.shouldDeclareUploadLength() // Creation-defer-length extension + if ( + process.env.AZURE_ACCOUNT_ID && + process.env.AZURE_ACCOUNT_KEY && + process.env.AZURE_CONTAINER_NAME + ) { + shared.shouldCreateUploads() + shared.shouldWriteUploads() + shared.shouldHandleOffset() + shared.shouldDeclareUploadLength() + } + + describe('constructor', () => { + it('should accept a TokenCredential instead of accountKey', () => { + const mockCredential: TokenCredential = { + getToken: async () => ({ + token: 'mock-token', + expiresOnTimestamp: Date.now() + 3600_000, + }), + } + const store = new AzureStore({ + account: 'testaccount', + containerName: 'testcontainer', + credential: mockCredential, + }) + assert.ok(store) + }) + + it('should throw when account is missing', () => { + assert.throws( + () => + new AzureStore({ + account: '', + containerName: 'test', + accountKey: 'key', + }), + /account/ + ) + }) + + it('should throw when containerName is missing', () => { + assert.throws( + () => + new AzureStore({ + account: 'test', + containerName: '', + accountKey: 'key', + }), + /container name/ + ) + }) + }) })