From 2ce5812ff55dff32e4e21139e23429b7e403ebb9 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Sat, 18 Sep 2021 20:55:53 -0700 Subject: [PATCH] server: basic auth. object stats. --- server/package-lock.json | 130 +++++++++++++++++++++++++++++---- server/package.json | 3 + server/src/component/plugin.ts | 5 ++ server/src/db-types.ts | 1 + server/src/scrypted-main.ts | 55 +++++++++++++- 5 files changed, 179 insertions(+), 15 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 733670d56..adc728faa 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@scrypted/common": "file:../common", "@scrypted/sdk": "file:../sdk", "@types/adm-zip": "^0.4.33", "@types/cookie-parser": "^1.4.2", @@ -26,6 +25,7 @@ "engine.io": "^5.2.0", "express": "^4.17.1", "ffmpeg-for-homebridge": "^0.0.9", + "http-auth": "^4.1.9", "level": "^6.0.1", "lodash": "^4.17.21", "memfs": "^3.2.2", @@ -44,6 +44,7 @@ "zwave-js": "^8.1.1" }, "devDependencies": { + "@types/http-auth": "^4.1.1", "@types/lodash": "^4.14.168", "@types/mkdirp": "^1.0.2", "@types/source-map-support": "^0.5.4", @@ -57,7 +58,9 @@ } }, "../common": { + "name": "@scrypted/common", "version": "1.0.1", + "extraneous": true, "license": "ISC", "dependencies": { "@scrypted/sdk": "file:../sdk" @@ -118,7 +121,7 @@ }, "../sdk": { "name": "@scrypted/sdk", - "version": "0.0.72", + "version": "0.0.77", "license": "ISC", "dependencies": { "@babel/core": "^7.2.2", @@ -163,6 +166,9 @@ "scrypted-package-json": "bin/scrypted-package-json.js", "scrypted-readme": "bin/scrypted-readme.js", "scrypted-webpack": "bin/scrypted-webpack.js" + }, + "devDependencies": { + "typescript-json-schema": "^0.50.1" } }, "node_modules/@alcalzone/jsonl-db": { @@ -228,10 +234,6 @@ "kuler": "^2.0.0" } }, - "node_modules/@scrypted/common": { - "resolved": "../common", - "link": true - }, "node_modules/@scrypted/sdk": { "resolved": "../sdk", "link": true @@ -567,6 +569,15 @@ "@types/range-parser": "*" } }, + "node_modules/@types/http-auth": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/http-auth/-/http-auth-4.1.1.tgz", + "integrity": "sha512-oBOiSe402K7P2yoCDLctjFOoUfiBBbvoZ3RVRr3vZg7R/RW2U+BbYR2MYFNrQ6S3Kwtvt893DowfNe9g5LyIXQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/lodash": { "version": "4.14.168", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz", @@ -920,6 +931,25 @@ "node": ">=0.10.0" } }, + "node_modules/apache-crypt": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.5.tgz", + "integrity": "sha512-ICnYQH+DFVmw+S4Q0QY2XRXD8Ne8ewh8HgbuFH4K7022zCxgHM0Hz1xkRnUlEfAXNbwp1Cnhbedu60USIfDxvg==", + "dependencies": { + "unix-crypt-td-js": "^1.1.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/apache-md5": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.7.tgz", + "integrity": "sha512-JtHjzZmJxtzfTSjsCyHgPR155HBe5WGyUyHTaEkfy46qhwCFKx1Epm6nAxgUG3WfUZP1dWhGqj9Z2NOBeZ+uBw==", + "engines": { + "node": ">=8" + } + }, "node_modules/aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -991,6 +1021,11 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, "node_modules/bindings": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", @@ -1772,6 +1807,20 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "node_modules/http-auth": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-4.1.9.tgz", + "integrity": "sha512-kvPYxNGc9EKGTXvOMnTBQw2RZfuiSihK/mLw/a4pbtRueTE45S55Lw/3k5CktIf7Ak0veMKEIteDj4YkNmCzmQ==", + "dependencies": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.4.3", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -3294,6 +3343,11 @@ "node": ">= 10.0.0" } }, + "node_modules/unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -3315,6 +3369,14 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3619,13 +3681,6 @@ "kuler": "^2.0.0" } }, - "@scrypted/common": { - "version": "file:../common", - "requires": { - "@scrypted/sdk": "file:../sdk", - "@types/node": "^16.9.0" - } - }, "@scrypted/sdk": { "version": "file:../sdk", "requires": { @@ -3660,6 +3715,7 @@ "ts-loader": "^5.4.5", "tsconfig-paths-webpack-plugin": "^3.2.0", "typescript": "^4.3.5", + "typescript-json-schema": "^0.50.1", "webpack": "^4.28.1", "webpack-cli": "^3.1.2", "webpack-inject-plugin": "^1.0.2" @@ -3930,6 +3986,15 @@ "@types/range-parser": "*" } }, + "@types/http-auth": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/http-auth/-/http-auth-4.1.1.tgz", + "integrity": "sha512-oBOiSe402K7P2yoCDLctjFOoUfiBBbvoZ3RVRr3vZg7R/RW2U+BbYR2MYFNrQ6S3Kwtvt893DowfNe9g5LyIXQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/lodash": { "version": "4.14.168", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz", @@ -4217,6 +4282,19 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, + "apache-crypt": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.5.tgz", + "integrity": "sha512-ICnYQH+DFVmw+S4Q0QY2XRXD8Ne8ewh8HgbuFH4K7022zCxgHM0Hz1xkRnUlEfAXNbwp1Cnhbedu60USIfDxvg==", + "requires": { + "unix-crypt-td-js": "^1.1.4" + } + }, + "apache-md5": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.7.tgz", + "integrity": "sha512-JtHjzZmJxtzfTSjsCyHgPR155HBe5WGyUyHTaEkfy46qhwCFKx1Epm6nAxgUG3WfUZP1dWhGqj9Z2NOBeZ+uBw==" + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -4279,6 +4357,11 @@ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, "bindings": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", @@ -4921,6 +5004,17 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "http-auth": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-4.1.9.tgz", + "integrity": "sha512-kvPYxNGc9EKGTXvOMnTBQw2RZfuiSihK/mLw/a4pbtRueTE45S55Lw/3k5CktIf7Ak0veMKEIteDj4YkNmCzmQ==", + "requires": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.4.3", + "uuid": "^8.3.2" + } + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -6119,6 +6213,11 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" }, + "unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -6134,6 +6233,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/server/package.json b/server/package.json index 749ce827b..e73065d24 100644 --- a/server/package.json +++ b/server/package.json @@ -2,6 +2,7 @@ "name": "scrypted", "version": "1.0.0", "description": "", + "private": true, "dependencies": { "@scrypted/sdk": "file:../sdk", "@types/adm-zip": "^0.4.33", @@ -19,6 +20,7 @@ "engine.io": "^5.2.0", "express": "^4.17.1", "ffmpeg-for-homebridge": "^0.0.9", + "http-auth": "^4.1.9", "level": "^6.0.1", "lodash": "^4.17.21", "memfs": "^3.2.2", @@ -40,6 +42,7 @@ "mdns": "^2.7.2" }, "devDependencies": { + "@types/http-auth": "^4.1.1", "@types/lodash": "^4.14.168", "@types/mkdirp": "^1.0.2", "@types/source-map-support": "^0.5.4", diff --git a/server/src/component/plugin.ts b/server/src/component/plugin.ts index 381fdb00c..cd40e2d43 100644 --- a/server/src/component/plugin.ts +++ b/server/src/component/plugin.ts @@ -67,9 +67,14 @@ export class PluginComponent { async getPluginInfo(pluginId: string) { const plugin = await this.scrypted.datastore.tryGet(Plugin, pluginId); const host = this.scrypted.plugins[pluginId]; + let rpcObjects = 0; + if (host.peer) { + rpcObjects = host.peer.localProxied.size + Object.keys(host.peer.remoteWeakProxies).length; + } return { pid: host?.worker?.process.pid, stats: host?.stats, + rpcObjects, packageJson: plugin.packageJson, id: this.scrypted.findPluginDevice(pluginId), } diff --git a/server/src/db-types.ts b/server/src/db-types.ts index 2b812a7c9..b13e7e34b 100644 --- a/server/src/db-types.ts +++ b/server/src/db-types.ts @@ -18,6 +18,7 @@ export class Plugin extends ScryptedDocument { export class ScryptedUser extends ScryptedDocument { passwordDate: number; passwordHash: string; + token: string; salt: string; } diff --git a/server/src/scrypted-main.ts b/server/src/scrypted-main.ts index bb3c36b54..cdbe79586 100644 --- a/server/src/scrypted-main.ts +++ b/server/src/scrypted-main.ts @@ -18,11 +18,11 @@ import cookieParser from 'cookie-parser'; import axios from 'axios'; import qs from 'query-string'; import { RPCResultError } from './rpc'; -import child_process from 'child_process'; -import os from 'os'; import fs from 'fs'; import mkdirp from 'mkdirp'; import { install as installSourceMapSupport } from 'source-map-support'; +import httpAuth from 'http-auth'; + installSourceMapSupport(); @@ -106,6 +106,24 @@ else { certSetting = await db.upsert(certSetting); } + + const basicAuth = httpAuth.basic({ + realm: 'Scrypted', + }, async (username, password, callback) => { + const user = await db.tryGet(ScryptedUser, username); + if (!user) { + callback(false); + return; + } + + const salted = user.salt + password; + const hash = crypto.createHash('sha256'); + hash.update(salted); + const sha = hash.digest().toString('hex'); + + callback(sha === user.passwordHash || password === user.token); + }); + const keys = certSetting.value; const secure = https.createServer({ key: keys.serviceKey, cert: keys.certificate }, app).listen(SCRYPTED_SECURE_PORT); const insecure = http.createServer(app).listen(SCRYPTED_INSECURE_PORT); @@ -134,6 +152,16 @@ else { res.locals.username = username; } + else if (req.protocol === 'https' && req.headers.authorization) { + const basicChecker = basicAuth.check((req) => { + res.locals.username = req.user; + next(); + }); + + // this automatically handles unauthorized. + basicChecker(req, res); + return; + } next(); }); @@ -253,6 +281,7 @@ else { const timestamp = Date.now(); if (hasLogin) { + const user = await db.tryGet(ScryptedUser, username); if (!user) { res.send({ @@ -319,6 +348,28 @@ else { }); app.get('/login', async (req, res) => { + if (req.protocol === 'https' && req.headers.authorization) { + const username = await new Promise(resolve => { + const basicChecker = basicAuth.check((req) => { + resolve(req.user); + }); + + // this automatically handles unauthorized. + basicChecker(req, res); + }); + + const user = await db.tryGet(ScryptedUser, username); + if (!user.token) { + user.token = crypto.randomBytes(16).toString('hex'); + await db.upsert(user); + } + res.send({ + username, + token: user.token, + }); + return; + } + const hasLogin = await db.getCount(ScryptedUser) > 0; const { login_user_token } = req.signedCookies; if (!login_user_token) {