diff --git a/backend/app.js b/backend/app.js index 0dc80bf..87c7b17 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1762,8 +1762,14 @@ const optionalJwt = function (req, res, next) { const uuid = using_body ? req.body.uuid : req.query.uuid; const uid = using_body ? req.body.uid : req.query.uid; const type = using_body ? req.body.type : req.query.type; - const is_shared = auth_api.getUserVideo(uuid, uid, type, true); - if (is_shared) return next(); + const is_shared = !req.query.id ? auth_api.getUserVideo(uuid, uid, type, true) : auth_api.getUserPlaylist(uuid, req.query.id, null, true); + if (is_shared) { + req.can_watch = true; + return next(); + } else { + res.sendStatus(401); + return; + } } else if (multiUserMode && !(req.path.includes('/api/auth/register') && !req.query.jwt)) { // registration should get passed through if (!req.query.jwt) { res.sendStatus(401); @@ -1983,7 +1989,6 @@ app.post('/api/enableSharing', optionalJwt, function(req, res) { if (req.isAuthenticated()) { // if multi user mode, use this method instead success = auth_api.changeSharingMode(req.user.uid, uid, type, is_playlist, true); - console.log(success); res.send({success: success}); return; } @@ -2262,11 +2267,12 @@ app.post('/api/createPlaylist', optionalJwt, async (req, res) => { app.post('/api/getPlaylist', optionalJwt, async (req, res) => { let playlistID = req.body.playlistID; let type = req.body.type; + let uuid = req.body.uuid; let playlist = null; if (req.isAuthenticated()) { - playlist = auth_api.getUserPlaylist(req.user.uid, playlistID, type); + playlist = auth_api.getUserPlaylist(uuid ? uuid : req.user.uid, playlistID, type); type = playlist.type; } else { if (!type) { @@ -2563,13 +2569,13 @@ app.get('/api/video/:id', optionalJwt, function(req , res){ let optionalParams = url_api.parse(req.url,true).query; let id = decodeURIComponent(req.params.id); let file_path = videoFolderPath + id + '.mp4'; - if (req.isAuthenticated()) { + if (req.isAuthenticated() || req.can_watch) { let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path'); if (optionalParams['subName']) { const isPlaylist = optionalParams['subPlaylist']; file_path = path.join(usersFileFolder, req.user.uid, 'subscriptions', (isPlaylist === 'true' ? 'playlists/' : 'channels/'),optionalParams['subName'], id + '.mp4') } else { - file_path = path.join(usersFileFolder, req.user.uid, 'video', id + '.mp4'); + file_path = path.join(usersFileFolder, req.query.uuid ? req.query.uuid : req.user.uid, 'video', id + '.mp4'); } } else if (optionalParams['subName']) { let basePath = config_api.getConfigItem('ytdl_subscriptions_base_path'); diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js index aabb978..5098738 100644 --- a/backend/authentication/auth.js +++ b/backend/authentication/auth.js @@ -157,7 +157,7 @@ exports.passport.use(new LocalStrategy({ passwordField: 'password'}, function(username, password, done) { const user = users_db.get('users').find({name: username}).value(); - if (!user) { console.log('user not found'); return done(null, false); } + if (!user) { logger.error(`User ${username} not found`); return done(null, false); } if (user) { return done(null, bcrypt.compareSync(password, user.passhash) ? user : false); } @@ -346,7 +346,7 @@ exports.getUserPlaylists = function(user_uid, type) { return user['playlists'][type]; } -exports.getUserPlaylist = function(user_uid, playlistID, type) { +exports.getUserPlaylist = function(user_uid, playlistID, type, requireSharing = false) { let playlist = null; if (!type) { playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.audio`).find({id: playlistID}).value(); @@ -358,6 +358,10 @@ exports.getUserPlaylist = function(user_uid, playlistID, type) { } } if (!playlist) playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID}).value(); + + // prevent unauthorized users from accessing the file info + if (requireSharing && !playlist['sharingEnabled']) playlist = null; + return playlist; } @@ -434,7 +438,7 @@ exports.deleteUserFile = function(user_uid, file_uid, type, blacklistMode = fals success = true; } else { success = false; - console.log('file does not exist!'); + logger.warn(`User file ${file_uid} does not exist!`); } return success; @@ -444,7 +448,7 @@ exports.changeSharingMode = function(user_uid, file_uid, type, is_playlist, enab let success = false; const user_db_obj = users_db.get('users').find({uid: user_uid}); if (user_db_obj.value()) { - const file_db_obj = is_playlist ? user_db_obj.get(`playlists.${type}`).find({uid: file_uid}) : user_db_obj.get(`files.${type}`).find({uid: file_uid}); + const file_db_obj = is_playlist ? user_db_obj.get(`playlists.${type}`).find({id: file_uid}) : user_db_obj.get(`files.${type}`).find({uid: file_uid}); if (file_db_obj.value()) { success = true; file_db_obj.assign({sharingEnabled: enabled}).write(); diff --git a/backend/subscriptions.js b/backend/subscriptions.js index cccd224..91cc7a6 100644 --- a/backend/subscriptions.js +++ b/backend/subscriptions.js @@ -49,7 +49,7 @@ async function subscribe(sub, user_uid = null) { else db.get('subscriptions').push(sub).write(); - let success = await getSubscriptionInfo(sub); + let success = await getSubscriptionInfo(sub, user_uid); result_obj.success = success; result_obj.sub = sub; getVideosForSub(sub, user_uid); diff --git a/src/app/dialogs/share-media-dialog/share-media-dialog.component.html b/src/app/dialogs/share-media-dialog/share-media-dialog.component.html index 02dff40..1609af0 100644 --- a/src/app/dialogs/share-media-dialog/share-media-dialog.component.html +++ b/src/app/dialogs/share-media-dialog/share-media-dialog.component.html @@ -9,6 +9,12 @@
Enable sharing
+
+ Use timestamp + + + +
diff --git a/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts b/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts index d6771f5..137e8fc 100644 --- a/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts +++ b/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts @@ -15,8 +15,11 @@ export class ShareMediaDialogComponent implements OnInit { uid = null; uuid = null; share_url = null; + default_share_url = null; sharing_enabled = null; is_playlist = null; + current_timestamp = null + timestamp_enabled = false; constructor(@Inject(MAT_DIALOG_DATA) public data: any, public router: Router, private snackBar: MatSnackBar, private postsService: PostsService) { } @@ -28,15 +31,34 @@ export class ShareMediaDialogComponent implements OnInit { this.uuid = this.data.uuid; this.sharing_enabled = this.data.sharing_enabled; this.is_playlist = this.data.is_playlist; + this.current_timestamp = (this.data.current_timestamp / 1000).toFixed(2); const arg = (this.is_playlist ? ';id=' : ';uid='); - this.share_url = window.location.href.split(';')[0] + arg + this.uid; + this.default_share_url = window.location.href.split(';')[0] + arg + this.uid; if (this.uuid) { - this.share_url += ';uuid=' + this.uuid; + this.default_share_url += ';uuid=' + this.uuid; } + this.share_url = this.default_share_url; } } + timestampInputChanged(change) { + if (!this.timestamp_enabled) { + this.share_url = this.default_share_url; + return; + } + const new_val = change.target.value; + if (new_val > 0) { + this.share_url = this.default_share_url + ';timestamp=' + new_val; + } else { + this.share_url = this.default_share_url; + } + } + + useTimestampChanged() { + this.timestampInputChanged({target: {value: this.current_timestamp}}) + } + copiedToClipboard() { this.openSnackBar('Copied to clipboard!'); } diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index c7b20ec..30b79a8 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -491,7 +491,7 @@ export class MainComponent implements OnInit { if (is_playlist) { this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'audio'}]); } else { - this.router.navigate(['/player', {fileNames: name, type: 'audio', uid: uid}]); + this.router.navigate(['/player', {type: 'audio', uid: uid}]); } } } @@ -528,7 +528,7 @@ export class MainComponent implements OnInit { if (is_playlist) { this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'video'}]); } else { - this.router.navigate(['/player', {fileNames: name, type: 'video', uid: uid}]); + this.router.navigate(['/player', {type: 'video', uid: uid}]); } } } diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts index 2cad9c6..9b0c316 100644 --- a/src/app/player/player.component.ts +++ b/src/app/player/player.component.ts @@ -40,6 +40,7 @@ export class PlayerComponent implements OnInit { subscriptionName = null; subPlaylist = null; uuid = null; // used for sharing in multi-user mode, uuid is the user that downloaded the video + timestamp = null; is_shared = false; @@ -77,6 +78,7 @@ export class PlayerComponent implements OnInit { this.url = this.route.snapshot.paramMap.get('url'); this.name = this.route.snapshot.paramMap.get('name'); this.uuid = this.route.snapshot.paramMap.get('uuid'); + this.timestamp = this.route.snapshot.paramMap.get('timestamp'); // loading config if (this.postsService.initialized) { @@ -102,7 +104,7 @@ export class PlayerComponent implements OnInit { this.subscriptionFolderPath = this.postsService.config['Subscriptions']['subscriptions_base_path']; this.fileNames = this.route.snapshot.paramMap.get('fileNames') ? this.route.snapshot.paramMap.get('fileNames').split('|nvr|') : null; - if (!this.fileNames) { + if (!this.fileNames && !this.type) { this.is_shared = true; } @@ -149,7 +151,7 @@ export class PlayerComponent implements OnInit { if (!already_has_filenames) { this.parseFileNames(); } } } - if (this.db_file['sharingEnabled']) { + if (this.db_file['sharingEnabled'] || !this.uuid) { this.show_player = true; } else if (!already_has_filenames) { this.openSnackBar('Error: Sharing has been disabled for this video!', 'Dismiss'); @@ -158,12 +160,18 @@ export class PlayerComponent implements OnInit { } getPlaylistFiles() { - this.postsService.getPlaylist(this.id, null).subscribe(res => { - this.db_playlist = res['playlist']; - this.fileNames = this.db_playlist['fileNames']; - this.type = res['type']; - this.show_player = true; - this.parseFileNames(); + this.postsService.getPlaylist(this.id, null, this.uuid).subscribe(res => { + if (res['playlist']) { + this.db_playlist = res['playlist']; + this.fileNames = this.db_playlist['fileNames']; + this.type = res['type']; + this.show_player = true; + this.parseFileNames(); + } else { + this.openSnackBar('Failed to load playlist!', ''); + } + }, err => { + this.openSnackBar('Failed to load playlist!', ''); }); } @@ -196,11 +204,15 @@ export class PlayerComponent implements OnInit { } // adds user token if in multi-user-mode + const uuid_str = this.uuid ? `&uuid=${this.uuid}` : ''; + const uid_str = (this.id || !this.db_file) ? '' : `&uid=${this.db_file.uid}`; + const type_str = (this.id || !this.db_file) ? '' : `&type=${this.db_file.type}` + const id_str = this.id ? `&id=${this.id}` : ''; if (this.postsService.isLoggedIn) { fullLocation += (this.subscriptionName ? '&' : '?') + `jwt=${this.postsService.token}`; - if (this.is_shared) { fullLocation += `&uuid=${this.uuid}&uid=${this.db_file.uid}&type=${this.db_file.type}`; } + if (this.is_shared) { fullLocation += `${uuid_str}${uid_str}${type_str}${id_str}`; } } else if (this.is_shared) { - fullLocation += (this.subscriptionName ? '&' : '?') + `uuid=${this.uuid}&uid=${this.db_file.uid}&type=${this.db_file.type}`; + fullLocation += (this.subscriptionName ? '&' : '?') + `test=test${uuid_str}${uid_str}${type_str}${id_str}`; } // if it has a slash (meaning it's in a directory), only get the file name for the label let label = null; @@ -228,6 +240,10 @@ export class PlayerComponent implements OnInit { this.api.getDefaultMedia().subscriptions.loadedMetadata.subscribe(this.playVideo.bind(this)); this.api.getDefaultMedia().subscriptions.ended.subscribe(this.nextVideo.bind(this)); + + if (this.timestamp) { + this.api.seekTime(+this.timestamp); + } } nextVideo() { @@ -381,7 +397,8 @@ export class PlayerComponent implements OnInit { type: this.type, sharing_enabled: this.id ? this.db_playlist.sharingEnabled : this.db_file.sharingEnabled, is_playlist: !!this.id, - uuid: this.postsService.isLoggedIn ? this.postsService.user.uid : null + uuid: this.postsService.isLoggedIn ? this.postsService.user.uid : null, + current_timestamp: this.api.time.current }, width: '60vw' }); diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 84c8edc..b793345 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -258,9 +258,9 @@ export class PostsService implements CanActivate { thumbnailURL: thumbnailURL}, this.httpOptions); } - getPlaylist(playlistID, type) { + getPlaylist(playlistID, type, uuid = null) { return this.http.post(this.path + 'getPlaylist', {playlistID: playlistID, - type: type}, this.httpOptions); + type: type, uuid: uuid}, this.httpOptions); } updatePlaylist(playlistID, fileNames, type) { @@ -335,6 +335,7 @@ export class PostsService implements CanActivate { this.permissions = permissions; this.available_permissions = available_permissions; this.token = token; + this.setInitialized(); localStorage.setItem('jwt_token', this.token); @@ -365,7 +366,6 @@ export class PostsService implements CanActivate { call.subscribe(res => { if (res['token']) { this.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']); - this.setInitialized(); } }, err => { if (err.status === 401) {