Compare commits

...

24 Commits

Author SHA1 Message Date
Koushik Dutta
7065365a47 postrelease 2023-04-07 08:30:21 -07:00
Koushik Dutta
b82520776e sdk/server: search for TypedDict in typing and typing_extensions 2023-04-07 08:29:47 -07:00
Koushik Dutta
638c1f77fd ring: fix login issues 2023-04-07 08:06:16 -07:00
Koushik Dutta
73a489ea37 rtc: null check double offer error 2023-04-06 22:35:52 -07:00
Koushik Dutta
77d69f025a server: fix release build scripts 2023-04-06 08:32:44 -07:00
Koushik Dutta
3bc14ad248 prebeta 2023-04-06 08:32:33 -07:00
Koushik Dutta
03e5a9dec1 Merge branch 'main' of github.com:koush/scrypted 2023-04-06 08:22:11 -07:00
Koushik Dutta
57b790c332 server: publish beta 2023-04-06 08:22:05 -07:00
Koushik Dutta
ce2ea63be7 server: add hook for npm exec in non-node environment (electron) 2023-04-06 08:21:37 -07:00
Alex Leeds
2dd4721b7f ring: fix login 406 error (#698) 2023-04-06 07:45:33 -07:00
Justin Angevaare
667075dfad Add tip about digest authentication (#697) 2023-04-06 07:45:24 -07:00
Koushik Dutta
7abdb06b66 postrelease 2023-04-05 14:39:28 -07:00
Koushik Dutta
43e5822c93 server: fix first run account creation bug 2023-04-05 14:39:20 -07:00
Koushik Dutta
bc579514e7 python-codecs: add numpy to requirements.txt 2023-04-05 11:55:04 -07:00
Koushik Dutta
825100f94e webrtc: add answer only option 2023-04-05 10:17:17 -07:00
Koushik Dutta
803bfc1560 pam-diff: tweak default motion percent 2023-04-05 10:16:46 -07:00
Koushik Dutta
b2013a54ed pam-diff: tweak default motion percent 2023-04-05 10:15:40 -07:00
Koushik Dutta
f252407935 rebroadcast: fix settings clear issue 2023-04-04 11:37:37 -07:00
Koushik Dutta
516f2a2a7b server: fetch version from package registry 2023-04-04 10:14:31 -07:00
Koushik Dutta
c1677ce691 postrelease 2023-04-04 09:59:39 -07:00
Koushik Dutta
5028fb812d server: storage polyfill should serialize keys and values as strings 2023-04-04 09:58:51 -07:00
Koushik Dutta
2db4e2579f server: add more files to .npmignore 2023-04-04 08:24:23 -07:00
Koushik Dutta
b339ca6cd2 fix bug where deleted users have continued/escalated permissions 2023-04-04 08:17:44 -07:00
Koushik Dutta
f100999cb1 postrelease 2023-04-04 08:17:13 -07:00
37 changed files with 218 additions and 100 deletions

View File

@@ -298,7 +298,7 @@ export async function connectRTCSignalingClients(
if (offerOptions?.offer && answerOptions?.offer)
throw new Error('Both RTC clients have offers and can not negotiate. Consider implementing this in @scrypted/webrtc.');
if (offerOptions?.requiresOffer && answerOptions.requiresOffer)
if (offerOptions?.requiresOffer && answerOptions?.requiresOffer)
throw new Error('Both RTC clients require offers and can not negotiate.');
offerSetup.type = 'offer';

View File

@@ -27,13 +27,6 @@ echo "sdk > npm run build"
npm run build
popd
pushd external/HAP-NodeJS
echo "external/HAP-NodeJS > npm install"
npm install
echo "external/HAP-NodeJS > npm run build"
npm run build
popd
pushd external/werift
echo "external/werift > npm install"
npm install

View File

@@ -41,7 +41,7 @@ The Channel number is the hundreds digit and (sub-)stream is ones digit:
# Troubleshooting
## General
* Not receiving motion alerts in the device's Scrypted event log? Check all of the following: **(1)** device has a motion detection grid drawn and enabled, **(2)** user or group access permissions of account used for device **(3)** do not use self-signed certs for HTTPS on the device, and **(4)** `CGI` and `ISAPI` integration protocol/service on device is enabled.
* Not receiving motion alerts in the device's Scrypted event log? Check all of the following: **(1)** device has a motion detection grid drawn and enabled, **(2)** user or group access permissions of account used for device **(3)** do not use self-signed certs for HTTPS on the device, **(4)** `CGI` and `ISAPI` integration protocol/service on device is enabled, and **(5)** that the authentication method on the device is set to "digest".
* If device has HTTPS enabled, try disabling HTTPS on the device to see if that resolves issue (do not use self-signed certs).
* If device has enabled user lockout, max connections, concurrent requests, etc., try disabling and/or increasing to max allowed for troubleshooting.
* Does your account (`Username`) have proper user and/or group permissions? Try granting all permissions for testing.

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/pam-diff",
"version": "0.0.18",
"version": "0.0.20",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/pam-diff",
"version": "0.0.18",
"version": "0.0.20",
"hasInstallScript": true,
"dependencies": {
"@types/node": "^16.6.1",

View File

@@ -43,5 +43,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.0.18"
"version": "0.0.20"
}

View File

@@ -9,7 +9,7 @@ import { PassThrough, Writable } from 'stream';
const { mediaManager } = sdk;
const defaultDifference = 9;
const defaultPercentage = 15;
const defaultPercentage = 2;
interface PamDiffSession {
id: string;

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/prebuffer-mixin",
"version": "0.9.79",
"version": "0.9.80",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/prebuffer-mixin",
"version": "0.9.79",
"version": "0.9.80",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/prebuffer-mixin",
"version": "0.9.79",
"version": "0.9.80",
"description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -111,7 +111,7 @@ export function createStreamSettings(device: MixinDeviceBase<VideoCamera>) {
placeholder: '-hwaccel auto',
choices: Object.keys(getH264DecoderArgs()),
combobox: true,
mapPut: (oldValue, newValue) => getH264DecoderArgs()[newValue]?.join(' ') || newValue,
mapPut: (oldValue, newValue) => getH264DecoderArgs()[newValue]?.join(' ') || newValue || '',
hide: true,
},
videoFilterArguments: {

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/python-codecs",
"version": "0.1.27",
"version": "0.1.30",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/python-codecs",
"version": "0.1.27",
"version": "0.1.30",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/python-codecs",
"version": "0.1.27",
"version": "0.1.30",
"description": "Python Codecs for Scrypted",
"keywords": [
"scrypted",

View File

@@ -1,3 +1,4 @@
import traceback
import asyncio
import scrypted_sdk
from scrypted_sdk import Setting, SettingValue
@@ -6,6 +7,7 @@ import gstreamer
import libav
import vipsimage
import pilimage
import time
Gst = None
try:
@@ -128,19 +130,29 @@ def create_scrypted_plugin():
class CodecFork:
async def generateVideoFramesGstreamer(self, mediaObject: scrypted_sdk.MediaObject, options: scrypted_sdk.VideoFrameGeneratorOptions = None, filter: Any = None, h264Decoder: str = None) -> scrypted_sdk.VideoFrame:
start = time.time()
try:
async for data in gstreamer.generateVideoFramesGstreamer(mediaObject, options, filter, h264Decoder):
yield data
except Exception as e:
traceback.print_exc()
raise
finally:
print('gstreamer finished after %s' % (time.time() - start))
import os
os._exit(os.EX_OK)
pass
async def generateVideoFramesLibav(self, mediaObject: scrypted_sdk.MediaObject, options: scrypted_sdk.VideoFrameGeneratorOptions = None, filter: Any = None) -> scrypted_sdk.VideoFrame:
start = time.time()
try:
async for data in libav.generateVideoFramesLibav(mediaObject, options, filter):
yield data
except Exception as e:
traceback.print_exc()
raise
finally:
print('libav finished after %s' % (time.time() - start))
import os
os._exit(os.EX_OK)
pass

View File

@@ -1,3 +1,6 @@
# needed by libav to_ndarray
numpy>=1.16.2
# gobject instrospection for gstreamer.
PyGObject>=3.30.4; sys_platform != 'win32'

View File

@@ -1,18 +1,18 @@
{
"name": "@scrypted/ring",
"version": "0.0.107",
"version": "0.0.109",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/ring",
"version": "0.0.107",
"version": "0.0.109",
"dependencies": {
"@koush/ring-client-api": "file:../../external/ring-client-api",
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@types/node": "^18.15.3",
"axios": "^1.3.4",
"@types/node": "^18.15.11",
"axios": "^1.3.5",
"rxjs": "^7.8.0"
},
"optionalDependencies": {
@@ -42,14 +42,14 @@
],
"devDependencies": {
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.0",
"@changesets/cli": "^2.26.1",
"@swc-node/register": "^1.6.2",
"turbo": "^1.8.2"
"turbo": "^1.8.5"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.2.85",
"version": "0.2.86",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
@@ -148,9 +148,9 @@
}
},
"node_modules/@types/node": {
"version": "18.15.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz",
"integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw=="
"version": "18.15.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
},
"node_modules/@types/responselike": {
"version": "1.0.0",
@@ -179,9 +179,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.5.tgz",
"integrity": "sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",

View File

@@ -36,13 +36,13 @@
"@koush/ring-client-api": "file:../../external/ring-client-api",
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@types/node": "^18.15.3",
"axios": "^1.3.4",
"@types/node": "^18.15.11",
"axios": "^1.3.5",
"rxjs": "^7.8.0"
},
"optionalDependencies": {
"got": "11.8.6",
"socket.io-client": "^2.5.0"
},
"version": "0.0.107"
"version": "0.0.109"
}

View File

@@ -2,6 +2,7 @@ import sdk, { Device, DeviceProvider, ScryptedDeviceBase, ScryptedDeviceType, Sc
import { StorageSettings } from '@scrypted/sdk/storage-settings';
import { RingLocationDevice } from './location';
import { generateUuid, Location, RingBaseApi, RingRestClient } from './ring-client-api';
import { sleep } from '@scrypted/common/src/sleep';
const { deviceManager, mediaManager } = sdk;
@@ -19,12 +20,18 @@ class RingPlugin extends ScryptedDeviceBase implements DeviceProvider, Settings
},
email: {
title: 'Email',
onPut: async () => this.clearTryDiscoverDevices(),
onPut: async () => {
if (await this.loginNextTick())
this.clearTryDiscoverDevices();
},
},
password: {
title: 'Password',
type: 'password',
onPut: async () => this.clearTryDiscoverDevices(),
onPut: async () => {
if (await this.loginNextTick())
this.clearTryDiscoverDevices();
},
},
loginCode: {
title: 'Two Factor Code',
@@ -72,8 +79,18 @@ class RingPlugin extends ScryptedDeviceBase implements DeviceProvider, Settings
this.settingsStorage.values.systemId = generateUuid();
}
waiting = false;
async loginNextTick() {
if (this.waiting)
return false;
this.waiting = true;
await sleep(500);
this.waiting = false;
return true;
}
async clearTryDiscoverDevices() {
this.settingsStorage.values.refreshToken = undefined;
this.settingsStorage.values.refreshToken = '';
await this.discoverDevices();
this.console.log('discovery completed successfully');
}
@@ -192,7 +209,7 @@ class RingPlugin extends ScryptedDeviceBase implements DeviceProvider, Settings
return this.devices.get(nativeId);
}
async releaseDevice(id: string, nativeId: string): Promise<void> {}
async releaseDevice(id: string, nativeId: string): Promise<void> { }
}
export default RingPlugin;

View File

@@ -1,4 +1,4 @@
{
"scrypted.debugHost": "127.0.0.1",
"scrypted.debugHost": "koushik-ubuntu",
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/webrtc",
"version": "0.1.40",
"version": "0.1.41",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/webrtc",
"version": "0.1.40",
"version": "0.1.41",
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/webrtc",
"version": "0.1.40",
"version": "0.1.41",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",

View File

@@ -128,6 +128,7 @@ class WebRTCMixin extends SettingsMixinDeviceBase<RTCSignalingClient & VideoCame
this.plugin.storageSettings.values.maximumCompatibilityMode,
this.plugin.getRTCConfiguration(),
await this.plugin.getWeriftConfiguration(options?.disableTurn),
options?.requiresAnswer === true ? false : true,
);
}

View File

@@ -1,6 +1,9 @@
from __future__ import annotations
from typing import AbstractSet, Any, Callable
from typing_extensions import TypedDict
try:
from typing import TypedDict
except:
from typing_extensions import TypedDict
SettingValue = str
EventListener = Callable[[Any, Any, Any], None]

View File

@@ -1,6 +1,9 @@
from __future__ import annotations
from enum import Enum
from typing_extensions import TypedDict
try:
from typing import TypedDict
except:
from typing_extensions import TypedDict
from typing import Any
from typing import Callable

View File

@@ -256,7 +256,10 @@ class ${td.name}(TypedDict):
const pythonTypes = `from __future__ import annotations
from enum import Enum
from typing_extensions import TypedDict
try:
from typing import TypedDict
except:
from typing_extensions import TypedDict
from typing import Any
from typing import Callable

View File

@@ -1967,6 +1967,7 @@ export interface RTCSignalingOptions {
*/
offer?: RTCSessionDescriptionInit;
requiresOffer?: boolean;
requiresAnswer?: boolean;
/**
* Disables trickle ICE. All candidates must be sent in the initial offer/answer sdp.
*/

View File

@@ -10,3 +10,6 @@ scrypted.db.bak
__pycache__
.venv
.vscode
tsconfig.json
test
scripts

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/server",
"version": "0.7.43",
"version": "0.7.50",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/server",
"version": "0.7.43",
"version": "0.7.50",
"license": "ISC",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.10",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/server",
"version": "0.7.44",
"version": "0.7.51",
"description": "",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.10",
@@ -72,7 +72,8 @@
"prebeta": "npm version patch && git add package.json && npm run build && git commit -m prebeta",
"beta": "npm publish --tag beta",
"release": "npm publish",
"postrelease": "git tag v$npm_package_version && git push origin v$npm_package_version && npm version patch && git add package.json && npm run build && git commit -m postrelease",
"prepublish": "npm run build",
"postrelease": "git tag v$npm_package_version && git push origin v$npm_package_version && npm version patch && git add package.json && git commit -m postrelease",
"docker": "scripts/github-workflow-publish-docker.sh"
},
"author": "",

View File

@@ -2,13 +2,12 @@ from __future__ import annotations
import asyncio
import gc
import sys
import os
import platform
import shutil
import subprocess
import sys
import threading
import concurrent.futures
import time
import traceback
import zipfile
@@ -21,14 +20,23 @@ from os import sys
from typing import Any, Optional, Set, Tuple
import scrypted_python.scrypted_sdk.types
from scrypted_python.scrypted_sdk import ScryptedStatic, PluginFork
from scrypted_python.scrypted_sdk.types import Device, DeviceManifest, EventDetails, ScryptedInterfaceProperty, Storage
from typing_extensions import TypedDict
import rpc
import rpc_reader
from scrypted_python.scrypted_sdk import PluginFork, ScryptedStatic
from scrypted_python.scrypted_sdk.types import (Device, DeviceManifest,
EventDetails,
ScryptedInterfaceProperty,
Storage)
try:
from typing import TypedDict
except:
from typing_extensions import TypedDict
import hashlib
import multiprocessing
import multiprocessing.connection
import hashlib
import rpc
import rpc_reader
class SystemDeviceState(TypedDict):
lastEventTime: int

View File

@@ -1,8 +1,13 @@
from asyncio.futures import Future
from typing import Any, Callable, Dict, Mapping, List
import traceback
import inspect
from typing_extensions import TypedDict
import traceback
from asyncio.futures import Future
from typing import Any, Callable, Dict, List, Mapping
try:
from typing import TypedDict
except:
from typing_extensions import TypedDict
import weakref
jsonSerializable = set()

View File

@@ -1,2 +1,9 @@
const packageJson = require('../package.json');
console.log(packageJson.version);
async function main() {
const response = await fetch('https://registry.npmjs.org/@scrypted/server');
const json = await response.json();
console.log(json['dist-tags'].latest);
// const packageJson = require('../package.json');
// console.log(packageJson.version);
}
main();

View File

@@ -9,6 +9,19 @@ import rimraf from "rimraf";
import semver from 'semver';
import { ensurePluginVolume } from "./plugin-volume";
export function defaultNpmExec(args: string[], options: child_process.SpawnOptions) {
let npm = 'npm';
if (os.platform() === 'win32')
npm += '.cmd';
const cp = child_process.spawn(npm, args, options);
return cp;
}
let npmExecFunction = defaultNpmExec;
export function setNpmExecFunction(f: typeof npmExecFunction) {
npmExecFunction = f;
}
export function getPluginNodePath(name: string) {
const pluginVolume = ensurePluginVolume(name);
const nodeMajorVersion = semver.parse(process.version).major;
@@ -56,10 +69,7 @@ export async function installOptionalDependencies(console: Console, packageJson:
mkdirp.sync(nodePrefix);
fs.writeFileSync(packageJsonPath, JSON.stringify(reduced));
let npm = 'npm';
if (os.platform() === 'win32')
npm += '.cmd';
const cp = child_process.spawn(npm, ['--prefix', nodePrefix, 'install'], {
const cp = npmExecFunction(['--prefix', nodePrefix, 'install'], {
cwd: nodePrefix,
stdio: 'inherit',
});

View File

@@ -279,6 +279,15 @@ export class DeviceManagerImpl implements DeviceManager {
}
}
function toStorageString(value: any) {
if (value === null)
return 'null';
if (value === undefined)
return 'undefined;'
return value.toString();
}
class StorageImpl implements Storage {
api: PluginAPI;
[name: string]: any;
@@ -293,17 +302,17 @@ class StorageImpl implements Storage {
];
private static indexedHandler: ProxyHandler<StorageImpl> = {
get(target, property) {
if (StorageImpl.allowedMethods.includes(property.toString())) {
const prop = property.toString();
const f = target[property.toString()];
if (prop === 'length')
const keyString = property.toString();
if (StorageImpl.allowedMethods.includes(keyString)) {
const f = target[keyString];
if (keyString === 'length')
return f;
return f.bind(target);
}
return target.getItem(property.toString());
return target.getItem(toStorageString(property));
},
set(target, property, value): boolean {
target.setItem(property.toString(), value);
target.setItem(toStorageString(property), value);
return true;
}
};
@@ -351,6 +360,8 @@ class StorageImpl implements Storage {
this.api.setStorage(this.nativeId, this.storage);
}
setItem(key: string, value: string): void {
key = toStorageString(key);
value = toStorageString(value);
if (this.storage[this.prefix + key] === value)
return;
this.storage[this.prefix + key] = value;

View File

@@ -94,6 +94,8 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
super(app);
this.datastore = datastore;
this.app = app;
// ensure that all the users are loaded from the db.
this.usersService.getAllUsers();
this.pluginHosts.set('python', (_, pluginId, options) => new PythonRuntimeWorker(pluginId, options));
this.pluginHosts.set('node', (mainFilename, pluginId, options) => new NodeForkWorker(mainFilename, pluginId, options));

View File

@@ -212,7 +212,7 @@ async function start(mainFilename: string, options?: {
const sha = hash.digest().toString('hex');
if (checkHash === sha) {
const userToken = validateToken(tokenPart);
const userToken = checkValidUserToken(tokenPart);
if (userToken) {
res.locals.username = userToken.username;
res.locals.aclId = userToken.aclId;
@@ -420,19 +420,23 @@ async function start(mainFilename: string, options?: {
return req.secure ? 'login_user_token' : 'login_user_token_insecure';
};
const validateToken = (token: string) => {
const checkValidUserToken = (token: string) => {
if (!token)
return;
try {
return UserToken.validateToken(token);
const userToken = UserToken.validateToken(token);
if (scrypted.usersService.users.has(userToken.username))
return userToken;
}
catch (e) {
console.warn('invalid token', e.message);
// console.warn('invalid token', e.message);
}
}
const getSignedLoginUserTokenRawValue = (req: Request<any>) => req.signedCookies[getLoginUserToken(req)] as string;
const getSignedLoginUserToken = (req: Request<any>) => validateToken(getSignedLoginUserTokenRawValue(req));
const getSignedLoginUserToken = (req: Request<any>) => {
const token = req.signedCookies[getLoginUserToken(req)] as string;
return checkValidUserToken(token)
};
app.get('/logout', (req, res) => {
res.clearCookie(getLoginUserToken(req));
@@ -449,11 +453,7 @@ async function start(mainFilename: string, options?: {
if (process.env.SCRYPTED_ADMIN_USERNAME && process.env.SCRYPTED_ADMIN_TOKEN) {
let user = await db.tryGet(ScryptedUser, process.env.SCRYPTED_ADMIN_USERNAME);
if (!user) {
user = new ScryptedUser();
user._id = process.env.SCRYPTED_ADMIN_USERNAME;
setScryptedUserPassword(user, crypto.randomBytes(8).toString('hex'), Date.now());
user.token = crypto.randomBytes(16).toString('hex');
await db.upsert(user);
user = await scrypted.usersService.addUserInternal(process.env.SCRYPTED_ADMIN_USERNAME, crypto.randomBytes(8).toString('hex'), undefined);
hasLogin = true;
}
}
@@ -524,11 +524,7 @@ async function start(mainFilename: string, options?: {
return;
}
const user = new ScryptedUser();
user._id = username;
setScryptedUserPassword(user, password, timestamp);
user.token = crypto.randomBytes(16).toString('hex');
await db.upsert(user);
const user = await scrypted.usersService.addUserInternal(username, password, undefined);
hasLogin = true;
const userToken = new UserToken(username, user.aclId, timestamp);
@@ -621,10 +617,9 @@ async function start(mainFilename: string, options?: {
// cookie auth
try {
const login_user_token = getSignedLoginUserTokenRawValue(req);
if (!login_user_token)
const userToken = getSignedLoginUserToken(req);
if (!userToken)
throw new Error('Not logged in.');
const userToken = UserToken.validateToken(login_user_token);
res.send({
...createTokens(userToken),

View File

@@ -3,14 +3,32 @@ import { ScryptedRuntime } from "../runtime";
import crypto from 'crypto';
export class UsersService {
users = new Map<string, ScryptedUser>();
usersPromise: Promise<ScryptedUser[]>;
constructor(public scrypted: ScryptedRuntime) {
}
async getAllUsers() {
const users: ScryptedUser[] = [];
for await (const user of this.scrypted.datastore.getAll(ScryptedUser)) {
users.push(user);
private async ensureUsersPromise() {
if (!this.usersPromise) {
this.usersPromise = (async() => {
const users = new Map<string, ScryptedUser>();
for await (const user of this.scrypted.datastore.getAll(ScryptedUser)) {
users.set(user._id, user);
}
this.users = users;
return [...this.users.values()];
})();
}
return this.usersPromise;
}
private updateUsersPromise() {
this.usersPromise = Promise.resolve([...this.users.values()]);
}
async getAllUsers() {
const users = await this.ensureUsersPromise();
return users.map(user => ({
username: user._id,
@@ -19,19 +37,38 @@ export class UsersService {
}
async removeUser(username: string) {
await this.ensureUsersPromise();
await this.scrypted.datastore.removeId(ScryptedUser, username);
this.users.delete(username);
this.updateUsersPromise();
}
async removeAllUsers() {
await this.ensureUsersPromise();
await this.scrypted.datastore.removeAll(ScryptedUser);
this.users.clear();
this.updateUsersPromise();
}
async addUser(username: string, password: string, aclId: string) {
async addUserInternal(username: string, password: string, aclId: string) {
await this.ensureUsersPromise();
const user = new ScryptedUser();
user._id = username;
user.aclId = aclId;
user.token = crypto.randomBytes(16).toString('hex');
setScryptedUserPassword(user, password, Date.now());
await this.scrypted.datastore.upsert(user);
this.users.set(username, user);
this.updateUsersPromise();
return user;
}
async addUser(username: string, password: string, aclId: string) {
await this.addUserInternal(username, password, aclId);
}
}

View File

@@ -6,6 +6,9 @@ export class UserToken {
}
static validateToken(token: string): UserToken {
if (!token)
throw new Error('Token not found.');
let json: {
u: string,
a: string,