From 3a4992633ee62d5edfbb484d9c6bcb3cf158489d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 31 Jul 2023 14:34:36 +0200 Subject: Migrate server to ESM Sorry for the very big commit that may lead to git log issues and merge conflicts, but it's a major step forward: * Server can be faster at startup because imports() are async and we can easily lazy import big modules * Angular doesn't seem to support ES import (with .js extension), so we had to correctly organize peertube into a monorepo: * Use yarn workspace feature * Use typescript reference projects for dependencies * Shared projects have been moved into "packages", each one is now a node module (with a dedicated package.json/tsconfig.json) * server/tools have been moved into apps/ and is now a dedicated app bundled and published on NPM so users don't have to build peertube cli tools manually * server/tests have been moved into packages/ so we don't compile them every time we want to run the server * Use isolatedModule option: * Had to move from const enum to const (https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums) * Had to explictely specify "type" imports when used in decorators * Prefer tsx (that uses esbuild under the hood) instead of ts-node to load typescript files (tests with mocha or scripts): * To reduce test complexity as esbuild doesn't support decorator metadata, we only test server files that do not import server models * We still build tests files into js files for a faster CI * Remove unmaintained peertube CLI import script * Removed some barrels to speed up execution (less imports) --- server/controllers/api/users/index.ts | 319 ---------------------------------- 1 file changed, 319 deletions(-) delete mode 100644 server/controllers/api/users/index.ts (limited to 'server/controllers/api/users/index.ts') diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts deleted file mode 100644 index 5eac6fd0f..000000000 --- a/server/controllers/api/users/index.ts +++ /dev/null @@ -1,319 +0,0 @@ -import express from 'express' -import { tokensRouter } from '@server/controllers/api/users/token' -import { Hooks } from '@server/lib/plugins/hooks' -import { OAuthTokenModel } from '@server/models/oauth/oauth-token' -import { MUserAccountDefault } from '@server/types/models' -import { pick } from '@shared/core-utils' -import { HttpStatusCode, UserCreate, UserCreateResult, UserRight, UserUpdate } from '@shared/models' -import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' -import { logger } from '../../../helpers/logger' -import { generateRandomString, getFormattedObjects } from '../../../helpers/utils' -import { WEBSERVER } from '../../../initializers/constants' -import { sequelizeTypescript } from '../../../initializers/database' -import { Emailer } from '../../../lib/emailer' -import { Redis } from '../../../lib/redis' -import { buildUser, createUserAccountAndChannelAndPlaylist } from '../../../lib/user' -import { - adminUsersSortValidator, - apiRateLimiter, - asyncMiddleware, - asyncRetryTransactionMiddleware, - authenticate, - ensureUserHasRight, - paginationValidator, - setDefaultPagination, - setDefaultSort, - userAutocompleteValidator, - usersAddValidator, - usersGetValidator, - usersListValidator, - usersRemoveValidator, - usersUpdateValidator -} from '../../../middlewares' -import { - ensureCanModerateUser, - usersAskResetPasswordValidator, - usersBlockingValidator, - usersResetPasswordValidator -} from '../../../middlewares/validators' -import { UserModel } from '../../../models/user/user' -import { emailVerificationRouter } from './email-verification' -import { meRouter } from './me' -import { myAbusesRouter } from './my-abuses' -import { myBlocklistRouter } from './my-blocklist' -import { myVideosHistoryRouter } from './my-history' -import { myNotificationsRouter } from './my-notifications' -import { mySubscriptionsRouter } from './my-subscriptions' -import { myVideoPlaylistsRouter } from './my-video-playlists' -import { registrationsRouter } from './registrations' -import { twoFactorRouter } from './two-factor' - -const auditLogger = auditLoggerFactory('users') - -const usersRouter = express.Router() - -usersRouter.use(apiRateLimiter) - -usersRouter.use('/', emailVerificationRouter) -usersRouter.use('/', registrationsRouter) -usersRouter.use('/', twoFactorRouter) -usersRouter.use('/', tokensRouter) -usersRouter.use('/', myNotificationsRouter) -usersRouter.use('/', mySubscriptionsRouter) -usersRouter.use('/', myBlocklistRouter) -usersRouter.use('/', myVideosHistoryRouter) -usersRouter.use('/', myVideoPlaylistsRouter) -usersRouter.use('/', myAbusesRouter) -usersRouter.use('/', meRouter) - -usersRouter.get('/autocomplete', - userAutocompleteValidator, - asyncMiddleware(autocompleteUsers) -) - -usersRouter.get('/', - authenticate, - ensureUserHasRight(UserRight.MANAGE_USERS), - paginationValidator, - adminUsersSortValidator, - setDefaultSort, - setDefaultPagination, - usersListValidator, - asyncMiddleware(listUsers) -) - -usersRouter.post('/:id/block', - authenticate, - ensureUserHasRight(UserRight.MANAGE_USERS), - asyncMiddleware(usersBlockingValidator), - ensureCanModerateUser, - asyncMiddleware(blockUser) -) -usersRouter.post('/:id/unblock', - authenticate, - ensureUserHasRight(UserRight.MANAGE_USERS), - asyncMiddleware(usersBlockingValidator), - ensureCanModerateUser, - asyncMiddleware(unblockUser) -) - -usersRouter.get('/:id', - authenticate, - ensureUserHasRight(UserRight.MANAGE_USERS), - asyncMiddleware(usersGetValidator), - getUser -) - -usersRouter.post('/', - authenticate, - ensureUserHasRight(UserRight.MANAGE_USERS), - asyncMiddleware(usersAddValidator), - asyncRetryTransactionMiddleware(createUser) -) - -usersRouter.put('/:id', - authenticate, - ensureUserHasRight(UserRight.MANAGE_USERS), - asyncMiddleware(usersUpdateValidator), - ensureCanModerateUser, - asyncMiddleware(updateUser) -) - -usersRouter.delete('/:id', - authenticate, - ensureUserHasRight(UserRight.MANAGE_USERS), - asyncMiddleware(usersRemoveValidator), - ensureCanModerateUser, - asyncMiddleware(removeUser) -) - -usersRouter.post('/ask-reset-password', - asyncMiddleware(usersAskResetPasswordValidator), - asyncMiddleware(askResetUserPassword) -) - -usersRouter.post('/:id/reset-password', - asyncMiddleware(usersResetPasswordValidator), - asyncMiddleware(resetUserPassword) -) - -// --------------------------------------------------------------------------- - -export { - usersRouter -} - -// --------------------------------------------------------------------------- - -async function createUser (req: express.Request, res: express.Response) { - const body: UserCreate = req.body - - const userToCreate = buildUser({ - ...pick(body, [ 'username', 'password', 'email', 'role', 'videoQuota', 'videoQuotaDaily', 'adminFlags' ]), - - emailVerified: null - }) - - // 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, - channelNames: body.channelName && { name: body.channelName, displayName: body.channelName } - }) - - auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) - 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 - Emailer.Instance.addPasswordCreateEmailJob(userToCreate.username, user.email, url) - } - - Hooks.runAction('action:api.user.created', { body, user, account, videoChannel, req, res }) - - return res.json({ - user: { - id: user.id, - account: { - id: account.id - } - } as UserCreateResult - }) -} - -async function unblockUser (req: express.Request, res: express.Response) { - const user = res.locals.user - - await changeUserBlock(res, user, false) - - Hooks.runAction('action:api.user.unblocked', { user, req, res }) - - return res.status(HttpStatusCode.NO_CONTENT_204).end() -} - -async function blockUser (req: express.Request, res: express.Response) { - const user = res.locals.user - const reason = req.body.reason - - await changeUserBlock(res, user, true, reason) - - Hooks.runAction('action:api.user.blocked', { user, req, res }) - - return res.status(HttpStatusCode.NO_CONTENT_204).end() -} - -function getUser (req: express.Request, res: express.Response) { - return res.json(res.locals.user.toFormattedJSON({ withAdminFlags: true })) -} - -async function autocompleteUsers (req: express.Request, res: express.Response) { - const resultList = await UserModel.autoComplete(req.query.search as string) - - return res.json(resultList) -} - -async function listUsers (req: express.Request, res: express.Response) { - const resultList = await UserModel.listForAdminApi({ - start: req.query.start, - count: req.query.count, - sort: req.query.sort, - search: req.query.search, - blocked: req.query.blocked - }) - - return res.json(getFormattedObjects(resultList.data, resultList.total, { withAdminFlags: true })) -} - -async function removeUser (req: express.Request, res: express.Response) { - const user = res.locals.user - - auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) - - await sequelizeTypescript.transaction(async t => { - // Use a transaction to avoid inconsistencies with hooks (account/channel deletion & federation) - await user.destroy({ transaction: t }) - }) - - Hooks.runAction('action:api.user.deleted', { user, req, res }) - - return res.status(HttpStatusCode.NO_CONTENT_204).end() -} - -async function updateUser (req: express.Request, res: express.Response) { - const body: UserUpdate = req.body - const userToUpdate = res.locals.user - const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON()) - const roleChanged = body.role !== undefined && body.role !== userToUpdate.role - - const keysToUpdate: (keyof UserUpdate)[] = [ - 'password', - 'email', - 'emailVerified', - 'videoQuota', - 'videoQuotaDaily', - 'role', - 'adminFlags', - 'pluginAuth' - ] - - for (const key of keysToUpdate) { - if (body[key] !== undefined) userToUpdate.set(key, body[key]) - } - - const user = await userToUpdate.save() - - // Destroy user token to refresh rights - if (roleChanged || body.password !== undefined) await OAuthTokenModel.deleteUserToken(userToUpdate.id) - - auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) - - Hooks.runAction('action:api.user.updated', { user, req, res }) - - // Don't need to send this update to followers, these attributes are not federated - - return res.status(HttpStatusCode.NO_CONTENT_204).end() -} - -async function askResetUserPassword (req: express.Request, res: express.Response) { - const user = res.locals.user - - const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id) - const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString - Emailer.Instance.addPasswordResetEmailJob(user.username, user.email, url) - - return res.status(HttpStatusCode.NO_CONTENT_204).end() -} - -async function resetUserPassword (req: express.Request, res: express.Response) { - const user = res.locals.user - user.password = req.body.password - - await user.save() - await Redis.Instance.removePasswordVerificationString(user.id) - - return res.status(HttpStatusCode.NO_CONTENT_204).end() -} - -async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { - const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) - - user.blocked = block - user.blockedReason = reason || null - - await sequelizeTypescript.transaction(async t => { - await OAuthTokenModel.deleteUserToken(user.id, t) - - await user.save({ transaction: t }) - }) - - Emailer.Instance.addUserBlockJob(user, block, reason) - - auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) -} -- cgit v1.2.3