Skip to content

Commit

Permalink
Rebuild database task (#900)
Browse files Browse the repository at this point in the history
* Improved tests for multi-user mode
* Adds task to rebuild database
* Updated subscriptions.js export syntax
* Subscription metadata is now backed up
* Added typing to task key
* Updated api models
* Tasks actions styling update
  • Loading branch information
Tzahi12345 authored May 24, 2023
1 parent 441131e commit c207e56
Show file tree
Hide file tree
Showing 26 changed files with 305 additions and 146 deletions.
21 changes: 16 additions & 5 deletions Public API v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1758,14 +1758,14 @@ components:
type: object
properties:
task_key:
type: string
$ref: '#/components/schemas/TaskType'
required:
- task_key
UpdateTaskScheduleRequest:
type: object
properties:
task_key:
type: string
$ref: '#/components/schemas/TaskType'
new_schedule:
$ref: '#/components/schemas/Schedule'
required:
Expand All @@ -1775,7 +1775,7 @@ components:
type: object
properties:
task_key:
type: string
$ref: '#/components/schemas/TaskType'
new_data:
type: object
required:
Expand All @@ -1785,7 +1785,7 @@ components:
type: object
properties:
task_key:
type: string
$ref: '#/components/schemas/TaskType'
new_options:
type: object
required:
Expand Down Expand Up @@ -2726,7 +2726,7 @@ components:
type: object
properties:
key:
type: string
$ref: '#/components/schemas/TaskType'
title:
type: string
last_ran:
Expand All @@ -2745,6 +2745,17 @@ components:
$ref: '#/components/schemas/Schedule'
options:
type: object
TaskType:
type: string
enum:
- backup_local_db
- missing_files_check
- missing_db_records
- duplicate_files_check
- youtubedl_update_check
- delete_old_files
- import_legacy_archives
- rebuild_database
Schedule:
required:
- type
Expand Down
45 changes: 30 additions & 15 deletions backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ async function loadConfig() {
if (allowSubscriptions) {
// set downloading to false
let subscriptions = await subscriptions_api.getAllSubscriptions();
subscriptions.forEach(async sub => subscriptions_api.writeSubscriptionMetadata(sub));
subscriptions_api.updateSubscriptionPropertyMultiple(subscriptions, {downloading: false});
// runs initially, then runs every ${subscriptionCheckInterval} seconds
const watchSubscriptionsInterval = function() {
Expand Down Expand Up @@ -1928,9 +1929,34 @@ app.post('/api/clearAllLogs', optionalJwt, async function(req, res) {

// user authentication

app.post('/api/auth/register'
, optionalJwt
, auth_api.registerUser);
app.post('/api/auth/register', optionalJwt, async (req, res) => {
const userid = req.body.userid;
const username = req.body.username;
const plaintextPassword = req.body.password;

if (userid !== 'admin' && !config_api.getConfigItem('ytdl_allow_registration') && !req.isAuthenticated() && (!req.user || !exports.userHasPermission(req.user.uid, 'settings'))) {
logger.error(`Registration failed for user ${userid}. Registration is disabled.`);
res.sendStatus(409);
return;
}

if (plaintextPassword === "") {
logger.error(`Registration failed for user ${userid}. A password must be provided.`);
res.sendStatus(409);
return;
}

const new_user = await auth_api.registerUser(userid, username, plaintextPassword);

if (!new_user) {
res.sendStatus(409);
return;
}

res.send({
user: new_user
});
});
app.post('/api/auth/login'
, auth_api.passport.authenticate(['local', 'ldapauth'], {})
, auth_api.generateJWT
Expand Down Expand Up @@ -1982,18 +2008,7 @@ app.post('/api/updateUser', optionalJwt, async (req, res) => {
app.post('/api/deleteUser', optionalJwt, async (req, res) => {
let uid = req.body.uid;
try {
let success = false;
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
const user_folder = path.join(__dirname, usersFileFolder, uid);
const user_db_obj = await db_api.getRecord('users', {uid: uid});
if (user_db_obj) {
// user exists, let's delete
await fs.remove(user_folder);
await db_api.removeRecord('users', {uid: uid});
success = true;
} else {
logger.error(`Could not find user with uid ${uid}`);
}
const success = await auth_api.deleteUser(uid);
res.send({success: success});
} catch (err) {
logger.error(err);
Expand Down
96 changes: 41 additions & 55 deletions backend/authentication/auth.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
const config_api = require('../config');
const consts = require('../consts');
const CONSTS = require('../consts');
const logger = require('../logger');
const db_api = require('../db');

const jwt = require('jsonwebtoken');
const { uuid } = require('uuidv4');
const bcrypt = require('bcryptjs');
const fs = require('fs-extra');
const path = require('path');

var LocalStrategy = require('passport-local').Strategy;
var LdapStrategy = require('passport-ldapauth');
Expand All @@ -16,7 +18,7 @@ var JwtStrategy = require('passport-jwt').Strategy,
let SERVER_SECRET = null;
let JWT_EXPIRATION = null;
let opts = null;
let saltRounds = null;
let saltRounds = 10;

exports.initialize = function () {
/*************************
Expand All @@ -31,8 +33,6 @@ exports.initialize = function () {
});
}

saltRounds = 10;

// Sometimes this value is not properly typed: https://github.com/Tzahi12345/YoutubeDL-Material/issues/813
JWT_EXPIRATION = config_api.getConfigItem('ytdl_jwt_expiration');
if (!(+JWT_EXPIRATION)) {
Expand Down Expand Up @@ -68,7 +68,7 @@ exports.initialize = function () {
const setupRoles = async () => {
const required_roles = {
admin: {
permissions: consts.AVAILABLE_PERMISSIONS
permissions: CONSTS.AVAILABLE_PERMISSIONS
},
user: {
permissions: [
Expand Down Expand Up @@ -106,55 +106,41 @@ exports.passport.deserializeUser(function(user, done) {
/***************************************
* Register user with hashed password
**************************************/
exports.registerUser = async function(req, res) {
var userid = req.body.userid;
var username = req.body.username;
var plaintextPassword = req.body.password;

if (userid !== 'admin' && !config_api.getConfigItem('ytdl_allow_registration') && !req.isAuthenticated() && (!req.user || !exports.userHasPermission(req.user.uid, 'settings'))) {
res.sendStatus(409);
logger.error(`Registration failed for user ${userid}. Registration is disabled.`);
return;
}

if (plaintextPassword === "") {
res.sendStatus(400);
logger.error(`Registration failed for user ${userid}. A password must be provided.`);
return;
exports.registerUser = async (userid, username, plaintextPassword) => {
const hash = await bcrypt.hash(plaintextPassword, saltRounds);
const new_user = generateUserObject(userid, username, hash);
// check if user exists
if (await db_api.getRecord('users', {uid: userid})) {
// user id is taken!
logger.error('Registration failed: UID is already taken!');
return null;
} else if (await db_api.getRecord('users', {name: username})) {
// user name is taken!
logger.error('Registration failed: User name is already taken!');
return null;
} else {
// add to db
await db_api.insertRecordIntoTable('users', new_user);
logger.verbose(`New user created: ${new_user.name}`);
return new_user;
}
}

bcrypt.hash(plaintextPassword, saltRounds)
.then(async function(hash) {
let new_user = generateUserObject(userid, username, hash);
// check if user exists
if (await db_api.getRecord('users', {uid: userid})) {
// user id is taken!
logger.error('Registration failed: UID is already taken!');
res.status(409).send('UID is already taken!');
} else if (await db_api.getRecord('users', {name: username})) {
// user name is taken!
logger.error('Registration failed: User name is already taken!');
res.status(409).send('User name is already taken!');
} else {
// add to db
await db_api.insertRecordIntoTable('users', new_user);
logger.verbose(`New user created: ${new_user.name}`);
res.send({
user: new_user
});
}
})
.then(function(result) {

})
.catch(function(err) {
logger.error(err);
if( err.code == 'ER_DUP_ENTRY' ) {
res.status(409).send('UserId already taken');
} else {
res.sendStatus(409);
}
});
exports.deleteUser = async (uid) => {
let success = false;
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
const user_folder = path.join(__dirname, usersFileFolder, uid);
const user_db_obj = await db_api.getRecord('users', {uid: uid});
if (user_db_obj) {
// user exists, let's delete
await fs.remove(user_folder);
await db_api.removeRecord('users', {uid: uid});
success = true;
} else {
logger.error(`Could not find user with uid ${uid}`);
}
return success;
}

/***************************************
Expand Down Expand Up @@ -235,7 +221,7 @@ exports.returnAuthResponse = async function(req, res) {
user: req.user,
token: req.token,
permissions: await exports.userPermissions(req.user.uid),
available_permissions: consts['AVAILABLE_PERMISSIONS']
available_permissions: CONSTS.AVAILABLE_PERMISSIONS
});
}

Expand Down Expand Up @@ -319,7 +305,7 @@ exports.getUserVideos = async function(user_uid, type) {
}

exports.getUserVideo = async function(user_uid, file_uid, requireSharing = false) {
let file = await db_api.getRecord('files', {file_uid: file_uid});
let file = await db_api.getRecord('files', {uid: file_uid});

// prevent unauthorized users from accessing the file info
if (file && !file['sharingEnabled'] && requireSharing) file = null;
Expand Down Expand Up @@ -406,8 +392,8 @@ exports.userPermissions = async function(user_uid) {
const role_obj = await db_api.getRecord('roles', {key: role});
const role_permissions = role_obj['permissions'];

for (let i = 0; i < consts['AVAILABLE_PERMISSIONS'].length; i++) {
let permission = consts['AVAILABLE_PERMISSIONS'][i];
for (let i = 0; i < CONSTS.AVAILABLE_PERMISSIONS.length; i++) {
let permission = CONSTS.AVAILABLE_PERMISSIONS[i];

const user_has_explicit_permission = user_obj['permissions'].includes(permission);
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
Expand Down
2 changes: 2 additions & 0 deletions backend/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ const YTDL_ARGS_WITH_VALUES = [
'--convert-subs'
];

exports.SUBSCRIPTION_BACKUP_PATH = 'subscription_backup.json'

// we're using a Set here for performance
exports.YTDL_ARGS_WITH_VALUES = new Set(YTDL_ARGS_WITH_VALUES);

Expand Down
Loading

0 comments on commit c207e56

Please sign in to comment.