Add ability to not send an email for registration

This commit is contained in:
Chocobozzz 2023-01-20 15:34:01 +01:00
parent e854d57bed
commit 4115f20084
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
10 changed files with 133 additions and 47 deletions

View File

@ -5,7 +5,7 @@ import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core'
import { arrayify } from '@shared/core-utils'
import { ResultList, UserRegistration } from '@shared/models'
import { ResultList, UserRegistration, UserRegistrationUpdateState } from '@shared/models'
import { environment } from '../../../../environments/environment'
@Injectable()
@ -40,17 +40,29 @@ export class AdminRegistrationService {
)
}
acceptRegistration (registration: UserRegistration, moderationResponse: string) {
acceptRegistration (options: {
registration: UserRegistration
moderationResponse: string
preventEmailDelivery: boolean
}) {
const { registration, moderationResponse, preventEmailDelivery } = options
const url = AdminRegistrationService.BASE_REGISTRATION_URL + '/' + registration.id + '/accept'
const body = { moderationResponse }
const body: UserRegistrationUpdateState = { moderationResponse, preventEmailDelivery }
return this.authHttp.post(url, body)
.pipe(catchError(res => this.restExtractor.handleError(res)))
}
rejectRegistration (registration: UserRegistration, moderationResponse: string) {
rejectRegistration (options: {
registration: UserRegistration
moderationResponse: string
preventEmailDelivery: boolean
}) {
const { registration, moderationResponse, preventEmailDelivery } = options
const url = AdminRegistrationService.BASE_REGISTRATION_URL + '/' + registration.id + '/reject'
const body = { moderationResponse }
const body: UserRegistrationUpdateState = { moderationResponse, preventEmailDelivery }
return this.authHttp.post(url, body)
.pipe(catchError(res => this.restExtractor.handleError(res)))

View File

@ -12,7 +12,7 @@
<div class="modal-body mb-3">
<div i18n *ngIf="!registration.emailVerified" class="alert alert-warning">
Registration email has not been verified.
Registration email has not been verified. Email delivery has been disabled by default.
</div>
<div class="description">
@ -21,7 +21,7 @@
<strong>Accepting</strong>&nbsp;<em>{{ registration.username }}</em> registration will create the account and channel.
</p>
<p *ngIf="isEmailEnabled()" i18n>
<p *ngIf="isEmailEnabled()" i18n [ngClass]="{ 'text-decoration-line-through': isPreventEmailDeliveryChecked() }">
An email will be sent to <em>{{ registration.email }}</em> explaining its account has been created with the moderation response you'll write below.
</p>
@ -31,7 +31,7 @@
</ng-container>
<ng-container *ngIf="isReject()">
<p i18n>
<p i18n [ngClass]="{ 'text-decoration-line-through': isPreventEmailDeliveryChecked() }">
An email will be sent to <em>{{ registration.email }}</em> explaining its registration request has been <strong>rejected</strong> with the moderation response you'll write below.
</p>
@ -53,6 +53,13 @@
{{ formErrors.moderationResponse }}
</div>
</div>
<div class="form-group">
<my-peertube-checkbox
inputName="preventEmailDelivery" formControlName="preventEmailDelivery" [disabled]="!isEmailEnabled()"
i18n-labelText labelText="Prevent email from being sent to the user"
></my-peertube-checkbox>
</div>
</div>
<div class="modal-footer inputs">

View File

@ -34,7 +34,8 @@ export class ProcessRegistrationModalComponent extends FormReactive implements O
ngOnInit () {
this.buildForm({
moderationResponse: REGISTRATION_MODERATION_RESPONSE_VALIDATOR
moderationResponse: REGISTRATION_MODERATION_RESPONSE_VALIDATOR,
preventEmailDelivery: null
})
}
@ -50,6 +51,10 @@ export class ProcessRegistrationModalComponent extends FormReactive implements O
this.processMode = mode
this.registration = registration
this.form.patchValue({
preventEmailDelivery: !this.isEmailEnabled() || registration.emailVerified !== true
})
this.openedModal = this.modalService.open(this.modal, { centered: true })
}
@ -77,31 +82,41 @@ export class ProcessRegistrationModalComponent extends FormReactive implements O
return this.server.getHTMLConfig().email.enabled
}
isPreventEmailDeliveryChecked () {
return this.form.value.preventEmailDelivery
}
private acceptRegistration () {
this.registrationService.acceptRegistration(this.registration, this.form.value.moderationResponse)
.subscribe({
next: () => {
this.notifier.success($localize`${this.registration.username} account created`)
this.registrationService.acceptRegistration({
registration: this.registration,
moderationResponse: this.form.value.moderationResponse,
preventEmailDelivery: this.form.value.preventEmailDelivery
}).subscribe({
next: () => {
this.notifier.success($localize`${this.registration.username} account created`)
this.registrationProcessed.emit()
this.hide()
},
this.registrationProcessed.emit()
this.hide()
},
error: err => this.notifier.error(err.message)
})
error: err => this.notifier.error(err.message)
})
}
private rejectRegistration () {
this.registrationService.rejectRegistration(this.registration, this.form.value.moderationResponse)
.subscribe({
next: () => {
this.notifier.success($localize`${this.registration.username} registration rejected`)
this.registrationService.rejectRegistration({
registration: this.registration,
moderationResponse: this.form.value.moderationResponse,
preventEmailDelivery: this.form.value.preventEmailDelivery
}).subscribe({
next: () => {
this.notifier.success($localize`${this.registration.username} registration rejected`)
this.registrationProcessed.emit()
this.hide()
},
this.registrationProcessed.emit()
this.hide()
},
error: err => this.notifier.error(err.message)
})
error: err => this.notifier.error(err.message)
})
}
}

View File

@ -12,5 +12,5 @@ export type BuildFormArgument = {
}
export type BuildFormDefaultValues = {
[ name: string ]: number | string | string[] | BuildFormDefaultValues
[ name: string ]: boolean | number | string | string[] | BuildFormDefaultValues
}

View File

@ -3,7 +3,14 @@ import { Emailer } from '@server/lib/emailer'
import { Hooks } from '@server/lib/plugins/hooks'
import { UserRegistrationModel } from '@server/models/user/user-registration'
import { pick } from '@shared/core-utils'
import { HttpStatusCode, UserRegister, UserRegistrationRequest, UserRegistrationState, UserRight } from '@shared/models'
import {
HttpStatusCode,
UserRegister,
UserRegistrationRequest,
UserRegistrationState,
UserRegistrationUpdateState,
UserRight
} from '@shared/models'
import { auditLoggerFactory, UserAuditView } from '../../../helpers/audit-logger'
import { logger } from '../../../helpers/logger'
import { CONFIG } from '../../../initializers/config'
@ -125,6 +132,7 @@ async function requestRegistration (req: express.Request, res: express.Response)
async function acceptRegistration (req: express.Request, res: express.Response) {
const registration = res.locals.userRegistration
const body: UserRegistrationUpdateState = req.body
const userToCreate = buildUser({
username: registration.username,
@ -150,26 +158,31 @@ async function acceptRegistration (req: express.Request, res: express.Response)
registration.userId = user.id
registration.state = UserRegistrationState.ACCEPTED
registration.moderationResponse = req.body.moderationResponse
registration.moderationResponse = body.moderationResponse
await registration.save()
logger.info('Registration of %s accepted', registration.username)
Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
if (body.preventEmailDelivery !== true) {
Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
}
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
}
async function rejectRegistration (req: express.Request, res: express.Response) {
const registration = res.locals.userRegistration
const body: UserRegistrationUpdateState = req.body
registration.state = UserRegistrationState.REJECTED
registration.moderationResponse = req.body.moderationResponse
registration.moderationResponse = body.moderationResponse
await registration.save()
Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
if (body.preventEmailDelivery !== true) {
Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
}
logger.info('Registration of %s rejected', registration.username)

View File

@ -1,6 +1,6 @@
import express from 'express'
import { body, param, query, ValidationChain } from 'express-validator'
import { exists, isIdValid } from '@server/helpers/custom-validators/misc'
import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '@server/helpers/custom-validators/misc'
import { isRegistrationModerationResponseValid, isRegistrationReasonValid } from '@server/helpers/custom-validators/user-registration'
import { CONFIG } from '@server/initializers/config'
import { Hooks } from '@server/lib/plugins/hooks'
@ -91,6 +91,11 @@ const acceptOrRejectRegistrationValidator = [
body('moderationResponse')
.custom(isRegistrationModerationResponseValid),
body('preventEmailDelivery')
.optional()
.customSanitizer(toBooleanOrNull)
.custom(isBooleanValid).withMessage('Should have preventEmailDelivery boolean'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
if (!await checkRegistrationIdExist(req.params.registrationId, res)) return

View File

@ -329,6 +329,42 @@ describe('Test registrations', function () {
}
})
it('Should be able to prevent email delivery on accept/reject', async function () {
this.timeout(50000)
let id1: number
let id2: number
{
const { id } = await server.registrations.requestRegistration({
username: 'user7',
email: 'user7@example.com',
registrationReason: 'tt'
})
id1 = id
}
{
const { id } = await server.registrations.requestRegistration({
username: 'user8',
email: 'user8@example.com',
registrationReason: 'tt'
})
id2 = id
}
await server.registrations.accept({ id: id1, moderationResponse: 'tt', preventEmailDelivery: true })
await server.registrations.reject({ id: id2, moderationResponse: 'tt', preventEmailDelivery: true })
await waitJobs([ server ])
const filtered = emails.filter(e => {
const address = e['to'][0]['address']
return address === 'user7@example.com' || address === 'user8@example.com'
})
expect(filtered).to.have.lengthOf(0)
})
it('Should request a registration without a channel, that will conflict with an already existing channel', async function () {
let id1: number
let id2: number

View File

@ -1,3 +1,4 @@
export interface UserRegistrationUpdateState {
moderationResponse: string
preventEmailDelivery?: boolean
}

View File

@ -1,5 +1,5 @@
import { pick } from '@shared/core-utils'
import { HttpStatusCode, ResultList, UserRegistration, UserRegistrationRequest } from '@shared/models'
import { HttpStatusCode, ResultList, UserRegistration, UserRegistrationRequest, UserRegistrationUpdateState } from '@shared/models'
import { unwrapBody } from '../requests'
import { AbstractCommand, OverrideCommandOptions } from '../shared'
@ -47,35 +47,29 @@ export class RegistrationsCommand extends AbstractCommand {
// ---------------------------------------------------------------------------
accept (options: OverrideCommandOptions & {
id: number
moderationResponse: string
}) {
const { id, moderationResponse } = options
accept (options: OverrideCommandOptions & { id: number } & UserRegistrationUpdateState) {
const { id } = options
const path = '/api/v1/users/registrations/' + id + '/accept'
return this.postBodyRequest({
...options,
path,
fields: { moderationResponse },
fields: pick(options, [ 'moderationResponse', 'preventEmailDelivery' ]),
implicitToken: true,
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
})
}
reject (options: OverrideCommandOptions & {
id: number
moderationResponse: string
}) {
const { id, moderationResponse } = options
reject (options: OverrideCommandOptions & { id: number } & UserRegistrationUpdateState) {
const { id } = options
const path = '/api/v1/users/registrations/' + id + '/reject'
return this.postBodyRequest({
...options,
path,
fields: { moderationResponse },
fields: pick(options, [ 'moderationResponse', 'preventEmailDelivery' ]),
implicitToken: true,
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
})

View File

@ -7961,6 +7961,9 @@ components:
moderationResponse:
type: string
description: Moderation response to send to the user
preventEmailDelivery:
type: boolean
description: Set it to true if you don't want PeerTube to send an email to the user
required:
- moderationResponse