Creating a user with an empty password will send an email to let him set his password (#2479)
* Creating a user with an empty password will send an email to let him set his password * Consideration of Chocobozzz's comments * Tips for optional password * API documentation * Fix circular imports * Tests
This commit is contained in:
parent
c5621bd23b
commit
45f1bd72a0
|
@ -1,5 +1,5 @@
|
||||||
import { Component, OnInit } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
import { Router } from '@angular/router'
|
import { Router, ActivatedRoute } from '@angular/router'
|
||||||
import { AuthService, Notifier, ServerService } from '@app/core'
|
import { AuthService, Notifier, ServerService } from '@app/core'
|
||||||
import { UserCreate, UserRole } from '../../../../../../shared'
|
import { UserCreate, UserRole } from '../../../../../../shared'
|
||||||
import { UserEdit } from './user-edit'
|
import { UserEdit } from './user-edit'
|
||||||
|
@ -23,6 +23,7 @@ export class UserCreateComponent extends UserEdit implements OnInit {
|
||||||
protected configService: ConfigService,
|
protected configService: ConfigService,
|
||||||
protected auth: AuthService,
|
protected auth: AuthService,
|
||||||
private userValidatorsService: UserValidatorsService,
|
private userValidatorsService: UserValidatorsService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private notifier: Notifier,
|
private notifier: Notifier,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
|
@ -45,7 +46,7 @@ export class UserCreateComponent extends UserEdit implements OnInit {
|
||||||
this.buildForm({
|
this.buildForm({
|
||||||
username: this.userValidatorsService.USER_USERNAME,
|
username: this.userValidatorsService.USER_USERNAME,
|
||||||
email: this.userValidatorsService.USER_EMAIL,
|
email: this.userValidatorsService.USER_EMAIL,
|
||||||
password: this.userValidatorsService.USER_PASSWORD,
|
password: this.isPasswordOptional() ? this.userValidatorsService.USER_PASSWORD_OPTIONAL : this.userValidatorsService.USER_PASSWORD,
|
||||||
role: this.userValidatorsService.USER_ROLE,
|
role: this.userValidatorsService.USER_ROLE,
|
||||||
videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
|
videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
|
||||||
videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY,
|
videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY,
|
||||||
|
@ -78,6 +79,11 @@ export class UserCreateComponent extends UserEdit implements OnInit {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isPasswordOptional () {
|
||||||
|
const serverConfig = this.route.snapshot.data.serverConfig
|
||||||
|
return serverConfig.email.enabled
|
||||||
|
}
|
||||||
|
|
||||||
getFormButtonTitle () {
|
getFormButtonTitle () {
|
||||||
return this.i18n('Create user')
|
return this.i18n('Create user')
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,13 @@
|
||||||
|
|
||||||
<div class="form-group" *ngIf="isCreation()">
|
<div class="form-group" *ngIf="isCreation()">
|
||||||
<label i18n for="password">Password</label>
|
<label i18n for="password">Password</label>
|
||||||
|
<my-help *ngIf="isPasswordOptional()">
|
||||||
|
<ng-template ptTemplate="customHtml">
|
||||||
|
<ng-container i18n>
|
||||||
|
If you leave the password empty, an email will be sent to the user.
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
</my-help>
|
||||||
<input
|
<input
|
||||||
type="password" id="password" autocomplete="new-password"
|
type="password" id="password" autocomplete="new-password"
|
||||||
formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
|
formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
|
||||||
|
|
|
@ -92,6 +92,10 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isPasswordOptional () {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
getFormButtonTitle () {
|
getFormButtonTitle () {
|
||||||
return this.i18n('Update user')
|
return this.i18n('Update user')
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { UserRight } from '../../../../../shared'
|
||||||
import { UsersComponent } from './users.component'
|
import { UsersComponent } from './users.component'
|
||||||
import { UserCreateComponent, UserUpdateComponent } from './user-edit'
|
import { UserCreateComponent, UserUpdateComponent } from './user-edit'
|
||||||
import { UserListComponent } from './user-list'
|
import { UserListComponent } from './user-list'
|
||||||
|
import { ServerConfigResolver } from '@app/core/routing/server-config-resolver.service'
|
||||||
|
|
||||||
export const UsersRoutes: Routes = [
|
export const UsersRoutes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -36,6 +37,9 @@ export const UsersRoutes: Routes = [
|
||||||
meta: {
|
meta: {
|
||||||
title: 'Create a user'
|
title: 'Create a user'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
serverConfig: ServerConfigResolver
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,6 +8,7 @@ export class UserValidatorsService {
|
||||||
readonly USER_USERNAME: BuildFormValidator
|
readonly USER_USERNAME: BuildFormValidator
|
||||||
readonly USER_EMAIL: BuildFormValidator
|
readonly USER_EMAIL: BuildFormValidator
|
||||||
readonly USER_PASSWORD: BuildFormValidator
|
readonly USER_PASSWORD: BuildFormValidator
|
||||||
|
readonly USER_PASSWORD_OPTIONAL: BuildFormValidator
|
||||||
readonly USER_CONFIRM_PASSWORD: BuildFormValidator
|
readonly USER_CONFIRM_PASSWORD: BuildFormValidator
|
||||||
readonly USER_VIDEO_QUOTA: BuildFormValidator
|
readonly USER_VIDEO_QUOTA: BuildFormValidator
|
||||||
readonly USER_VIDEO_QUOTA_DAILY: BuildFormValidator
|
readonly USER_VIDEO_QUOTA_DAILY: BuildFormValidator
|
||||||
|
@ -56,6 +57,17 @@ export class UserValidatorsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.USER_PASSWORD_OPTIONAL = {
|
||||||
|
VALIDATORS: [
|
||||||
|
Validators.minLength(6),
|
||||||
|
Validators.maxLength(255)
|
||||||
|
],
|
||||||
|
MESSAGES: {
|
||||||
|
'minlength': this.i18n('Password must be at least 6 characters long.'),
|
||||||
|
'maxlength': this.i18n('Password cannot be more than 255 characters long.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.USER_CONFIRM_PASSWORD = {
|
this.USER_CONFIRM_PASSWORD = {
|
||||||
VALIDATORS: [],
|
VALIDATORS: [],
|
||||||
MESSAGES: {
|
MESSAGES: {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as express from 'express'
|
||||||
import * as RateLimit from 'express-rate-limit'
|
import * as RateLimit from 'express-rate-limit'
|
||||||
import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared'
|
import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { getFormattedObjects } from '../../../helpers/utils'
|
import { generateRandomString, getFormattedObjects } from '../../../helpers/utils'
|
||||||
import { WEBSERVER } from '../../../initializers/constants'
|
import { WEBSERVER } from '../../../initializers/constants'
|
||||||
import { Emailer } from '../../../lib/emailer'
|
import { Emailer } from '../../../lib/emailer'
|
||||||
import { Redis } from '../../../lib/redis'
|
import { Redis } from '../../../lib/redis'
|
||||||
|
@ -197,11 +197,25 @@ async function createUser (req: express.Request, res: express.Response) {
|
||||||
adminFlags: body.adminFlags || UserAdminFlag.NONE
|
adminFlags: body.adminFlags || UserAdminFlag.NONE
|
||||||
}) as MUser
|
}) as MUser
|
||||||
|
|
||||||
|
// NB: due to the validator usersAddValidator, password==='' can only be true if we can send the mail.
|
||||||
|
const createPassword = userToCreate.password === ''
|
||||||
|
if (createPassword) {
|
||||||
|
userToCreate.password = await generateRandomString(20)
|
||||||
|
}
|
||||||
|
|
||||||
const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate })
|
const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate })
|
||||||
|
|
||||||
auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
|
auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
|
||||||
logger.info('User %s with its channel and account created.', body.username)
|
logger.info('User %s with its channel and account created.', body.username)
|
||||||
|
|
||||||
|
if (createPassword) {
|
||||||
|
// this will send an email for newly created users, so then can set their first password.
|
||||||
|
logger.info('Sending to user %s a create password email', body.username)
|
||||||
|
const verificationString = await Redis.Instance.setCreatePasswordVerificationString(user.id)
|
||||||
|
const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
|
||||||
|
await Emailer.Instance.addPasswordCreateEmailJob(userToCreate.username, user.email, url)
|
||||||
|
}
|
||||||
|
|
||||||
Hooks.runAction('action:api.user.created', { body, user, account, videoChannel })
|
Hooks.runAction('action:api.user.created', { body, user, account, videoChannel })
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { UserRole } from '../../../shared'
|
||||||
import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants'
|
import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants'
|
||||||
import { exists, isArray, isBooleanValid, isFileValid } from './misc'
|
import { exists, isArray, isBooleanValid, isFileValid } from './misc'
|
||||||
import { values } from 'lodash'
|
import { values } from 'lodash'
|
||||||
|
import { CONFIG } from '../../initializers/config'
|
||||||
|
|
||||||
const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
|
const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
|
||||||
|
|
||||||
|
@ -10,6 +11,14 @@ function isUserPasswordValid (value: string) {
|
||||||
return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD)
|
return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isUserPasswordValidOrEmpty (value: string) {
|
||||||
|
// Empty password is only possible if emailing is enabled.
|
||||||
|
if (value === '') {
|
||||||
|
return !!CONFIG.SMTP.HOSTNAME && !!CONFIG.SMTP.PORT
|
||||||
|
}
|
||||||
|
return isUserPasswordValid(value)
|
||||||
|
}
|
||||||
|
|
||||||
function isUserVideoQuotaValid (value: string) {
|
function isUserVideoQuotaValid (value: string) {
|
||||||
return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA)
|
return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA)
|
||||||
}
|
}
|
||||||
|
@ -103,6 +112,7 @@ export {
|
||||||
isUserVideosHistoryEnabledValid,
|
isUserVideosHistoryEnabledValid,
|
||||||
isUserBlockedValid,
|
isUserBlockedValid,
|
||||||
isUserPasswordValid,
|
isUserPasswordValid,
|
||||||
|
isUserPasswordValidOrEmpty,
|
||||||
isUserVideoLanguages,
|
isUserVideoLanguages,
|
||||||
isUserBlockedReasonValid,
|
isUserBlockedReasonValid,
|
||||||
isUserRoleValid,
|
isUserRoleValid,
|
||||||
|
|
|
@ -502,6 +502,7 @@ let PRIVATE_RSA_KEY_SIZE = 2048
|
||||||
const BCRYPT_SALT_SIZE = 10
|
const BCRYPT_SALT_SIZE = 10
|
||||||
|
|
||||||
const USER_PASSWORD_RESET_LIFETIME = 60000 * 60 // 60 minutes
|
const USER_PASSWORD_RESET_LIFETIME = 60000 * 60 // 60 minutes
|
||||||
|
const USER_PASSWORD_CREATE_LIFETIME = 60000 * 60 * 24 * 7 // 7 days
|
||||||
|
|
||||||
const USER_EMAIL_VERIFY_LIFETIME = 60000 * 60 // 60 minutes
|
const USER_EMAIL_VERIFY_LIFETIME = 60000 * 60 // 60 minutes
|
||||||
|
|
||||||
|
@ -764,6 +765,7 @@ export {
|
||||||
LRU_CACHE,
|
LRU_CACHE,
|
||||||
JOB_REQUEST_TIMEOUT,
|
JOB_REQUEST_TIMEOUT,
|
||||||
USER_PASSWORD_RESET_LIFETIME,
|
USER_PASSWORD_RESET_LIFETIME,
|
||||||
|
USER_PASSWORD_CREATE_LIFETIME,
|
||||||
MEMOIZE_TTL,
|
MEMOIZE_TTL,
|
||||||
USER_EMAIL_VERIFY_LIFETIME,
|
USER_EMAIL_VERIFY_LIFETIME,
|
||||||
OVERVIEWS,
|
OVERVIEWS,
|
||||||
|
|
|
@ -384,6 +384,22 @@ class Emailer {
|
||||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addPasswordCreateEmailJob (username: string, to: string, resetPasswordUrl: string) {
|
||||||
|
const text = 'Hi,\n\n' +
|
||||||
|
`Welcome to your ${WEBSERVER.HOST} PeerTube instance. Your username is: ${username}.\n\n` +
|
||||||
|
`Please set your password by following this link: ${resetPasswordUrl} (this link will expire within seven days).\n\n` +
|
||||||
|
'Cheers,\n' +
|
||||||
|
`${CONFIG.EMAIL.BODY.SIGNATURE}`
|
||||||
|
|
||||||
|
const emailPayload: EmailPayload = {
|
||||||
|
to: [ to ],
|
||||||
|
subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New PeerTube account password',
|
||||||
|
text
|
||||||
|
}
|
||||||
|
|
||||||
|
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||||
|
}
|
||||||
|
|
||||||
addVerifyEmailJob (to: string, verifyEmailUrl: string) {
|
addVerifyEmailJob (to: string, verifyEmailUrl: string) {
|
||||||
const text = 'Welcome to PeerTube,\n\n' +
|
const text = 'Welcome to PeerTube,\n\n' +
|
||||||
`To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` +
|
`To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` +
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
CONTACT_FORM_LIFETIME,
|
CONTACT_FORM_LIFETIME,
|
||||||
USER_EMAIL_VERIFY_LIFETIME,
|
USER_EMAIL_VERIFY_LIFETIME,
|
||||||
USER_PASSWORD_RESET_LIFETIME,
|
USER_PASSWORD_RESET_LIFETIME,
|
||||||
|
USER_PASSWORD_CREATE_LIFETIME,
|
||||||
VIDEO_VIEW_LIFETIME,
|
VIDEO_VIEW_LIFETIME,
|
||||||
WEBSERVER
|
WEBSERVER
|
||||||
} from '../initializers/constants'
|
} from '../initializers/constants'
|
||||||
|
@ -74,6 +75,14 @@ class Redis {
|
||||||
return generatedString
|
return generatedString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setCreatePasswordVerificationString (userId: number) {
|
||||||
|
const generatedString = await generateRandomString(32)
|
||||||
|
|
||||||
|
await this.setValue(this.generateResetPasswordKey(userId), generatedString, USER_PASSWORD_CREATE_LIFETIME)
|
||||||
|
|
||||||
|
return generatedString
|
||||||
|
}
|
||||||
|
|
||||||
async getResetPasswordLink (userId: number) {
|
async getResetPasswordLink (userId: number) {
|
||||||
return this.getValue(this.generateResetPasswordKey(userId))
|
return this.getValue(this.generateResetPasswordKey(userId))
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
isUserDisplayNameValid,
|
isUserDisplayNameValid,
|
||||||
isUserNSFWPolicyValid,
|
isUserNSFWPolicyValid,
|
||||||
isUserPasswordValid,
|
isUserPasswordValid,
|
||||||
|
isUserPasswordValidOrEmpty,
|
||||||
isUserRoleValid,
|
isUserRoleValid,
|
||||||
isUserUsernameValid,
|
isUserUsernameValid,
|
||||||
isUserVideoLanguages,
|
isUserVideoLanguages,
|
||||||
|
@ -39,7 +40,7 @@ import { Hooks } from '@server/lib/plugins/hooks'
|
||||||
|
|
||||||
const usersAddValidator = [
|
const usersAddValidator = [
|
||||||
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
|
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
|
||||||
body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
|
body('password').custom(isUserPasswordValidOrEmpty).withMessage('Should have a valid password'),
|
||||||
body('email').isEmail().withMessage('Should have a valid email'),
|
body('email').isEmail().withMessage('Should have a valid email'),
|
||||||
body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
|
body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
|
||||||
body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
|
body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
|
||||||
|
|
|
@ -16,12 +16,14 @@ import {
|
||||||
getMyUserVideoRating,
|
getMyUserVideoRating,
|
||||||
getUsersList,
|
getUsersList,
|
||||||
immutableAssign,
|
immutableAssign,
|
||||||
|
killallServers,
|
||||||
makeGetRequest,
|
makeGetRequest,
|
||||||
makePostBodyRequest,
|
makePostBodyRequest,
|
||||||
makePutBodyRequest,
|
makePutBodyRequest,
|
||||||
makeUploadRequest,
|
makeUploadRequest,
|
||||||
registerUser,
|
registerUser,
|
||||||
removeUser,
|
removeUser,
|
||||||
|
reRunServer,
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
unblockUser,
|
unblockUser,
|
||||||
|
@ -39,6 +41,7 @@ import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||||
import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
|
import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
|
import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
|
||||||
|
import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
|
||||||
|
|
||||||
describe('Test users API validators', function () {
|
describe('Test users API validators', function () {
|
||||||
const path = '/api/v1/users/'
|
const path = '/api/v1/users/'
|
||||||
|
@ -50,6 +53,8 @@ describe('Test users API validators', function () {
|
||||||
let serverWithRegistrationDisabled: ServerInfo
|
let serverWithRegistrationDisabled: ServerInfo
|
||||||
let userAccessToken = ''
|
let userAccessToken = ''
|
||||||
let moderatorAccessToken = ''
|
let moderatorAccessToken = ''
|
||||||
|
let emailPort: number
|
||||||
|
let overrideConfig: Object
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
let channelId: number
|
let channelId: number
|
||||||
|
|
||||||
|
@ -58,9 +63,14 @@ describe('Test users API validators', function () {
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(30000)
|
this.timeout(30000)
|
||||||
|
|
||||||
|
const emails: object[] = []
|
||||||
|
emailPort = await MockSmtpServer.Instance.collectEmails(emails)
|
||||||
|
|
||||||
|
overrideConfig = { signup: { limit: 8 } }
|
||||||
|
|
||||||
{
|
{
|
||||||
const res = await Promise.all([
|
const res = await Promise.all([
|
||||||
flushAndRunServer(1, { signup: { limit: 7 } }),
|
flushAndRunServer(1, overrideConfig),
|
||||||
flushAndRunServer(2)
|
flushAndRunServer(2)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -229,6 +239,40 @@ describe('Test users API validators', function () {
|
||||||
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
|
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should fail with empty password and no smtp configured', async function () {
|
||||||
|
const fields = immutableAssign(baseCorrectParams, { password: '' })
|
||||||
|
|
||||||
|
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed with no password on a server with smtp enabled', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
killallServers([ server ])
|
||||||
|
|
||||||
|
const config = immutableAssign(overrideConfig, {
|
||||||
|
smtp: {
|
||||||
|
hostname: 'localhost',
|
||||||
|
port: emailPort
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await reRunServer(server, config)
|
||||||
|
|
||||||
|
const fields = immutableAssign(baseCorrectParams, {
|
||||||
|
password: '',
|
||||||
|
username: 'create_password',
|
||||||
|
email: 'create_password@example.com'
|
||||||
|
})
|
||||||
|
|
||||||
|
await makePostBodyRequest({
|
||||||
|
url: server.url,
|
||||||
|
path: path,
|
||||||
|
token: server.accessToken,
|
||||||
|
fields,
|
||||||
|
statusCodeExpected: 200
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('Should fail with invalid admin flags', async function () {
|
it('Should fail with invalid admin flags', async function () {
|
||||||
const fields = immutableAssign(baseCorrectParams, { adminFlags: 'toto' })
|
const fields = immutableAssign(baseCorrectParams, { adminFlags: 'toto' })
|
||||||
|
|
||||||
|
@ -1102,6 +1146,8 @@ describe('Test users API validators', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
|
MockSmtpServer.Instance.kill()
|
||||||
|
|
||||||
await cleanupTests([ server, serverWithRegistrationDisabled ])
|
await cleanupTests([ server, serverWithRegistrationDisabled ])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -28,10 +28,12 @@ const expect = chai.expect
|
||||||
describe('Test emails', function () {
|
describe('Test emails', function () {
|
||||||
let server: ServerInfo
|
let server: ServerInfo
|
||||||
let userId: number
|
let userId: number
|
||||||
|
let userId2: number
|
||||||
let userAccessToken: string
|
let userAccessToken: string
|
||||||
let videoUUID: string
|
let videoUUID: string
|
||||||
let videoUserUUID: string
|
let videoUserUUID: string
|
||||||
let verificationString: string
|
let verificationString: string
|
||||||
|
let verificationString2: string
|
||||||
const emails: object[] = []
|
const emails: object[] = []
|
||||||
const user = {
|
const user = {
|
||||||
username: 'user_1',
|
username: 'user_1',
|
||||||
|
@ -122,6 +124,56 @@ describe('Test emails', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('When creating a user without password', function () {
|
||||||
|
it('Should send a create password email', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
await createUser({
|
||||||
|
url: server.url,
|
||||||
|
accessToken: server.accessToken,
|
||||||
|
username: 'create_password',
|
||||||
|
password: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitJobs(server)
|
||||||
|
expect(emails).to.have.lengthOf(2)
|
||||||
|
|
||||||
|
const email = emails[1]
|
||||||
|
|
||||||
|
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
||||||
|
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
||||||
|
expect(email['to'][0]['address']).equal('create_password@example.com')
|
||||||
|
expect(email['subject']).contains('account')
|
||||||
|
expect(email['subject']).contains('password')
|
||||||
|
|
||||||
|
const verificationStringMatches = /verificationString=([a-z0-9]+)/.exec(email['text'])
|
||||||
|
expect(verificationStringMatches).not.to.be.null
|
||||||
|
|
||||||
|
verificationString2 = verificationStringMatches[1]
|
||||||
|
expect(verificationString2).to.have.length.above(2)
|
||||||
|
|
||||||
|
const userIdMatches = /userId=([0-9]+)/.exec(email['text'])
|
||||||
|
expect(userIdMatches).not.to.be.null
|
||||||
|
|
||||||
|
userId2 = parseInt(userIdMatches[1], 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not reset the password with an invalid verification string', async function () {
|
||||||
|
await resetPassword(server.url, userId2, verificationString2 + 'c', 'newly_created_password', 403)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should reset the password', async function () {
|
||||||
|
await resetPassword(server.url, userId2, verificationString2, 'newly_created_password')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should login with this new password', async function () {
|
||||||
|
await userLogin(server, {
|
||||||
|
username: 'create_password',
|
||||||
|
password: 'newly_created_password'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('When creating a video abuse', function () {
|
describe('When creating a video abuse', function () {
|
||||||
it('Should send the notification email', async function () {
|
it('Should send the notification email', async function () {
|
||||||
this.timeout(10000)
|
this.timeout(10000)
|
||||||
|
@ -130,9 +182,9 @@ describe('Test emails', function () {
|
||||||
await reportVideoAbuse(server.url, server.accessToken, videoUUID, reason)
|
await reportVideoAbuse(server.url, server.accessToken, videoUUID, reason)
|
||||||
|
|
||||||
await waitJobs(server)
|
await waitJobs(server)
|
||||||
expect(emails).to.have.lengthOf(2)
|
expect(emails).to.have.lengthOf(3)
|
||||||
|
|
||||||
const email = emails[1]
|
const email = emails[2]
|
||||||
|
|
||||||
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
||||||
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
||||||
|
@ -151,9 +203,9 @@ describe('Test emails', function () {
|
||||||
await blockUser(server.url, userId, server.accessToken, 204, reason)
|
await blockUser(server.url, userId, server.accessToken, 204, reason)
|
||||||
|
|
||||||
await waitJobs(server)
|
await waitJobs(server)
|
||||||
expect(emails).to.have.lengthOf(3)
|
expect(emails).to.have.lengthOf(4)
|
||||||
|
|
||||||
const email = emails[2]
|
const email = emails[3]
|
||||||
|
|
||||||
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
||||||
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
||||||
|
@ -169,9 +221,9 @@ describe('Test emails', function () {
|
||||||
await unblockUser(server.url, userId, server.accessToken, 204)
|
await unblockUser(server.url, userId, server.accessToken, 204)
|
||||||
|
|
||||||
await waitJobs(server)
|
await waitJobs(server)
|
||||||
expect(emails).to.have.lengthOf(4)
|
expect(emails).to.have.lengthOf(5)
|
||||||
|
|
||||||
const email = emails[3]
|
const email = emails[4]
|
||||||
|
|
||||||
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
||||||
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
||||||
|
@ -189,9 +241,9 @@ describe('Test emails', function () {
|
||||||
await addVideoToBlacklist(server.url, server.accessToken, videoUserUUID, reason)
|
await addVideoToBlacklist(server.url, server.accessToken, videoUserUUID, reason)
|
||||||
|
|
||||||
await waitJobs(server)
|
await waitJobs(server)
|
||||||
expect(emails).to.have.lengthOf(5)
|
expect(emails).to.have.lengthOf(6)
|
||||||
|
|
||||||
const email = emails[4]
|
const email = emails[5]
|
||||||
|
|
||||||
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
||||||
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
||||||
|
@ -207,9 +259,9 @@ describe('Test emails', function () {
|
||||||
await removeVideoFromBlacklist(server.url, server.accessToken, videoUserUUID)
|
await removeVideoFromBlacklist(server.url, server.accessToken, videoUserUUID)
|
||||||
|
|
||||||
await waitJobs(server)
|
await waitJobs(server)
|
||||||
expect(emails).to.have.lengthOf(6)
|
expect(emails).to.have.lengthOf(7)
|
||||||
|
|
||||||
const email = emails[5]
|
const email = emails[6]
|
||||||
|
|
||||||
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
||||||
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
||||||
|
@ -227,9 +279,9 @@ describe('Test emails', function () {
|
||||||
await askSendVerifyEmail(server.url, 'user_1@example.com')
|
await askSendVerifyEmail(server.url, 'user_1@example.com')
|
||||||
|
|
||||||
await waitJobs(server)
|
await waitJobs(server)
|
||||||
expect(emails).to.have.lengthOf(7)
|
expect(emails).to.have.lengthOf(8)
|
||||||
|
|
||||||
const email = emails[6]
|
const email = emails[7]
|
||||||
|
|
||||||
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
expect(email['from'][0]['name']).equal('localhost:' + server.port)
|
||||||
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
expect(email['from'][0]['address']).equal('test-admin@localhost')
|
||||||
|
|
|
@ -2781,7 +2781,7 @@ components:
|
||||||
description: 'The user username '
|
description: 'The user username '
|
||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
description: 'The user password '
|
description: 'The user password. If the smtp server is configured, you can leave empty and an email will be sent '
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
description: 'The user email '
|
description: 'The user email '
|
||||||
|
|
Loading…
Reference in New Issue