feat: plugin support to filter email addresses (#6752)

* feat: plugin support to filter email addresses

Add support for plugins to filter user email addresses.

* Add missing `totalNotDeletedComments` doc

* Styling

---------

Co-authored-by: Chocobozzz <me@florianbigard.com>
This commit is contained in:
kontrollanten 2025-01-14 11:02:12 +01:00 committed by GitHub
parent 8a0f9b6af3
commit 3f30458c37
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 39 additions and 14 deletions

View File

@ -140,7 +140,12 @@ export const serverFilterHookObject = {
// Peertube >= 5.2
'filter:feed.podcast.video.create-custom-tags.result': true,
// Peertube >= 6.1
'filter:api.user.me.get.result': true
'filter:api.user.me.get.result': true,
// Peertube >= 7.1
'filter:oauth.password-grant.get-user.params': true,
'filter:api.email-verification.ask-send-verify-email.body': true,
'filter:api.users.ask-reset-password.body': true
}
export type ServerFilterHookName = keyof typeof serverFilterHookObject

View File

@ -17,6 +17,7 @@ import { sha1 } from '@peertube/peertube-node-utils'
import { HttpStatusCode, ServerErrorCode, UserRegistrationState } from '@peertube/peertube-models'
import { OTP } from '../../initializers/constants.js'
import { BypassLogin, getAccessToken, getClient, getRefreshToken, getUser, revokeToken, saveToken } from './oauth-model.js'
import { Hooks } from '../plugins/hooks.js'
class MissingTwoFactorError extends Error {
code = HttpStatusCode.UNAUTHORIZED_401
@ -135,19 +136,25 @@ async function handlePasswordGrant (options: {
client: MOAuthClient
bypassLogin?: BypassLogin
}) {
const { request, client, bypassLogin } = options
const { client } = options
if (!request.body.username) {
const { bypassLogin, usernameOrEmail, password } = await Hooks.wrapObject({
bypassLogin: options.bypassLogin,
usernameOrEmail: options.request.body.username,
password: options.request.body.password
}, 'filter:oauth.password-grant.get-user.params')
if (!options.request.body.username) {
throw new InvalidRequestError('Missing parameter: `username`')
}
if (!bypassLogin && !request.body.password) {
if (!bypassLogin && !options.request.body.password) {
throw new InvalidRequestError('Missing parameter: `password`')
}
const user = await getUser(request.body.username, request.body.password, bypassLogin)
const user = await getUser(usernameOrEmail, password, bypassLogin)
if (!user) {
const registration = await UserRegistrationModel.loadByEmailOrUsername(request.body.username)
const registration = await UserRegistrationModel.loadByEmailOrUsername(usernameOrEmail)
if (registration?.state === UserRegistrationState.REJECTED) {
throw new RegistrationApprovalRejected('Registration approval for this account has been rejected')
@ -159,11 +166,11 @@ async function handlePasswordGrant (options: {
}
if (user.otpSecret) {
if (!request.headers[OTP.HEADER_NAME]) {
if (!options.request.headers[OTP.HEADER_NAME]) {
throw new MissingTwoFactorError('Missing two factor header')
}
if (await isOTPValid({ encryptedSecret: user.otpSecret, token: request.headers[OTP.HEADER_NAME] }) !== true) {
if (await isOTPValid({ encryptedSecret: user.otpSecret, token: options.request.headers[OTP.HEADER_NAME] }) !== true) {
throw new InvalidTwoFactorError('Invalid two factor header')
}
}

View File

@ -6,6 +6,7 @@ import { logger } from '../../../helpers/logger.js'
import { Redis } from '../../../lib/redis.js'
import { areValidationErrors, checkUserEmailExist, checkUserIdExist } from '../shared/index.js'
import { checkRegistrationEmailExist, checkRegistrationIdExist } from './shared/user-registrations.js'
import { Hooks } from '@server/lib/plugins/hooks.js'
const usersAskSendVerifyEmailValidator = [
body('email').isEmail().not().isEmpty().withMessage('Should have a valid email'),
@ -13,13 +14,17 @@ const usersAskSendVerifyEmailValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
const { email } = await Hooks.wrapObject({
email: req.body.email
}, 'filter:api.email-verification.ask-send-verify-email.body')
const [ userExists, registrationExists ] = await Promise.all([
checkUserEmailExist(req.body.email, res, false),
checkRegistrationEmailExist(req.body.email, res, false)
checkUserEmailExist(email, res, false),
checkRegistrationEmailExist(email, res, false)
])
if (!userExists && !registrationExists) {
logger.debug('User or registration with email %s does not exist (asking verify email).', req.body.email)
logger.debug('User or registration with email %s does not exist (asking verify email).', email)
// Do not leak our emails
return res.status(HttpStatusCode.NO_CONTENT_204).end()
}

View File

@ -39,6 +39,7 @@ import {
doesVideoExist,
isValidVideoIdParam
} from '../shared/index.js'
import { Hooks } from '@server/lib/plugins/hooks.js'
export const usersListValidator = [
query('blocked')
@ -334,9 +335,13 @@ export const usersAskResetPasswordValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
const exists = await checkUserEmailExist(req.body.email, res, false)
const { email } = await Hooks.wrapObject({
email: req.body.email
}, 'filter:api.users.ask-reset-password.body')
const exists = await checkUserEmailExist(email, res, false)
if (!exists) {
logger.debug('User with email %s does not exist (asking reset password).', req.body.email)
logger.debug('User with email %s does not exist (asking reset password).', email)
// Do not leak our emails
return res.status(HttpStatusCode.NO_CONTENT_204).end()
}

View File

@ -9933,7 +9933,10 @@ components:
properties:
total:
type: integer
example: 1
description: Total threads (included deleted ones) on this video
totalNotDeletedComments:
type: integer
description: Total not-deleted threads (included deleted ones) on this video
data:
type: array
maxItems: 100