]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add signup approval API tests
authorChocobozzz <me@florianbigard.com>
Thu, 19 Jan 2023 08:28:29 +0000 (09:28 +0100)
committerChocobozzz <chocobozzz@cpy.re>
Thu, 19 Jan 2023 12:53:40 +0000 (13:53 +0100)
30 files changed:
server/tests/api/check-params/config.ts
server/tests/api/check-params/contact-form.ts
server/tests/api/check-params/index.ts
server/tests/api/check-params/registrations.ts [new file with mode: 0644]
server/tests/api/check-params/upload-quota.ts
server/tests/api/check-params/users-admin.ts
server/tests/api/check-params/users-emails.ts [new file with mode: 0644]
server/tests/api/check-params/users.ts [deleted file]
server/tests/api/notifications/index.ts
server/tests/api/notifications/moderation-notifications.ts
server/tests/api/notifications/registrations-notifications.ts [new file with mode: 0644]
server/tests/api/server/config-defaults.ts
server/tests/api/server/config.ts
server/tests/api/server/contact-form.ts
server/tests/api/server/email.ts
server/tests/api/server/reverse-proxy.ts
server/tests/api/users/index.ts
server/tests/api/users/registrations.ts [new file with mode: 0644]
server/tests/api/users/users-email-verification.ts [moved from server/tests/api/users/users-verification.ts with 86% similarity]
server/tests/api/users/users.ts
server/tests/external-plugins/akismet.ts
server/tests/fixtures/peertube-plugin-test/main.js
server/tests/plugins/action-hooks.ts
server/tests/plugins/filter-hooks.ts
server/tests/shared/notifications.ts
shared/server-commands/server/config-command.ts
shared/server-commands/server/server.ts
shared/server-commands/users/index.ts
shared/server-commands/users/registrations-command.ts [new file with mode: 0644]
shared/server-commands/users/users-command.ts

index 3415625ca9b1b437f568d052ac4b8efbc9efb539..93a3f3eb9c3a18418e1b64f3050cc575bc830acc 100644 (file)
@@ -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
         }
       }
index 7968ef80235491da1f9b5469c38d5d1c97910d99..f0f8819b923d7c1b0023279c1c999cc96ecf6589 100644 (file)
@@ -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 })
index 961093bb563191fc83f445342d5b2d38c875a622..ddbcb42f8d3db622bdd6cb508daed3940db4323e 100644 (file)
@@ -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 (file)
index 0000000..9f04623
--- /dev/null
@@ -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 ])
+  })
+})
index 70e6f4af9322548925ada4b350fa717530e2a9a3..fdc711bd52420b46f2f387e48e7801d09d59f0e1 100644 (file)
@@ -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' }
index 7ba709c4a57601e3b90edc4e27f4cc29df4b8318..be2496bb47be80b9b9394289ee82977e238e6bd6 100644 (file)
@@ -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 (file)
index 0000000..8cfb1d1
--- /dev/null
@@ -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 (file)
index 7acfd8c..0000000
+++ /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 ])
-  })
-})
index 8caa30a3d2c9304603c46aa6fc0c937e6f4a12c0..c0216b74fd3a6e8b03c130e5aa4c77ed5b0f9013 100644 (file)
@@ -2,4 +2,5 @@ import './admin-notifications'
 import './comments-notifications'
 import './moderation-notifications'
 import './notifications-api'
+import './registrations-notifications'
 import './user-notifications'
index c7b9b5fb03a6d8bf4094653d604bf4bd7317cf1d..bb11a08aa28e2dbee77f9b32e9d7d4a479a880db 100644 (file)
@@ -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 (file)
index 0000000..b5a7c2b
--- /dev/null
@@ -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 ])
+  })
+})
index 4fa37d0e2eadd31f1e7506da4efcd2d17d0b2a02..d3b3a24475ee16ad0c60b6ee373e5da37b59631c 100644 (file)
@@ -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')
 
index 22446fe0c9c2863ccf8dda6d724af9a8f2e18e87..b91519660dbfbdf19c10cd76ba3f4a224d2f76d2 100644 (file)
@@ -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()
index 3252180085da206670ecae5cd763b796a79d064c..dd971203a050ccf3560311e582eb831cc0c2b45d 100644 (file)
@@ -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
index 4ab5463fe2d5696bcad683df485b248e018189af..db7aa65bd70f082af429a946259998405977af6b 100644 (file)
@@ -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()
 
index d882f0bde79fe70e8c65069da6837eaed9adaed1..11c96c4b524c755ce0d841a11b2f1f304d794782 100644 (file)
@@ -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 })
 
   })
 
index 0313845ef0fb2fa94593c9f3dab6b9957726fe47..a4443a8ec9d7e87cb4b37b2bb32404045cf1c000 100644 (file)
@@ -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 (file)
index 0000000..a9e1114
--- /dev/null
@@ -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 ])
+  })
+})
similarity index 86%
rename from server/tests/api/users/users-verification.ts
rename to server/tests/api/users/users-email-verification.ts
index 19a8df9e105e4dbb39a1457c75d013fa4cf14217..cb84dc75868cbcfccd050dd8f69737e444589b9f 100644 (file)
@@ -3,9 +3,16 @@
 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 () {
+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
@@ -25,14 +32,7 @@ describe('Test users account verification', 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)
+    server = await createSingleServer(1, ConfigCommand.getEmailOverrideConfig(port))
 
     await setAccessTokensToServers([ server ])
   })
@@ -40,17 +40,18 @@ describe('Test users account verification', function () {
   it('Should register user and send verification email if verification required', async function () {
     this.timeout(30000)
 
-    await server.config.updateCustomSubConfig({
+    await server.config.updateExistingSubConfig({
       newConfig: {
         signup: {
           enabled: true,
+          requiresApproval: false,
           requiresEmailVerification: true,
           limit: 10
         }
       }
     })
 
-    await server.users.register(user1)
+    await server.registrations.register(user1)
 
     await waitJobs(server)
     expectedEmailsLength++
@@ -127,17 +128,15 @@ describe('Test users account verification', function () {
 
   it('Should register user not requiring email verification if setting not enabled', async function () {
     this.timeout(5000)
-    await server.config.updateCustomSubConfig({
+    await server.config.updateExistingSubConfig({
       newConfig: {
         signup: {
-          enabled: true,
-          requiresEmailVerification: false,
-          limit: 10
+          requiresEmailVerification: false
         }
       }
     })
 
-    await server.users.register(user2)
+    await server.registrations.register(user2)
 
     await waitJobs(server)
     expect(emails).to.have.lengthOf(expectedEmailsLength)
@@ -152,9 +151,7 @@ describe('Test users account verification', function () {
     await server.config.updateCustomSubConfig({
       newConfig: {
         signup: {
-          enabled: true,
-          requiresEmailVerification: true,
-          limit: 10
+          requiresEmailVerification: true
         }
       }
     })
index 93e2e489a3d4f06b39e0810f1d8848a282f6590f..f1e170971ef0a5e4adb5fdcb875b0d5606cd8769 100644 (file)
@@ -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
index 974bf0011f7861c419fd51fbb6a09a6f9111b971..e964bf0c2c1d5ad2d7bb43a5133a0811b0669d64 100644 (file)
@@ -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',
index 19ba9f2784c01ff4e56cd506bee5b3fd1674331a..5b4d34f1547629d00231c2e3a172cf5a94ab0e23 100644 (file)
@@ -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',
index 36f8052c0a1744e7b1f8badb59c500a9ee4b45bb..a266ae7f14e0c59811dff62cc737a74364c204ba 100644 (file)
@@ -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')
     })
index 6724b3bf86dcfcc7b6fd3946be8fbc4bd70b8ad9..37eef6cf36385536107853cf990bde8c733bd513 100644 (file)
@@ -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')
     })
   })
 
index e600bd6b2b3e7bb80d783a88687d80835cf1c7f3..6c0688d5aaf82e372d5c9e40fa23e8b66a88a614 100644 (file)
@@ -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
 }
 
 // ---------------------------------------------------------------------------
index 1c2315ed15685f249d52fa641360b97a8b5945bf..51267b85b31088a63ecac17e1020b0a7cdad2e49 100644 (file)
@@ -18,6 +18,33 @@ export class ConfigCommand extends AbstractCommand {
     }
   }
 
+  // ---------------------------------------------------------------------------
+
+  static getEmailOverrideConfig (emailPort: number) {
+    return {
+      smtp: {
+        hostname: '127.0.0.1',
+        port: emailPort
+      }
+    }
+  }
+
+  // ---------------------------------------------------------------------------
+
+  enableSignup (requiresApproval: boolean) {
+    return this.updateExistingSubConfig({
+      newConfig: {
+        signup: {
+          enabled: true,
+          requiresApproval,
+          limit: -1
+        }
+      }
+    })
+  }
+
+  // ---------------------------------------------------------------------------
+
   disableImports () {
     return this.setImportsEnabled(false)
   }
@@ -44,6 +71,16 @@ export class ConfigCommand extends AbstractCommand {
     })
   }
 
+  // ---------------------------------------------------------------------------
+
+  enableChannelSync () {
+    return this.setChannelSyncEnabled(true)
+  }
+
+  disableChannelSync () {
+    return this.setChannelSyncEnabled(false)
+  }
+
   private setChannelSyncEnabled (enabled: boolean) {
     return this.updateExistingSubConfig({
       newConfig: {
@@ -56,13 +93,7 @@ export class ConfigCommand extends AbstractCommand {
     })
   }
 
-  enableChannelSync () {
-    return this.setChannelSyncEnabled(true)
-  }
-
-  disableChannelSync () {
-    return this.setChannelSyncEnabled(false)
-  }
+  // ---------------------------------------------------------------------------
 
   enableLive (options: {
     allowReplay?: boolean
@@ -142,6 +173,8 @@ export class ConfigCommand extends AbstractCommand {
     })
   }
 
+  // ---------------------------------------------------------------------------
+
   enableStudio () {
     return this.updateExistingSubConfig({
       newConfig: {
@@ -152,6 +185,8 @@ export class ConfigCommand extends AbstractCommand {
     })
   }
 
+  // ---------------------------------------------------------------------------
+
   getConfig (options: OverrideCommandOptions = {}) {
     const path = '/api/v1/config'
 
@@ -304,6 +339,7 @@ export class ConfigCommand extends AbstractCommand {
       signup: {
         enabled: false,
         limit: 5,
+        requiresApproval: true,
         requiresEmailVerification: false,
         minimumAge: 16
       },
index ae1395a74246002bb34fbeabe846b729309958b6..793fae3a85b900ad8d5e4b59791ac04a022b9d4c 100644 (file)
@@ -18,6 +18,7 @@ import {
   BlocklistCommand,
   LoginCommand,
   NotificationsCommand,
+  RegistrationsCommand,
   SubscriptionsCommand,
   TwoFactorCommand,
   UsersCommand
@@ -147,6 +148,7 @@ export class PeerTubeServer {
   views?: ViewsCommand
   twoFactor?: TwoFactorCommand
   videoToken?: VideoTokenCommand
+  registrations?: RegistrationsCommand
 
   constructor (options: { serverNumber: number } | { url: string }) {
     if ((options as any).url) {
@@ -430,5 +432,6 @@ export class PeerTubeServer {
     this.views = new ViewsCommand(this)
     this.twoFactor = new TwoFactorCommand(this)
     this.videoToken = new VideoTokenCommand(this)
+    this.registrations = new RegistrationsCommand(this)
   }
 }
index 1afc02dc1854d9354918d67bfba5e816610ef90f..404756539fbf101c51369da0ec93c0336cd4b3f3 100644 (file)
@@ -4,6 +4,7 @@ export * from './blocklist-command'
 export * from './login'
 export * from './login-command'
 export * from './notifications-command'
+export * from './registrations-command'
 export * from './subscriptions-command'
 export * from './two-factor-command'
 export * from './users-command'
diff --git a/shared/server-commands/users/registrations-command.ts b/shared/server-commands/users/registrations-command.ts
new file mode 100644 (file)
index 0000000..4e97571
--- /dev/null
@@ -0,0 +1,157 @@
+import { pick } from '@shared/core-utils'
+import { HttpStatusCode, ResultList, UserRegistration, UserRegistrationRequest } from '@shared/models'
+import { unwrapBody } from '../requests'
+import { AbstractCommand, OverrideCommandOptions } from '../shared'
+
+export class RegistrationsCommand extends AbstractCommand {
+
+  register (options: OverrideCommandOptions & Partial<UserRegistrationRequest> & Pick<UserRegistrationRequest, 'username'>) {
+    const { password = 'password', email = options.username + '@example.com' } = options
+    const path = '/api/v1/users/register'
+
+    return this.postBodyRequest({
+      ...options,
+
+      path,
+      fields: {
+        ...pick(options, [ 'username', 'displayName', 'channel' ]),
+
+        password,
+        email
+      },
+      implicitToken: false,
+      defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+    })
+  }
+
+  requestRegistration (
+    options: OverrideCommandOptions & Partial<UserRegistrationRequest> & Pick<UserRegistrationRequest, 'username' | 'registrationReason'>
+  ) {
+    const { password = 'password', email = options.username + '@example.com' } = options
+    const path = '/api/v1/users/registrations/request'
+
+    return unwrapBody<UserRegistration>(this.postBodyRequest({
+      ...options,
+
+      path,
+      fields: {
+        ...pick(options, [ 'username', 'displayName', 'channel', 'registrationReason' ]),
+
+        password,
+        email
+      },
+      implicitToken: false,
+      defaultExpectedStatus: HttpStatusCode.OK_200
+    }))
+  }
+
+  // ---------------------------------------------------------------------------
+
+  accept (options: OverrideCommandOptions & {
+    id: number
+    moderationResponse: string
+  }) {
+    const { id, moderationResponse } = options
+    const path = '/api/v1/users/registrations/' + id + '/accept'
+
+    return this.postBodyRequest({
+      ...options,
+
+      path,
+      fields: { moderationResponse },
+      implicitToken: true,
+      defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+    })
+  }
+
+  reject (options: OverrideCommandOptions & {
+    id: number
+    moderationResponse: string
+  }) {
+    const { id, moderationResponse } = options
+    const path = '/api/v1/users/registrations/' + id + '/reject'
+
+    return this.postBodyRequest({
+      ...options,
+
+      path,
+      fields: { moderationResponse },
+      implicitToken: true,
+      defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+    })
+  }
+
+  // ---------------------------------------------------------------------------
+
+  delete (options: OverrideCommandOptions & {
+    id: number
+  }) {
+    const { id } = options
+    const path = '/api/v1/users/registrations/' + id
+
+    return this.deleteRequest({
+      ...options,
+
+      path,
+      implicitToken: true,
+      defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+    })
+  }
+
+  // ---------------------------------------------------------------------------
+
+  list (options: OverrideCommandOptions & {
+    start?: number
+    count?: number
+    sort?: string
+    search?: string
+  } = {}) {
+    const path = '/api/v1/users/registrations'
+
+    return this.getRequestBody<ResultList<UserRegistration>>({
+      ...options,
+
+      path,
+      query: pick(options, [ 'start', 'count', 'sort', 'search' ]),
+      implicitToken: true,
+      defaultExpectedStatus: HttpStatusCode.OK_200
+    })
+  }
+
+  // ---------------------------------------------------------------------------
+
+  askSendVerifyEmail (options: OverrideCommandOptions & {
+    email: string
+  }) {
+    const { email } = options
+    const path = '/api/v1/users/registrations/ask-send-verify-email'
+
+    return this.postBodyRequest({
+      ...options,
+
+      path,
+      fields: { email },
+      implicitToken: false,
+      defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+    })
+  }
+
+  verifyEmail (options: OverrideCommandOptions & {
+    registrationId: number
+    verificationString: string
+  }) {
+    const { registrationId, verificationString } = options
+    const path = '/api/v1/users/registrations/' + registrationId + '/verify-email'
+
+    return this.postBodyRequest({
+      ...options,
+
+      path,
+      fields: {
+        verificationString
+      },
+      implicitToken: false,
+      defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+    })
+  }
+}
index 811b9685b42c4c3293a5dfcf5b6500cd3eb3dc8f..8a42fafc817a65503e59ac5f5acad2e68cc0c6d7 100644 (file)
@@ -214,35 +214,6 @@ export class UsersCommand extends AbstractCommand {
     return this.server.login.getAccessToken({ username, password })
   }
 
-  register (options: OverrideCommandOptions & {
-    username: string
-    password?: string
-    displayName?: string
-    email?: string
-    channel?: {
-      name: string
-      displayName: string
-    }
-  }) {
-    const { username, password = 'password', displayName, channel, email = username + '@example.com' } = options
-    const path = '/api/v1/users/register'
-
-    return this.postBodyRequest({
-      ...options,
-
-      path,
-      fields: {
-        username,
-        password,
-        email,
-        displayName,
-        channel
-      },
-      implicitToken: false,
-      defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
-    })
-  }
-
   // ---------------------------------------------------------------------------
 
   getMyInfo (options: OverrideCommandOptions = {}) {