From b379759f55a35837b803a3b988674972db2903d1 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 19 Jan 2023 09:28:29 +0100 Subject: Add signup approval API tests --- server/tests/api/check-params/config.ts | 2 + server/tests/api/check-params/contact-form.ts | 13 +- server/tests/api/check-params/index.ts | 3 +- server/tests/api/check-params/registrations.ts | 402 +++++++++++++++++++++ server/tests/api/check-params/upload-quota.ts | 4 +- server/tests/api/check-params/users-admin.ts | 9 +- server/tests/api/check-params/users-emails.ts | 119 ++++++ server/tests/api/check-params/users.ts | 255 ------------- server/tests/api/notifications/index.ts | 1 + .../api/notifications/moderation-notifications.ts | 27 -- .../notifications/registrations-notifications.ts | 88 +++++ server/tests/api/server/config-defaults.ts | 4 +- server/tests/api/server/config.ts | 9 +- server/tests/api/server/contact-form.ts | 9 +- server/tests/api/server/email.ts | 77 +++- server/tests/api/server/reverse-proxy.ts | 8 +- server/tests/api/users/index.ts | 3 +- server/tests/api/users/registrations.ts | 379 +++++++++++++++++++ server/tests/api/users/users-email-verification.ts | 167 +++++++++ server/tests/api/users/users-verification.ts | 170 --------- server/tests/api/users/users.ts | 50 --- server/tests/external-plugins/akismet.ts | 4 +- server/tests/fixtures/peertube-plugin-test/main.js | 29 +- server/tests/plugins/action-hooks.ts | 2 +- server/tests/plugins/filter-hooks.ts | 49 ++- server/tests/shared/notifications.ts | 45 ++- 26 files changed, 1363 insertions(+), 565 deletions(-) create mode 100644 server/tests/api/check-params/registrations.ts create mode 100644 server/tests/api/check-params/users-emails.ts delete mode 100644 server/tests/api/check-params/users.ts create mode 100644 server/tests/api/notifications/registrations-notifications.ts create mode 100644 server/tests/api/users/registrations.ts create mode 100644 server/tests/api/users/users-email-verification.ts delete mode 100644 server/tests/api/users/users-verification.ts (limited to 'server') diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index 3415625ca..93a3f3eb9 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts @@ -79,6 +79,7 @@ describe('Test config API validators', function () { signup: { enabled: false, limit: 5, + requiresApproval: false, requiresEmailVerification: false, minimumAge: 16 }, @@ -313,6 +314,7 @@ describe('Test config API validators', function () { signup: { enabled: true, limit: 5, + requiresApproval: true, requiresEmailVerification: true } } diff --git a/server/tests/api/check-params/contact-form.ts b/server/tests/api/check-params/contact-form.ts index 7968ef802..f0f8819b9 100644 --- a/server/tests/api/check-params/contact-form.ts +++ b/server/tests/api/check-params/contact-form.ts @@ -2,7 +2,14 @@ import { MockSmtpServer } from '@server/tests/shared' import { HttpStatusCode } from '@shared/models' -import { cleanupTests, ContactFormCommand, createSingleServer, killallServers, PeerTubeServer } from '@shared/server-commands' +import { + cleanupTests, + ConfigCommand, + ContactFormCommand, + createSingleServer, + killallServers, + PeerTubeServer +} from '@shared/server-commands' describe('Test contact form API validators', function () { let server: PeerTubeServer @@ -38,7 +45,7 @@ describe('Test contact form API validators', function () { await killallServers([ server ]) // Contact form is disabled - await server.run({ smtp: { hostname: '127.0.0.1', port: emailPort }, contact_form: { enabled: false } }) + await server.run({ ...ConfigCommand.getEmailOverrideConfig(emailPort), contact_form: { enabled: false } }) await command.send({ ...defaultBody, expectedStatus: HttpStatusCode.CONFLICT_409 }) }) @@ -48,7 +55,7 @@ describe('Test contact form API validators', function () { await killallServers([ server ]) // Email & contact form enabled - await server.run({ smtp: { hostname: '127.0.0.1', port: emailPort } }) + await server.run(ConfigCommand.getEmailOverrideConfig(emailPort)) await command.send({ ...defaultBody, fromEmail: 'badEmail', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) await command.send({ ...defaultBody, fromEmail: 'badEmail@', expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index 961093bb5..ddbcb42f8 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts @@ -15,6 +15,7 @@ import './metrics' import './my-user' import './plugins' import './redundancy' +import './registrations' import './search' import './services' import './transcoding' @@ -23,7 +24,7 @@ import './upload-quota' import './user-notifications' import './user-subscriptions' import './users-admin' -import './users' +import './users-emails' import './video-blacklist' import './video-captions' import './video-channel-syncs' diff --git a/server/tests/api/check-params/registrations.ts b/server/tests/api/check-params/registrations.ts new file mode 100644 index 000000000..9f0462378 --- /dev/null +++ b/server/tests/api/check-params/registrations.ts @@ -0,0 +1,402 @@ +import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared' +import { omit } from '@shared/core-utils' +import { HttpStatusCode, UserRole } from '@shared/models' +import { cleanupTests, createSingleServer, makePostBodyRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' + +describe('Test registrations API validators', function () { + let server: PeerTubeServer + let userToken: string + let moderatorToken: string + + // --------------------------------------------------------------- + + before(async function () { + this.timeout(30000) + + server = await createSingleServer(1) + + await setAccessTokensToServers([ server ]) + await server.config.enableSignup(false); + + ({ token: moderatorToken } = await server.users.generate('moderator', UserRole.MODERATOR)); + ({ token: userToken } = await server.users.generate('user', UserRole.USER)) + }) + + describe('Register', function () { + const registrationPath = '/api/v1/users/register' + const registrationRequestPath = '/api/v1/users/registrations/request' + + const baseCorrectParams = { + username: 'user3', + displayName: 'super user', + email: 'test3@example.com', + password: 'my super password', + registrationReason: 'my super registration reason' + } + + describe('When registering a new user or requesting user registration', function () { + + async function check (fields: any, expectedStatus = HttpStatusCode.BAD_REQUEST_400) { + await makePostBodyRequest({ url: server.url, path: registrationPath, fields, expectedStatus }) + await makePostBodyRequest({ url: server.url, path: registrationRequestPath, fields, expectedStatus }) + } + + it('Should fail with a too small username', async function () { + const fields = { ...baseCorrectParams, username: '' } + + await check(fields) + }) + + it('Should fail with a too long username', async function () { + const fields = { ...baseCorrectParams, username: 'super'.repeat(50) } + + await check(fields) + }) + + it('Should fail with an incorrect username', async function () { + const fields = { ...baseCorrectParams, username: 'my username' } + + await check(fields) + }) + + it('Should fail with a missing email', async function () { + const fields = omit(baseCorrectParams, [ 'email' ]) + + await check(fields) + }) + + it('Should fail with an invalid email', async function () { + const fields = { ...baseCorrectParams, email: 'test_example.com' } + + await check(fields) + }) + + it('Should fail with a too small password', async function () { + const fields = { ...baseCorrectParams, password: 'bla' } + + await check(fields) + }) + + it('Should fail with a too long password', async function () { + const fields = { ...baseCorrectParams, password: 'super'.repeat(61) } + + await check(fields) + }) + + it('Should fail if we register a user with the same username', async function () { + const fields = { ...baseCorrectParams, username: 'root' } + + await check(fields, HttpStatusCode.CONFLICT_409) + }) + + it('Should fail with a "peertube" username', async function () { + const fields = { ...baseCorrectParams, username: 'peertube' } + + await check(fields, HttpStatusCode.CONFLICT_409) + }) + + it('Should fail if we register a user with the same email', async function () { + const fields = { ...baseCorrectParams, email: 'admin' + server.internalServerNumber + '@example.com' } + + await check(fields, HttpStatusCode.CONFLICT_409) + }) + + it('Should fail with a bad display name', async function () { + const fields = { ...baseCorrectParams, displayName: 'a'.repeat(150) } + + await check(fields) + }) + + it('Should fail with a bad channel name', async function () { + const fields = { ...baseCorrectParams, channel: { name: '[]azf', displayName: 'toto' } } + + await check(fields) + }) + + it('Should fail with a bad channel display name', async function () { + const fields = { ...baseCorrectParams, channel: { name: 'toto', displayName: '' } } + + await check(fields) + }) + + it('Should fail with a channel name that is the same as username', async function () { + const source = { username: 'super_user', channel: { name: 'super_user', displayName: 'display name' } } + const fields = { ...baseCorrectParams, ...source } + + await check(fields) + }) + + it('Should fail with an existing channel', async function () { + const attributes = { name: 'existing_channel', displayName: 'hello', description: 'super description' } + await server.channels.create({ attributes }) + + const fields = { ...baseCorrectParams, channel: { name: 'existing_channel', displayName: 'toto' } } + + await check(fields, HttpStatusCode.CONFLICT_409) + }) + + it('Should fail on a server with registration disabled', async function () { + this.timeout(60000) + + await server.config.updateCustomSubConfig({ + newConfig: { + signup: { + enabled: false + } + } + }) + + await server.registrations.register({ username: 'user4', expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + await server.registrations.requestRegistration({ + username: 'user4', + registrationReason: 'reason', + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should fail if the user limit is reached', async function () { + this.timeout(60000) + + const { total } = await server.users.list() + + await server.config.updateCustomSubConfig({ newConfig: { signup: { limit: total } } }) + + await server.registrations.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + await server.registrations.requestRegistration({ + username: 'user42', + registrationReason: 'reason', + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + }) + + describe('On direct registration', function () { + + it('Should succeed with the correct params', async function () { + await server.config.enableSignup(false) + + const fields = { + username: 'user_direct_1', + displayName: 'super user direct 1', + email: 'user_direct_1@example.com', + password: 'my super password', + channel: { name: 'super_user_direct_1_channel', displayName: 'super user direct 1 channel' } + } + + await makePostBodyRequest({ url: server.url, path: registrationPath, fields, expectedStatus: HttpStatusCode.NO_CONTENT_204 }) + }) + + it('Should fail if the instance requires approval', async function () { + this.timeout(60000) + + await server.config.enableSignup(true) + await server.registrations.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + }) + }) + + describe('On registration request', function () { + + before(async function () { + this.timeout(60000) + + await server.config.enableSignup(true) + }) + + it('Should fail with an invalid registration reason', async function () { + for (const registrationReason of [ '', 't', 't'.repeat(5000) ]) { + await server.registrations.requestRegistration({ + username: 'user_request_1', + registrationReason, + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) + } + }) + + it('Should succeed with the correct params', async function () { + await server.registrations.requestRegistration({ + username: 'user_request_2', + registrationReason: 'tt', + channel: { + displayName: 'my user request 2 channel', + name: 'user_request_2_channel' + } + }) + }) + + it('Should fail if the user is already awaiting registration approval', async function () { + await server.registrations.requestRegistration({ + username: 'user_request_2', + registrationReason: 'tt', + channel: { + displayName: 'my user request 42 channel', + name: 'user_request_42_channel' + }, + expectedStatus: HttpStatusCode.CONFLICT_409 + }) + }) + + it('Should fail if the channel is already awaiting registration approval', async function () { + await server.registrations.requestRegistration({ + username: 'user42', + registrationReason: 'tt', + channel: { + displayName: 'my user request 2 channel', + name: 'user_request_2_channel' + }, + expectedStatus: HttpStatusCode.CONFLICT_409 + }) + }) + + it('Should fail if the instance does not require approval', async function () { + this.timeout(60000) + + await server.config.enableSignup(false) + + await server.registrations.requestRegistration({ + username: 'user42', + registrationReason: 'toto', + expectedStatus: HttpStatusCode.BAD_REQUEST_400 + }) + }) + }) + }) + + describe('Registrations accept/reject', function () { + let id1: number + let id2: number + + before(async function () { + this.timeout(60000) + + await server.config.enableSignup(true); + + ({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_2', registrationReason: 'toto' })); + ({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_3', registrationReason: 'toto' })) + }) + + it('Should fail to accept/reject registration without token', async function () { + const options = { id: id1, moderationResponse: 'tt', token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 } + await server.registrations.accept(options) + await server.registrations.reject(options) + }) + + it('Should fail to accept/reject registration with a non moderator user', async function () { + const options = { id: id1, moderationResponse: 'tt', token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 } + await server.registrations.accept(options) + await server.registrations.reject(options) + }) + + it('Should fail to accept/reject registration with a bad registration id', async function () { + { + const options = { id: 't' as any, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 } + await server.registrations.accept(options) + await server.registrations.reject(options) + } + + { + const options = { id: 42, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 } + await server.registrations.accept(options) + await server.registrations.reject(options) + } + }) + + it('Should fail to accept/reject registration with a bad moderation resposne', async function () { + for (const moderationResponse of [ '', 't', 't'.repeat(5000) ]) { + const options = { id: id1, moderationResponse, token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 } + await server.registrations.accept(options) + await server.registrations.reject(options) + } + }) + + it('Should succeed to accept a registration', async function () { + await server.registrations.accept({ id: id1, moderationResponse: 'tt', token: moderatorToken }) + }) + + it('Should succeed to reject a registration', async function () { + await server.registrations.reject({ id: id2, moderationResponse: 'tt', token: moderatorToken }) + }) + + it('Should fail to accept/reject a registration that was already accepted/rejected', async function () { + for (const id of [ id1, id2 ]) { + const options = { id, moderationResponse: 'tt', token: moderatorToken, expectedStatus: HttpStatusCode.CONFLICT_409 } + await server.registrations.accept(options) + await server.registrations.reject(options) + } + }) + }) + + describe('Registrations deletion', function () { + let id1: number + let id2: number + let id3: number + + before(async function () { + ({ id: id1 } = await server.registrations.requestRegistration({ username: 'request_4', registrationReason: 'toto' })); + ({ id: id2 } = await server.registrations.requestRegistration({ username: 'request_5', registrationReason: 'toto' })); + ({ id: id3 } = await server.registrations.requestRegistration({ username: 'request_6', registrationReason: 'toto' })) + + await server.registrations.accept({ id: id2, moderationResponse: 'tt' }) + await server.registrations.reject({ id: id3, moderationResponse: 'tt' }) + }) + + it('Should fail to delete registration without token', async function () { + await server.registrations.delete({ id: id1, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) + }) + + it('Should fail to delete registration with a non moderator user', async function () { + await server.registrations.delete({ id: id1, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + }) + + it('Should fail to delete registration with a bad registration id', async function () { + await server.registrations.delete({ id: 't' as any, token: moderatorToken, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + await server.registrations.delete({ id: 42, token: moderatorToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) + }) + + it('Should succeed with the correct params', async function () { + await server.registrations.delete({ id: id1, token: moderatorToken }) + await server.registrations.delete({ id: id2, token: moderatorToken }) + await server.registrations.delete({ id: id3, token: moderatorToken }) + }) + }) + + describe('Listing registrations', function () { + const path = '/api/v1/users/registrations' + + it('Should fail with a bad start pagination', async function () { + await checkBadStartPagination(server.url, path, server.accessToken) + }) + + it('Should fail with a bad count pagination', async function () { + await checkBadCountPagination(server.url, path, server.accessToken) + }) + + it('Should fail with an incorrect sort', async function () { + await checkBadSortPagination(server.url, path, server.accessToken) + }) + + it('Should fail with a non authenticated user', async function () { + await server.registrations.list({ + token: null, + expectedStatus: HttpStatusCode.UNAUTHORIZED_401 + }) + }) + + it('Should fail with a non admin user', async function () { + await server.registrations.list({ + token: userToken, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should succeed with the correct params', async function () { + await server.registrations.list({ + token: moderatorToken, + search: 'toto' + }) + }) + }) + + after(async function () { + await cleanupTests([ server ]) + }) +}) diff --git a/server/tests/api/check-params/upload-quota.ts b/server/tests/api/check-params/upload-quota.ts index 70e6f4af9..fdc711bd5 100644 --- a/server/tests/api/check-params/upload-quota.ts +++ b/server/tests/api/check-params/upload-quota.ts @@ -42,7 +42,7 @@ describe('Test upload quota', function () { this.timeout(30000) const user = { username: 'registered' + randomInt(1, 1500), password: 'password' } - await server.users.register(user) + await server.registrations.register(user) const userToken = await server.login.getAccessToken(user) const attributes = { fixture: 'video_short2.webm' } @@ -57,7 +57,7 @@ describe('Test upload quota', function () { this.timeout(30000) const user = { username: 'registered' + randomInt(1, 1500), password: 'password' } - await server.users.register(user) + await server.registrations.register(user) const userToken = await server.login.getAccessToken(user) const attributes = { fixture: 'video_short2.webm' } diff --git a/server/tests/api/check-params/users-admin.ts b/server/tests/api/check-params/users-admin.ts index 7ba709c4a..be2496bb4 100644 --- a/server/tests/api/check-params/users-admin.ts +++ b/server/tests/api/check-params/users-admin.ts @@ -5,6 +5,7 @@ import { omit } from '@shared/core-utils' import { HttpStatusCode, UserAdminFlag, UserRole } from '@shared/models' import { cleanupTests, + ConfigCommand, createSingleServer, killallServers, makeGetRequest, @@ -156,13 +157,7 @@ describe('Test users admin API validators', function () { await killallServers([ server ]) - const config = { - smtp: { - hostname: '127.0.0.1', - port: emailPort - } - } - await server.run(config) + await server.run(ConfigCommand.getEmailOverrideConfig(emailPort)) const fields = { ...baseCorrectParams, diff --git a/server/tests/api/check-params/users-emails.ts b/server/tests/api/check-params/users-emails.ts new file mode 100644 index 000000000..8cfb1d15f --- /dev/null +++ b/server/tests/api/check-params/users-emails.ts @@ -0,0 +1,119 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ +import { MockSmtpServer } from '@server/tests/shared' +import { HttpStatusCode, UserRole } from '@shared/models' +import { cleanupTests, createSingleServer, makePostBodyRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' + +describe('Test users API validators', function () { + let server: PeerTubeServer + + // --------------------------------------------------------------- + + before(async function () { + this.timeout(30000) + + server = await createSingleServer(1, { + rates_limit: { + ask_send_email: { + max: 10 + } + } + }) + + await setAccessTokensToServers([ server ]) + await server.config.enableSignup(true) + + await server.users.generate('moderator2', UserRole.MODERATOR) + + await server.registrations.requestRegistration({ + username: 'request1', + registrationReason: 'tt' + }) + }) + + describe('When asking a password reset', function () { + const path = '/api/v1/users/ask-reset-password' + + it('Should fail with a missing email', async function () { + const fields = {} + + await makePostBodyRequest({ url: server.url, path, fields }) + }) + + it('Should fail with an invalid email', async function () { + const fields = { email: 'hello' } + + await makePostBodyRequest({ url: server.url, path, fields }) + }) + + it('Should success with the correct params', async function () { + const fields = { email: 'admin@example.com' } + + await makePostBodyRequest({ + url: server.url, + path, + fields, + expectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + }) + }) + + describe('When asking for an account verification email', function () { + const path = '/api/v1/users/ask-send-verify-email' + + it('Should fail with a missing email', async function () { + const fields = {} + + await makePostBodyRequest({ url: server.url, path, fields }) + }) + + it('Should fail with an invalid email', async function () { + const fields = { email: 'hello' } + + await makePostBodyRequest({ url: server.url, path, fields }) + }) + + it('Should succeed with the correct params', async function () { + const fields = { email: 'admin@example.com' } + + await makePostBodyRequest({ + url: server.url, + path, + fields, + expectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + }) + }) + + describe('When asking for a registration verification email', function () { + const path = '/api/v1/users/registrations/ask-send-verify-email' + + it('Should fail with a missing email', async function () { + const fields = {} + + await makePostBodyRequest({ url: server.url, path, fields }) + }) + + it('Should fail with an invalid email', async function () { + const fields = { email: 'hello' } + + await makePostBodyRequest({ url: server.url, path, fields }) + }) + + it('Should succeed with the correct params', async function () { + const fields = { email: 'request1@example.com' } + + await makePostBodyRequest({ + url: server.url, + path, + fields, + expectedStatus: HttpStatusCode.NO_CONTENT_204 + }) + }) + }) + + after(async function () { + MockSmtpServer.Instance.kill() + + await cleanupTests([ server ]) + }) +}) diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts deleted file mode 100644 index 7acfd8c2c..000000000 --- a/server/tests/api/check-params/users.ts +++ /dev/null @@ -1,255 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import { MockSmtpServer } from '@server/tests/shared' -import { omit } from '@shared/core-utils' -import { HttpStatusCode, UserRole } from '@shared/models' -import { cleanupTests, createSingleServer, makePostBodyRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' - -describe('Test users API validators', function () { - const path = '/api/v1/users/' - let server: PeerTubeServer - let serverWithRegistrationDisabled: PeerTubeServer - - // --------------------------------------------------------------- - - before(async function () { - this.timeout(30000) - - const res = await Promise.all([ - createSingleServer(1, { signup: { limit: 3 } }), - createSingleServer(2) - ]) - - server = res[0] - serverWithRegistrationDisabled = res[1] - - await setAccessTokensToServers([ server ]) - - await server.users.generate('moderator2', UserRole.MODERATOR) - }) - - describe('When registering a new user', function () { - const registrationPath = path + '/register' - const baseCorrectParams = { - username: 'user3', - displayName: 'super user', - email: 'test3@example.com', - password: 'my super password' - } - - it('Should fail with a too small username', async function () { - const fields = { ...baseCorrectParams, username: '' } - - await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) - }) - - it('Should fail with a too long username', async function () { - const fields = { ...baseCorrectParams, username: 'super'.repeat(50) } - - await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) - }) - - it('Should fail with an incorrect username', async function () { - const fields = { ...baseCorrectParams, username: 'my username' } - - await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) - }) - - it('Should fail with a missing email', async function () { - const fields = omit(baseCorrectParams, [ 'email' ]) - - await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) - }) - - it('Should fail with an invalid email', async function () { - const fields = { ...baseCorrectParams, email: 'test_example.com' } - - await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) - }) - - it('Should fail with a too small password', async function () { - const fields = { ...baseCorrectParams, password: 'bla' } - - await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) - }) - - it('Should fail with a too long password', async function () { - const fields = { ...baseCorrectParams, password: 'super'.repeat(61) } - - await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) - }) - - it('Should fail if we register a user with the same username', async function () { - const fields = { ...baseCorrectParams, username: 'root' } - - await makePostBodyRequest({ - url: server.url, - path: registrationPath, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.CONFLICT_409 - }) - }) - - it('Should fail with a "peertube" username', async function () { - const fields = { ...baseCorrectParams, username: 'peertube' } - - await makePostBodyRequest({ - url: server.url, - path: registrationPath, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.CONFLICT_409 - }) - }) - - it('Should fail if we register a user with the same email', async function () { - const fields = { ...baseCorrectParams, email: 'admin' + server.internalServerNumber + '@example.com' } - - await makePostBodyRequest({ - url: server.url, - path: registrationPath, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.CONFLICT_409 - }) - }) - - it('Should fail with a bad display name', async function () { - const fields = { ...baseCorrectParams, displayName: 'a'.repeat(150) } - - await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) - }) - - it('Should fail with a bad channel name', async function () { - const fields = { ...baseCorrectParams, channel: { name: '[]azf', displayName: 'toto' } } - - await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) - }) - - it('Should fail with a bad channel display name', async function () { - const fields = { ...baseCorrectParams, channel: { name: 'toto', displayName: '' } } - - await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) - }) - - it('Should fail with a channel name that is the same as username', async function () { - const source = { username: 'super_user', channel: { name: 'super_user', displayName: 'display name' } } - const fields = { ...baseCorrectParams, ...source } - - await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields }) - }) - - it('Should fail with an existing channel', async function () { - const attributes = { name: 'existing_channel', displayName: 'hello', description: 'super description' } - await server.channels.create({ attributes }) - - const fields = { ...baseCorrectParams, channel: { name: 'existing_channel', displayName: 'toto' } } - - await makePostBodyRequest({ - url: server.url, - path: registrationPath, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.CONFLICT_409 - }) - }) - - it('Should succeed with the correct params', async function () { - const fields = { ...baseCorrectParams, channel: { name: 'super_channel', displayName: 'toto' } } - - await makePostBodyRequest({ - url: server.url, - path: registrationPath, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - }) - - it('Should fail on a server with registration disabled', async function () { - const fields = { - username: 'user4', - email: 'test4@example.com', - password: 'my super password 4' - } - - await makePostBodyRequest({ - url: serverWithRegistrationDisabled.url, - path: registrationPath, - token: serverWithRegistrationDisabled.accessToken, - fields, - expectedStatus: HttpStatusCode.FORBIDDEN_403 - }) - }) - }) - - describe('When registering multiple users on a server with users limit', function () { - - it('Should fail when after 3 registrations', async function () { - await server.users.register({ username: 'user42', expectedStatus: HttpStatusCode.FORBIDDEN_403 }) - }) - - }) - - describe('When asking a password reset', function () { - const path = '/api/v1/users/ask-reset-password' - - it('Should fail with a missing email', async function () { - const fields = {} - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with an invalid email', async function () { - const fields = { email: 'hello' } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should success with the correct params', async function () { - const fields = { email: 'admin@example.com' } - - await makePostBodyRequest({ - url: server.url, - path, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - }) - }) - - describe('When asking for an account verification email', function () { - const path = '/api/v1/users/ask-send-verify-email' - - it('Should fail with a missing email', async function () { - const fields = {} - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should fail with an invalid email', async function () { - const fields = { email: 'hello' } - - await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) - }) - - it('Should succeed with the correct params', async function () { - const fields = { email: 'admin@example.com' } - - await makePostBodyRequest({ - url: server.url, - path, - token: server.accessToken, - fields, - expectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - }) - }) - - after(async function () { - MockSmtpServer.Instance.kill() - - await cleanupTests([ server, serverWithRegistrationDisabled ]) - }) -}) diff --git a/server/tests/api/notifications/index.ts b/server/tests/api/notifications/index.ts index 8caa30a3d..c0216b74f 100644 --- a/server/tests/api/notifications/index.ts +++ b/server/tests/api/notifications/index.ts @@ -2,4 +2,5 @@ import './admin-notifications' import './comments-notifications' import './moderation-notifications' import './notifications-api' +import './registrations-notifications' import './user-notifications' diff --git a/server/tests/api/notifications/moderation-notifications.ts b/server/tests/api/notifications/moderation-notifications.ts index c7b9b5fb0..bb11a08aa 100644 --- a/server/tests/api/notifications/moderation-notifications.ts +++ b/server/tests/api/notifications/moderation-notifications.ts @@ -11,7 +11,6 @@ import { checkNewInstanceFollower, checkNewVideoAbuseForModerators, checkNewVideoFromSubscription, - checkUserRegistered, checkVideoAutoBlacklistForModerators, checkVideoIsPublished, MockInstancesIndex, @@ -327,32 +326,6 @@ describe('Test moderation notifications', function () { }) }) - describe('New registration', function () { - let baseParams: CheckerBaseParams - - before(() => { - baseParams = { - server: servers[0], - emails, - socketNotifications: adminNotifications, - token: servers[0].accessToken - } - }) - - it('Should send a notification only to moderators when a user registers on the instance', async function () { - this.timeout(10000) - - await servers[0].users.register({ username: 'user_45' }) - - await waitJobs(servers) - - await checkUserRegistered({ ...baseParams, username: 'user_45', checkType: 'presence' }) - - const userOverride = { socketNotifications: userNotifications, token: userToken1, check: { web: true, mail: false } } - await checkUserRegistered({ ...baseParams, ...userOverride, username: 'user_45', checkType: 'absence' }) - }) - }) - describe('New instance follows', function () { const instanceIndexServer = new MockInstancesIndex() let config: any diff --git a/server/tests/api/notifications/registrations-notifications.ts b/server/tests/api/notifications/registrations-notifications.ts new file mode 100644 index 000000000..b5a7c2bb5 --- /dev/null +++ b/server/tests/api/notifications/registrations-notifications.ts @@ -0,0 +1,88 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import { + CheckerBaseParams, + checkRegistrationRequest, + checkUserRegistered, + MockSmtpServer, + prepareNotificationsTest +} from '@server/tests/shared' +import { UserNotification } from '@shared/models' +import { cleanupTests, PeerTubeServer, waitJobs } from '@shared/server-commands' + +describe('Test registrations notifications', function () { + let server: PeerTubeServer + let userToken1: string + + let userNotifications: UserNotification[] = [] + let adminNotifications: UserNotification[] = [] + let emails: object[] = [] + + let baseParams: CheckerBaseParams + + before(async function () { + this.timeout(50000) + + const res = await prepareNotificationsTest(1) + + server = res.servers[0] + emails = res.emails + userToken1 = res.userAccessToken + adminNotifications = res.adminNotifications + userNotifications = res.userNotifications + + baseParams = { + server, + emails, + socketNotifications: adminNotifications, + token: server.accessToken + } + }) + + describe('New direct registration for moderators', function () { + + before(async function () { + await server.config.enableSignup(false) + }) + + it('Should send a notification only to moderators when a user registers on the instance', async function () { + this.timeout(50000) + + await server.registrations.register({ username: 'user_10' }) + + await waitJobs([ server ]) + + await checkUserRegistered({ ...baseParams, username: 'user_10', checkType: 'presence' }) + + const userOverride = { socketNotifications: userNotifications, token: userToken1, check: { web: true, mail: false } } + await checkUserRegistered({ ...baseParams, ...userOverride, username: 'user_10', checkType: 'absence' }) + }) + }) + + describe('New registration request for moderators', function () { + + before(async function () { + await server.config.enableSignup(true) + }) + + it('Should send a notification on new registration request', async function () { + this.timeout(50000) + + const registrationReason = 'my reason' + await server.registrations.requestRegistration({ username: 'user_11', registrationReason }) + + await waitJobs([ server ]) + + await checkRegistrationRequest({ ...baseParams, username: 'user_11', registrationReason, checkType: 'presence' }) + + const userOverride = { socketNotifications: userNotifications, token: userToken1, check: { web: true, mail: false } } + await checkRegistrationRequest({ ...baseParams, ...userOverride, username: 'user_11', registrationReason, checkType: 'absence' }) + }) + }) + + after(async function () { + MockSmtpServer.Instance.kill() + + await cleanupTests([ server ]) + }) +}) diff --git a/server/tests/api/server/config-defaults.ts b/server/tests/api/server/config-defaults.ts index 4fa37d0e2..d3b3a2447 100644 --- a/server/tests/api/server/config-defaults.ts +++ b/server/tests/api/server/config-defaults.ts @@ -149,7 +149,7 @@ describe('Test config defaults', function () { }) it('Should register a user with this default setting', async function () { - await server.users.register({ username: 'user_p2p_2' }) + await server.registrations.register({ username: 'user_p2p_2' }) const userToken = await server.login.getAccessToken('user_p2p_2') @@ -194,7 +194,7 @@ describe('Test config defaults', function () { }) it('Should register a user with this default setting', async function () { - await server.users.register({ username: 'user_p2p_4' }) + await server.registrations.register({ username: 'user_p2p_4' }) const userToken = await server.login.getAccessToken('user_p2p_4') diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index 22446fe0c..b91519660 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts @@ -50,6 +50,7 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) { expect(data.signup.enabled).to.be.true expect(data.signup.limit).to.equal(4) expect(data.signup.minimumAge).to.equal(16) + expect(data.signup.requiresApproval).to.be.false expect(data.signup.requiresEmailVerification).to.be.false expect(data.admin.email).to.equal('admin' + server.internalServerNumber + '@example.com') @@ -152,6 +153,7 @@ function checkUpdatedConfig (data: CustomConfig) { expect(data.signup.enabled).to.be.false expect(data.signup.limit).to.equal(5) + expect(data.signup.requiresApproval).to.be.false expect(data.signup.requiresEmailVerification).to.be.false expect(data.signup.minimumAge).to.equal(10) @@ -285,6 +287,7 @@ const newCustomConfig: CustomConfig = { signup: { enabled: false, limit: 5, + requiresApproval: false, requiresEmailVerification: false, minimumAge: 10 }, @@ -468,9 +471,9 @@ describe('Test config', function () { this.timeout(5000) await Promise.all([ - server.users.register({ username: 'user1' }), - server.users.register({ username: 'user2' }), - server.users.register({ username: 'user3' }) + server.registrations.register({ username: 'user1' }), + server.registrations.register({ username: 'user2' }), + server.registrations.register({ username: 'user3' }) ]) const data = await server.config.getConfig() diff --git a/server/tests/api/server/contact-form.ts b/server/tests/api/server/contact-form.ts index 325218008..dd971203a 100644 --- a/server/tests/api/server/contact-form.ts +++ b/server/tests/api/server/contact-form.ts @@ -6,6 +6,7 @@ import { wait } from '@shared/core-utils' import { HttpStatusCode } from '@shared/models' import { cleanupTests, + ConfigCommand, ContactFormCommand, createSingleServer, PeerTubeServer, @@ -23,13 +24,7 @@ describe('Test contact form', function () { const port = await MockSmtpServer.Instance.collectEmails(emails) - const overrideConfig = { - smtp: { - hostname: '127.0.0.1', - port - } - } - server = await createSingleServer(1, overrideConfig) + server = await createSingleServer(1, ConfigCommand.getEmailOverrideConfig(port)) await setAccessTokensToServers([ server ]) command = server.contactForm diff --git a/server/tests/api/server/email.ts b/server/tests/api/server/email.ts index 4ab5463fe..db7aa65bd 100644 --- a/server/tests/api/server/email.ts +++ b/server/tests/api/server/email.ts @@ -3,7 +3,14 @@ import { expect } from 'chai' import { MockSmtpServer } from '@server/tests/shared' import { HttpStatusCode } from '@shared/models' -import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/server-commands' +import { + cleanupTests, + ConfigCommand, + createSingleServer, + PeerTubeServer, + setAccessTokensToServers, + waitJobs +} from '@shared/server-commands' describe('Test emails', function () { let server: PeerTubeServer @@ -24,21 +31,15 @@ describe('Test emails', function () { username: 'user_1', password: 'super_password' } - let emailPort: number before(async function () { this.timeout(50000) - emailPort = await MockSmtpServer.Instance.collectEmails(emails) + const emailPort = await MockSmtpServer.Instance.collectEmails(emails) + server = await createSingleServer(1, ConfigCommand.getEmailOverrideConfig(emailPort)) - const overrideConfig = { - smtp: { - hostname: '127.0.0.1', - port: emailPort - } - } - server = await createSingleServer(1, overrideConfig) await setAccessTokensToServers([ server ]) + await server.config.enableSignup(true) { const created = await server.users.create({ username: user.username, password: user.password }) @@ -322,6 +323,62 @@ describe('Test emails', function () { }) }) + describe('When verifying a registration email', function () { + let registrationId: number + let registrationIdEmail: number + + before(async function () { + const { id } = await server.registrations.requestRegistration({ + username: 'request_1', + email: 'request_1@example.com', + registrationReason: 'tt' + }) + registrationId = id + }) + + it('Should ask to send the verification email', async function () { + this.timeout(10000) + + await server.registrations.askSendVerifyEmail({ email: 'request_1@example.com' }) + + await waitJobs(server) + expect(emails).to.have.lengthOf(9) + + const email = emails[8] + + expect(email['from'][0]['name']).equal('PeerTube') + expect(email['from'][0]['address']).equal('test-admin@127.0.0.1') + expect(email['to'][0]['address']).equal('request_1@example.com') + expect(email['subject']).contains('Verify') + + const verificationStringMatches = /verificationString=([a-z0-9]+)/.exec(email['text']) + expect(verificationStringMatches).not.to.be.null + + verificationString = verificationStringMatches[1] + expect(verificationString).to.not.be.undefined + expect(verificationString).to.have.length.above(2) + + const registrationIdMatches = /registrationId=([0-9]+)/.exec(email['text']) + expect(registrationIdMatches).not.to.be.null + + registrationIdEmail = parseInt(registrationIdMatches[1], 10) + + expect(registrationId).to.equal(registrationIdEmail) + }) + + it('Should not verify the email with an invalid verification string', async function () { + await server.registrations.verifyEmail({ + registrationId: registrationIdEmail, + verificationString: verificationString + 'b', + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + }) + + it('Should verify the email', async function () { + await server.registrations.verifyEmail({ registrationId: registrationIdEmail, verificationString }) + }) + }) + after(async function () { MockSmtpServer.Instance.kill() diff --git a/server/tests/api/server/reverse-proxy.ts b/server/tests/api/server/reverse-proxy.ts index d882f0bde..11c96c4b5 100644 --- a/server/tests/api/server/reverse-proxy.ts +++ b/server/tests/api/server/reverse-proxy.ts @@ -106,13 +106,13 @@ describe('Test application behind a reverse proxy', function () { it('Should rate limit signup', async function () { for (let i = 0; i < 10; i++) { try { - await server.users.register({ username: 'test' + i }) + await server.registrations.register({ username: 'test' + i }) } catch { // empty } } - await server.users.register({ username: 'test42', expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 }) + await server.registrations.register({ username: 'test42', expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 }) }) it('Should not rate limit failed signup', async function () { @@ -121,10 +121,10 @@ describe('Test application behind a reverse proxy', function () { await wait(7000) for (let i = 0; i < 3; i++) { - await server.users.register({ username: 'test' + i, expectedStatus: HttpStatusCode.CONFLICT_409 }) + await server.registrations.register({ username: 'test' + i, expectedStatus: HttpStatusCode.CONFLICT_409 }) } - await server.users.register({ username: 'test43', expectedStatus: HttpStatusCode.NO_CONTENT_204 }) + await server.registrations.register({ username: 'test43', expectedStatus: HttpStatusCode.NO_CONTENT_204 }) }) diff --git a/server/tests/api/users/index.ts b/server/tests/api/users/index.ts index 0313845ef..a4443a8ec 100644 --- a/server/tests/api/users/index.ts +++ b/server/tests/api/users/index.ts @@ -1,7 +1,8 @@ import './oauth' +import './registrations`' import './two-factor' import './user-subscriptions' import './user-videos' import './users' import './users-multiple-servers' -import './users-verification' +import './users-email-verification' diff --git a/server/tests/api/users/registrations.ts b/server/tests/api/users/registrations.ts new file mode 100644 index 000000000..a9e1114e8 --- /dev/null +++ b/server/tests/api/users/registrations.ts @@ -0,0 +1,379 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import { expect } from 'chai' +import { MockSmtpServer } from '@server/tests/shared' +import { UserRegistrationState, UserRole } from '@shared/models' +import { + cleanupTests, + ConfigCommand, + createSingleServer, + PeerTubeServer, + setAccessTokensToServers, + waitJobs +} from '@shared/server-commands' + +describe('Test registrations', function () { + let server: PeerTubeServer + + const emails: object[] = [] + let emailPort: number + + before(async function () { + this.timeout(30000) + + emailPort = await MockSmtpServer.Instance.collectEmails(emails) + + server = await createSingleServer(1, ConfigCommand.getEmailOverrideConfig(emailPort)) + + await setAccessTokensToServers([ server ]) + await server.config.enableSignup(false) + }) + + describe('Direct registrations of a new user', function () { + let user1Token: string + + it('Should register a new user', async function () { + const user = { displayName: 'super user 1', username: 'user_1', password: 'my super password' } + const channel = { name: 'my_user_1_channel', displayName: 'my channel rocks' } + + await server.registrations.register({ ...user, channel }) + }) + + it('Should be able to login with this registered user', async function () { + const user1 = { username: 'user_1', password: 'my super password' } + + user1Token = await server.login.getAccessToken(user1) + }) + + it('Should have the correct display name', async function () { + const user = await server.users.getMyInfo({ token: user1Token }) + expect(user.account.displayName).to.equal('super user 1') + }) + + it('Should have the correct video quota', async function () { + const user = await server.users.getMyInfo({ token: user1Token }) + expect(user.videoQuota).to.equal(5 * 1024 * 1024) + }) + + it('Should have created the channel', async function () { + const { displayName } = await server.channels.get({ channelName: 'my_user_1_channel' }) + + expect(displayName).to.equal('my channel rocks') + }) + + it('Should remove me', async function () { + { + const { data } = await server.users.list() + expect(data.find(u => u.username === 'user_1')).to.not.be.undefined + } + + await server.users.deleteMe({ token: user1Token }) + + { + const { data } = await server.users.list() + expect(data.find(u => u.username === 'user_1')).to.be.undefined + } + }) + }) + + describe('Registration requests', function () { + let id2: number + let id3: number + let id4: number + + let user2Token: string + let user3Token: string + + before(async function () { + this.timeout(60000) + + await server.config.enableSignup(true) + + { + const { id } = await server.registrations.requestRegistration({ + username: 'user4', + registrationReason: 'registration reason 4' + }) + + id4 = id + } + }) + + it('Should request a registration without a channel', async function () { + { + const { id } = await server.registrations.requestRegistration({ + username: 'user2', + displayName: 'my super user 2', + email: 'user2@example.com', + password: 'user2password', + registrationReason: 'registration reason 2' + }) + + id2 = id + } + }) + + it('Should request a registration with a channel', async function () { + const { id } = await server.registrations.requestRegistration({ + username: 'user3', + displayName: 'my super user 3', + channel: { + displayName: 'my user 3 channel', + name: 'super_user3_channel' + }, + email: 'user3@example.com', + password: 'user3password', + registrationReason: 'registration reason 3' + }) + + id3 = id + }) + + it('Should list these registration requests', async function () { + { + const { total, data } = await server.registrations.list({ sort: '-createdAt' }) + expect(total).to.equal(3) + expect(data).to.have.lengthOf(3) + + { + expect(data[0].id).to.equal(id3) + expect(data[0].username).to.equal('user3') + expect(data[0].accountDisplayName).to.equal('my super user 3') + + expect(data[0].channelDisplayName).to.equal('my user 3 channel') + expect(data[0].channelHandle).to.equal('super_user3_channel') + + expect(data[0].createdAt).to.exist + expect(data[0].updatedAt).to.exist + + expect(data[0].email).to.equal('user3@example.com') + expect(data[0].emailVerified).to.be.null + + expect(data[0].moderationResponse).to.be.null + expect(data[0].registrationReason).to.equal('registration reason 3') + expect(data[0].state.id).to.equal(UserRegistrationState.PENDING) + expect(data[0].state.label).to.equal('Pending') + expect(data[0].user).to.be.null + } + + { + expect(data[1].id).to.equal(id2) + expect(data[1].username).to.equal('user2') + expect(data[1].accountDisplayName).to.equal('my super user 2') + + expect(data[1].channelDisplayName).to.be.null + expect(data[1].channelHandle).to.be.null + + expect(data[1].createdAt).to.exist + expect(data[1].updatedAt).to.exist + + expect(data[1].email).to.equal('user2@example.com') + expect(data[1].emailVerified).to.be.null + + expect(data[1].moderationResponse).to.be.null + expect(data[1].registrationReason).to.equal('registration reason 2') + expect(data[1].state.id).to.equal(UserRegistrationState.PENDING) + expect(data[1].state.label).to.equal('Pending') + expect(data[1].user).to.be.null + } + + { + expect(data[2].username).to.equal('user4') + } + } + + { + const { total, data } = await server.registrations.list({ count: 1, start: 1, sort: 'createdAt' }) + + expect(total).to.equal(3) + expect(data).to.have.lengthOf(1) + expect(data[0].id).to.equal(id2) + } + + { + const { total, data } = await server.registrations.list({ search: 'user3' }) + expect(total).to.equal(1) + expect(data).to.have.lengthOf(1) + expect(data[0].id).to.equal(id3) + } + }) + + it('Should reject a registration request', async function () { + await server.registrations.reject({ id: id4, moderationResponse: 'I do not want id 4 on this instance' }) + }) + + it('Should have sent an email to the user explanining the registration has been rejected', async function () { + this.timeout(50000) + + await waitJobs([ server ]) + + const email = emails.find(e => e['to'][0]['address'] === 'user4@example.com') + expect(email).to.exist + + expect(email['subject']).to.contain('been rejected') + expect(email['text']).to.contain('been rejected') + expect(email['text']).to.contain('I do not want id 4 on this instance') + }) + + it('Should accept registration requests', async function () { + await server.registrations.accept({ id: id2, moderationResponse: 'Welcome id 2' }) + await server.registrations.accept({ id: id3, moderationResponse: 'Welcome id 3' }) + }) + + it('Should have sent an email to the user explanining the registration has been accepted', async function () { + this.timeout(50000) + + await waitJobs([ server ]) + + { + const email = emails.find(e => e['to'][0]['address'] === 'user2@example.com') + expect(email).to.exist + + expect(email['subject']).to.contain('been accepted') + expect(email['text']).to.contain('been accepted') + expect(email['text']).to.contain('Welcome id 2') + } + + { + const email = emails.find(e => e['to'][0]['address'] === 'user3@example.com') + expect(email).to.exist + + expect(email['subject']).to.contain('been accepted') + expect(email['text']).to.contain('been accepted') + expect(email['text']).to.contain('Welcome id 3') + } + }) + + it('Should login with these users', async function () { + user2Token = await server.login.getAccessToken({ username: 'user2', password: 'user2password' }) + user3Token = await server.login.getAccessToken({ username: 'user3', password: 'user3password' }) + }) + + it('Should have created the appropriate attributes for user 2', async function () { + const me = await server.users.getMyInfo({ token: user2Token }) + + expect(me.username).to.equal('user2') + expect(me.account.displayName).to.equal('my super user 2') + expect(me.videoQuota).to.equal(5 * 1024 * 1024) + expect(me.videoChannels[0].name).to.equal('user2_channel') + expect(me.videoChannels[0].displayName).to.equal('Main user2 channel') + expect(me.role.id).to.equal(UserRole.USER) + expect(me.email).to.equal('user2@example.com') + }) + + it('Should have created the appropriate attributes for user 3', async function () { + const me = await server.users.getMyInfo({ token: user3Token }) + + expect(me.username).to.equal('user3') + expect(me.account.displayName).to.equal('my super user 3') + expect(me.videoQuota).to.equal(5 * 1024 * 1024) + expect(me.videoChannels[0].name).to.equal('super_user3_channel') + expect(me.videoChannels[0].displayName).to.equal('my user 3 channel') + expect(me.role.id).to.equal(UserRole.USER) + expect(me.email).to.equal('user3@example.com') + }) + + it('Should list these accepted/rejected registration requests', async function () { + const { data } = await server.registrations.list({ sort: 'createdAt' }) + const { data: users } = await server.users.list() + + { + expect(data[0].id).to.equal(id4) + expect(data[0].state.id).to.equal(UserRegistrationState.REJECTED) + expect(data[0].state.label).to.equal('Rejected') + + expect(data[0].moderationResponse).to.equal('I do not want id 4 on this instance') + expect(data[0].user).to.be.null + + expect(users.find(u => u.username === 'user4')).to.not.exist + } + + { + expect(data[1].id).to.equal(id2) + expect(data[1].state.id).to.equal(UserRegistrationState.ACCEPTED) + expect(data[1].state.label).to.equal('Accepted') + + expect(data[1].moderationResponse).to.equal('Welcome id 2') + expect(data[1].user).to.exist + + const user2 = users.find(u => u.username === 'user2') + expect(data[1].user.id).to.equal(user2.id) + } + + { + expect(data[2].id).to.equal(id3) + expect(data[2].state.id).to.equal(UserRegistrationState.ACCEPTED) + expect(data[2].state.label).to.equal('Accepted') + + expect(data[2].moderationResponse).to.equal('Welcome id 3') + expect(data[2].user).to.exist + + const user3 = users.find(u => u.username === 'user3') + expect(data[2].user.id).to.equal(user3.id) + } + }) + + it('Shoulde delete a registration', async function () { + await server.registrations.delete({ id: id2 }) + await server.registrations.delete({ id: id3 }) + + const { total, data } = await server.registrations.list() + expect(total).to.equal(1) + expect(data).to.have.lengthOf(1) + expect(data[0].id).to.equal(id4) + + const { data: users } = await server.users.list() + + for (const username of [ 'user2', 'user3' ]) { + expect(users.find(u => u.username === username)).to.exist + } + }) + + it('Should request a registration without a channel, that will conflict with an already existing channel', async function () { + let id1: number + let id2: number + + { + const { id } = await server.registrations.requestRegistration({ + registrationReason: 'tt', + username: 'user5', + password: 'user5password', + channel: { + displayName: 'channel 6', + name: 'user6_channel' + } + }) + + id1 = id + } + + { + const { id } = await server.registrations.requestRegistration({ + registrationReason: 'tt', + username: 'user6', + password: 'user6password' + }) + + id2 = id + } + + await server.registrations.accept({ id: id1, moderationResponse: 'tt' }) + await server.registrations.accept({ id: id2, moderationResponse: 'tt' }) + + const user5Token = await server.login.getAccessToken('user5', 'user5password') + const user6Token = await server.login.getAccessToken('user6', 'user6password') + + const user5 = await server.users.getMyInfo({ token: user5Token }) + const user6 = await server.users.getMyInfo({ token: user6Token }) + + expect(user5.videoChannels[0].name).to.equal('user6_channel') + expect(user6.videoChannels[0].name).to.equal('user6_channel-1') + }) + }) + + after(async function () { + MockSmtpServer.Instance.kill() + + await cleanupTests([ server ]) + }) +}) diff --git a/server/tests/api/users/users-email-verification.ts b/server/tests/api/users/users-email-verification.ts new file mode 100644 index 000000000..cb84dc758 --- /dev/null +++ b/server/tests/api/users/users-email-verification.ts @@ -0,0 +1,167 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ + +import { expect } from 'chai' +import { MockSmtpServer } from '@server/tests/shared' +import { HttpStatusCode } from '@shared/models' +import { + cleanupTests, + ConfigCommand, + createSingleServer, + PeerTubeServer, + setAccessTokensToServers, + waitJobs +} from '@shared/server-commands' + +describe('Test users email verification', function () { + let server: PeerTubeServer + let userId: number + let userAccessToken: string + let verificationString: string + let expectedEmailsLength = 0 + const user1 = { + username: 'user_1', + password: 'super password' + } + const user2 = { + username: 'user_2', + password: 'super password' + } + const emails: object[] = [] + + before(async function () { + this.timeout(30000) + + const port = await MockSmtpServer.Instance.collectEmails(emails) + server = await createSingleServer(1, ConfigCommand.getEmailOverrideConfig(port)) + + await setAccessTokensToServers([ server ]) + }) + + it('Should register user and send verification email if verification required', async function () { + this.timeout(30000) + + await server.config.updateExistingSubConfig({ + newConfig: { + signup: { + enabled: true, + requiresApproval: false, + requiresEmailVerification: true, + limit: 10 + } + } + }) + + await server.registrations.register(user1) + + await waitJobs(server) + expectedEmailsLength++ + expect(emails).to.have.lengthOf(expectedEmailsLength) + + const email = emails[expectedEmailsLength - 1] + + const verificationStringMatches = /verificationString=([a-z0-9]+)/.exec(email['text']) + expect(verificationStringMatches).not.to.be.null + + verificationString = verificationStringMatches[1] + expect(verificationString).to.have.length.above(2) + + const userIdMatches = /userId=([0-9]+)/.exec(email['text']) + expect(userIdMatches).not.to.be.null + + userId = parseInt(userIdMatches[1], 10) + + const body = await server.users.get({ userId }) + expect(body.emailVerified).to.be.false + }) + + it('Should not allow login for user with unverified email', async function () { + const { detail } = await server.login.login({ user: user1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) + expect(detail).to.contain('User email is not verified.') + }) + + it('Should verify the user via email and allow login', async function () { + await server.users.verifyEmail({ userId, verificationString }) + + const body = await server.login.login({ user: user1 }) + userAccessToken = body.access_token + + const user = await server.users.get({ userId }) + expect(user.emailVerified).to.be.true + }) + + it('Should be able to change the user email', async function () { + this.timeout(10000) + + let updateVerificationString: string + + { + await server.users.updateMe({ + token: userAccessToken, + email: 'updated@example.com', + currentPassword: user1.password + }) + + await waitJobs(server) + expectedEmailsLength++ + expect(emails).to.have.lengthOf(expectedEmailsLength) + + const email = emails[expectedEmailsLength - 1] + + const verificationStringMatches = /verificationString=([a-z0-9]+)/.exec(email['text']) + updateVerificationString = verificationStringMatches[1] + } + + { + const me = await server.users.getMyInfo({ token: userAccessToken }) + expect(me.email).to.equal('user_1@example.com') + expect(me.pendingEmail).to.equal('updated@example.com') + } + + { + await server.users.verifyEmail({ userId, verificationString: updateVerificationString, isPendingEmail: true }) + + const me = await server.users.getMyInfo({ token: userAccessToken }) + expect(me.email).to.equal('updated@example.com') + expect(me.pendingEmail).to.be.null + } + }) + + it('Should register user not requiring email verification if setting not enabled', async function () { + this.timeout(5000) + await server.config.updateExistingSubConfig({ + newConfig: { + signup: { + requiresEmailVerification: false + } + } + }) + + await server.registrations.register(user2) + + await waitJobs(server) + expect(emails).to.have.lengthOf(expectedEmailsLength) + + const accessToken = await server.login.getAccessToken(user2) + + const user = await server.users.getMyInfo({ token: accessToken }) + expect(user.emailVerified).to.be.null + }) + + it('Should allow login for user with unverified email when setting later enabled', async function () { + await server.config.updateCustomSubConfig({ + newConfig: { + signup: { + requiresEmailVerification: true + } + } + }) + + await server.login.getAccessToken(user2) + }) + + after(async function () { + MockSmtpServer.Instance.kill() + + await cleanupTests([ server ]) + }) +}) diff --git a/server/tests/api/users/users-verification.ts b/server/tests/api/users/users-verification.ts deleted file mode 100644 index 19a8df9e1..000000000 --- a/server/tests/api/users/users-verification.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ - -import { expect } from 'chai' -import { MockSmtpServer } from '@server/tests/shared' -import { HttpStatusCode } from '@shared/models' -import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/server-commands' - -describe('Test users account verification', function () { - let server: PeerTubeServer - let userId: number - let userAccessToken: string - let verificationString: string - let expectedEmailsLength = 0 - const user1 = { - username: 'user_1', - password: 'super password' - } - const user2 = { - username: 'user_2', - password: 'super password' - } - const emails: object[] = [] - - before(async function () { - this.timeout(30000) - - const port = await MockSmtpServer.Instance.collectEmails(emails) - - const overrideConfig = { - smtp: { - hostname: '127.0.0.1', - port - } - } - server = await createSingleServer(1, overrideConfig) - - await setAccessTokensToServers([ server ]) - }) - - it('Should register user and send verification email if verification required', async function () { - this.timeout(30000) - - await server.config.updateCustomSubConfig({ - newConfig: { - signup: { - enabled: true, - requiresEmailVerification: true, - limit: 10 - } - } - }) - - await server.users.register(user1) - - await waitJobs(server) - expectedEmailsLength++ - expect(emails).to.have.lengthOf(expectedEmailsLength) - - const email = emails[expectedEmailsLength - 1] - - const verificationStringMatches = /verificationString=([a-z0-9]+)/.exec(email['text']) - expect(verificationStringMatches).not.to.be.null - - verificationString = verificationStringMatches[1] - expect(verificationString).to.have.length.above(2) - - const userIdMatches = /userId=([0-9]+)/.exec(email['text']) - expect(userIdMatches).not.to.be.null - - userId = parseInt(userIdMatches[1], 10) - - const body = await server.users.get({ userId }) - expect(body.emailVerified).to.be.false - }) - - it('Should not allow login for user with unverified email', async function () { - const { detail } = await server.login.login({ user: user1, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) - expect(detail).to.contain('User email is not verified.') - }) - - it('Should verify the user via email and allow login', async function () { - await server.users.verifyEmail({ userId, verificationString }) - - const body = await server.login.login({ user: user1 }) - userAccessToken = body.access_token - - const user = await server.users.get({ userId }) - expect(user.emailVerified).to.be.true - }) - - it('Should be able to change the user email', async function () { - this.timeout(10000) - - let updateVerificationString: string - - { - await server.users.updateMe({ - token: userAccessToken, - email: 'updated@example.com', - currentPassword: user1.password - }) - - await waitJobs(server) - expectedEmailsLength++ - expect(emails).to.have.lengthOf(expectedEmailsLength) - - const email = emails[expectedEmailsLength - 1] - - const verificationStringMatches = /verificationString=([a-z0-9]+)/.exec(email['text']) - updateVerificationString = verificationStringMatches[1] - } - - { - const me = await server.users.getMyInfo({ token: userAccessToken }) - expect(me.email).to.equal('user_1@example.com') - expect(me.pendingEmail).to.equal('updated@example.com') - } - - { - await server.users.verifyEmail({ userId, verificationString: updateVerificationString, isPendingEmail: true }) - - const me = await server.users.getMyInfo({ token: userAccessToken }) - expect(me.email).to.equal('updated@example.com') - expect(me.pendingEmail).to.be.null - } - }) - - it('Should register user not requiring email verification if setting not enabled', async function () { - this.timeout(5000) - await server.config.updateCustomSubConfig({ - newConfig: { - signup: { - enabled: true, - requiresEmailVerification: false, - limit: 10 - } - } - }) - - await server.users.register(user2) - - await waitJobs(server) - expect(emails).to.have.lengthOf(expectedEmailsLength) - - const accessToken = await server.login.getAccessToken(user2) - - const user = await server.users.getMyInfo({ token: accessToken }) - expect(user.emailVerified).to.be.null - }) - - it('Should allow login for user with unverified email when setting later enabled', async function () { - await server.config.updateCustomSubConfig({ - newConfig: { - signup: { - enabled: true, - requiresEmailVerification: true, - limit: 10 - } - } - }) - - await server.login.getAccessToken(user2) - }) - - after(async function () { - MockSmtpServer.Instance.kill() - - await cleanupTests([ server ]) - }) -}) diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 93e2e489a..f1e170971 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts @@ -429,56 +429,6 @@ describe('Test users', function () { }) }) - describe('Registering a new user', function () { - let user15AccessToken: string - - it('Should register a new user', async function () { - const user = { displayName: 'super user 15', username: 'user_15', password: 'my super password' } - const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' } - - await server.users.register({ ...user, channel }) - }) - - it('Should be able to login with this registered user', async function () { - const user15 = { - username: 'user_15', - password: 'my super password' - } - - user15AccessToken = await server.login.getAccessToken(user15) - }) - - it('Should have the correct display name', async function () { - const user = await server.users.getMyInfo({ token: user15AccessToken }) - expect(user.account.displayName).to.equal('super user 15') - }) - - it('Should have the correct video quota', async function () { - const user = await server.users.getMyInfo({ token: user15AccessToken }) - expect(user.videoQuota).to.equal(5 * 1024 * 1024) - }) - - it('Should have created the channel', async function () { - const { displayName } = await server.channels.get({ channelName: 'my_user_15_channel' }) - - expect(displayName).to.equal('my channel rocks') - }) - - it('Should remove me', async function () { - { - const { data } = await server.users.list() - expect(data.find(u => u.username === 'user_15')).to.not.be.undefined - } - - await server.users.deleteMe({ token: user15AccessToken }) - - { - const { data } = await server.users.list() - expect(data.find(u => u.username === 'user_15')).to.be.undefined - } - }) - }) - describe('User blocking', function () { let user16Id: number let user16AccessToken: string diff --git a/server/tests/external-plugins/akismet.ts b/server/tests/external-plugins/akismet.ts index 974bf0011..e964bf0c2 100644 --- a/server/tests/external-plugins/akismet.ts +++ b/server/tests/external-plugins/akismet.ts @@ -138,14 +138,14 @@ describe('Official plugin Akismet', function () { }) it('Should allow signup', async function () { - await servers[0].users.register({ + await servers[0].registrations.register({ username: 'user1', displayName: 'user 1' }) }) it('Should detect a signup as SPAM', async function () { - await servers[0].users.register({ + await servers[0].registrations.register({ username: 'user2', displayName: 'user 2', email: 'akismet-guaranteed-spam@example.com', diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js index 19ba9f278..5b4d34f15 100644 --- a/server/tests/fixtures/peertube-plugin-test/main.js +++ b/server/tests/fixtures/peertube-plugin-test/main.js @@ -226,16 +226,29 @@ async function register ({ registerHook, registerSetting, settingsManager, stora } }) - registerHook({ - target: 'filter:api.user.signup.allowed.result', - handler: (result, params) => { - if (params && params.body && params.body.email && params.body.email.includes('jma')) { - return { allowed: false, errorMessage: 'No jma' } + { + registerHook({ + target: 'filter:api.user.signup.allowed.result', + handler: (result, params) => { + if (params && params.body && params.body.email && params.body.email.includes('jma 1')) { + return { allowed: false, errorMessage: 'No jma 1' } + } + + return result } + }) - return result - } - }) + registerHook({ + target: 'filter:api.user.request-signup.allowed.result', + handler: (result, params) => { + if (params && params.body && params.body.email && params.body.email.includes('jma 2')) { + return { allowed: false, errorMessage: 'No jma 2' } + } + + return result + } + }) + } registerHook({ target: 'filter:api.download.torrent.allowed.result', diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts index 36f8052c0..a266ae7f1 100644 --- a/server/tests/plugins/action-hooks.ts +++ b/server/tests/plugins/action-hooks.ts @@ -153,7 +153,7 @@ describe('Test plugin action hooks', function () { let userId: number it('Should run action:api.user.registered', async function () { - await servers[0].users.register({ username: 'registered_user' }) + await servers[0].registrations.register({ username: 'registered_user' }) await checkHook('action:api.user.registered') }) diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts index 6724b3bf8..37eef6cf3 100644 --- a/server/tests/plugins/filter-hooks.ts +++ b/server/tests/plugins/filter-hooks.ts @@ -1,7 +1,15 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import { expect } from 'chai' -import { HttpStatusCode, VideoDetails, VideoImportState, VideoPlaylist, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' +import { + HttpStatusCode, + PeerTubeProblemDocument, + VideoDetails, + VideoImportState, + VideoPlaylist, + VideoPlaylistPrivacy, + VideoPrivacy +} from '@shared/models' import { cleanupTests, createMultipleServers, @@ -408,23 +416,52 @@ describe('Test plugin filter hooks', function () { describe('Should run filter:api.user.signup.allowed.result', function () { + before(async function () { + await servers[0].config.updateExistingSubConfig({ newConfig: { signup: { requiresApproval: false } } }) + }) + it('Should run on config endpoint', async function () { const body = await servers[0].config.getConfig() expect(body.signup.allowed).to.be.true }) it('Should allow a signup', async function () { - await servers[0].users.register({ username: 'john', password: 'password' }) + await servers[0].registrations.register({ username: 'john1' }) }) it('Should not allow a signup', async function () { - const res = await servers[0].users.register({ - username: 'jma', - password: 'password', + const res = await servers[0].registrations.register({ + username: 'jma 1', + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + + expect(res.body.error).to.equal('No jma 1') + }) + }) + + describe('Should run filter:api.user.request-signup.allowed.result', function () { + + before(async function () { + await servers[0].config.updateExistingSubConfig({ newConfig: { signup: { requiresApproval: true } } }) + }) + + it('Should run on config endpoint', async function () { + const body = await servers[0].config.getConfig() + expect(body.signup.allowed).to.be.true + }) + + it('Should allow a signup request', async function () { + await servers[0].registrations.requestRegistration({ username: 'john2', registrationReason: 'tt' }) + }) + + it('Should not allow a signup request', async function () { + const body = await servers[0].registrations.requestRegistration({ + username: 'jma 2', + registrationReason: 'tt', expectedStatus: HttpStatusCode.FORBIDDEN_403 }) - expect(res.body.error).to.equal('No jma') + expect((body as unknown as PeerTubeProblemDocument).error).to.equal('No jma 2') }) }) diff --git a/server/tests/shared/notifications.ts b/server/tests/shared/notifications.ts index e600bd6b2..6c0688d5a 100644 --- a/server/tests/shared/notifications.ts +++ b/server/tests/shared/notifications.ts @@ -11,6 +11,7 @@ import { UserNotificationType } from '@shared/models' import { + ConfigCommand, createMultipleServers, doubleFollow, PeerTubeServer, @@ -173,6 +174,8 @@ async function checkMyVideoImportIsFinished (options: CheckerBaseParams & { await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } +// --------------------------------------------------------------------------- + async function checkUserRegistered (options: CheckerBaseParams & { username: string checkType: CheckerType @@ -201,6 +204,36 @@ async function checkUserRegistered (options: CheckerBaseParams & { await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) } +async function checkRegistrationRequest (options: CheckerBaseParams & { + username: string + registrationReason: string + checkType: CheckerType +}) { + const { username, registrationReason } = options + const notificationType = UserNotificationType.NEW_USER_REGISTRATION_REQUEST + + function notificationChecker (notification: UserNotification, checkType: CheckerType) { + if (checkType === 'presence') { + expect(notification).to.not.be.undefined + expect(notification.type).to.equal(notificationType) + + expect(notification.registration.username).to.equal(username) + } else { + expect(notification).to.satisfy(n => n.type !== notificationType || n.registration.username !== username) + } + } + + function emailNotificationFinder (email: object) { + const text: string = email['text'] + + return text.includes(' wants to register ') && text.includes(username) && text.includes(registrationReason) + } + + await checkNotification({ ...options, notificationChecker, emailNotificationFinder }) +} + +// --------------------------------------------------------------------------- + async function checkNewActorFollow (options: CheckerBaseParams & { followType: 'channel' | 'account' followerName: string @@ -673,10 +706,8 @@ async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: an const port = await MockSmtpServer.Instance.collectEmails(emails) const overrideConfig = { - smtp: { - hostname: '127.0.0.1', - port - }, + ...ConfigCommand.getEmailOverrideConfig(port), + signup: { limit: 20 } @@ -735,7 +766,8 @@ async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: an userAccessToken, emails, servers, - channelId + channelId, + baseOverrideConfig: overrideConfig } } @@ -765,7 +797,8 @@ export { checkNewAccountAbuseForModerators, checkNewPeerTubeVersion, checkNewPluginVersion, - checkVideoStudioEditionIsFinished + checkVideoStudioEditionIsFinished, + checkRegistrationRequest } // --------------------------------------------------------------------------- -- cgit v1.2.3