We must not expose private actor objects to clients
Just make 2 GET requests on channel/accounts instead
This commit is contained in:
Chocobozzz 2021-05-28 10:21:39 +02:00
parent d6d96bed80
commit 012580d98f
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
21 changed files with 221 additions and 397 deletions

View File

@ -3,6 +3,7 @@ import { Title } from '@angular/platform-browser'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
@Component({ @Component({
selector: 'my-page-not-found', selector: 'my-page-not-found',
templateUrl: './page-not-found.component.html', templateUrl: './page-not-found.component.html',
@ -10,7 +11,7 @@ import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
}) })
export class PageNotFoundComponent implements OnInit { export class PageNotFoundComponent implements OnInit {
status = HttpStatusCode.NOT_FOUND_404 status = HttpStatusCode.NOT_FOUND_404
type: string type: 'video' | 'other' = 'other'
public constructor ( public constructor (
private titleService: Title, private titleService: Title,

View File

@ -5,7 +5,8 @@ import { MenuGuards } from '@app/core/routing/menu-guard.service'
import { POSSIBLE_LOCALES } from '@shared/core-utils/i18n' import { POSSIBLE_LOCALES } from '@shared/core-utils/i18n'
import { MetaGuard, PreloadSelectedModulesList } from './core' import { MetaGuard, PreloadSelectedModulesList } from './core'
import { EmptyComponent } from './empty.component' import { EmptyComponent } from './empty.component'
import { RootComponent } from './root.component' import { USER_USERNAME_REGEX_CHARACTERS } from './shared/form-validators/user-validators'
import { ActorRedirectGuard } from './shared/shared-main'
const routes: Routes = [ const routes: Routes = [
{ {
@ -17,7 +18,8 @@ const routes: Routes = [
}, },
{ {
path: 'home', path: 'home',
loadChildren: () => import('./+home/home.module').then(m => m.HomeModule) loadChildren: () => import('./+home/home.module').then(m => m.HomeModule),
canActivateChild: [ MetaGuard ]
}, },
{ {
path: 'my-account', path: 'my-account',
@ -94,18 +96,22 @@ const routes: Routes = [
{ {
matcher: (url): UrlMatchResult => { matcher: (url): UrlMatchResult => {
// Matches /@:actorName // Matches /@:actorName
if (url.length === 1 && url[0].path.match(/^@[\w]+$/gm)) { const regex = new RegExp(`^@(${USER_USERNAME_REGEX_CHARACTERS}+)$`)
return { if (url.length !== 1) return null
consumed: url,
posParams: { const matchResult = url[0].path.match(regex)
actorName: new UrlSegment(url[0].path.substr(1), {}) if (!matchResult) return null
}
return {
consumed: url,
posParams: {
actorName: new UrlSegment(matchResult[1], {})
} }
} }
return null
}, },
component: RootComponent pathMatch: 'full',
canActivate: [ ActorRedirectGuard ],
component: EmptyComponent
}, },
{ {
path: '', path: '',

View File

@ -1,12 +1,14 @@
import { Validators } from '@angular/forms' import { Validators } from '@angular/forms'
import { BuildFormValidator } from './form-validator.model' import { BuildFormValidator } from './form-validator.model'
export const USER_USERNAME_REGEX_CHARACTERS = '[a-z0-9][a-z0-9._]'
export const USER_USERNAME_VALIDATOR: BuildFormValidator = { export const USER_USERNAME_VALIDATOR: BuildFormValidator = {
VALIDATORS: [ VALIDATORS: [
Validators.required, Validators.required,
Validators.minLength(1), Validators.minLength(1),
Validators.maxLength(50), Validators.maxLength(50),
Validators.pattern(/^[a-z0-9][a-z0-9._]*$/) Validators.pattern(new RegExp(`^${USER_USERNAME_REGEX_CHARACTERS}*$`))
], ],
MESSAGES: { MESSAGES: {
'required': $localize`Username is required.`, 'required': $localize`Username is required.`,

View File

@ -1,37 +0,0 @@
import { Observable, ReplaySubject } from 'rxjs'
import { catchError, map, tap } from 'rxjs/operators'
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { RestExtractor } from '@app/core'
import { Account as ServerAccount, VideoChannel as ServerVideoChannel } from '@shared/models'
import { environment } from '../../../../environments/environment'
type KeysOfUnion<T> = T extends T ? keyof T: never
type ServerActor = KeysOfUnion<ServerAccount | ServerVideoChannel>
@Injectable()
export class ActorService {
static BASE_ACTOR_API_URL = environment.apiUrl + '/api/v1/actors/'
actorLoaded = new ReplaySubject<string>(1)
constructor (
private authHttp: HttpClient,
private restExtractor: RestExtractor
) {}
getActorType (actorName: string): Observable<string> {
return this.authHttp.get<ServerActor>(ActorService.BASE_ACTOR_API_URL + actorName)
.pipe(
map(actorHash => {
if (actorHash[ 'userId' ]) {
return 'Account'
}
return 'VideoChannel'
}),
tap(actor => this.actorLoaded.next(actor)),
catchError(res => this.restExtractor.handleError(res))
)
}
}

View File

@ -1,4 +1,3 @@
export * from './account.model' export * from './account.model'
export * from './account.service' export * from './account.service'
export * from './actor.model' export * from './actor.model'
export * from './actor.service'

View File

@ -5,6 +5,9 @@ export * from './date'
export * from './feeds' export * from './feeds'
export * from './loaders' export * from './loaders'
export * from './misc' export * from './misc'
export * from './peertube-modal'
export * from './plugins'
export * from './router'
export * from './users' export * from './users'
export * from './video' export * from './video'
export * from './video-caption' export * from './video-caption'

View File

@ -0,0 +1,46 @@
import { forkJoin, of } from 'rxjs'
import { catchError, map } from 'rxjs/operators'
import { Injectable } from '@angular/core'
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'
import { AccountService } from '../account'
import { VideoChannelService } from '../video-channel'
@Injectable()
export class ActorRedirectGuard implements CanActivate {
constructor (
private router: Router,
private accountService: AccountService,
private channelService: VideoChannelService
) {}
canActivate (route: ActivatedRouteSnapshot) {
const actorName = route.params.actorName
return forkJoin([
this.accountService.getAccount(actorName).pipe(this.orUndefined()),
this.channelService.getVideoChannel(actorName).pipe(this.orUndefined())
]).pipe(
map(([ account, channel ]) => {
if (!account && !channel) {
this.router.navigate([ '/404' ])
return false
}
if (account) {
this.router.navigate([ `/a/${actorName}` ], { skipLocationChange: true })
}
if (channel) {
this.router.navigate([ `/c/${actorName}` ], { skipLocationChange: true })
}
return true
})
)
}
private orUndefined () {
return catchError(() => of(undefined))
}
}

View File

@ -0,0 +1 @@
export * from './actor-redirect-guard.service'

View File

@ -4,7 +4,7 @@ import { CommonModule, DatePipe } from '@angular/common'
import { HttpClientModule } from '@angular/common/http' import { HttpClientModule } from '@angular/common/http'
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { RouterModule } from '@angular/router' import { ActivatedRouteSnapshot, RouterModule } from '@angular/router'
import { import {
NgbButtonsModule, NgbButtonsModule,
NgbCollapseModule, NgbCollapseModule,
@ -17,7 +17,7 @@ import {
import { LoadingBarModule } from '@ngx-loading-bar/core' import { LoadingBarModule } from '@ngx-loading-bar/core'
import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client'
import { SharedGlobalIconModule } from '../shared-icons' import { SharedGlobalIconModule } from '../shared-icons'
import { AccountService, ActorService } from './account' import { AccountService } from './account'
import { import {
AutofocusDirective, AutofocusDirective,
BytesPipe, BytesPipe,
@ -39,6 +39,7 @@ import { UserHistoryService, UserNotificationsComponent, UserNotificationService
import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video' import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video'
import { VideoCaptionService } from './video-caption' import { VideoCaptionService } from './video-caption'
import { VideoChannelService } from './video-channel' import { VideoChannelService } from './video-channel'
import { ActorRedirectGuard } from './router'
@NgModule({ @NgModule({
imports: [ imports: [
@ -161,7 +162,6 @@ import { VideoChannelService } from './video-channel'
AUTH_INTERCEPTOR_PROVIDER, AUTH_INTERCEPTOR_PROVIDER,
AccountService, AccountService,
ActorService,
UserHistoryService, UserHistoryService,
UserNotificationService, UserNotificationService,
@ -175,7 +175,9 @@ import { VideoChannelService } from './video-channel'
VideoChannelService, VideoChannelService,
CustomPageService CustomPageService,
ActorRedirectGuard
] ]
}) })
export class SharedMainModule { } export class SharedMainModule { }

View File

@ -1,37 +0,0 @@
import * as express from 'express'
import { JobQueue } from '../../lib/job-queue'
import { asyncMiddleware } from '../../middlewares'
import { actorNameWithHostGetValidator } from '../../middlewares/validators'
const actorRouter = express.Router()
actorRouter.get('/:actorName',
asyncMiddleware(actorNameWithHostGetValidator),
getActor
)
// ---------------------------------------------------------------------------
export {
actorRouter
}
// ---------------------------------------------------------------------------
function getActor (req: express.Request, res: express.Response) {
let accountOrVideoChannel
if (res.locals.account) {
accountOrVideoChannel = res.locals.account
}
if (res.locals.videoChannel) {
accountOrVideoChannel = res.locals.videoChannel
}
if (accountOrVideoChannel.isOutdated()) {
JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: accountOrVideoChannel.Actor.url } })
}
return res.json(accountOrVideoChannel.toFormattedJSON())
}

View File

@ -16,7 +16,6 @@ import { pluginRouter } from './plugins'
import { searchRouter } from './search' import { searchRouter } from './search'
import { serverRouter } from './server' import { serverRouter } from './server'
import { usersRouter } from './users' import { usersRouter } from './users'
import { actorRouter } from './actor'
import { videoChannelRouter } from './video-channel' import { videoChannelRouter } from './video-channel'
import { videoPlaylistRouter } from './video-playlist' import { videoPlaylistRouter } from './video-playlist'
import { videosRouter } from './videos' import { videosRouter } from './videos'
@ -41,7 +40,6 @@ apiRouter.use('/bulk', bulkRouter)
apiRouter.use('/oauth-clients', oauthClientsRouter) apiRouter.use('/oauth-clients', oauthClientsRouter)
apiRouter.use('/config', configRouter) apiRouter.use('/config', configRouter)
apiRouter.use('/users', usersRouter) apiRouter.use('/users', usersRouter)
apiRouter.use('/actors', actorRouter)
apiRouter.use('/accounts', accountsRouter) apiRouter.use('/accounts', accountsRouter)
apiRouter.use('/video-channels', videoChannelRouter) apiRouter.use('/video-channels', videoChannelRouter)
apiRouter.use('/video-playlists', videoPlaylistRouter) apiRouter.use('/video-playlists', videoPlaylistRouter)

View File

@ -1,10 +0,0 @@
import { isAccountNameValid } from './accounts'
import { isVideoChannelNameValid } from './video-channels'
function isActorNameValid (value: string) {
return isAccountNameValid(value) || isVideoChannelNameValid(value)
}
export {
isActorNameValid
}

View File

@ -3,22 +3,22 @@ import { MChannelBannerAccountDefault } from '@server/types/models'
import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
import { VideoChannelModel } from '../../models/video/video-channel' import { VideoChannelModel } from '../../models/video/video-channel'
async function doesLocalVideoChannelNameExist (name: string, res: express.Response, sendNotFound = true) { async function doesLocalVideoChannelNameExist (name: string, res: express.Response) {
const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name)
return processVideoChannelExist(videoChannel, res, sendNotFound) return processVideoChannelExist(videoChannel, res)
} }
async function doesVideoChannelIdExist (id: number, res: express.Response, sendNotFound = true) { async function doesVideoChannelIdExist (id: number, res: express.Response) {
const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
return processVideoChannelExist(videoChannel, res, sendNotFound) return processVideoChannelExist(videoChannel, res)
} }
async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response, sendNotFound = true) { async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) {
const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain)
return processVideoChannelExist(videoChannel, res, sendNotFound) return processVideoChannelExist(videoChannel, res)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -29,12 +29,10 @@ export {
doesVideoChannelNameWithHostExist doesVideoChannelNameWithHostExist
} }
function processVideoChannelExist (videoChannel: MChannelBannerAccountDefault, res: express.Response, sendNotFound = true) { function processVideoChannelExist (videoChannel: MChannelBannerAccountDefault, res: express.Response) {
if (!videoChannel) { if (!videoChannel) {
if (sendNotFound) { res.status(HttpStatusCode.NOT_FOUND_404)
res.status(HttpStatusCode.NOT_FOUND_404) .json({ error: 'Video channel not found' })
.json({ error: 'Video channel not found' })
}
return false return false
} }

View File

@ -208,14 +208,12 @@ class ClientHtml {
} }
static async getActorHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) { static async getActorHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) {
const accountModel = await AccountModel.loadByNameWithHost(nameWithHost) const [ account, channel ] = await Promise.all([
AccountModel.loadByNameWithHost(nameWithHost),
VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithHost)
])
if (accountModel) { return this.getAccountOrChannelHTMLPage(() => Promise.resolve(account || channel), req, res)
return this.getAccountOrChannelHTMLPage(() => new Promise(resolve => resolve(accountModel)), req, res)
} else {
const videoChannelModelPromise = VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithHost)
return this.getAccountOrChannelHTMLPage(() => videoChannelModelPromise, req, res)
}
} }
static async getEmbedHTML () { static async getEmbedHTML () {

View File

@ -1,59 +0,0 @@
import * as express from 'express'
import { param } from 'express-validator'
import { isActorNameValid } from '../../helpers/custom-validators/actor'
import { logger } from '../../helpers/logger'
import { areValidationErrors } from './utils'
import {
doesAccountNameWithHostExist,
doesLocalAccountNameExist,
doesVideoChannelNameWithHostExist,
doesLocalVideoChannelNameExist
} from '../../helpers/middlewares'
import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
const localActorValidator = [
param('actorName').custom(isActorNameValid).withMessage('Should have a valid actor name'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking localActorValidator parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
const isAccount = await doesLocalAccountNameExist(req.params.actorName, res, false)
const isVideoChannel = await doesLocalVideoChannelNameExist(req.params.actorName, res, false)
if (!isAccount || !isVideoChannel) {
res.status(HttpStatusCode.NOT_FOUND_404)
.json({ error: 'Actor not found' })
}
return next()
}
]
const actorNameWithHostGetValidator = [
param('actorName').exists().withMessage('Should have an actor name with host'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking actorNameWithHostGetValidator parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
const isAccount = await doesAccountNameWithHostExist(req.params.actorName, res, false)
const isVideoChannel = await doesVideoChannelNameWithHostExist(req.params.actorName, res, false)
if (!isAccount && !isVideoChannel) {
res.status(HttpStatusCode.NOT_FOUND_404)
.json({ error: 'Actor not found' })
}
return next()
}
]
// ---------------------------------------------------------------------------
export {
localActorValidator,
actorNameWithHostGetValidator
}

View File

@ -1,6 +1,5 @@
export * from './abuse' export * from './abuse'
export * from './account' export * from './account'
export * from './actor'
export * from './actor-image' export * from './actor-image'
export * from './blocklist' export * from './blocklist'
export * from './oembed' export * from './oembed'

View File

@ -1,37 +0,0 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import 'mocha'
import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../../shared/extra-utils'
import { getActor } from '../../../../shared/extra-utils/actors/actors'
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
describe('Test actors API validators', function () {
let server: ServerInfo
// ---------------------------------------------------------------
before(async function () {
this.timeout(30000)
server = await flushAndRunServer(1)
})
describe('When getting an actor', function () {
it('Should return 404 with a non existing actorName', async function () {
await getActor(server.url, 'arfaze', HttpStatusCode.NOT_FOUND_404)
})
it('Should return 200 with an existing accountName', async function () {
await getActor(server.url, 'root', HttpStatusCode.OK_200)
})
it('Should return 200 with an existing channelName', async function () {
await getActor(server.url, 'root_channel', HttpStatusCode.OK_200)
})
})
after(async function () {
await cleanupTests([ server ])
})
})

View File

@ -2,8 +2,10 @@
import 'mocha' import 'mocha'
import * as chai from 'chai' import * as chai from 'chai'
import { omit } from 'lodash'
import * as request from 'supertest' import * as request from 'supertest'
import { Account, HTMLServerConfig, ServerConfig, VideoPlaylistPrivacy } from '@shared/models' import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
import { Account, CustomConfig, HTMLServerConfig, ServerConfig, VideoPlaylistPrivacy } from '@shared/models'
import { import {
addVideoInPlaylist, addVideoInPlaylist,
cleanupTests, cleanupTests,
@ -14,6 +16,7 @@ import {
getConfig, getConfig,
getCustomConfig, getCustomConfig,
getVideosList, getVideosList,
makeGetRequest,
makeHTMLRequest, makeHTMLRequest,
ServerInfo, ServerInfo,
setAccessTokensToServers, setAccessTokensToServers,
@ -25,8 +28,6 @@ import {
uploadVideo, uploadVideo,
waitJobs waitJobs
} from '../../shared/extra-utils' } from '../../shared/extra-utils'
import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
import { omit } from 'lodash'
const expect = chai.expect const expect = chai.expect
@ -144,52 +145,36 @@ describe('Test a client controllers', function () {
describe('Open Graph', function () { describe('Open Graph', function () {
async function accountPageTest (path: string) {
const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
const text = res.text
expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
expect(text).to.contain('<meta property="og:type" content="website" />')
expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].user.username}" />`)
}
async function channelPageTest (path: string) {
const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
const text = res.text
expect(text).to.contain(`<meta property="og:title" content="${servers[0].videoChannel.displayName}" />`)
expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
expect(text).to.contain('<meta property="og:type" content="website" />')
expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`)
}
it('Should have valid Open Graph tags on the account page', async function () { it('Should have valid Open Graph tags on the account page', async function () {
const accountPageTests = (res) => { await accountPageTest('/accounts/' + servers[0].user.username)
expect(res.text).to.contain(`<meta property="og:title" content="${account.displayName}" />`) await accountPageTest('/a/' + servers[0].user.username)
expect(res.text).to.contain(`<meta property="og:description" content="${account.description}" />`) await accountPageTest('/@' + servers[0].user.username)
expect(res.text).to.contain('<meta property="og:type" content="website" />')
expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].user.username}" />`)
}
accountPageTests(await request(servers[0].url)
.get('/accounts/' + servers[0].user.username)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
accountPageTests(await request(servers[0].url)
.get('/a/' + servers[0].user.username)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
accountPageTests(await request(servers[0].url)
.get('/@' + servers[0].user.username)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
}) })
it('Should have valid Open Graph tags on the channel page', async function () { it('Should have valid Open Graph tags on the channel page', async function () {
const channelPageOGtests = (res) => { await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
expect(res.text).to.contain(`<meta property="og:title" content="${servers[0].videoChannel.displayName}" />`) await channelPageTest('/c/' + servers[0].videoChannel.name)
expect(res.text).to.contain(`<meta property="og:description" content="${channelDescription}" />`) await channelPageTest('/@' + servers[0].videoChannel.name)
expect(res.text).to.contain('<meta property="og:type" content="website" />')
expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`)
}
channelPageOGtests(await request(servers[0].url)
.get('/video-channels/' + servers[0].videoChannel.name)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
channelPageOGtests(await request(servers[0].url)
.get('/c/' + servers[0].videoChannel.name)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
channelPageOGtests(await request(servers[0].url)
.get('/@' + servers[0].videoChannel.name)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
}) })
it('Should have valid Open Graph tags on the watch page with video id', async function () { it('Should have valid Open Graph tags on the watch page with video id', async function () {
@ -231,142 +216,125 @@ describe('Test a client controllers', function () {
describe('Twitter card', async function () { describe('Twitter card', async function () {
it('Should have valid twitter card on the watch video page', async function () { describe('Not whitelisted', function () {
const res = await request(servers[0].url)
.get('/videos/watch/' + servers[0].video.uuid)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
expect(res.text).to.contain('<meta property="twitter:card" content="summary_large_image" />') async function accountPageTest (path: string) {
expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
expect(res.text).to.contain(`<meta property="twitter:title" content="${videoName}" />`) const text = res.text
expect(res.text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
})
it('Should have valid twitter card on the watch playlist page', async function () { expect(text).to.contain('<meta property="twitter:card" content="summary" />')
const res = await request(servers[0].url) expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
.get('/videos/watch/playlist/' + playlistUUID) expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`)
.set('Accept', 'text/html') expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`)
.expect(HttpStatusCode.OK_200) }
expect(res.text).to.contain('<meta property="twitter:card" content="summary" />') async function channelPageTest (path: string) {
expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
expect(res.text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`) const text = res.text
expect(res.text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
}) expect(text).to.contain('<meta property="twitter:card" content="summary" />')
expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].videoChannel.displayName}" />`)
expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`)
}
it('Should have valid twitter card on the watch video page', async function () {
const res = await request(servers[0].url)
.get('/videos/watch/' + servers[0].video.uuid)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
expect(res.text).to.contain('<meta property="twitter:card" content="summary_large_image" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
expect(res.text).to.contain(`<meta property="twitter:title" content="${videoName}" />`)
expect(res.text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`)
})
it('Should have valid twitter card on the watch playlist page', async function () {
const res = await request(servers[0].url)
.get('/videos/watch/playlist/' + playlistUUID)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
it('Should have valid twitter card on the account page', async function () {
const accountPageTests = (res) => {
expect(res.text).to.contain('<meta property="twitter:card" content="summary" />') expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />')
expect(res.text).to.contain(`<meta property="twitter:title" content="${account.name}" />`) expect(res.text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`)
expect(res.text).to.contain(`<meta property="twitter:description" content="${account.description}" />`) expect(res.text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`)
} })
accountPageTests(await request(servers[0].url) it('Should have valid twitter card on the account page', async function () {
.get('/accounts/' + account.name) await accountPageTest('/accounts/' + account.name)
.set('Accept', 'text/html') await accountPageTest('/a/' + account.name)
.expect(HttpStatusCode.OK_200)) await accountPageTest('/@' + account.name)
})
accountPageTests(await request(servers[0].url) it('Should have valid twitter card on the channel page', async function () {
.get('/a/' + account.name) await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
.set('Accept', 'text/html') await channelPageTest('/c/' + servers[0].videoChannel.name)
.expect(HttpStatusCode.OK_200)) await channelPageTest('/@' + servers[0].videoChannel.name)
})
accountPageTests(await request(servers[0].url)
.get('/@' + account.name)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
}) })
it('Should have valid twitter card on the channel page', async function () { describe('Whitelisted', function () {
const channelPageTests = (res) => {
expect(res.text).to.contain('<meta property="twitter:card" content="summary" />') before(async function () {
expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') const res = await getCustomConfig(servers[0].url, servers[0].accessToken)
expect(res.text).to.contain(`<meta property="twitter:title" content="${servers[0].videoChannel.displayName}" />`) const config = res.body as CustomConfig
expect(res.text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`) config.services.twitter = {
username: '@Kuja',
whitelisted: true
}
await updateCustomConfig(servers[0].url, servers[0].accessToken, config)
})
async function accountPageTest (path: string) {
const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
const text = res.text
expect(text).to.contain('<meta property="twitter:card" content="summary" />')
expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
} }
channelPageTests(await request(servers[0].url) async function channelPageTest (path: string) {
.get('/video-channels/' + servers[0].videoChannel.name) const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 })
.set('Accept', 'text/html') const text = res.text
.expect(HttpStatusCode.OK_200))
channelPageTests(await request(servers[0].url) expect(text).to.contain('<meta property="twitter:card" content="summary" />')
.get('/c/' + servers[0].videoChannel.name) expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
channelPageTests(await request(servers[0].url)
.get('/@' + servers[0].videoChannel.name)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
})
it('Should have valid twitter card if Twitter is whitelisted', async function () {
const res1 = await getCustomConfig(servers[0].url, servers[0].accessToken)
const config = res1.body
config.services.twitter = {
username: '@Kuja',
whitelisted: true
} }
await updateCustomConfig(servers[0].url, servers[0].accessToken, config)
const resVideoRequest = await request(servers[0].url) it('Should have valid twitter card on the watch video page', async function () {
.get('/videos/watch/' + servers[0].video.uuid) const res = await request(servers[0].url)
.set('Accept', 'text/html') .get('/videos/watch/' + servers[0].video.uuid)
.expect(HttpStatusCode.OK_200) .set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
expect(resVideoRequest.text).to.contain('<meta property="twitter:card" content="player" />') expect(res.text).to.contain('<meta property="twitter:card" content="player" />')
expect(resVideoRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
const resVideoPlaylistRequest = await request(servers[0].url)
.get('/videos/watch/playlist/' + playlistUUID)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:card" content="player" />')
expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
const accountTests = (res) => {
expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />') expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
} })
accountTests(await request(servers[0].url) it('Should have valid twitter card on the watch playlist page', async function () {
.get('/accounts/' + account.name) const res = await request(servers[0].url)
.set('Accept', 'text/html') .get('/videos/watch/playlist/' + playlistUUID)
.expect(HttpStatusCode.OK_200)) .set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200)
accountTests(await request(servers[0].url) expect(res.text).to.contain('<meta property="twitter:card" content="player" />')
.get('/a/' + account.name)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
accountTests(await request(servers[0].url)
.get('/@' + account.name)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
const channelTests = (res) => {
expect(res.text).to.contain('<meta property="twitter:card" content="summary" />')
expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />') expect(res.text).to.contain('<meta property="twitter:site" content="@Kuja" />')
} })
channelTests(await request(servers[0].url) it('Should have valid twitter card on the account page', async function () {
.get('/video-channels/' + servers[0].videoChannel.name) await accountPageTest('/accounts/' + account.name)
.set('Accept', 'text/html') await accountPageTest('/a/' + account.name)
.expect(HttpStatusCode.OK_200)) await accountPageTest('/@' + account.name)
})
channelTests(await request(servers[0].url) it('Should have valid twitter card on the channel page', async function () {
.get('/c/' + servers[0].videoChannel.name) await channelPageTest('/video-channels/' + servers[0].videoChannel.name)
.set('Accept', 'text/html') await channelPageTest('/c/' + servers[0].videoChannel.name)
.expect(HttpStatusCode.OK_200)) await channelPageTest('/@' + servers[0].videoChannel.name)
})
channelTests(await request(servers[0].url)
.get('/@' + servers[0].videoChannel.name)
.set('Accept', 'text/html')
.expect(HttpStatusCode.OK_200))
}) })
}) })

View File

@ -1,18 +0,0 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { makeGetRequest } from '../requests/requests'
import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
function getActor (url: string, actorName: string, statusCodeExpected = HttpStatusCode.OK_200) {
const path = '/api/v1/actors/' + actorName
return makeGetRequest({
url,
path,
statusCodeExpected
})
}
export {
getActor
}

View File

@ -1,4 +1,3 @@
export * from './actors/actors'
export * from './bulk/bulk' export * from './bulk/bulk'
export * from './cli/cli' export * from './cli/cli'

View File

@ -26,6 +26,7 @@ function makeGetRequest (options: {
contentType?: string contentType?: string
range?: string range?: string
redirects?: number redirects?: number
accept?: string
}) { }) {
if (!options.statusCodeExpected) options.statusCodeExpected = HttpStatusCode.BAD_REQUEST_400 if (!options.statusCodeExpected) options.statusCodeExpected = HttpStatusCode.BAD_REQUEST_400
if (options.contentType === undefined) options.contentType = 'application/json' if (options.contentType === undefined) options.contentType = 'application/json'
@ -36,6 +37,7 @@ function makeGetRequest (options: {
if (options.token) req.set('Authorization', 'Bearer ' + options.token) if (options.token) req.set('Authorization', 'Bearer ' + options.token)
if (options.query) req.query(options.query) if (options.query) req.query(options.query)
if (options.range) req.set('Range', options.range) if (options.range) req.set('Range', options.range)
if (options.accept) req.set('Accept', options.accept)
if (options.redirects) req.redirects(options.redirects) if (options.redirects) req.redirects(options.redirects)
return req.expect(options.statusCodeExpected) return req.expect(options.statusCodeExpected)