Compare commits

...

4 Commits

Author SHA1 Message Date
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
8 changed files with 74 additions and 22 deletions

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.45",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/server",
"version": "0.7.43",
"version": "0.7.45",
"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.45",
"description": "",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.10",

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));
@@ -621,10 +625,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,31 @@ 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) {
await this.ensureUsersPromise();
const user = new ScryptedUser();
user._id = username;
user.aclId = aclId;
setScryptedUserPassword(user, password, Date.now());
await this.scrypted.datastore.upsert(user);
this.users.set(username, user);
this.updateUsersPromise();
}
}

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,