sillytavern / src /endpoints /users-private.js
Nocigar's picture
Upload 72 files
1307964 verified
const path = require('path');
const fsPromises = require('fs').promises;
const storage = require('node-persist');
const express = require('express');
const crypto = require('crypto');
const { jsonParser } = require('../express-common');
const { getUserAvatar, toKey, getPasswordHash, getPasswordSalt, createBackupArchive, ensurePublicDirectoriesExist, toAvatarKey } = require('../users');
const { SETTINGS_FILE } = require('../constants');
const contentManager = require('./content-manager');
const { color, Cache } = require('../util');
const { checkForNewContent } = require('./content-manager');
const RESET_CACHE = new Cache(5 * 60 * 1000);
const router = express.Router();
router.post('/logout', async (request, response) => {
try {
if (!request.session) {
console.error('Session not available');
return response.sendStatus(500);
}
request.session.handle = null;
return response.sendStatus(204);
} catch (error) {
console.error(error);
return response.sendStatus(500);
}
});
router.get('/me', async (request, response) => {
try {
if (!request.user) {
return response.sendStatus(403);
}
const user = request.user.profile;
const viewModel = {
handle: user.handle,
name: user.name,
avatar: await getUserAvatar(user.handle),
admin: user.admin,
password: !!user.password,
created: user.created,
};
return response.json(viewModel);
} catch (error) {
console.error(error);
return response.sendStatus(500);
}
});
router.post('/change-avatar', jsonParser, async (request, response) => {
try {
if (!request.body.handle) {
console.log('Change avatar failed: Missing required fields');
return response.status(400).json({ error: 'Missing required fields' });
}
if (request.body.handle !== request.user.profile.handle && !request.user.profile.admin) {
console.log('Change avatar failed: Unauthorized');
return response.status(403).json({ error: 'Unauthorized' });
}
// Avatar is not a data URL or not an empty string
if (!request.body.avatar.startsWith('data:image/') && request.body.avatar !== '') {
console.log('Change avatar failed: Invalid data URL');
return response.status(400).json({ error: 'Invalid data URL' });
}
/** @type {import('../users').User} */
const user = await storage.getItem(toKey(request.body.handle));
if (!user) {
console.log('Change avatar failed: User not found');
return response.status(404).json({ error: 'User not found' });
}
await storage.setItem(toAvatarKey(request.body.handle), request.body.avatar);
return response.sendStatus(204);
} catch (error) {
console.error(error);
return response.sendStatus(500);
}
});
router.post('/change-password', jsonParser, async (request, response) => {
try {
if (!request.body.handle) {
console.log('Change password failed: Missing required fields');
return response.status(400).json({ error: 'Missing required fields' });
}
if (request.body.handle !== request.user.profile.handle && !request.user.profile.admin) {
console.log('Change password failed: Unauthorized');
return response.status(403).json({ error: 'Unauthorized' });
}
/** @type {import('../users').User} */
const user = await storage.getItem(toKey(request.body.handle));
if (!user) {
console.log('Change password failed: User not found');
return response.status(404).json({ error: 'User not found' });
}
if (!user.enabled) {
console.log('Change password failed: User is disabled');
return response.status(403).json({ error: 'User is disabled' });
}
if (!request.user.profile.admin && user.password && user.password !== getPasswordHash(request.body.oldPassword, user.salt)) {
console.log('Change password failed: Incorrect password');
return response.status(403).json({ error: 'Incorrect password' });
}
if (request.body.newPassword) {
const salt = getPasswordSalt();
user.password = getPasswordHash(request.body.newPassword, salt);
user.salt = salt;
} else {
user.password = '';
user.salt = '';
}
await storage.setItem(toKey(request.body.handle), user);
return response.sendStatus(204);
} catch (error) {
console.error(error);
return response.sendStatus(500);
}
});
router.post('/backup', jsonParser, async (request, response) => {
try {
const handle = request.body.handle;
if (!handle) {
console.log('Backup failed: Missing required fields');
return response.status(400).json({ error: 'Missing required fields' });
}
if (handle !== request.user.profile.handle && !request.user.profile.admin) {
console.log('Backup failed: Unauthorized');
return response.status(403).json({ error: 'Unauthorized' });
}
await createBackupArchive(handle, response);
} catch (error) {
console.error('Backup failed', error);
return response.sendStatus(500);
}
});
router.post('/reset-settings', jsonParser, async (request, response) => {
try {
const password = request.body.password;
if (request.user.profile.password && request.user.profile.password !== getPasswordHash(password, request.user.profile.salt)) {
console.log('Reset settings failed: Incorrect password');
return response.status(403).json({ error: 'Incorrect password' });
}
const pathToFile = path.join(request.user.directories.root, SETTINGS_FILE);
await fsPromises.rm(pathToFile, { force: true });
await contentManager.checkForNewContent([request.user.directories], [contentManager.CONTENT_TYPES.SETTINGS]);
return response.sendStatus(204);
} catch (error) {
console.error('Reset settings failed', error);
return response.sendStatus(500);
}
});
router.post('/change-name', jsonParser, async (request, response) => {
try {
if (!request.body.name || !request.body.handle) {
console.log('Change name failed: Missing required fields');
return response.status(400).json({ error: 'Missing required fields' });
}
if (request.body.handle !== request.user.profile.handle && !request.user.profile.admin) {
console.log('Change name failed: Unauthorized');
return response.status(403).json({ error: 'Unauthorized' });
}
/** @type {import('../users').User} */
const user = await storage.getItem(toKey(request.body.handle));
if (!user) {
console.log('Change name failed: User not found');
return response.status(404).json({ error: 'User not found' });
}
user.name = request.body.name;
await storage.setItem(toKey(request.body.handle), user);
return response.sendStatus(204);
} catch (error) {
console.error('Change name failed', error);
return response.sendStatus(500);
}
});
router.post('/reset-step1', jsonParser, async (request, response) => {
try {
const resetCode = String(crypto.randomInt(1000, 9999));
console.log();
console.log(color.magenta(`${request.user.profile.name}, your account reset code is: `) + color.red(resetCode));
console.log();
RESET_CACHE.set(request.user.profile.handle, resetCode);
return response.sendStatus(204);
} catch (error) {
console.error('Recover step 1 failed:', error);
return response.sendStatus(500);
}
});
router.post('/reset-step2', jsonParser, async (request, response) => {
try {
if (!request.body.code) {
console.log('Recover step 2 failed: Missing required fields');
return response.status(400).json({ error: 'Missing required fields' });
}
if (request.user.profile.password && request.user.profile.password !== getPasswordHash(request.body.password, request.user.profile.salt)) {
console.log('Recover step 2 failed: Incorrect password');
return response.status(400).json({ error: 'Incorrect password' });
}
const code = RESET_CACHE.get(request.user.profile.handle);
if (!code || code !== request.body.code) {
console.log('Recover step 2 failed: Incorrect code');
return response.status(400).json({ error: 'Incorrect code' });
}
console.log('Resetting account data:', request.user.profile.handle);
await fsPromises.rm(request.user.directories.root, { recursive: true, force: true });
await ensurePublicDirectoriesExist();
await checkForNewContent([request.user.directories]);
RESET_CACHE.remove(request.user.profile.handle);
return response.sendStatus(204);
} catch (error) {
console.error('Recover step 2 failed:', error);
return response.sendStatus(500);
}
});
module.exports = {
router,
};