Add ability to get user from file token
This commit is contained in:
parent
93293ca788
commit
868314e8bf
|
@ -22,7 +22,7 @@ export {
|
||||||
function generateToken (req: express.Request, res: express.Response) {
|
function generateToken (req: express.Request, res: express.Response) {
|
||||||
const video = res.locals.onlyVideo
|
const video = res.locals.onlyVideo
|
||||||
|
|
||||||
const { token, expires } = VideoTokensManager.Instance.create(video.uuid)
|
const { token, expires } = VideoTokensManager.Instance.create({ videoUUID: video.uuid, user: res.locals.oauth.token.User })
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
files: {
|
files: {
|
||||||
|
|
|
@ -245,7 +245,7 @@ function buildUserHelpers () {
|
||||||
},
|
},
|
||||||
|
|
||||||
getAuthUser: (res: express.Response) => {
|
getAuthUser: (res: express.Response) => {
|
||||||
const user = res.locals.oauth?.token?.User
|
const user = res.locals.oauth?.token?.User || res.locals.videoFileToken?.user
|
||||||
if (!user) return undefined
|
if (!user) return undefined
|
||||||
|
|
||||||
return UserModel.loadByIdFull(user.id)
|
return UserModel.loadByIdFull(user.id)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import LRUCache from 'lru-cache'
|
import LRUCache from 'lru-cache'
|
||||||
import { LRU_CACHE } from '@server/initializers/constants'
|
import { LRU_CACHE } from '@server/initializers/constants'
|
||||||
|
import { MUserAccountUrl } from '@server/types/models'
|
||||||
|
import { pick } from '@shared/core-utils'
|
||||||
import { buildUUID } from '@shared/extra-utils'
|
import { buildUUID } from '@shared/extra-utils'
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -10,19 +12,22 @@ class VideoTokensManager {
|
||||||
|
|
||||||
private static instance: VideoTokensManager
|
private static instance: VideoTokensManager
|
||||||
|
|
||||||
private readonly lruCache = new LRUCache<string, string>({
|
private readonly lruCache = new LRUCache<string, { videoUUID: string, user: MUserAccountUrl }>({
|
||||||
max: LRU_CACHE.VIDEO_TOKENS.MAX_SIZE,
|
max: LRU_CACHE.VIDEO_TOKENS.MAX_SIZE,
|
||||||
ttl: LRU_CACHE.VIDEO_TOKENS.TTL
|
ttl: LRU_CACHE.VIDEO_TOKENS.TTL
|
||||||
})
|
})
|
||||||
|
|
||||||
private constructor () {}
|
private constructor () {}
|
||||||
|
|
||||||
create (videoUUID: string) {
|
create (options: {
|
||||||
|
user: MUserAccountUrl
|
||||||
|
videoUUID: string
|
||||||
|
}) {
|
||||||
const token = buildUUID()
|
const token = buildUUID()
|
||||||
|
|
||||||
const expires = new Date(new Date().getTime() + LRU_CACHE.VIDEO_TOKENS.TTL)
|
const expires = new Date(new Date().getTime() + LRU_CACHE.VIDEO_TOKENS.TTL)
|
||||||
|
|
||||||
this.lruCache.set(token, videoUUID)
|
this.lruCache.set(token, pick(options, [ 'user', 'videoUUID' ]))
|
||||||
|
|
||||||
return { token, expires }
|
return { token, expires }
|
||||||
}
|
}
|
||||||
|
@ -34,7 +39,16 @@ class VideoTokensManager {
|
||||||
const value = this.lruCache.get(options.token)
|
const value = this.lruCache.get(options.token)
|
||||||
if (!value) return false
|
if (!value) return false
|
||||||
|
|
||||||
return value === options.videoUUID
|
return value.videoUUID === options.videoUUID
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserFromToken (options: {
|
||||||
|
token: string
|
||||||
|
}) {
|
||||||
|
const value = this.lruCache.get(options.token)
|
||||||
|
if (!value) return undefined
|
||||||
|
|
||||||
|
return value.user
|
||||||
}
|
}
|
||||||
|
|
||||||
static get Instance () {
|
static get Instance () {
|
||||||
|
|
|
@ -180,18 +180,16 @@ async function checkCanAccessVideoStaticFiles (options: {
|
||||||
return checkCanSeeVideo(options)
|
return checkCanSeeVideo(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!video.hasPrivateStaticPath()) return true
|
|
||||||
|
|
||||||
const videoFileToken = req.query.videoFileToken
|
const videoFileToken = req.query.videoFileToken
|
||||||
if (!videoFileToken) {
|
if (videoFileToken && VideoTokensManager.Instance.hasToken({ token: videoFileToken, videoUUID: video.uuid })) {
|
||||||
res.sendStatus(HttpStatusCode.FORBIDDEN_403)
|
const user = VideoTokensManager.Instance.getUserFromToken({ token: videoFileToken })
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (VideoTokensManager.Instance.hasToken({ token: videoFileToken, videoUUID: video.uuid })) {
|
res.locals.videoFileToken = { user }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!video.hasPrivateStaticPath()) return true
|
||||||
|
|
||||||
res.sendStatus(HttpStatusCode.FORBIDDEN_403)
|
res.sendStatus(HttpStatusCode.FORBIDDEN_403)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,7 +250,10 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
|
||||||
|
|
||||||
registerHook({
|
registerHook({
|
||||||
target: 'filter:api.download.video.allowed.result',
|
target: 'filter:api.download.video.allowed.result',
|
||||||
handler: (result, params) => {
|
handler: async (result, params) => {
|
||||||
|
const loggedInUser = await peertubeHelpers.user.getAuthUser(params.res)
|
||||||
|
if (loggedInUser) return { allowed: true }
|
||||||
|
|
||||||
if (params && !params.streamingPlaylist && params.video.name.includes('bad file')) {
|
if (params && !params.streamingPlaylist && params.video.name.includes('bad file')) {
|
||||||
return { allowed: false, errorMessage: 'Cao Cao' }
|
return { allowed: false, errorMessage: 'Cao Cao' }
|
||||||
}
|
}
|
||||||
|
|
|
@ -430,6 +430,7 @@ describe('Test plugin filter hooks', function () {
|
||||||
|
|
||||||
describe('Download hooks', function () {
|
describe('Download hooks', function () {
|
||||||
const downloadVideos: VideoDetails[] = []
|
const downloadVideos: VideoDetails[] = []
|
||||||
|
let downloadVideo2Token: string
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(120000)
|
this.timeout(120000)
|
||||||
|
@ -459,6 +460,8 @@ describe('Test plugin filter hooks', function () {
|
||||||
for (const uuid of uuids) {
|
for (const uuid of uuids) {
|
||||||
downloadVideos.push(await servers[0].videos.get({ id: uuid }))
|
downloadVideos.push(await servers[0].videos.get({ id: uuid }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadVideo2Token = await servers[0].videoToken.getVideoFileToken({ videoId: downloadVideos[2].uuid })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should run filter:api.download.torrent.allowed.result', async function () {
|
it('Should run filter:api.download.torrent.allowed.result', async function () {
|
||||||
|
@ -471,32 +474,42 @@ describe('Test plugin filter hooks', function () {
|
||||||
|
|
||||||
it('Should run filter:api.download.video.allowed.result', async function () {
|
it('Should run filter:api.download.video.allowed.result', async function () {
|
||||||
{
|
{
|
||||||
const res = await makeRawRequest({ url: downloadVideos[1].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
|
const refused = downloadVideos[1].files[0].fileDownloadUrl
|
||||||
|
const allowed = [
|
||||||
|
downloadVideos[0].files[0].fileDownloadUrl,
|
||||||
|
downloadVideos[2].files[0].fileDownloadUrl
|
||||||
|
]
|
||||||
|
|
||||||
|
const res = await makeRawRequest({ url: refused, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
|
||||||
expect(res.body.error).to.equal('Cao Cao')
|
expect(res.body.error).to.equal('Cao Cao')
|
||||||
|
|
||||||
await makeRawRequest({ url: downloadVideos[0].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.OK_200 })
|
for (const url of allowed) {
|
||||||
await makeRawRequest({ url: downloadVideos[2].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.OK_200 })
|
await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const res = await makeRawRequest({
|
const refused = downloadVideos[2].streamingPlaylists[0].files[0].fileDownloadUrl
|
||||||
url: downloadVideos[2].streamingPlaylists[0].files[0].fileDownloadUrl,
|
|
||||||
expectedStatus: HttpStatusCode.FORBIDDEN_403
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const allowed = [
|
||||||
|
downloadVideos[2].files[0].fileDownloadUrl,
|
||||||
|
downloadVideos[0].streamingPlaylists[0].files[0].fileDownloadUrl,
|
||||||
|
downloadVideos[1].streamingPlaylists[0].files[0].fileDownloadUrl
|
||||||
|
]
|
||||||
|
|
||||||
|
// Only streaming playlist is refuse
|
||||||
|
const res = await makeRawRequest({ url: refused, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
|
||||||
expect(res.body.error).to.equal('Sun Jian')
|
expect(res.body.error).to.equal('Sun Jian')
|
||||||
|
|
||||||
await makeRawRequest({ url: downloadVideos[2].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.OK_200 })
|
// But not we there is a user in res
|
||||||
|
await makeRawRequest({ url: refused, token: servers[0].accessToken, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
await makeRawRequest({ url: refused, query: { videoFileToken: downloadVideo2Token }, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
|
||||||
await makeRawRequest({
|
// Other files work
|
||||||
url: downloadVideos[0].streamingPlaylists[0].files[0].fileDownloadUrl,
|
for (const url of allowed) {
|
||||||
expectedStatus: HttpStatusCode.OK_200
|
await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
})
|
}
|
||||||
|
|
||||||
await makeRawRequest({
|
|
||||||
url: downloadVideos[1].streamingPlaylists[0].files[0].fileDownloadUrl,
|
|
||||||
expectedStatus: HttpStatusCode.OK_200
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
MChannelBannerAccountDefault,
|
MChannelBannerAccountDefault,
|
||||||
MChannelSyncChannel,
|
MChannelSyncChannel,
|
||||||
MStreamingPlaylist,
|
MStreamingPlaylist,
|
||||||
|
MUserAccountUrl,
|
||||||
MVideoChangeOwnershipFull,
|
MVideoChangeOwnershipFull,
|
||||||
MVideoFile,
|
MVideoFile,
|
||||||
MVideoFormattableDetails,
|
MVideoFormattableDetails,
|
||||||
|
@ -187,6 +188,10 @@ declare module 'express' {
|
||||||
actor: MActorAccountChannelId
|
actor: MActorAccountChannelId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
videoFileToken?: {
|
||||||
|
user: MUserAccountUrl
|
||||||
|
}
|
||||||
|
|
||||||
authenticated?: boolean
|
authenticated?: boolean
|
||||||
|
|
||||||
registeredPlugin?: RegisteredPlugin
|
registeredPlugin?: RegisteredPlugin
|
||||||
|
|
Loading…
Reference in New Issue