refactor scoped token service

This commit is contained in:
Rigel Kent 2020-11-09 16:25:27 +01:00 committed by Chocobozzz
parent afff310e50
commit 5beb89f223
26 changed files with 223 additions and 176 deletions

View File

@ -3,7 +3,7 @@ import { AfterViewChecked, Component, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
import { Notifier } from '@app/core'
import { copyToClipboard } from '../../../assets/player/utils'
import { copyToClipboard } from '../../../root-helpers/utils'
import { InstanceService } from '@app/shared/shared-instance'
import { ServerConfig } from '@shared/models'
import { ResolverData } from './about-instance.resolver'

View File

@ -161,7 +161,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
getVideoEmbed (entry: VideoBlacklist) {
return buildVideoOrPlaylistEmbed(
buildVideoLink({
baseUrl: `${environment.embedUrl}/videos/embed/${entry.video.uuid}`,
baseUrl: `${environment.originServerUrl}/videos/embed/${entry.video.uuid}`,
title: false,
warningTitle: false
})

View File

@ -7,8 +7,8 @@
<div class="form-group col-12 col-lg-4 col-xl-3">
<h2 i18n class="applications-title">SUBSCRIPTION FEED</h2>
<div i18n class="applications-description">
Used to retrieve the list of videos of the creators
you subscribed to from outside PeerTube
Use third-party feed aggregators to retrieve the list of videos from
channels you subscribed to. Make sure to keep your token private.
</div>
</div>

View File

@ -1,10 +1,10 @@
import { Component, OnInit } from '@angular/core'
import { AuthService, Notifier, ConfirmService } from '@app/core'
import { AuthService, Notifier, ConfirmService, ScopedTokensService } from '@app/core'
import { VideoService } from '@app/shared/shared-main'
import { FeedFormat } from '@shared/models'
import { Subject, merge } from 'rxjs'
import { debounceTime } from 'rxjs/operators'
import { ScopedToken } from '@shared/models/users/user-scoped-token'
import { environment } from '../../../environments/environment'
@Component({
selector: 'my-account-applications',
@ -15,11 +15,11 @@ export class MyAccountApplicationsComponent implements OnInit {
feedUrl: string
feedToken: string
private baseURL = window.location.protocol + '//' + window.location.host
private tokenStream = new Subject()
private baseURL = environment.originServerUrl
constructor (
private authService: AuthService,
private scopedTokensService: ScopedTokensService,
private videoService: VideoService,
private notifier: Notifier,
private confirmService: ConfirmService
@ -27,31 +27,40 @@ export class MyAccountApplicationsComponent implements OnInit {
ngOnInit () {
this.feedUrl = this.baseURL
this.scopedTokensService.getScopedTokens()
.subscribe(
tokens => this.regenApplications(tokens),
merge(
this.tokenStream,
this.authService.userInformationLoaded
).pipe(debounceTime(400))
.subscribe(
_ => {
const user = this.authService.getUser()
this.videoService.getVideoSubscriptionFeedUrls(user.account.id)
.then(feeds => this.feedUrl = this.baseURL + feeds.find(f => f.format === FeedFormat.RSS).url)
.then(_ => this.authService.getScopedTokens().then(tokens => this.feedToken = tokens.feedToken))
},
err => {
this.notifier.error(err.message)
}
)
err => {
this.notifier.error(err.message)
}
)
}
async renewToken () {
const res = await this.confirmService.confirm('Renewing the token will disallow previously configured clients from retrieving the feed until they use the new token. Proceed?', 'Renew token')
const res = await this.confirmService.confirm(
$localize`Renewing the token will disallow previously configured clients from retrieving the feed until they use the new token. Proceed?`,
$localize`Renew token`
)
if (res === false) return
await this.authService.renewScopedTokens()
this.notifier.success('Token renewed. Update your client configuration accordingly.')
this.tokenStream.next()
this.scopedTokensService.renewScopedTokens().subscribe(
tokens => {
this.regenApplications(tokens)
this.notifier.success($localize`Token renewed. Update your client configuration accordingly.`)
},
err => {
this.notifier.error(err.message)
}
)
}
private regenApplications (tokens: ScopedToken) {
const user = this.authService.getUser()
const feeds = this.videoService.getVideoSubscriptionFeedUrls(user.account.id, tokens.feedToken)
this.feedUrl = this.baseURL + feeds.find(f => f.format === FeedFormat.RSS).url
this.feedToken = tokens.feedToken
}
}

View File

@ -41,11 +41,6 @@ export class MyAccountComponent implements OnInit {
label: $localize`Abuse reports`,
routerLink: '/my-account/abuses',
iconName: 'flag'
},
{
label: $localize`Applications`,
routerLink: '/my-account/applications',
iconName: 'codesandbox'
}
]
}
@ -61,6 +56,11 @@ export class MyAccountComponent implements OnInit {
routerLink: '/my-account/notifications'
},
{
label: $localize`Applications`,
routerLink: '/my-account/applications'
},
moderationEntries
]
}

View File

@ -20,8 +20,8 @@ import { MyAccountDangerZoneComponent } from './my-account-settings/my-account-d
import { MyAccountNotificationPreferencesComponent } from './my-account-settings/my-account-notification-preferences'
import { MyAccountProfileComponent } from './my-account-settings/my-account-profile/my-account-profile.component'
import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
import { MyAccountApplicationsComponent } from './my-account-applications/my-account-applications.component'
import { MyAccountComponent } from './my-account.component'
import { VideoChangeOwnershipComponent } from './my-account-applications/my-account-applications.component'
@NgModule({
imports: [
@ -46,13 +46,13 @@ import { VideoChangeOwnershipComponent } from './my-account-applications/my-acco
MyAccountChangePasswordComponent,
MyAccountProfileComponent,
MyAccountChangeEmailComponent,
MyAccountApplicationsComponent,
MyAccountDangerZoneComponent,
MyAccountBlocklistComponent,
MyAccountAbusesListComponent,
MyAccountServerBlocklistComponent,
MyAccountNotificationsComponent,
MyAccountNotificationPreferencesComponent,
MyAccountNotificationPreferencesComponent
],

View File

@ -1,6 +1,6 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core'
import { AuthService, LocalStorageService, Notifier, ScopedTokensService, ScreenService, ServerService, UserService } from '@app/core'
import { HooksService } from '@app/core/plugins/hooks.service'
import { immutableAssign } from '@app/helpers'
import { VideoService } from '@app/shared/shared-main'
@ -9,6 +9,7 @@ import { AbstractVideoList, OwnerDisplayType } from '@app/shared/shared-video-mi
import { VideoSortField, FeedFormat } from '@shared/models'
import { copyToClipboard } from '../../../root-helpers/utils'
import { environment } from '../../../environments/environment'
import { forkJoin } from 'rxjs'
@Component({
selector: 'my-videos-user-subscriptions',
@ -32,7 +33,8 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
protected storageService: LocalStorageService,
private userSubscription: UserSubscriptionService,
private hooks: HooksService,
private videoService: VideoService
private videoService: VideoService,
private scopedTokensService: ScopedTokensService
) {
super()
@ -49,9 +51,19 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
super.ngOnInit()
const user = this.authService.getUser()
let feedUrl = environment.embedUrl
this.videoService.getVideoSubscriptionFeedUrls(user.account.id)
.then((feeds: any) => feedUrl = feedUrl + feeds.find((f: any) => f.format === FeedFormat.RSS).url)
let feedUrl = environment.originServerUrl
this.scopedTokensService.getScopedTokens().subscribe(
tokens => {
const feeds = this.videoService.getVideoSubscriptionFeedUrls(user.account.id, tokens.feedToken)
feedUrl = feedUrl + feeds.find((f: any) => f.format === FeedFormat.RSS).url
},
err => {
this.notifier.error(err.message)
}
)
this.actions.unshift({
label: $localize`Feed`,
iconName: 'syndication',

View File

@ -11,7 +11,6 @@ import { environment } from '../../../environments/environment'
import { RestExtractor } from '../rest/rest-extractor.service'
import { AuthStatus } from './auth-status.model'
import { AuthUser } from './auth-user.model'
import { ScopedTokenType, ScopedToken } from '@shared/models/users/user-scoped-token'
interface UserLoginWithUsername extends UserLogin {
access_token: string
@ -27,7 +26,6 @@ export class AuthService {
private static BASE_CLIENT_URL = environment.apiUrl + '/api/v1/oauth-clients/local'
private static BASE_TOKEN_URL = environment.apiUrl + '/api/v1/users/token'
private static BASE_REVOKE_TOKEN_URL = environment.apiUrl + '/api/v1/users/revoke-token'
private static BASE_SCOPED_TOKENS_URL = environment.apiUrl + '/api/v1/users/scoped-tokens'
private static BASE_USER_INFORMATION_URL = environment.apiUrl + '/api/v1/users/me'
private static LOCAL_STORAGE_OAUTH_CLIENT_KEYS = {
CLIENT_ID: 'client_id',
@ -43,7 +41,6 @@ export class AuthService {
private loginChanged: Subject<AuthStatus>
private user: AuthUser = null
private refreshingTokenObservable: Observable<any>
private scopedTokens: ScopedToken
constructor (
private http: HttpClient,
@ -247,48 +244,6 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
)
}
getScopedTokens (): Promise<ScopedToken> {
return new Promise((res, rej) => {
if (this.scopedTokens) return res(this.scopedTokens)
const authHeaderValue = this.getRequestHeaderValue()
const headers = new HttpHeaders().set('Authorization', authHeaderValue)
this.http.get<ScopedToken>(AuthService.BASE_SCOPED_TOKENS_URL, { headers })
.subscribe(
scopedTokens => {
this.scopedTokens = scopedTokens
res(this.scopedTokens)
},
err => {
console.error(err)
rej(err)
}
)
})
}
renewScopedTokens (): Promise<ScopedToken> {
return new Promise((res, rej) => {
const authHeaderValue = this.getRequestHeaderValue()
const headers = new HttpHeaders().set('Authorization', authHeaderValue)
this.http.post<ScopedToken>(AuthService.BASE_SCOPED_TOKENS_URL, {}, { headers })
.subscribe(
scopedTokens => {
this.scopedTokens = scopedTokens
res(this.scopedTokens)
},
err => {
console.error(err)
rej(err)
}
)
})
}
private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> {
// User is not loaded yet, set manually auth header
const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`)

View File

@ -12,6 +12,7 @@ import { LoadingBarModule } from '@ngx-loading-bar/core'
import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client'
import { LoadingBarRouterModule } from '@ngx-loading-bar/router'
import { AuthService } from './auth'
import { ScopedTokensService } from './scoped-tokens'
import { ConfirmService } from './confirm'
import { CheatSheetComponent } from './hotkeys'
import { MenuService } from './menu'
@ -57,6 +58,7 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra
providers: [
AuthService,
ScopedTokensService,
ConfirmService,
ServerService,
ThemeService,

View File

@ -1,4 +1,5 @@
export * from './auth'
export * from './scoped-tokens'
export * from './confirm'
export * from './hotkeys'
export * from './menu'

View File

@ -0,0 +1 @@
export * from './scoped-tokens.service'

View File

@ -0,0 +1,33 @@
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { environment } from '../../../environments/environment'
import { AuthService } from '../auth'
import { ScopedToken } from '@shared/models/users/user-scoped-token'
import { catchError } from 'rxjs/operators'
import { RestExtractor } from '../rest'
@Injectable()
export class ScopedTokensService {
private static BASE_SCOPED_TOKENS_URL = environment.apiUrl + '/api/v1/users/scoped-tokens'
constructor (
private authHttp: HttpClient,
private restExtractor: RestExtractor
) {}
getScopedTokens () {
return this.authHttp
.get<ScopedToken>(ScopedTokensService.BASE_SCOPED_TOKENS_URL)
.pipe(
catchError(res => this.restExtractor.handleError(res))
)
}
renewScopedTokens () {
return this.authHttp
.post<ScopedToken>(ScopedTokensService.BASE_SCOPED_TOKENS_URL, {})
.pipe(
catchError(res => this.restExtractor.handleError(res))
)
}
}

View File

@ -58,7 +58,7 @@ function getAbsoluteAPIUrl () {
}
function getAbsoluteEmbedUrl () {
let absoluteEmbedUrl = environment.embedUrl
let absoluteEmbedUrl = environment.originServerUrl
if (!absoluteEmbedUrl) {
// The Embed is on the same domain
absoluteEmbedUrl = window.location.origin

View File

@ -112,7 +112,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
getVideoEmbed (abuse: AdminAbuse) {
return buildVideoOrPlaylistEmbed(
buildVideoLink({
baseUrl: `${environment.embedUrl}/videos/embed/${abuse.video.uuid}`,
baseUrl: `${environment.originServerUrl}/videos/embed/${abuse.video.uuid}`,
title: false,
warningTitle: false,
startTime: abuse.video.startAt,

View File

@ -1,6 +1,5 @@
import { Component, Input } from '@angular/core'
import { Notifier } from '@app/core'
import { FormGroup } from '@angular/forms'
@Component({
selector: 'my-input-readonly-copy',

View File

@ -18,8 +18,7 @@ import {
VideoFilter,
VideoPrivacy,
VideoSortField,
VideoUpdate,
VideoCreate
VideoUpdate
} from '@shared/models'
import { environment } from '../../../../environments/environment'
import { Account } from '../account/account.model'
@ -44,13 +43,13 @@ export interface VideosProvider {
export class VideoService implements VideosProvider {
static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.'
static BASE_SUBSCRIPTION_FEEDS_URL = environment.apiUrl + '/feeds/subscriptions.'
constructor (
private authHttp: HttpClient,
private restExtractor: RestExtractor,
private restService: RestService,
private serverService: ServerService,
private authService: AuthService
private serverService: ServerService
) {}
getVideoViewUrl (uuid: string) {
@ -238,22 +237,22 @@ export class VideoService implements VideosProvider {
)
}
buildBaseFeedUrls (params: HttpParams) {
buildBaseFeedUrls (params: HttpParams, base = VideoService.BASE_FEEDS_URL) {
const feeds = [
{
format: FeedFormat.RSS,
label: 'media rss 2.0',
url: VideoService.BASE_FEEDS_URL + FeedFormat.RSS.toLowerCase()
url: base + FeedFormat.RSS.toLowerCase()
},
{
format: FeedFormat.ATOM,
label: 'atom 1.0',
url: VideoService.BASE_FEEDS_URL + FeedFormat.ATOM.toLowerCase()
url: base + FeedFormat.ATOM.toLowerCase()
},
{
format: FeedFormat.JSON,
label: 'json 1.0',
url: VideoService.BASE_FEEDS_URL + FeedFormat.JSON.toLowerCase()
url: base + FeedFormat.JSON.toLowerCase()
}
]
@ -294,14 +293,12 @@ export class VideoService implements VideosProvider {
return this.buildBaseFeedUrls(params)
}
async getVideoSubscriptionFeedUrls (accountId: number) {
getVideoSubscriptionFeedUrls (accountId: number, feedToken: string) {
let params = this.restService.addRestGetParams(new HttpParams())
params = params.set('accountId', accountId.toString())
const { feedToken } = await this.authService.getScopedTokens()
params = params.set('token', feedToken)
return this.buildBaseFeedUrls(params)
return this.buildBaseFeedUrls(params, VideoService.BASE_SUBSCRIPTION_FEEDS_URL)
}
getVideoFileMetadata (metadataUrl: string) {

View File

@ -2,5 +2,5 @@ export const environment = {
production: false,
hmr: false,
apiUrl: 'http://localhost:9001',
embedUrl: 'http://localhost:9001'
originServerUrl: 'http://localhost:9001'
}

View File

@ -2,5 +2,5 @@ export const environment = {
production: false,
hmr: true,
apiUrl: '',
embedUrl: 'http://localhost:9000'
originServerUrl: 'http://localhost:9000'
}

View File

@ -2,5 +2,5 @@ export const environment = {
production: true,
hmr: false,
apiUrl: '',
embedUrl: ''
originServerUrl: ''
}

View File

@ -12,5 +12,5 @@ export const environment = {
production: true,
hmr: false,
apiUrl: '',
embedUrl: ''
originServerUrl: ''
}

View File

@ -225,7 +225,7 @@
line-height: $button-height;
border-radius: 3px;
text-align: center;
padding: 0 13px 0 13px;
padding: 0 17px 0 13px;
cursor: pointer;
}

View File

@ -18,7 +18,6 @@ import { cacheRoute } from '../middlewares/cache'
import { VideoModel } from '../models/video/video'
import { VideoCommentModel } from '../models/video/video-comment'
import { VideoFilter } from '../../shared/models/videos/video-query.type'
import { logger } from '../helpers/logger'
const feedsRouter = express.Router()
@ -47,10 +46,24 @@ feedsRouter.get('/feeds/videos.:format',
})(ROUTE_CACHE_LIFETIME.FEEDS)),
commonVideosFiltersValidator,
asyncMiddleware(videoFeedsValidator),
asyncMiddleware(videoSubscriptonFeedsValidator),
asyncMiddleware(generateVideoFeed)
)
feedsRouter.get('/feeds/subscriptions.:format',
videosSortValidator,
setDefaultVideosSort,
feedsFormatValidator,
setFeedFormatContentType,
asyncMiddleware(cacheRoute({
headerBlacklist: [
'Content-Type'
]
})(ROUTE_CACHE_LIFETIME.FEEDS)),
commonVideosFiltersValidator,
asyncMiddleware(videoSubscriptonFeedsValidator),
asyncMiddleware(generateVideoFeedForSubscriptions)
)
// ---------------------------------------------------------------------------
export {
@ -61,7 +74,6 @@ export {
async function generateVideoCommentsFeed (req: express.Request, res: express.Response) {
const start = 0
const video = res.locals.videoAll
const account = res.locals.account
const videoChannel = res.locals.videoChannel
@ -125,10 +137,8 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res
async function generateVideoFeed (req: express.Request, res: express.Response) {
const start = 0
const account = res.locals.account
const videoChannel = res.locals.videoChannel
const token = req.query.token
const nsfw = buildNSFWFilter(res, req.query.nsfw)
let name: string
@ -152,21 +162,10 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
queryString: new URL(WEBSERVER.URL + req.url).search
})
/**
* We have two ways to query video results:
* - one with account and token -> get subscription videos
* - one with either account, channel, or nothing: just videos with these filters
*/
const options = token && token !== '' && res.locals.user
? {
followerActorId: res.locals.user.Account.Actor.id,
user: res.locals.user,
includeLocalVideos: false
}
: {
accountId: account ? account.id : null,
videoChannelId: videoChannel ? videoChannel.id : null
}
const options = {
accountId: account ? account.id : null,
videoChannelId: videoChannel ? videoChannel.id : null
}
const resultList = await VideoModel.listForApi({
start,
@ -179,10 +178,86 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
...options
})
addVideosToFeed(feed, resultList.data)
// Now the feed generation is done, let's send it!
return sendFeed(feed, req, res)
}
async function generateVideoFeedForSubscriptions (req: express.Request, res: express.Response) {
const start = 0
const account = res.locals.account
const nsfw = buildNSFWFilter(res, req.query.nsfw)
const name = account.getDisplayName()
const description = account.description
const feed = initFeed({
name,
description,
resourceType: 'videos',
queryString: new URL(WEBSERVER.URL + req.url).search
})
const options = {
followerActorId: res.locals.user.Account.Actor.id,
user: res.locals.user
}
const resultList = await VideoModel.listForApi({
start,
count: FEEDS.COUNT,
sort: req.query.sort,
includeLocalVideos: true,
nsfw,
filter: req.query.filter as VideoFilter,
withFiles: true,
...options
})
addVideosToFeed(feed, resultList.data)
// Now the feed generation is done, let's send it!
return sendFeed(feed, req, res)
}
function initFeed (parameters: {
name: string
description: string
resourceType?: 'videos' | 'video-comments'
queryString?: string
}) {
const webserverUrl = WEBSERVER.URL
const { name, description, resourceType, queryString } = parameters
return new Feed({
title: name,
description,
// updated: TODO: somehowGetLatestUpdate, // optional, default = today
id: webserverUrl,
link: webserverUrl,
image: webserverUrl + '/client/assets/images/icons/icon-96x96.png',
favicon: webserverUrl + '/client/assets/images/favicon.png',
copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` +
` and potential licenses granted by each content's rightholder.`,
generator: `Toraifōsu`, // ^.~
feedLinks: {
json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`,
atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`,
rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}`
},
author: {
name: 'Instance admin of ' + CONFIG.INSTANCE.NAME,
email: CONFIG.ADMIN.EMAIL,
link: `${webserverUrl}/about`
}
})
}
function addVideosToFeed (feed, videos: VideoModel[]) {
/**
* Adding video items to the feed object, one at a time
*/
resultList.data.forEach(video => {
for (const video of videos) {
const formattedVideoFiles = video.getFormattedVideoFilesJSON()
const torrents = formattedVideoFiles.map(videoFile => ({
@ -252,43 +327,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
}
]
})
})
// Now the feed generation is done, let's send it!
return sendFeed(feed, req, res)
}
function initFeed (parameters: {
name: string
description: string
resourceType?: 'videos' | 'video-comments'
queryString?: string
}) {
const webserverUrl = WEBSERVER.URL
const { name, description, resourceType, queryString } = parameters
return new Feed({
title: name,
description,
// updated: TODO: somehowGetLatestUpdate, // optional, default = today
id: webserverUrl,
link: webserverUrl,
image: webserverUrl + '/client/assets/images/icons/icon-96x96.png',
favicon: webserverUrl + '/client/assets/images/favicon.png',
copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` +
` and potential licenses granted by each content's rightholder.`,
generator: `Toraifōsu`, // ^.~
feedLinks: {
json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`,
atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`,
rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}`
},
author: {
name: 'Instance admin of ' + CONFIG.INSTANCE.NAME,
email: CONFIG.ADMIN.EMAIL,
link: `${webserverUrl}/about`
}
})
}
}
function sendFeed (feed, req: express.Request, res: express.Response) {

View File

@ -28,8 +28,7 @@ async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, se
if (!account) {
if (sendNotFound === true) {
res.status(404)
.send({ error: 'Account not found' })
.end()
.json({ error: 'Account not found' })
}
return false
@ -41,12 +40,11 @@ async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, se
}
async function doesUserFeedTokenCorrespond (id: number | string, token: string, res: Response) {
const user = await UserModel.loadById(parseInt(id + '', 10))
const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10))
if (token !== user.feedToken) {
res.status(401)
.send({ error: 'User and token mismatch' })
.end()
.json({ error: 'User and token mismatch' })
return false
}

View File

@ -64,8 +64,8 @@ const videoFeedsValidator = [
]
const videoSubscriptonFeedsValidator = [
query('accountId').optional().custom(isIdValid),
query('token').optional(),
query('accountId').custom(isIdValid),
query('token'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking feeds parameters', { parameters: req.query })
@ -74,6 +74,7 @@ const videoSubscriptonFeedsValidator = [
// a token alone is erroneous
if (req.query.token && !req.query.accountId) return
if (req.query.accountId && !await doesAccountIdExist(req.query.accountId, res)) return
if (req.query.token && !await doesUserFeedTokenCorrespond(res.locals.account.userId, req.query.token, res)) return
return next()

View File

@ -326,7 +326,7 @@ describe('Test syndication feeds', () => {
const res = await listUserSubscriptionVideos(servers[0].url, feeduserAccessToken)
expect(res.body.total).to.equal(0)
const json = await getJSONfeed(servers[0].url, 'videos', { accountId: feeduserAccountId, token: feeduserFeedToken })
const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: feeduserAccountId, token: feeduserFeedToken })
const jsonObj = JSON.parse(json.text)
expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos
}
@ -337,7 +337,7 @@ describe('Test syndication feeds', () => {
const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken)
expect(res.body.total).to.equal(0)
const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken })
const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken })
const jsonObj = JSON.parse(json.text)
expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos
}
@ -354,7 +354,7 @@ describe('Test syndication feeds', () => {
expect(res.body.total).to.equal(1)
expect(res.body.data[0].name).to.equal('user video')
const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken, version: 1 })
const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 1 })
const jsonObj = JSON.parse(json.text)
expect(jsonObj.items.length).to.be.equal(1) // subscribed to self, it should not list the instance's videos but list john's
}
@ -370,7 +370,7 @@ describe('Test syndication feeds', () => {
const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken)
expect(res.body.total).to.equal(2, "there should be 2 videos part of the subscription")
const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken, version: 2 })
const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 2 })
const jsonObj = JSON.parse(json.text)
expect(jsonObj.items.length).to.be.equal(2) // subscribed to root, it should not list the instance's videos but list root/john's
}

View File

@ -1,6 +1,6 @@
import * as request from 'supertest'
type FeedType = 'videos' | 'video-comments'
type FeedType = 'videos' | 'video-comments' | 'subscriptions'
function getXMLfeed (url: string, feed: FeedType, format?: string) {
const path = '/feeds/' + feed + '.xml'