Compare commits

..

1 Commits

Author SHA1 Message Date
Isaac Abadi
0c46b044da Improved tests for multi-user mode 2023-05-06 23:29:20 -04:00
7 changed files with 83 additions and 151 deletions

View File

@@ -1926,9 +1926,34 @@ app.post('/api/clearAllLogs', optionalJwt, async function(req, res) {
// user authentication // user authentication
app.post('/api/auth/register' app.post('/api/auth/register', optionalJwt, async (req, res) => {
, optionalJwt const userid = req.body.userid;
, auth_api.registerUser); 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' app.post('/api/auth/login'
, auth_api.passport.authenticate(['local', 'ldapauth'], {}) , auth_api.passport.authenticate(['local', 'ldapauth'], {})
, auth_api.generateJWT , auth_api.generateJWT
@@ -1980,18 +2005,7 @@ app.post('/api/updateUser', optionalJwt, async (req, res) => {
app.post('/api/deleteUser', optionalJwt, async (req, res) => { app.post('/api/deleteUser', optionalJwt, async (req, res) => {
let uid = req.body.uid; let uid = req.body.uid;
try { try {
let success = false; const success = await auth_api.deleteUser(uid);
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}`);
}
res.send({success: success}); res.send({success: success});
} catch (err) { } catch (err) {
logger.error(err); logger.error(err);

View File

@@ -6,6 +6,8 @@ const db_api = require('../db');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const { uuid } = require('uuidv4'); const { uuid } = require('uuidv4');
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');
const fs = require('fs-extra');
const path = require('path');
var LocalStrategy = require('passport-local').Strategy; var LocalStrategy = require('passport-local').Strategy;
var LdapStrategy = require('passport-ldapauth'); var LdapStrategy = require('passport-ldapauth');
@@ -16,7 +18,7 @@ var JwtStrategy = require('passport-jwt').Strategy,
let SERVER_SECRET = null; let SERVER_SECRET = null;
let JWT_EXPIRATION = null; let JWT_EXPIRATION = null;
let opts = null; let opts = null;
let saltRounds = null; let saltRounds = 10;
exports.initialize = function () { exports.initialize = function () {
/************************* /*************************
@@ -31,8 +33,6 @@ exports.initialize = function () {
}); });
} }
saltRounds = 10;
// Sometimes this value is not properly typed: https://github.com/Tzahi12345/YoutubeDL-Material/issues/813 // Sometimes this value is not properly typed: https://github.com/Tzahi12345/YoutubeDL-Material/issues/813
JWT_EXPIRATION = config_api.getConfigItem('ytdl_jwt_expiration'); JWT_EXPIRATION = config_api.getConfigItem('ytdl_jwt_expiration');
if (!(+JWT_EXPIRATION)) { if (!(+JWT_EXPIRATION)) {
@@ -113,55 +113,41 @@ exports.passport.deserializeUser(function(user, done) {
/*************************************** /***************************************
* Register user with hashed password * 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'))) { exports.registerUser = async (userid, username, plaintextPassword) => {
res.sendStatus(409); const hash = await bcrypt.hash(plaintextPassword, saltRounds);
logger.error(`Registration failed for user ${userid}. Registration is disabled.`); const new_user = generateUserObject(userid, username, hash);
return;
}
if (plaintextPassword === "") {
res.sendStatus(400);
logger.error(`Registration failed for user ${userid}. A password must be provided.`);
return;
}
bcrypt.hash(plaintextPassword, saltRounds)
.then(async function(hash) {
let new_user = generateUserObject(userid, username, hash);
// check if user exists // check if user exists
if (await db_api.getRecord('users', {uid: userid})) { if (await db_api.getRecord('users', {uid: userid})) {
// user id is taken! // user id is taken!
logger.error('Registration failed: UID is already taken!'); logger.error('Registration failed: UID is already taken!');
res.status(409).send('UID is already taken!'); return null;
} else if (await db_api.getRecord('users', {name: username})) { } else if (await db_api.getRecord('users', {name: username})) {
// user name is taken! // user name is taken!
logger.error('Registration failed: User name is already taken!'); logger.error('Registration failed: User name is already taken!');
res.status(409).send('User name is already taken!'); return null;
} else { } else {
// add to db // add to db
await db_api.insertRecordIntoTable('users', new_user); await db_api.insertRecordIntoTable('users', new_user);
logger.verbose(`New user created: ${new_user.name}`); logger.verbose(`New user created: ${new_user.name}`);
res.send({ return new_user;
user: new_user }
});
} }
})
.then(function(result) {
}) exports.deleteUser = async (uid) => {
.catch(function(err) { let success = false;
logger.error(err); let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
if( err.code == 'ER_DUP_ENTRY' ) { const user_folder = path.join(__dirname, usersFileFolder, uid);
res.status(409).send('UserId already taken'); 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 { } else {
res.sendStatus(409); logger.error(`Could not find user with uid ${uid}`);
} }
}); return success;
} }
/*************************************** /***************************************
@@ -326,7 +312,7 @@ exports.getUserVideos = async function(user_uid, type) {
} }
exports.getUserVideo = async function(user_uid, file_uid, requireSharing = false) { 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 // prevent unauthorized users from accessing the file info
if (file && !file['sharingEnabled'] && requireSharing) file = null; if (file && !file['sharingEnabled'] && requireSharing) file = null;

View File

@@ -220,8 +220,7 @@ const DEFAULT_CONFIG = {
"telegram_bot_token": "", "telegram_bot_token": "",
"telegram_chat_id": "", "telegram_chat_id": "",
"webhook_URL": "", "webhook_URL": "",
"discord_webhook_URL": "", "discord_webhook_URL": ""
"slack_webhook_URL": "",
}, },
"Themes": { "Themes": {
"default_theme": "default", "default_theme": "default",

View File

@@ -162,10 +162,6 @@ exports.CONFIG_ITEMS = {
'key': 'ytdl_discord_webhook_url', 'key': 'ytdl_discord_webhook_url',
'path': 'YoutubeDLMaterial.API.discord_webhook_URL' 'path': 'YoutubeDLMaterial.API.discord_webhook_URL'
}, },
'ytdl_slack_webhook_url': {
'key': 'ytdl_slack_webhook_url',
'path': 'YoutubeDLMaterial.API.slack_webhook_URL'
},
// Themes // Themes

View File

@@ -64,9 +64,6 @@ exports.sendNotification = async (notification) => {
if (config_api.getConfigItem('ytdl_discord_webhook_url')) { if (config_api.getConfigItem('ytdl_discord_webhook_url')) {
sendDiscordNotification(data); sendDiscordNotification(data);
} }
if (config_api.getConfigItem('ytdl_slack_webhook_url')) {
sendSlackNotification(data);
}
await db_api.insertRecordIntoTable('notifications', notification); await db_api.insertRecordIntoTable('notifications', notification);
return notification; return notification;
@@ -177,65 +174,6 @@ async function sendDiscordNotification({body, title, type, url, thumbnail}) {
return result; return result;
} }
function sendSlackNotification({body, title, type, url, thumbnail}) {
const slack_webhook_url = config_api.getConfigItem('ytdl_slack_webhook_url');
logger.verbose(`Sending slack notification to ${slack_webhook_url}`);
const data = {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `*${title}*`
}
},
{
type: "section",
text: {
type: "plain_text",
text: body
}
}
]
}
// add thumbnail if exists
if (thumbnail) {
data['blocks'].push({
type: "image",
image_url: thumbnail,
alt_text: "notification_thumbnail"
});
}
data['blocks'].push(
{
type: "section",
text: {
type: "mrkdwn",
text: `<${url}|${url}>`
}
},
{
type: "context",
elements: [
{
type: "mrkdwn",
text: `*ID:* ${type}`
}
]
}
);
fetch(slack_webhook_url, {
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data),
});
}
function sendGenericNotification(data) { function sendGenericNotification(data) {
const webhook_url = config_api.getConfigItem('ytdl_webhook_url'); const webhook_url = config_api.getConfigItem('ytdl_webhook_url');
logger.verbose(`Sending generic notification to ${webhook_url}`); logger.verbose(`Sending generic notification to ${webhook_url}`);

View File

@@ -336,16 +336,22 @@ describe('Database', async function() {
}); });
describe('Multi User', async function() { describe('Multi User', async function() {
let user = null; const user_to_test = 'test_user';
const user_to_test = 'admin'; const user_password = 'test_pass';
const sub_to_test = 'dc834388-3454-41bf-a618-e11cb8c7de1c'; const sub_to_test = '';
const playlist_to_test = 'ysabVZz4x'; const playlist_to_test = '';
beforeEach(async function() { beforeEach(async function() {
await db_api.connectToDB(); await db_api.connectToDB();
user = await auth_api.login('admin', 'pass'); await auth_api.deleteUser(user_to_test);
}); });
describe('Authentication', function() { describe('Basic', function() {
it('login', async function() { it('Register', async function() {
const user = await auth_api.registerUser(user_to_test, user_to_test, user_password);
assert(user);
});
it('Login', async function() {
await auth_api.registerUser(user_to_test, user_to_test, user_password);
const user = await auth_api.login(user_to_test, user_password);
assert(user); assert(user);
}); });
}); });
@@ -361,14 +367,14 @@ describe('Multi User', async function() {
}); });
it('Video access - disallowed', async function() { it('Video access - disallowed', async function() {
await db_api.setVideoProperty(video_to_test, {sharingEnabled: false}, user_to_test); await db_api.setVideoProperty(video_to_test, {sharingEnabled: false});
const video_obj = auth_api.getUserVideo('admin', video_to_test, true); const video_obj = auth_api.getUserVideo(user_to_test, video_to_test, true);
assert(!video_obj); assert(!video_obj);
}); });
it('Video access - allowed', async function() { it('Video access - allowed', async function() {
await db_api.setVideoProperty(video_to_test, {sharingEnabled: true}, user_to_test); await db_api.setVideoProperty(video_to_test, {sharingEnabled: true}, user_to_test);
const video_obj = auth_api.getUserVideo('admin', video_to_test, true); const video_obj = auth_api.getUserVideo(user_to_test, video_to_test, true);
assert(video_obj); assert(video_obj);
}); });
}); });

View File

@@ -387,16 +387,9 @@
</div> </div>
<div class="col-12 mb-2 mt-3"> <div class="col-12 mb-2 mt-3">
<mat-form-field class="text-field" color="accent"> <mat-form-field class="text-field" color="accent">
<mat-label i18n="Discord Webhook URL">Discord Webhook URL</mat-label> <mat-label i18n="Discord webhook URL">Discord Webhook URL</mat-label>
<input placeholder="https://discord.com/api/webhooks/<webhook_id>/<webhook_token>" [(ngModel)]="new_config['API']['discord_webhook_URL']" matInput> <input placeholder="https://discord.com/api/webhooks/<webhook_id>/<webhook_token>" [(ngModel)]="new_config['API']['discord_webhook_URL']" matInput>
<mat-hint><a target="_blank" href="https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks"><ng-container i18n="Discord API setting hint">See docs here.</ng-container></a></mat-hint> <mat-hint><a target="_blank" href="https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks"><ng-container i18n="Gotify API setting hint">See docs here.</ng-container></a></mat-hint>
</mat-form-field>
</div>
<div class="col-12 mb-2 mt-3">
<mat-form-field class="text-field" color="accent">
<mat-label i18n="Slack Webhook URL">Slack Webhook URL</mat-label>
<input placeholder="https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" [(ngModel)]="new_config['API']['slack_webhook_URL']" matInput>
<mat-hint><a target="_blank" href="https://api.slack.com/messaging/webhooks"><ng-container i18n="Slack API setting hint">See docs here.</ng-container></a></mat-hint>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="col-12 mt-3"> <div class="col-12 mt-3">