Add ability to customize token lifetime
This commit is contained in:
parent
518c5cc62d
commit
b65f5367ba
|
@ -37,6 +37,11 @@ rates_limit:
|
||||||
window: 10 minutes
|
window: 10 minutes
|
||||||
max: 10
|
max: 10
|
||||||
|
|
||||||
|
oauth2:
|
||||||
|
token_lifetime:
|
||||||
|
access_token: '1 day'
|
||||||
|
refresh_token: '2 weeks'
|
||||||
|
|
||||||
# Proxies to trust to get real client IP
|
# Proxies to trust to get real client IP
|
||||||
# If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
|
# If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
|
||||||
# If you run PeerTube behind a remote proxy, add the proxy IP address (or subnet)
|
# If you run PeerTube behind a remote proxy, add the proxy IP address (or subnet)
|
||||||
|
|
|
@ -35,6 +35,11 @@ rates_limit:
|
||||||
window: 10 minutes
|
window: 10 minutes
|
||||||
max: 10
|
max: 10
|
||||||
|
|
||||||
|
oauth2:
|
||||||
|
token_lifetime:
|
||||||
|
access_token: '1 day'
|
||||||
|
refresh_token: '2 weeks'
|
||||||
|
|
||||||
# Proxies to trust to get real client IP
|
# Proxies to trust to get real client IP
|
||||||
# If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
|
# If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
|
||||||
# If you run PeerTube behind a remote proxy, add the proxy IP address (or subnet)
|
# If you run PeerTube behind a remote proxy, add the proxy IP address (or subnet)
|
||||||
|
|
|
@ -13,6 +13,7 @@ function checkMissedConfig () {
|
||||||
'webserver.https', 'webserver.hostname', 'webserver.port',
|
'webserver.https', 'webserver.hostname', 'webserver.port',
|
||||||
'secrets.peertube',
|
'secrets.peertube',
|
||||||
'trust_proxy',
|
'trust_proxy',
|
||||||
|
'oauth2.token_lifetime.access_token', 'oauth2.token_lifetime.refresh_token',
|
||||||
'database.hostname', 'database.port', 'database.username', 'database.password', 'database.pool.max',
|
'database.hostname', 'database.port', 'database.username', 'database.password', 'database.pool.max',
|
||||||
'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
|
'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
|
||||||
'email.body.signature', 'email.subject.prefix',
|
'email.body.signature', 'email.subject.prefix',
|
||||||
|
|
|
@ -149,6 +149,12 @@ const CONFIG = {
|
||||||
HOSTNAME: config.get<string>('webserver.hostname'),
|
HOSTNAME: config.get<string>('webserver.hostname'),
|
||||||
PORT: config.get<number>('webserver.port')
|
PORT: config.get<number>('webserver.port')
|
||||||
},
|
},
|
||||||
|
OAUTH2: {
|
||||||
|
TOKEN_LIFETIME: {
|
||||||
|
ACCESS_TOKEN: parseDurationToMs(config.get<string>('oauth2.token_lifetime.access_token')),
|
||||||
|
REFRESH_TOKEN: parseDurationToMs(config.get<string>('oauth2.token_lifetime.refresh_token'))
|
||||||
|
}
|
||||||
|
},
|
||||||
RATES_LIMIT: {
|
RATES_LIMIT: {
|
||||||
API: {
|
API: {
|
||||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.api.window')),
|
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.api.window')),
|
||||||
|
|
|
@ -101,11 +101,6 @@ const SORTABLE_COLUMNS = {
|
||||||
VIDEO_REDUNDANCIES: [ 'name' ]
|
VIDEO_REDUNDANCIES: [ 'name' ]
|
||||||
}
|
}
|
||||||
|
|
||||||
const OAUTH_LIFETIME = {
|
|
||||||
ACCESS_TOKEN: 3600 * 24, // 1 day, for upload
|
|
||||||
REFRESH_TOKEN: 1209600 // 2 weeks
|
|
||||||
}
|
|
||||||
|
|
||||||
const ROUTE_CACHE_LIFETIME = {
|
const ROUTE_CACHE_LIFETIME = {
|
||||||
FEEDS: '15 minutes',
|
FEEDS: '15 minutes',
|
||||||
ROBOTS: '2 hours',
|
ROBOTS: '2 hours',
|
||||||
|
@ -1033,7 +1028,6 @@ export {
|
||||||
JOB_ATTEMPTS,
|
JOB_ATTEMPTS,
|
||||||
AP_CLEANER,
|
AP_CLEANER,
|
||||||
LAST_MIGRATION_VERSION,
|
LAST_MIGRATION_VERSION,
|
||||||
OAUTH_LIFETIME,
|
|
||||||
CUSTOM_HTML_TAG_COMMENTS,
|
CUSTOM_HTML_TAG_COMMENTS,
|
||||||
STATS_TIMESERIE,
|
STATS_TIMESERIE,
|
||||||
BROADCAST_CONCURRENCY,
|
BROADCAST_CONCURRENCY,
|
||||||
|
|
|
@ -10,10 +10,11 @@ import OAuth2Server, {
|
||||||
} from '@node-oauth/oauth2-server'
|
} from '@node-oauth/oauth2-server'
|
||||||
import { randomBytesPromise } from '@server/helpers/core-utils'
|
import { randomBytesPromise } from '@server/helpers/core-utils'
|
||||||
import { isOTPValid } from '@server/helpers/otp'
|
import { isOTPValid } from '@server/helpers/otp'
|
||||||
|
import { CONFIG } from '@server/initializers/config'
|
||||||
import { MOAuthClient } from '@server/types/models'
|
import { MOAuthClient } from '@server/types/models'
|
||||||
import { sha1 } from '@shared/extra-utils'
|
import { sha1 } from '@shared/extra-utils'
|
||||||
import { HttpStatusCode } from '@shared/models'
|
import { HttpStatusCode } from '@shared/models'
|
||||||
import { OAUTH_LIFETIME, OTP } from '../../initializers/constants'
|
import { OTP } from '../../initializers/constants'
|
||||||
import { BypassLogin, getClient, getRefreshToken, getUser, revokeToken, saveToken } from './oauth-model'
|
import { BypassLogin, getClient, getRefreshToken, getUser, revokeToken, saveToken } from './oauth-model'
|
||||||
|
|
||||||
class MissingTwoFactorError extends Error {
|
class MissingTwoFactorError extends Error {
|
||||||
|
@ -32,8 +33,9 @@ class InvalidTwoFactorError extends Error {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const oAuthServer = new OAuth2Server({
|
const oAuthServer = new OAuth2Server({
|
||||||
accessTokenLifetime: OAUTH_LIFETIME.ACCESS_TOKEN,
|
// Wants seconds
|
||||||
refreshTokenLifetime: OAUTH_LIFETIME.REFRESH_TOKEN,
|
accessTokenLifetime: CONFIG.OAUTH2.TOKEN_LIFETIME.ACCESS_TOKEN / 1000,
|
||||||
|
refreshTokenLifetime: CONFIG.OAUTH2.TOKEN_LIFETIME.REFRESH_TOKEN / 1000,
|
||||||
|
|
||||||
// See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
|
// See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
|
||||||
model: require('./oauth-model')
|
model: require('./oauth-model')
|
||||||
|
@ -182,10 +184,10 @@ function generateRandomToken () {
|
||||||
|
|
||||||
function getTokenExpiresAt (type: 'access' | 'refresh') {
|
function getTokenExpiresAt (type: 'access' | 'refresh') {
|
||||||
const lifetime = type === 'access'
|
const lifetime = type === 'access'
|
||||||
? OAUTH_LIFETIME.ACCESS_TOKEN
|
? CONFIG.OAUTH2.TOKEN_LIFETIME.ACCESS_TOKEN
|
||||||
: OAUTH_LIFETIME.REFRESH_TOKEN
|
: CONFIG.OAUTH2.TOKEN_LIFETIME.REFRESH_TOKEN
|
||||||
|
|
||||||
return new Date(Date.now() + lifetime * 1000)
|
return new Date(Date.now() + lifetime)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildToken () {
|
async function buildToken () {
|
||||||
|
|
|
@ -36,8 +36,8 @@ export class TokensCache {
|
||||||
const token = this.userHavingToken.get(userId)
|
const token = this.userHavingToken.get(userId)
|
||||||
|
|
||||||
if (token !== undefined) {
|
if (token !== undefined) {
|
||||||
this.accessTokenCache.del(token)
|
this.accessTokenCache.delete(token)
|
||||||
this.userHavingToken.del(userId)
|
this.userHavingToken.delete(userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +45,8 @@ export class TokensCache {
|
||||||
const tokenModel = this.accessTokenCache.get(token)
|
const tokenModel = this.accessTokenCache.get(token)
|
||||||
|
|
||||||
if (tokenModel !== undefined) {
|
if (tokenModel !== undefined) {
|
||||||
this.userHavingToken.del(tokenModel.userId)
|
this.userHavingToken.delete(tokenModel.userId)
|
||||||
this.accessTokenCache.del(token)
|
this.accessTokenCache.delete(token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import './oauth'
|
||||||
import './two-factor'
|
import './two-factor'
|
||||||
import './user-subscriptions'
|
import './user-subscriptions'
|
||||||
import './user-videos'
|
import './user-videos'
|
||||||
|
|
192
server/tests/api/users/oauth.ts
Normal file
192
server/tests/api/users/oauth.ts
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import { wait } from '@shared/core-utils'
|
||||||
|
import { HttpStatusCode, OAuth2ErrorCode, PeerTubeProblemDocument } from '@shared/models'
|
||||||
|
import { cleanupTests, createSingleServer, killallServers, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
|
||||||
|
|
||||||
|
describe('Test oauth', function () {
|
||||||
|
let server: PeerTubeServer
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(30000)
|
||||||
|
|
||||||
|
server = await createSingleServer(1, {
|
||||||
|
rates_limit: {
|
||||||
|
login: {
|
||||||
|
max: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await setAccessTokensToServers([ server ])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('OAuth client', function () {
|
||||||
|
|
||||||
|
function expectInvalidClient (body: PeerTubeProblemDocument) {
|
||||||
|
expect(body.code).to.equal(OAuth2ErrorCode.INVALID_CLIENT)
|
||||||
|
expect(body.error).to.contain('client is invalid')
|
||||||
|
expect(body.type.startsWith('https://')).to.be.true
|
||||||
|
expect(body.type).to.contain(OAuth2ErrorCode.INVALID_CLIENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Should create a new client')
|
||||||
|
|
||||||
|
it('Should return the first client')
|
||||||
|
|
||||||
|
it('Should remove the last client')
|
||||||
|
|
||||||
|
it('Should not login with an invalid client id', async function () {
|
||||||
|
const client = { id: 'client', secret: server.store.client.secret }
|
||||||
|
const body = await server.login.login({ client, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||||
|
|
||||||
|
expectInvalidClient(body)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not login with an invalid client secret', async function () {
|
||||||
|
const client = { id: server.store.client.id, secret: 'coucou' }
|
||||||
|
const body = await server.login.login({ client, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||||
|
|
||||||
|
expectInvalidClient(body)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Login', function () {
|
||||||
|
|
||||||
|
function expectInvalidCredentials (body: PeerTubeProblemDocument) {
|
||||||
|
expect(body.code).to.equal(OAuth2ErrorCode.INVALID_GRANT)
|
||||||
|
expect(body.error).to.contain('credentials are invalid')
|
||||||
|
expect(body.type.startsWith('https://')).to.be.true
|
||||||
|
expect(body.type).to.contain(OAuth2ErrorCode.INVALID_GRANT)
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Should not login with an invalid username', async function () {
|
||||||
|
const user = { username: 'captain crochet', password: server.store.user.password }
|
||||||
|
const body = await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||||
|
|
||||||
|
expectInvalidCredentials(body)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not login with an invalid password', async function () {
|
||||||
|
const user = { username: server.store.user.username, password: 'mew_three' }
|
||||||
|
const body = await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||||
|
|
||||||
|
expectInvalidCredentials(body)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should be able to login', async function () {
|
||||||
|
await server.login.login({ expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should be able to login with an insensitive username', async function () {
|
||||||
|
const user = { username: 'RoOt', password: server.store.user.password }
|
||||||
|
await server.login.login({ user, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
|
||||||
|
const user2 = { username: 'rOoT', password: server.store.user.password }
|
||||||
|
await server.login.login({ user: user2, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
|
||||||
|
const user3 = { username: 'ROOt', password: server.store.user.password }
|
||||||
|
await server.login.login({ user: user3, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Logout', function () {
|
||||||
|
|
||||||
|
it('Should logout (revoke token)', async function () {
|
||||||
|
await server.login.logout({ token: server.accessToken })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not be able to get the user information', async function () {
|
||||||
|
await server.users.getMyInfo({ expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not be able to upload a video', async function () {
|
||||||
|
await server.videos.upload({ attributes: { name: 'video' }, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should be able to login again', async function () {
|
||||||
|
const body = await server.login.login()
|
||||||
|
server.accessToken = body.access_token
|
||||||
|
server.refreshToken = body.refresh_token
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should be able to get my user information again', async function () {
|
||||||
|
await server.users.getMyInfo()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have an expired access token', async function () {
|
||||||
|
this.timeout(60000)
|
||||||
|
|
||||||
|
await server.sql.setTokenField(server.accessToken, 'accessTokenExpiresAt', new Date().toISOString())
|
||||||
|
await server.sql.setTokenField(server.accessToken, 'refreshTokenExpiresAt', new Date().toISOString())
|
||||||
|
|
||||||
|
await killallServers([ server ])
|
||||||
|
await server.run()
|
||||||
|
|
||||||
|
await server.users.getMyInfo({ expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not be able to refresh an access token with an expired refresh token', async function () {
|
||||||
|
await server.login.refreshToken({ refreshToken: server.refreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should refresh the token', async function () {
|
||||||
|
this.timeout(50000)
|
||||||
|
|
||||||
|
const futureDate = new Date(new Date().getTime() + 1000 * 60).toISOString()
|
||||||
|
await server.sql.setTokenField(server.accessToken, 'refreshTokenExpiresAt', futureDate)
|
||||||
|
|
||||||
|
await killallServers([ server ])
|
||||||
|
await server.run()
|
||||||
|
|
||||||
|
const res = await server.login.refreshToken({ refreshToken: server.refreshToken })
|
||||||
|
server.accessToken = res.body.access_token
|
||||||
|
server.refreshToken = res.body.refresh_token
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should be able to get my user information again', async function () {
|
||||||
|
await server.users.getMyInfo()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Custom token lifetime', function () {
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(120_000)
|
||||||
|
|
||||||
|
await server.kill()
|
||||||
|
await server.run({
|
||||||
|
oauth2: {
|
||||||
|
token_lifetime: {
|
||||||
|
access_token: '2 seconds',
|
||||||
|
refresh_token: '2 seconds'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have a very short access token lifetime', async function () {
|
||||||
|
this.timeout(50000)
|
||||||
|
|
||||||
|
const { access_token: accessToken } = await server.login.login()
|
||||||
|
await server.users.getMyInfo({ token: accessToken })
|
||||||
|
|
||||||
|
await wait(3000)
|
||||||
|
await server.users.getMyInfo({ token: accessToken, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have a very short refresh token lifetime', async function () {
|
||||||
|
this.timeout(50000)
|
||||||
|
|
||||||
|
const { refresh_token: refreshToken } = await server.login.login()
|
||||||
|
await server.login.refreshToken({ refreshToken })
|
||||||
|
|
||||||
|
await wait(3000)
|
||||||
|
await server.login.refreshToken({ refreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await cleanupTests([ server ])
|
||||||
|
})
|
||||||
|
})
|
|
@ -2,15 +2,8 @@
|
||||||
|
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { testImage } from '@server/tests/shared'
|
import { testImage } from '@server/tests/shared'
|
||||||
import { AbuseState, HttpStatusCode, OAuth2ErrorCode, UserAdminFlag, UserRole, VideoPlaylistType } from '@shared/models'
|
import { AbuseState, HttpStatusCode, UserAdminFlag, UserRole, VideoPlaylistType } from '@shared/models'
|
||||||
import {
|
import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
|
||||||
cleanupTests,
|
|
||||||
createSingleServer,
|
|
||||||
killallServers,
|
|
||||||
makePutBodyRequest,
|
|
||||||
PeerTubeServer,
|
|
||||||
setAccessTokensToServers
|
|
||||||
} from '@shared/server-commands'
|
|
||||||
|
|
||||||
describe('Test users', function () {
|
describe('Test users', function () {
|
||||||
let server: PeerTubeServer
|
let server: PeerTubeServer
|
||||||
|
@ -39,166 +32,6 @@ describe('Test users', function () {
|
||||||
await server.plugins.install({ npmName: 'peertube-theme-background-red' })
|
await server.plugins.install({ npmName: 'peertube-theme-background-red' })
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('OAuth client', function () {
|
|
||||||
it('Should create a new client')
|
|
||||||
|
|
||||||
it('Should return the first client')
|
|
||||||
|
|
||||||
it('Should remove the last client')
|
|
||||||
|
|
||||||
it('Should not login with an invalid client id', async function () {
|
|
||||||
const client = { id: 'client', secret: server.store.client.secret }
|
|
||||||
const body = await server.login.login({ client, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
|
||||||
|
|
||||||
expect(body.code).to.equal(OAuth2ErrorCode.INVALID_CLIENT)
|
|
||||||
expect(body.error).to.contain('client is invalid')
|
|
||||||
expect(body.type.startsWith('https://')).to.be.true
|
|
||||||
expect(body.type).to.contain(OAuth2ErrorCode.INVALID_CLIENT)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not login with an invalid client secret', async function () {
|
|
||||||
const client = { id: server.store.client.id, secret: 'coucou' }
|
|
||||||
const body = await server.login.login({ client, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
|
||||||
|
|
||||||
expect(body.code).to.equal(OAuth2ErrorCode.INVALID_CLIENT)
|
|
||||||
expect(body.error).to.contain('client is invalid')
|
|
||||||
expect(body.type.startsWith('https://')).to.be.true
|
|
||||||
expect(body.type).to.contain(OAuth2ErrorCode.INVALID_CLIENT)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Login', function () {
|
|
||||||
|
|
||||||
it('Should not login with an invalid username', async function () {
|
|
||||||
const user = { username: 'captain crochet', password: server.store.user.password }
|
|
||||||
const body = await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
|
||||||
|
|
||||||
expect(body.code).to.equal(OAuth2ErrorCode.INVALID_GRANT)
|
|
||||||
expect(body.error).to.contain('credentials are invalid')
|
|
||||||
expect(body.type.startsWith('https://')).to.be.true
|
|
||||||
expect(body.type).to.contain(OAuth2ErrorCode.INVALID_GRANT)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not login with an invalid password', async function () {
|
|
||||||
const user = { username: server.store.user.username, password: 'mew_three' }
|
|
||||||
const body = await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
|
||||||
|
|
||||||
expect(body.code).to.equal(OAuth2ErrorCode.INVALID_GRANT)
|
|
||||||
expect(body.error).to.contain('credentials are invalid')
|
|
||||||
expect(body.type.startsWith('https://')).to.be.true
|
|
||||||
expect(body.type).to.contain(OAuth2ErrorCode.INVALID_GRANT)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not be able to upload a video', async function () {
|
|
||||||
token = 'my_super_token'
|
|
||||||
|
|
||||||
await server.videos.upload({ token, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not be able to follow', async function () {
|
|
||||||
token = 'my_super_token'
|
|
||||||
|
|
||||||
await server.follows.follow({
|
|
||||||
hosts: [ 'http://example.com' ],
|
|
||||||
token,
|
|
||||||
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not be able to unfollow')
|
|
||||||
|
|
||||||
it('Should be able to login', async function () {
|
|
||||||
const body = await server.login.login({ expectedStatus: HttpStatusCode.OK_200 })
|
|
||||||
|
|
||||||
token = body.access_token
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should be able to login with an insensitive username', async function () {
|
|
||||||
const user = { username: 'RoOt', password: server.store.user.password }
|
|
||||||
await server.login.login({ user, expectedStatus: HttpStatusCode.OK_200 })
|
|
||||||
|
|
||||||
const user2 = { username: 'rOoT', password: server.store.user.password }
|
|
||||||
await server.login.login({ user: user2, expectedStatus: HttpStatusCode.OK_200 })
|
|
||||||
|
|
||||||
const user3 = { username: 'ROOt', password: server.store.user.password }
|
|
||||||
await server.login.login({ user: user3, expectedStatus: HttpStatusCode.OK_200 })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Logout', function () {
|
|
||||||
it('Should logout (revoke token)', async function () {
|
|
||||||
await server.login.logout({ token: server.accessToken })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not be able to get the user information', async function () {
|
|
||||||
await server.users.getMyInfo({ expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not be able to upload a video', async function () {
|
|
||||||
await server.videos.upload({ attributes: { name: 'video' }, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not be able to rate a video', async function () {
|
|
||||||
const path = '/api/v1/videos/'
|
|
||||||
const data = {
|
|
||||||
rating: 'likes'
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
url: server.url,
|
|
||||||
path: path + videoId,
|
|
||||||
token: 'wrong token',
|
|
||||||
fields: data,
|
|
||||||
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
|
|
||||||
}
|
|
||||||
await makePutBodyRequest(options)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should be able to login again', async function () {
|
|
||||||
const body = await server.login.login()
|
|
||||||
server.accessToken = body.access_token
|
|
||||||
server.refreshToken = body.refresh_token
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should be able to get my user information again', async function () {
|
|
||||||
await server.users.getMyInfo()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should have an expired access token', async function () {
|
|
||||||
this.timeout(60000)
|
|
||||||
|
|
||||||
await server.sql.setTokenField(server.accessToken, 'accessTokenExpiresAt', new Date().toISOString())
|
|
||||||
await server.sql.setTokenField(server.accessToken, 'refreshTokenExpiresAt', new Date().toISOString())
|
|
||||||
|
|
||||||
await killallServers([ server ])
|
|
||||||
await server.run()
|
|
||||||
|
|
||||||
await server.users.getMyInfo({ expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should not be able to refresh an access token with an expired refresh token', async function () {
|
|
||||||
await server.login.refreshToken({ refreshToken: server.refreshToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should refresh the token', async function () {
|
|
||||||
this.timeout(50000)
|
|
||||||
|
|
||||||
const futureDate = new Date(new Date().getTime() + 1000 * 60).toISOString()
|
|
||||||
await server.sql.setTokenField(server.accessToken, 'refreshTokenExpiresAt', futureDate)
|
|
||||||
|
|
||||||
await killallServers([ server ])
|
|
||||||
await server.run()
|
|
||||||
|
|
||||||
const res = await server.login.refreshToken({ refreshToken: server.refreshToken })
|
|
||||||
server.accessToken = res.body.access_token
|
|
||||||
server.refreshToken = res.body.refresh_token
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should be able to get my user information again', async function () {
|
|
||||||
await server.users.getMyInfo()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Creating a user', function () {
|
describe('Creating a user', function () {
|
||||||
|
|
||||||
it('Should be able to create a new user', async function () {
|
it('Should be able to create a new user', async function () {
|
||||||
|
@ -512,6 +345,7 @@ describe('Test users', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Updating another user', function () {
|
describe('Updating another user', function () {
|
||||||
|
|
||||||
it('Should be able to update another user', async function () {
|
it('Should be able to update another user', async function () {
|
||||||
await server.users.update({
|
await server.users.update({
|
||||||
userId,
|
userId,
|
||||||
|
@ -562,13 +396,6 @@ describe('Test users', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Video blacklists', function () {
|
|
||||||
|
|
||||||
it('Should be able to list my video blacklist', async function () {
|
|
||||||
await server.blacklist.list({ token: userToken })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Remove a user', function () {
|
describe('Remove a user', function () {
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
|
@ -653,8 +480,9 @@ describe('Test users', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('User blocking', function () {
|
describe('User blocking', function () {
|
||||||
let user16Id
|
let user16Id: number
|
||||||
let user16AccessToken
|
let user16AccessToken: string
|
||||||
|
|
||||||
const user16 = {
|
const user16 = {
|
||||||
username: 'user_16',
|
username: 'user_16',
|
||||||
password: 'my super password'
|
password: 'my super password'
|
||||||
|
|
|
@ -199,7 +199,7 @@ function buildRequest (req: request.Test, options: CommonRequestParams) {
|
||||||
return req.expect((res) => {
|
return req.expect((res) => {
|
||||||
if (options.expectedStatus && res.status !== options.expectedStatus) {
|
if (options.expectedStatus && res.status !== options.expectedStatus) {
|
||||||
throw new Error(`Expected status ${options.expectedStatus}, got ${res.status}. ` +
|
throw new Error(`Expected status ${options.expectedStatus}, got ${res.status}. ` +
|
||||||
`\nThe server responded this error: "${res.body?.error ?? res.text}".\n` +
|
`\nThe server responded: "${res.body?.error ?? res.text}".\n` +
|
||||||
'You may take a closer look at the logs. To see how to do so, check out this page: ' +
|
'You may take a closer look at the logs. To see how to do so, check out this page: ' +
|
||||||
'https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/development/tests.md#debug-server-logs')
|
'https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/development/tests.md#debug-server-logs')
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user