From 6ac790f82451a5dd548326e42e1d01005a049946 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Mon, 7 Apr 2025 08:17:38 -0700 Subject: [PATCH] webhook: prevent enable on internal types, fixup code to not require mixin existence --- plugins/webhook/.vscode/settings.json | 2 +- plugins/webhook/package-lock.json | 4 +- plugins/webhook/package.json | 2 +- plugins/webhook/src/main.ts | 68 +++++++++++++++------------ plugins/webhook/tsconfig.json | 2 +- 5 files changed, 44 insertions(+), 34 deletions(-) diff --git a/plugins/webhook/.vscode/settings.json b/plugins/webhook/.vscode/settings.json index 77ccdbd6d..a620593fa 100644 --- a/plugins/webhook/.vscode/settings.json +++ b/plugins/webhook/.vscode/settings.json @@ -1,4 +1,4 @@ { - "scrypted.debugHost": "127.0.0.1", + "scrypted.debugHost": "scrypted-nvr", } \ No newline at end of file diff --git a/plugins/webhook/package-lock.json b/plugins/webhook/package-lock.json index 54135fef4..fd2685fd2 100644 --- a/plugins/webhook/package-lock.json +++ b/plugins/webhook/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/webhook", - "version": "0.0.26", + "version": "0.0.27", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/webhook", - "version": "0.0.26", + "version": "0.0.27", "dependencies": { "@types/node": "^16.6.1" }, diff --git a/plugins/webhook/package.json b/plugins/webhook/package.json index fff337580..f1cdda35b 100644 --- a/plugins/webhook/package.json +++ b/plugins/webhook/package.json @@ -35,5 +35,5 @@ "devDependencies": { "@scrypted/sdk": "file:../../sdk" }, - "version": "0.0.26" + "version": "0.0.27" } diff --git a/plugins/webhook/src/main.ts b/plugins/webhook/src/main.ts index 27dad5510..99e6c9ac5 100644 --- a/plugins/webhook/src/main.ts +++ b/plugins/webhook/src/main.ts @@ -1,12 +1,12 @@ -import { HttpRequest, HttpRequestHandler, HttpResponse, MixinProvider, PushHandler, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, Setting, Settings, SettingValue, WritableDeviceState } from '@scrypted/sdk'; -import sdk from '@scrypted/sdk'; -import { SettingsMixinDeviceBase } from "../../../common/src/settings-mixin"; +import sdk, { HttpRequest, HttpRequestHandler, HttpResponse, MixinProvider, PushHandler, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, Setting, Settings, SettingValue, WritableDeviceState } from '@scrypted/sdk'; +import { StorageSettings, StorageSettingsDevice } from '@scrypted/sdk/storage-settings'; import { randomBytes } from 'crypto'; +import { SettingsMixinDeviceBase } from "../../../common/src/settings-mixin"; const allInterfaceMethods: string[] = [].concat(...Object.values(ScryptedInterfaceDescriptors).map((type: any) => type.methods)); const allInterfaceProperties: string[] = [].concat(...Object.values(ScryptedInterfaceDescriptors).map((type: any) => type.properties)); -import { isPublishable} from '../../mqtt/src/publishable-types'; +import { isPublishable } from '../../mqtt/src/publishable-types'; const { systemManager, endpointManager, mediaManager } = sdk; @@ -15,7 +15,19 @@ const mediaObjectMethods = [ 'getVideoStream', ] +function getMixinStorageSettings(device: StorageSettingsDevice) { + return new StorageSettings(device, { + token: { + hide: true, + persistedDefaultValue: randomBytes(8).toString('hex'), + } + }) + +} + class WebhookMixin extends SettingsMixinDeviceBase { + storageSettings = getMixinStorageSettings(this); + async getMixinSettings(): Promise { const realDevice = systemManager.getDeviceById(this.id); return [ @@ -24,6 +36,8 @@ class WebhookMixin extends SettingsMixinDeviceBase { key: 'create', description: 'Create a Webhook for a device interface. E.g., OnOff to turn a light on or off, or Camera to retrieve an image. The created webhook will be viewable in the Console.', choices: realDevice.interfaces, + immediate: true, + console: true, } ] } @@ -31,11 +45,7 @@ class WebhookMixin extends SettingsMixinDeviceBase { async putMixinSetting(key: string, value: string | number | boolean): Promise { this.onDeviceEvent(ScryptedInterface.Settings, undefined); - let token = this.storage.getItem('token'); - if (!token) { - token = randomBytes(8).toString('hex'); - this.storage.setItem('token', token); - } + const { token } = this.storageSettings.values; this.console.log(); this.console.log(); @@ -79,7 +89,11 @@ class WebhookMixin extends SettingsMixinDeviceBase { this.console.log("##################################################") } - async maybeSendMediaObject(response: HttpResponse, value: any, method: string) { + +} + +class WebhookPlugin extends ScryptedDeviceBase implements Settings, MixinProvider, HttpRequestHandler, PushHandler { + static async maybeSendMediaObject(response: HttpResponse, value: any, method: string) { if (!mediaObjectMethods.includes(method)) { response?.send(value?.toString()); return; @@ -93,9 +107,16 @@ class WebhookMixin extends SettingsMixinDeviceBase { }); } - async handle(request: HttpRequest, response: HttpResponse, device: ScryptedDevice, pathSegments: string[]) { + static async handleMixin(request: HttpRequest, response: HttpResponse, device: ScryptedDevice, pathSegments: string[]) { const token = pathSegments[2]; - if (token !== this.storage.getItem('token')) { + const mixinStorage = sdk.deviceManager.getMixinStorage(device.id); + const console = sdk.deviceManager.getMixinConsole(device.id); + const storageSettings = getMixinStorageSettings({ + async onDeviceEvent() { }, + storage: mixinStorage, + }); + + if (token !== storageSettings.values.token) { response?.send('Invalid Token', { code: 401, }); @@ -124,7 +145,7 @@ class WebhookMixin extends SettingsMixinDeviceBase { } } catch (e) { - this.console.error('webhook action error', e); + console.error('webhook action error', e); response.send('Internal Error', { code: 500, }); @@ -144,16 +165,12 @@ class WebhookMixin extends SettingsMixinDeviceBase { } } else { - this.console.error('Unknown method or property', methodOrProperty); + console.error('Unknown method or property', methodOrProperty); response.send('Not Found', { code: 404, }); } } -} - -class WebhookPlugin extends ScryptedDeviceBase implements Settings, MixinProvider, HttpRequestHandler, PushHandler { - createdMixins = new Map(); async handle(request: HttpRequest, response?: HttpResponse) { this.console.log('received webhook', request); @@ -180,17 +197,14 @@ class WebhookPlugin extends ScryptedDeviceBase implements Settings, MixinProvide return; } - if (!this.createdMixins.has(id)) { - await device.getSettings(); - } - const mixin = this.createdMixins.get(id); - if (!mixin) { + if (!device.mixins?.includes(this.id)) { response.send('Not Found', { code: 404, }); return; } - await mixin.handle(request, response, device, pathSegments); + + await WebhookPlugin.handleMixin(request, response, device, pathSegments); } onRequest(request: HttpRequest, response: HttpResponse): Promise { @@ -228,15 +242,11 @@ class WebhookPlugin extends ScryptedDeviceBase implements Settings, MixinProvide groupKey: "webhook", }); - this.createdMixins.set(ret.id, ret); return ret; } async releaseMixin(id: string, mixinDevice: any): Promise { - if (this.createdMixins.get(id) === mixinDevice) { - this.createdMixins.delete(id); - } } } -export default new WebhookPlugin(); +export default WebhookPlugin; diff --git a/plugins/webhook/tsconfig.json b/plugins/webhook/tsconfig.json index 34a847ad8..ba9b4d395 100644 --- a/plugins/webhook/tsconfig.json +++ b/plugins/webhook/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "commonjs", + "module": "Node16", "target": "ES2021", "resolveJsonModule": true, "moduleResolution": "Node16",