mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-05 19:42:24 +02:00
server/server -> server/core
This commit is contained in:
parent
114327d4ce
commit
5a3d0650c9
838 changed files with 111 additions and 111 deletions
72
server/core/controllers/api/users/email-verification.ts
Normal file
72
server/core/controllers/api/users/email-verification.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import express from 'express'
|
||||
import { HttpStatusCode } from '@peertube/peertube-models'
|
||||
import { CONFIG } from '../../../initializers/config.js'
|
||||
import { sendVerifyRegistrationEmail, sendVerifyUserEmail } from '../../../lib/user.js'
|
||||
import { asyncMiddleware, buildRateLimiter } from '../../../middlewares/index.js'
|
||||
import {
|
||||
registrationVerifyEmailValidator,
|
||||
usersAskSendVerifyEmailValidator,
|
||||
usersVerifyEmailValidator
|
||||
} from '../../../middlewares/validators/index.js'
|
||||
|
||||
const askSendEmailLimiter = buildRateLimiter({
|
||||
windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS,
|
||||
max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX
|
||||
})
|
||||
|
||||
const emailVerificationRouter = express.Router()
|
||||
|
||||
emailVerificationRouter.post([ '/ask-send-verify-email', '/registrations/ask-send-verify-email' ],
|
||||
askSendEmailLimiter,
|
||||
asyncMiddleware(usersAskSendVerifyEmailValidator),
|
||||
asyncMiddleware(reSendVerifyUserEmail)
|
||||
)
|
||||
|
||||
emailVerificationRouter.post('/:id/verify-email',
|
||||
asyncMiddleware(usersVerifyEmailValidator),
|
||||
asyncMiddleware(verifyUserEmail)
|
||||
)
|
||||
|
||||
emailVerificationRouter.post('/registrations/:registrationId/verify-email',
|
||||
asyncMiddleware(registrationVerifyEmailValidator),
|
||||
asyncMiddleware(verifyRegistrationEmail)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
emailVerificationRouter
|
||||
}
|
||||
|
||||
async function reSendVerifyUserEmail (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.user
|
||||
const registration = res.locals.userRegistration
|
||||
|
||||
if (user) await sendVerifyUserEmail(user)
|
||||
else if (registration) await sendVerifyRegistrationEmail(registration)
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function verifyUserEmail (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.user
|
||||
user.emailVerified = true
|
||||
|
||||
if (req.body.isPendingEmail === true) {
|
||||
user.email = user.pendingEmail
|
||||
user.pendingEmail = null
|
||||
}
|
||||
|
||||
await user.save()
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function verifyRegistrationEmail (req: express.Request, res: express.Response) {
|
||||
const registration = res.locals.userRegistration
|
||||
registration.emailVerified = true
|
||||
|
||||
await registration.save()
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
319
server/core/controllers/api/users/index.ts
Normal file
319
server/core/controllers/api/users/index.ts
Normal file
|
@ -0,0 +1,319 @@
|
|||
import express from 'express'
|
||||
import { tokensRouter } from '@server/controllers/api/users/token.js'
|
||||
import { Hooks } from '@server/lib/plugins/hooks.js'
|
||||
import { OAuthTokenModel } from '@server/models/oauth/oauth-token.js'
|
||||
import { MUserAccountDefault } from '@server/types/models/index.js'
|
||||
import { pick } from '@peertube/peertube-core-utils'
|
||||
import { HttpStatusCode, UserCreate, UserCreateResult, UserRight, UserUpdate } from '@peertube/peertube-models'
|
||||
import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger.js'
|
||||
import { logger } from '../../../helpers/logger.js'
|
||||
import { generateRandomString, getFormattedObjects } from '../../../helpers/utils.js'
|
||||
import { WEBSERVER } from '../../../initializers/constants.js'
|
||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||
import { Emailer } from '../../../lib/emailer.js'
|
||||
import { Redis } from '../../../lib/redis.js'
|
||||
import { buildUser, createUserAccountAndChannelAndPlaylist } from '../../../lib/user.js'
|
||||
import {
|
||||
adminUsersSortValidator,
|
||||
apiRateLimiter,
|
||||
asyncMiddleware,
|
||||
asyncRetryTransactionMiddleware,
|
||||
authenticate,
|
||||
ensureUserHasRight,
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
setDefaultSort,
|
||||
userAutocompleteValidator,
|
||||
usersAddValidator,
|
||||
usersGetValidator,
|
||||
usersListValidator,
|
||||
usersRemoveValidator,
|
||||
usersUpdateValidator
|
||||
} from '../../../middlewares/index.js'
|
||||
import {
|
||||
ensureCanModerateUser,
|
||||
usersAskResetPasswordValidator,
|
||||
usersBlockingValidator,
|
||||
usersResetPasswordValidator
|
||||
} from '../../../middlewares/validators/index.js'
|
||||
import { UserModel } from '../../../models/user/user.js'
|
||||
import { emailVerificationRouter } from './email-verification.js'
|
||||
import { meRouter } from './me.js'
|
||||
import { myAbusesRouter } from './my-abuses.js'
|
||||
import { myBlocklistRouter } from './my-blocklist.js'
|
||||
import { myVideosHistoryRouter } from './my-history.js'
|
||||
import { myNotificationsRouter } from './my-notifications.js'
|
||||
import { mySubscriptionsRouter } from './my-subscriptions.js'
|
||||
import { myVideoPlaylistsRouter } from './my-video-playlists.js'
|
||||
import { registrationsRouter } from './registrations.js'
|
||||
import { twoFactorRouter } from './two-factor.js'
|
||||
|
||||
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)
|
||||
}
|
283
server/core/controllers/api/users/me.ts
Normal file
283
server/core/controllers/api/users/me.ts
Normal file
|
@ -0,0 +1,283 @@
|
|||
import 'multer'
|
||||
import express from 'express'
|
||||
import { pick } from '@peertube/peertube-core-utils'
|
||||
import {
|
||||
ActorImageType,
|
||||
HttpStatusCode,
|
||||
UserUpdateMe,
|
||||
UserVideoQuota,
|
||||
UserVideoRate as FormattedUserVideoRate
|
||||
} from '@peertube/peertube-models'
|
||||
import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '@server/helpers/audit-logger.js'
|
||||
import { Hooks } from '@server/lib/plugins/hooks.js'
|
||||
import { AttributesOnly } from '@peertube/peertube-typescript-utils'
|
||||
import { createReqFiles } from '../../../helpers/express-utils.js'
|
||||
import { getFormattedObjects } from '../../../helpers/utils.js'
|
||||
import { CONFIG } from '../../../initializers/config.js'
|
||||
import { MIMETYPES } from '../../../initializers/constants.js'
|
||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||
import { sendUpdateActor } from '../../../lib/activitypub/send/index.js'
|
||||
import { deleteLocalActorImageFile, updateLocalActorImageFiles } from '../../../lib/local-actor.js'
|
||||
import { getOriginalVideoFileTotalDailyFromUser, getOriginalVideoFileTotalFromUser, sendVerifyUserEmail } from '../../../lib/user.js'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
asyncRetryTransactionMiddleware,
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
setDefaultSort,
|
||||
setDefaultVideosSort,
|
||||
usersUpdateMeValidator,
|
||||
usersVideoRatingValidator
|
||||
} from '../../../middlewares/index.js'
|
||||
import { updateAvatarValidator } from '../../../middlewares/validators/actor-image.js'
|
||||
import {
|
||||
deleteMeValidator,
|
||||
getMyVideoImportsValidator,
|
||||
usersVideosValidator,
|
||||
videoImportsSortValidator,
|
||||
videosSortValidator
|
||||
} from '../../../middlewares/validators/index.js'
|
||||
import { AccountVideoRateModel } from '../../../models/account/account-video-rate.js'
|
||||
import { AccountModel } from '../../../models/account/account.js'
|
||||
import { UserModel } from '../../../models/user/user.js'
|
||||
import { VideoImportModel } from '../../../models/video/video-import.js'
|
||||
import { VideoModel } from '../../../models/video/video.js'
|
||||
|
||||
const auditLogger = auditLoggerFactory('users')
|
||||
|
||||
const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT)
|
||||
|
||||
const meRouter = express.Router()
|
||||
|
||||
meRouter.get('/me',
|
||||
authenticate,
|
||||
asyncMiddleware(getUserInformation)
|
||||
)
|
||||
meRouter.delete('/me',
|
||||
authenticate,
|
||||
deleteMeValidator,
|
||||
asyncMiddleware(deleteMe)
|
||||
)
|
||||
|
||||
meRouter.get('/me/video-quota-used',
|
||||
authenticate,
|
||||
asyncMiddleware(getUserVideoQuotaUsed)
|
||||
)
|
||||
|
||||
meRouter.get('/me/videos/imports',
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
videoImportsSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
getMyVideoImportsValidator,
|
||||
asyncMiddleware(getUserVideoImports)
|
||||
)
|
||||
|
||||
meRouter.get('/me/videos',
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
videosSortValidator,
|
||||
setDefaultVideosSort,
|
||||
setDefaultPagination,
|
||||
asyncMiddleware(usersVideosValidator),
|
||||
asyncMiddleware(getUserVideos)
|
||||
)
|
||||
|
||||
meRouter.get('/me/videos/:videoId/rating',
|
||||
authenticate,
|
||||
asyncMiddleware(usersVideoRatingValidator),
|
||||
asyncMiddleware(getUserVideoRating)
|
||||
)
|
||||
|
||||
meRouter.put('/me',
|
||||
authenticate,
|
||||
asyncMiddleware(usersUpdateMeValidator),
|
||||
asyncRetryTransactionMiddleware(updateMe)
|
||||
)
|
||||
|
||||
meRouter.post('/me/avatar/pick',
|
||||
authenticate,
|
||||
reqAvatarFile,
|
||||
updateAvatarValidator,
|
||||
asyncRetryTransactionMiddleware(updateMyAvatar)
|
||||
)
|
||||
|
||||
meRouter.delete('/me/avatar',
|
||||
authenticate,
|
||||
asyncRetryTransactionMiddleware(deleteMyAvatar)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
meRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getUserVideos (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
const apiOptions = await Hooks.wrapObject({
|
||||
accountId: user.Account.id,
|
||||
start: req.query.start,
|
||||
count: req.query.count,
|
||||
sort: req.query.sort,
|
||||
search: req.query.search,
|
||||
channelId: res.locals.videoChannel?.id,
|
||||
isLive: req.query.isLive
|
||||
}, 'filter:api.user.me.videos.list.params')
|
||||
|
||||
const resultList = await Hooks.wrapPromiseFun(
|
||||
VideoModel.listUserVideosForApi,
|
||||
apiOptions,
|
||||
'filter:api.user.me.videos.list.result'
|
||||
)
|
||||
|
||||
const additionalAttributes = {
|
||||
waitTranscoding: true,
|
||||
state: true,
|
||||
scheduledUpdate: true,
|
||||
blacklistInfo: true
|
||||
}
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
|
||||
}
|
||||
|
||||
async function getUserVideoImports (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
const resultList = await VideoImportModel.listUserVideoImportsForApi({
|
||||
userId: user.id,
|
||||
|
||||
...pick(req.query, [ 'targetUrl', 'start', 'count', 'sort', 'search', 'videoChannelSyncId' ])
|
||||
})
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function getUserInformation (req: express.Request, res: express.Response) {
|
||||
// We did not load channels in res.locals.user
|
||||
const user = await UserModel.loadForMeAPI(res.locals.oauth.token.user.id)
|
||||
|
||||
return res.json(user.toMeFormattedJSON())
|
||||
}
|
||||
|
||||
async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.user
|
||||
const videoQuotaUsed = await getOriginalVideoFileTotalFromUser(user)
|
||||
const videoQuotaUsedDaily = await getOriginalVideoFileTotalDailyFromUser(user)
|
||||
|
||||
const data: UserVideoQuota = {
|
||||
videoQuotaUsed,
|
||||
videoQuotaUsedDaily
|
||||
}
|
||||
return res.json(data)
|
||||
}
|
||||
|
||||
async function getUserVideoRating (req: express.Request, res: express.Response) {
|
||||
const videoId = res.locals.videoId.id
|
||||
const accountId = +res.locals.oauth.token.User.Account.id
|
||||
|
||||
const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null)
|
||||
const rating = ratingObj ? ratingObj.type : 'none'
|
||||
|
||||
const json: FormattedUserVideoRate = {
|
||||
videoId,
|
||||
rating
|
||||
}
|
||||
return res.json(json)
|
||||
}
|
||||
|
||||
async function deleteMe (req: express.Request, res: express.Response) {
|
||||
const user = await UserModel.loadByIdWithChannels(res.locals.oauth.token.User.id)
|
||||
|
||||
auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
|
||||
|
||||
await user.destroy()
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function updateMe (req: express.Request, res: express.Response) {
|
||||
const body: UserUpdateMe = req.body
|
||||
let sendVerificationEmail = false
|
||||
|
||||
const user = res.locals.oauth.token.user
|
||||
|
||||
const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [
|
||||
'password',
|
||||
'nsfwPolicy',
|
||||
'p2pEnabled',
|
||||
'autoPlayVideo',
|
||||
'autoPlayNextVideo',
|
||||
'autoPlayNextVideoPlaylist',
|
||||
'videosHistoryEnabled',
|
||||
'videoLanguages',
|
||||
'theme',
|
||||
'noInstanceConfigWarningModal',
|
||||
'noAccountSetupWarningModal',
|
||||
'noWelcomeModal',
|
||||
'emailPublic',
|
||||
'p2pEnabled'
|
||||
]
|
||||
|
||||
for (const key of keysToUpdate) {
|
||||
if (body[key] !== undefined) user.set(key, body[key])
|
||||
}
|
||||
|
||||
if (body.email !== undefined) {
|
||||
if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
|
||||
user.pendingEmail = body.email
|
||||
sendVerificationEmail = true
|
||||
} else {
|
||||
user.email = body.email
|
||||
}
|
||||
}
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
await user.save({ transaction: t })
|
||||
|
||||
if (body.displayName === undefined && body.description === undefined) return
|
||||
|
||||
const userAccount = await AccountModel.load(user.Account.id, t)
|
||||
|
||||
if (body.displayName !== undefined) userAccount.name = body.displayName
|
||||
if (body.description !== undefined) userAccount.description = body.description
|
||||
await userAccount.save({ transaction: t })
|
||||
|
||||
await sendUpdateActor(userAccount, t)
|
||||
})
|
||||
|
||||
if (sendVerificationEmail === true) {
|
||||
await sendVerifyUserEmail(user, true)
|
||||
}
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function updateMyAvatar (req: express.Request, res: express.Response) {
|
||||
const avatarPhysicalFile = req.files['avatarfile'][0]
|
||||
const user = res.locals.oauth.token.user
|
||||
|
||||
const userAccount = await AccountModel.load(user.Account.id)
|
||||
|
||||
const avatars = await updateLocalActorImageFiles(
|
||||
userAccount,
|
||||
avatarPhysicalFile,
|
||||
ActorImageType.AVATAR
|
||||
)
|
||||
|
||||
return res.json({
|
||||
avatars: avatars.map(avatar => avatar.toFormattedJSON())
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteMyAvatar (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.user
|
||||
|
||||
const userAccount = await AccountModel.load(user.Account.id)
|
||||
await deleteLocalActorImageFile(userAccount, ActorImageType.AVATAR)
|
||||
|
||||
return res.json({ avatars: [] })
|
||||
}
|
48
server/core/controllers/api/users/my-abuses.ts
Normal file
48
server/core/controllers/api/users/my-abuses.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import express from 'express'
|
||||
import { AbuseModel } from '@server/models/abuse/abuse.js'
|
||||
import {
|
||||
abuseListForUserValidator,
|
||||
abusesSortValidator,
|
||||
asyncMiddleware,
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
setDefaultSort
|
||||
} from '../../../middlewares/index.js'
|
||||
|
||||
const myAbusesRouter = express.Router()
|
||||
|
||||
myAbusesRouter.get('/me/abuses',
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
abusesSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
abuseListForUserValidator,
|
||||
asyncMiddleware(listMyAbuses)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
myAbusesRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function listMyAbuses (req: express.Request, res: express.Response) {
|
||||
const resultList = await AbuseModel.listForUserApi({
|
||||
start: req.query.start,
|
||||
count: req.query.count,
|
||||
sort: req.query.sort,
|
||||
id: req.query.id,
|
||||
search: req.query.search,
|
||||
state: req.query.state,
|
||||
user: res.locals.oauth.token.User
|
||||
})
|
||||
|
||||
return res.json({
|
||||
total: resultList.total,
|
||||
data: resultList.data.map(d => d.toFormattedUserJSON())
|
||||
})
|
||||
}
|
154
server/core/controllers/api/users/my-blocklist.ts
Normal file
154
server/core/controllers/api/users/my-blocklist.ts
Normal file
|
@ -0,0 +1,154 @@
|
|||
import 'multer'
|
||||
import express from 'express'
|
||||
import { HttpStatusCode } from '@peertube/peertube-models'
|
||||
import { logger } from '@server/helpers/logger.js'
|
||||
import { UserNotificationModel } from '@server/models/user/user-notification.js'
|
||||
import { getFormattedObjects } from '../../../helpers/utils.js'
|
||||
import {
|
||||
addAccountInBlocklist,
|
||||
addServerInBlocklist,
|
||||
removeAccountFromBlocklist,
|
||||
removeServerFromBlocklist
|
||||
} from '../../../lib/blocklist.js'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
asyncRetryTransactionMiddleware,
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
setDefaultSort,
|
||||
unblockAccountByAccountValidator
|
||||
} from '../../../middlewares/index.js'
|
||||
import {
|
||||
accountsBlocklistSortValidator,
|
||||
blockAccountValidator,
|
||||
blockServerValidator,
|
||||
serversBlocklistSortValidator,
|
||||
unblockServerByAccountValidator
|
||||
} from '../../../middlewares/validators/index.js'
|
||||
import { AccountBlocklistModel } from '../../../models/account/account-blocklist.js'
|
||||
import { ServerBlocklistModel } from '../../../models/server/server-blocklist.js'
|
||||
|
||||
const myBlocklistRouter = express.Router()
|
||||
|
||||
myBlocklistRouter.get('/me/blocklist/accounts',
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
accountsBlocklistSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
asyncMiddleware(listBlockedAccounts)
|
||||
)
|
||||
|
||||
myBlocklistRouter.post('/me/blocklist/accounts',
|
||||
authenticate,
|
||||
asyncMiddleware(blockAccountValidator),
|
||||
asyncRetryTransactionMiddleware(blockAccount)
|
||||
)
|
||||
|
||||
myBlocklistRouter.delete('/me/blocklist/accounts/:accountName',
|
||||
authenticate,
|
||||
asyncMiddleware(unblockAccountByAccountValidator),
|
||||
asyncRetryTransactionMiddleware(unblockAccount)
|
||||
)
|
||||
|
||||
myBlocklistRouter.get('/me/blocklist/servers',
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
serversBlocklistSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
asyncMiddleware(listBlockedServers)
|
||||
)
|
||||
|
||||
myBlocklistRouter.post('/me/blocklist/servers',
|
||||
authenticate,
|
||||
asyncMiddleware(blockServerValidator),
|
||||
asyncRetryTransactionMiddleware(blockServer)
|
||||
)
|
||||
|
||||
myBlocklistRouter.delete('/me/blocklist/servers/:host',
|
||||
authenticate,
|
||||
asyncMiddleware(unblockServerByAccountValidator),
|
||||
asyncRetryTransactionMiddleware(unblockServer)
|
||||
)
|
||||
|
||||
export {
|
||||
myBlocklistRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function listBlockedAccounts (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
const resultList = await AccountBlocklistModel.listForApi({
|
||||
start: req.query.start,
|
||||
count: req.query.count,
|
||||
sort: req.query.sort,
|
||||
search: req.query.search,
|
||||
accountId: user.Account.id
|
||||
})
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function blockAccount (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
const accountToBlock = res.locals.account
|
||||
|
||||
await addAccountInBlocklist(user.Account.id, accountToBlock.id)
|
||||
|
||||
UserNotificationModel.removeNotificationsOf({
|
||||
id: accountToBlock.id,
|
||||
type: 'account',
|
||||
forUserId: user.id
|
||||
}).catch(err => logger.error('Cannot remove notifications after an account mute.', { err }))
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function unblockAccount (req: express.Request, res: express.Response) {
|
||||
const accountBlock = res.locals.accountBlock
|
||||
|
||||
await removeAccountFromBlocklist(accountBlock)
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function listBlockedServers (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
const resultList = await ServerBlocklistModel.listForApi({
|
||||
start: req.query.start,
|
||||
count: req.query.count,
|
||||
sort: req.query.sort,
|
||||
search: req.query.search,
|
||||
accountId: user.Account.id
|
||||
})
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function blockServer (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
const serverToBlock = res.locals.server
|
||||
|
||||
await addServerInBlocklist(user.Account.id, serverToBlock.id)
|
||||
|
||||
UserNotificationModel.removeNotificationsOf({
|
||||
id: serverToBlock.id,
|
||||
type: 'server',
|
||||
forUserId: user.id
|
||||
}).catch(err => logger.error('Cannot remove notifications after a server mute.', { err }))
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function unblockServer (req: express.Request, res: express.Response) {
|
||||
const serverBlock = res.locals.serverBlock
|
||||
|
||||
await removeServerFromBlocklist(serverBlock)
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
75
server/core/controllers/api/users/my-history.ts
Normal file
75
server/core/controllers/api/users/my-history.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
import express from 'express'
|
||||
import { forceNumber } from '@peertube/peertube-core-utils'
|
||||
import { HttpStatusCode } from '@peertube/peertube-models'
|
||||
import { getFormattedObjects } from '../../../helpers/utils.js'
|
||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
asyncRetryTransactionMiddleware,
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
userHistoryListValidator,
|
||||
userHistoryRemoveAllValidator,
|
||||
userHistoryRemoveElementValidator
|
||||
} from '../../../middlewares/index.js'
|
||||
import { UserVideoHistoryModel } from '../../../models/user/user-video-history.js'
|
||||
|
||||
const myVideosHistoryRouter = express.Router()
|
||||
|
||||
myVideosHistoryRouter.get('/me/history/videos',
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
userHistoryListValidator,
|
||||
asyncMiddleware(listMyVideosHistory)
|
||||
)
|
||||
|
||||
myVideosHistoryRouter.delete('/me/history/videos/:videoId',
|
||||
authenticate,
|
||||
userHistoryRemoveElementValidator,
|
||||
asyncMiddleware(removeUserHistoryElement)
|
||||
)
|
||||
|
||||
myVideosHistoryRouter.post('/me/history/videos/remove',
|
||||
authenticate,
|
||||
userHistoryRemoveAllValidator,
|
||||
asyncRetryTransactionMiddleware(removeAllUserHistory)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
myVideosHistoryRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function listMyVideosHistory (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
const resultList = await UserVideoHistoryModel.listForApi(user, req.query.start, req.query.count, req.query.search)
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function removeUserHistoryElement (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
await UserVideoHistoryModel.removeUserHistoryElement(user, forceNumber(req.params.videoId))
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
async function removeAllUserHistory (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
const beforeDate = req.body.beforeDate || null
|
||||
|
||||
await sequelizeTypescript.transaction(t => {
|
||||
return UserVideoHistoryModel.removeUserHistoryBefore(user, beforeDate, t)
|
||||
})
|
||||
|
||||
return res.type('json')
|
||||
.status(HttpStatusCode.NO_CONTENT_204)
|
||||
.end()
|
||||
}
|
115
server/core/controllers/api/users/my-notifications.ts
Normal file
115
server/core/controllers/api/users/my-notifications.ts
Normal file
|
@ -0,0 +1,115 @@
|
|||
import 'multer'
|
||||
import express from 'express'
|
||||
import { HttpStatusCode, UserNotificationSetting } from '@peertube/peertube-models'
|
||||
import { getFormattedObjects } from '@server/helpers/utils.js'
|
||||
import { UserNotificationModel } from '@server/models/user/user-notification.js'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
asyncRetryTransactionMiddleware,
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
setDefaultSort,
|
||||
userNotificationsSortValidator
|
||||
} from '../../../middlewares/index.js'
|
||||
import {
|
||||
listUserNotificationsValidator,
|
||||
markAsReadUserNotificationsValidator,
|
||||
updateNotificationSettingsValidator
|
||||
} from '../../../middlewares/validators/user-notifications.js'
|
||||
import { UserNotificationSettingModel } from '../../../models/user/user-notification-setting.js'
|
||||
import { meRouter } from './me.js'
|
||||
|
||||
const myNotificationsRouter = express.Router()
|
||||
|
||||
meRouter.put('/me/notification-settings',
|
||||
authenticate,
|
||||
updateNotificationSettingsValidator,
|
||||
asyncRetryTransactionMiddleware(updateNotificationSettings)
|
||||
)
|
||||
|
||||
myNotificationsRouter.get('/me/notifications',
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
userNotificationsSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
listUserNotificationsValidator,
|
||||
asyncMiddleware(listUserNotifications)
|
||||
)
|
||||
|
||||
myNotificationsRouter.post('/me/notifications/read',
|
||||
authenticate,
|
||||
markAsReadUserNotificationsValidator,
|
||||
asyncMiddleware(markAsReadUserNotifications)
|
||||
)
|
||||
|
||||
myNotificationsRouter.post('/me/notifications/read-all',
|
||||
authenticate,
|
||||
asyncMiddleware(markAsReadAllUserNotifications)
|
||||
)
|
||||
|
||||
export {
|
||||
myNotificationsRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function updateNotificationSettings (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
const body = req.body as UserNotificationSetting
|
||||
|
||||
const query = {
|
||||
where: {
|
||||
userId: user.id
|
||||
}
|
||||
}
|
||||
|
||||
const values: UserNotificationSetting = {
|
||||
newVideoFromSubscription: body.newVideoFromSubscription,
|
||||
newCommentOnMyVideo: body.newCommentOnMyVideo,
|
||||
abuseAsModerator: body.abuseAsModerator,
|
||||
videoAutoBlacklistAsModerator: body.videoAutoBlacklistAsModerator,
|
||||
blacklistOnMyVideo: body.blacklistOnMyVideo,
|
||||
myVideoPublished: body.myVideoPublished,
|
||||
myVideoImportFinished: body.myVideoImportFinished,
|
||||
newFollow: body.newFollow,
|
||||
newUserRegistration: body.newUserRegistration,
|
||||
commentMention: body.commentMention,
|
||||
newInstanceFollower: body.newInstanceFollower,
|
||||
autoInstanceFollowing: body.autoInstanceFollowing,
|
||||
abuseNewMessage: body.abuseNewMessage,
|
||||
abuseStateChange: body.abuseStateChange,
|
||||
newPeerTubeVersion: body.newPeerTubeVersion,
|
||||
newPluginVersion: body.newPluginVersion,
|
||||
myVideoStudioEditionFinished: body.myVideoStudioEditionFinished
|
||||
}
|
||||
|
||||
await UserNotificationSettingModel.update(values, query)
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function listUserNotifications (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort, req.query.unread)
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function markAsReadUserNotifications (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
await UserNotificationModel.markAsRead(user.id, req.body.ids)
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function markAsReadAllUserNotifications (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
await UserNotificationModel.markAllAsRead(user.id)
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
193
server/core/controllers/api/users/my-subscriptions.ts
Normal file
193
server/core/controllers/api/users/my-subscriptions.ts
Normal file
|
@ -0,0 +1,193 @@
|
|||
import 'multer'
|
||||
import express from 'express'
|
||||
import { HttpStatusCode } from '@peertube/peertube-models'
|
||||
import { handlesToNameAndHost } from '@server/helpers/actors.js'
|
||||
import { pickCommonVideoQuery } from '@server/helpers/query.js'
|
||||
import { sendUndoFollow } from '@server/lib/activitypub/send/index.js'
|
||||
import { Hooks } from '@server/lib/plugins/hooks.js'
|
||||
import { VideoChannelModel } from '@server/models/video/video-channel.js'
|
||||
import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils.js'
|
||||
import { getFormattedObjects } from '../../../helpers/utils.js'
|
||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||
import { JobQueue } from '../../../lib/job-queue/index.js'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
asyncRetryTransactionMiddleware,
|
||||
authenticate,
|
||||
commonVideosFiltersValidator,
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
setDefaultSort,
|
||||
setDefaultVideosSort,
|
||||
userSubscriptionAddValidator,
|
||||
userSubscriptionGetValidator
|
||||
} from '../../../middlewares/index.js'
|
||||
import {
|
||||
areSubscriptionsExistValidator,
|
||||
userSubscriptionListValidator,
|
||||
userSubscriptionsSortValidator,
|
||||
videosSortValidator
|
||||
} from '../../../middlewares/validators/index.js'
|
||||
import { ActorFollowModel } from '../../../models/actor/actor-follow.js'
|
||||
import { guessAdditionalAttributesFromQuery } from '../../../models/video/formatter/index.js'
|
||||
import { VideoModel } from '../../../models/video/video.js'
|
||||
|
||||
const mySubscriptionsRouter = express.Router()
|
||||
|
||||
mySubscriptionsRouter.get('/me/subscriptions/videos',
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
videosSortValidator,
|
||||
setDefaultVideosSort,
|
||||
setDefaultPagination,
|
||||
commonVideosFiltersValidator,
|
||||
asyncMiddleware(getUserSubscriptionVideos)
|
||||
)
|
||||
|
||||
mySubscriptionsRouter.get('/me/subscriptions/exist',
|
||||
authenticate,
|
||||
areSubscriptionsExistValidator,
|
||||
asyncMiddleware(areSubscriptionsExist)
|
||||
)
|
||||
|
||||
mySubscriptionsRouter.get('/me/subscriptions',
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
userSubscriptionsSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
userSubscriptionListValidator,
|
||||
asyncMiddleware(getUserSubscriptions)
|
||||
)
|
||||
|
||||
mySubscriptionsRouter.post('/me/subscriptions',
|
||||
authenticate,
|
||||
userSubscriptionAddValidator,
|
||||
addUserSubscription
|
||||
)
|
||||
|
||||
mySubscriptionsRouter.get('/me/subscriptions/:uri',
|
||||
authenticate,
|
||||
userSubscriptionGetValidator,
|
||||
asyncMiddleware(getUserSubscription)
|
||||
)
|
||||
|
||||
mySubscriptionsRouter.delete('/me/subscriptions/:uri',
|
||||
authenticate,
|
||||
userSubscriptionGetValidator,
|
||||
asyncRetryTransactionMiddleware(deleteUserSubscription)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
mySubscriptionsRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function areSubscriptionsExist (req: express.Request, res: express.Response) {
|
||||
const uris = req.query.uris as string[]
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
const sanitizedHandles = handlesToNameAndHost(uris)
|
||||
|
||||
const results = await ActorFollowModel.listSubscriptionsOf(user.Account.Actor.id, sanitizedHandles)
|
||||
|
||||
const existObject: { [id: string ]: boolean } = {}
|
||||
for (const sanitizedHandle of sanitizedHandles) {
|
||||
const obj = results.find(r => {
|
||||
const server = r.ActorFollowing.Server
|
||||
|
||||
return r.ActorFollowing.preferredUsername.toLowerCase() === sanitizedHandle.name.toLowerCase() &&
|
||||
(
|
||||
(!server && !sanitizedHandle.host) ||
|
||||
(server.host === sanitizedHandle.host)
|
||||
)
|
||||
})
|
||||
|
||||
existObject[sanitizedHandle.handle] = obj !== undefined
|
||||
}
|
||||
|
||||
return res.json(existObject)
|
||||
}
|
||||
|
||||
function addUserSubscription (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
const [ name, host ] = req.body.uri.split('@')
|
||||
|
||||
const payload = {
|
||||
name,
|
||||
host,
|
||||
assertIsChannel: true,
|
||||
followerActorId: user.Account.Actor.id
|
||||
}
|
||||
|
||||
JobQueue.Instance.createJobAsync({ type: 'activitypub-follow', payload })
|
||||
|
||||
return res.status(HttpStatusCode.NO_CONTENT_204).end()
|
||||
}
|
||||
|
||||
async function getUserSubscription (req: express.Request, res: express.Response) {
|
||||
const subscription = res.locals.subscription
|
||||
const videoChannel = await VideoChannelModel.loadAndPopulateAccount(subscription.ActorFollowing.VideoChannel.id)
|
||||
|
||||
return res.json(videoChannel.toFormattedJSON())
|
||||
}
|
||||
|
||||
async function deleteUserSubscription (req: express.Request, res: express.Response) {
|
||||
const subscription = res.locals.subscription
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
if (subscription.state === 'accepted') {
|
||||
sendUndoFollow(subscription, t)
|
||||
}
|
||||
|
||||
return subscription.destroy({ transaction: t })
|
||||
})
|
||||
|
||||
return res.type('json')
|
||||
.status(HttpStatusCode.NO_CONTENT_204)
|
||||
.end()
|
||||
}
|
||||
|
||||
async function getUserSubscriptions (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
const actorId = user.Account.Actor.id
|
||||
|
||||
const resultList = await ActorFollowModel.listSubscriptionsForApi({
|
||||
actorId,
|
||||
start: req.query.start,
|
||||
count: req.query.count,
|
||||
sort: req.query.sort,
|
||||
search: req.query.search
|
||||
})
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function getUserSubscriptionVideos (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.User
|
||||
const countVideos = getCountVideos(req)
|
||||
const query = pickCommonVideoQuery(req.query)
|
||||
|
||||
const apiOptions = await Hooks.wrapObject({
|
||||
...query,
|
||||
|
||||
displayOnlyForFollower: {
|
||||
actorId: user.Account.Actor.id,
|
||||
orLocalVideos: false
|
||||
},
|
||||
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||
user,
|
||||
countVideos
|
||||
}, 'filter:api.user.me.subscription-videos.list.params')
|
||||
|
||||
const resultList = await Hooks.wrapPromiseFun(
|
||||
VideoModel.listForApi,
|
||||
apiOptions,
|
||||
'filter:api.user.me.subscription-videos.list.result'
|
||||
)
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
|
||||
}
|
51
server/core/controllers/api/users/my-video-playlists.ts
Normal file
51
server/core/controllers/api/users/my-video-playlists.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import express from 'express'
|
||||
import { forceNumber } from '@peertube/peertube-core-utils'
|
||||
import { VideosExistInPlaylists } from '@peertube/peertube-models'
|
||||
import { uuidToShort } from '@peertube/peertube-node-utils'
|
||||
import { asyncMiddleware, authenticate } from '../../../middlewares/index.js'
|
||||
import { doVideosInPlaylistExistValidator } from '../../../middlewares/validators/videos/video-playlists.js'
|
||||
import { VideoPlaylistModel } from '../../../models/video/video-playlist.js'
|
||||
|
||||
const myVideoPlaylistsRouter = express.Router()
|
||||
|
||||
myVideoPlaylistsRouter.get('/me/video-playlists/videos-exist',
|
||||
authenticate,
|
||||
doVideosInPlaylistExistValidator,
|
||||
asyncMiddleware(doVideosInPlaylistExist)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
myVideoPlaylistsRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function doVideosInPlaylistExist (req: express.Request, res: express.Response) {
|
||||
const videoIds = req.query.videoIds.map(i => forceNumber(i))
|
||||
const user = res.locals.oauth.token.User
|
||||
|
||||
const results = await VideoPlaylistModel.listPlaylistSummariesOf(user.Account.id, videoIds)
|
||||
|
||||
const existObject: VideosExistInPlaylists = {}
|
||||
|
||||
for (const videoId of videoIds) {
|
||||
existObject[videoId] = []
|
||||
}
|
||||
|
||||
for (const result of results) {
|
||||
for (const element of result.VideoPlaylistElements) {
|
||||
existObject[element.videoId].push({
|
||||
playlistElementId: element.id,
|
||||
playlistId: result.id,
|
||||
playlistDisplayName: result.name,
|
||||
playlistShortUUID: uuidToShort(result.uuid),
|
||||
startTimestamp: element.startTimestamp,
|
||||
stopTimestamp: element.stopTimestamp
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return res.json(existObject)
|
||||
}
|
249
server/core/controllers/api/users/registrations.ts
Normal file
249
server/core/controllers/api/users/registrations.ts
Normal file
|
@ -0,0 +1,249 @@
|
|||
import express from 'express'
|
||||
import { Emailer } from '@server/lib/emailer.js'
|
||||
import { Hooks } from '@server/lib/plugins/hooks.js'
|
||||
import { UserRegistrationModel } from '@server/models/user/user-registration.js'
|
||||
import { pick } from '@peertube/peertube-core-utils'
|
||||
import {
|
||||
HttpStatusCode,
|
||||
UserRegister,
|
||||
UserRegistrationRequest,
|
||||
UserRegistrationState,
|
||||
UserRegistrationUpdateState,
|
||||
UserRight
|
||||
} from '@peertube/peertube-models'
|
||||
import { auditLoggerFactory, UserAuditView } from '../../../helpers/audit-logger.js'
|
||||
import { logger } from '../../../helpers/logger.js'
|
||||
import { CONFIG } from '../../../initializers/config.js'
|
||||
import { Notifier } from '../../../lib/notifier/index.js'
|
||||
import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyRegistrationEmail, sendVerifyUserEmail } from '../../../lib/user.js'
|
||||
import {
|
||||
acceptOrRejectRegistrationValidator,
|
||||
asyncMiddleware,
|
||||
asyncRetryTransactionMiddleware,
|
||||
authenticate,
|
||||
buildRateLimiter,
|
||||
ensureUserHasRight,
|
||||
ensureUserRegistrationAllowedFactory,
|
||||
ensureUserRegistrationAllowedForIP,
|
||||
getRegistrationValidator,
|
||||
listRegistrationsValidator,
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
setDefaultSort,
|
||||
userRegistrationsSortValidator,
|
||||
usersDirectRegistrationValidator,
|
||||
usersRequestRegistrationValidator
|
||||
} from '../../../middlewares/index.js'
|
||||
|
||||
const auditLogger = auditLoggerFactory('users')
|
||||
|
||||
const registrationRateLimiter = buildRateLimiter({
|
||||
windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS,
|
||||
max: CONFIG.RATES_LIMIT.SIGNUP.MAX,
|
||||
skipFailedRequests: true
|
||||
})
|
||||
|
||||
const registrationsRouter = express.Router()
|
||||
|
||||
registrationsRouter.post('/registrations/request',
|
||||
registrationRateLimiter,
|
||||
asyncMiddleware(ensureUserRegistrationAllowedFactory('request-registration')),
|
||||
ensureUserRegistrationAllowedForIP,
|
||||
asyncMiddleware(usersRequestRegistrationValidator),
|
||||
asyncRetryTransactionMiddleware(requestRegistration)
|
||||
)
|
||||
|
||||
registrationsRouter.post('/registrations/:registrationId/accept',
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
|
||||
asyncMiddleware(acceptOrRejectRegistrationValidator),
|
||||
asyncRetryTransactionMiddleware(acceptRegistration)
|
||||
)
|
||||
registrationsRouter.post('/registrations/:registrationId/reject',
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
|
||||
asyncMiddleware(acceptOrRejectRegistrationValidator),
|
||||
asyncRetryTransactionMiddleware(rejectRegistration)
|
||||
)
|
||||
|
||||
registrationsRouter.delete('/registrations/:registrationId',
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
|
||||
asyncMiddleware(getRegistrationValidator),
|
||||
asyncRetryTransactionMiddleware(deleteRegistration)
|
||||
)
|
||||
|
||||
registrationsRouter.get('/registrations',
|
||||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_REGISTRATIONS),
|
||||
paginationValidator,
|
||||
userRegistrationsSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
listRegistrationsValidator,
|
||||
asyncMiddleware(listRegistrations)
|
||||
)
|
||||
|
||||
registrationsRouter.post('/register',
|
||||
registrationRateLimiter,
|
||||
asyncMiddleware(ensureUserRegistrationAllowedFactory('direct-registration')),
|
||||
ensureUserRegistrationAllowedForIP,
|
||||
asyncMiddleware(usersDirectRegistrationValidator),
|
||||
asyncRetryTransactionMiddleware(registerUser)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
registrationsRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function requestRegistration (req: express.Request, res: express.Response) {
|
||||
const body: UserRegistrationRequest = req.body
|
||||
|
||||
const registration = new UserRegistrationModel({
|
||||
...pick(body, [ 'username', 'password', 'email', 'registrationReason' ]),
|
||||
|
||||
accountDisplayName: body.displayName,
|
||||
channelDisplayName: body.channel?.displayName,
|
||||
channelHandle: body.channel?.name,
|
||||
|
||||
state: UserRegistrationState.PENDING,
|
||||
|
||||
emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
|
||||
})
|
||||
|
||||
await registration.save()
|
||||
|
||||
if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
|
||||
await sendVerifyRegistrationEmail(registration)
|
||||
}
|
||||
|
||||
Notifier.Instance.notifyOnNewRegistrationRequest(registration)
|
||||
|
||||
Hooks.runAction('action:api.user.requested-registration', { body, registration, req, res })
|
||||
|
||||
return res.json(registration.toFormattedJSON())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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,
|
||||
password: registration.password,
|
||||
email: registration.email,
|
||||
emailVerified: registration.emailVerified
|
||||
})
|
||||
// We already encrypted password in registration model
|
||||
userToCreate.skipPasswordEncryption = true
|
||||
|
||||
// TODO: handle conflicts if someone else created a channel handle/user handle/user email between registration and approval
|
||||
|
||||
const { user } = await createUserAccountAndChannelAndPlaylist({
|
||||
userToCreate,
|
||||
userDisplayName: registration.accountDisplayName,
|
||||
channelNames: registration.channelHandle && registration.channelDisplayName
|
||||
? {
|
||||
name: registration.channelHandle,
|
||||
displayName: registration.channelDisplayName
|
||||
}
|
||||
: undefined
|
||||
})
|
||||
|
||||
registration.userId = user.id
|
||||
registration.state = UserRegistrationState.ACCEPTED
|
||||
registration.moderationResponse = body.moderationResponse
|
||||
|
||||
await registration.save()
|
||||
|
||||
logger.info('Registration of %s accepted', registration.username)
|
||||
|
||||
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 = body.moderationResponse
|
||||
|
||||
await registration.save()
|
||||
|
||||
if (body.preventEmailDelivery !== true) {
|
||||
Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
|
||||
}
|
||||
|
||||
logger.info('Registration of %s rejected', registration.username)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function deleteRegistration (req: express.Request, res: express.Response) {
|
||||
const registration = res.locals.userRegistration
|
||||
|
||||
await registration.destroy()
|
||||
|
||||
logger.info('Registration of %s deleted', registration.username)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function listRegistrations (req: express.Request, res: express.Response) {
|
||||
const resultList = await UserRegistrationModel.listForApi({
|
||||
start: req.query.start,
|
||||
count: req.query.count,
|
||||
sort: req.query.sort,
|
||||
search: req.query.search
|
||||
})
|
||||
|
||||
return res.json({
|
||||
total: resultList.total,
|
||||
data: resultList.data.map(d => d.toFormattedJSON())
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function registerUser (req: express.Request, res: express.Response) {
|
||||
const body: UserRegister = req.body
|
||||
|
||||
const userToCreate = buildUser({
|
||||
...pick(body, [ 'username', 'password', 'email' ]),
|
||||
|
||||
emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
|
||||
})
|
||||
|
||||
const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({
|
||||
userToCreate,
|
||||
userDisplayName: body.displayName || undefined,
|
||||
channelNames: body.channel
|
||||
})
|
||||
|
||||
auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
|
||||
logger.info('User %s with its channel and account registered.', body.username)
|
||||
|
||||
if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
|
||||
await sendVerifyUserEmail(user)
|
||||
}
|
||||
|
||||
Notifier.Instance.notifyOnNewDirectRegistration(user)
|
||||
|
||||
Hooks.runAction('action:api.user.registered', { body, user, account, videoChannel, req, res })
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
131
server/core/controllers/api/users/token.ts
Normal file
131
server/core/controllers/api/users/token.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
import express from 'express'
|
||||
import { ScopedToken } from '@peertube/peertube-models'
|
||||
import { logger } from '@server/helpers/logger.js'
|
||||
import { CONFIG } from '@server/initializers/config.js'
|
||||
import { OTP } from '@server/initializers/constants.js'
|
||||
import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth.js'
|
||||
import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model.js'
|
||||
import { handleOAuthToken, MissingTwoFactorError } from '@server/lib/auth/oauth.js'
|
||||
import { Hooks } from '@server/lib/plugins/hooks.js'
|
||||
import { asyncMiddleware, authenticate, buildRateLimiter, openapiOperationDoc } from '@server/middlewares/index.js'
|
||||
import { buildUUID } from '@peertube/peertube-node-utils'
|
||||
|
||||
const tokensRouter = express.Router()
|
||||
|
||||
const loginRateLimiter = buildRateLimiter({
|
||||
windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS,
|
||||
max: CONFIG.RATES_LIMIT.LOGIN.MAX
|
||||
})
|
||||
|
||||
tokensRouter.post('/token',
|
||||
loginRateLimiter,
|
||||
openapiOperationDoc({ operationId: 'getOAuthToken' }),
|
||||
asyncMiddleware(handleToken)
|
||||
)
|
||||
|
||||
tokensRouter.post('/revoke-token',
|
||||
openapiOperationDoc({ operationId: 'revokeOAuthToken' }),
|
||||
authenticate,
|
||||
asyncMiddleware(handleTokenRevocation)
|
||||
)
|
||||
|
||||
tokensRouter.get('/scoped-tokens',
|
||||
authenticate,
|
||||
getScopedTokens
|
||||
)
|
||||
|
||||
tokensRouter.post('/scoped-tokens',
|
||||
authenticate,
|
||||
asyncMiddleware(renewScopedTokens)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
tokensRouter
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function handleToken (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const grantType = req.body.grant_type
|
||||
|
||||
try {
|
||||
const bypassLogin = await buildByPassLogin(req, grantType)
|
||||
|
||||
const refreshTokenAuthName = grantType === 'refresh_token'
|
||||
? await getAuthNameFromRefreshGrant(req.body.refresh_token)
|
||||
: undefined
|
||||
|
||||
const options = {
|
||||
refreshTokenAuthName,
|
||||
bypassLogin
|
||||
}
|
||||
|
||||
const token = await handleOAuthToken(req, options)
|
||||
|
||||
res.set('Cache-Control', 'no-store')
|
||||
res.set('Pragma', 'no-cache')
|
||||
|
||||
Hooks.runAction('action:api.user.oauth2-got-token', { username: token.user.username, ip: req.ip, req, res })
|
||||
|
||||
return res.json({
|
||||
token_type: 'Bearer',
|
||||
|
||||
access_token: token.accessToken,
|
||||
refresh_token: token.refreshToken,
|
||||
|
||||
expires_in: token.accessTokenExpiresIn,
|
||||
refresh_token_expires_in: token.refreshTokenExpiresIn
|
||||
})
|
||||
} catch (err) {
|
||||
logger.warn('Login error', { err })
|
||||
|
||||
if (err instanceof MissingTwoFactorError) {
|
||||
res.set(OTP.HEADER_NAME, OTP.HEADER_REQUIRED_VALUE)
|
||||
}
|
||||
|
||||
return res.fail({
|
||||
status: err.code,
|
||||
message: err.message,
|
||||
type: err.name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTokenRevocation (req: express.Request, res: express.Response) {
|
||||
const token = res.locals.oauth.token
|
||||
|
||||
const result = await revokeToken(token, { req, explicitLogout: true })
|
||||
|
||||
return res.json(result)
|
||||
}
|
||||
|
||||
function getScopedTokens (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.user
|
||||
|
||||
return res.json({
|
||||
feedToken: user.feedToken
|
||||
} as ScopedToken)
|
||||
}
|
||||
|
||||
async function renewScopedTokens (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.oauth.token.user
|
||||
|
||||
user.feedToken = buildUUID()
|
||||
await user.save()
|
||||
|
||||
return res.json({
|
||||
feedToken: user.feedToken
|
||||
} as ScopedToken)
|
||||
}
|
||||
|
||||
async function buildByPassLogin (req: express.Request, grantType: string): Promise<BypassLogin> {
|
||||
if (grantType !== 'password') return undefined
|
||||
|
||||
if (req.body.externalAuthToken) {
|
||||
// Consistency with the getBypassFromPasswordGrant promise
|
||||
return getBypassFromExternalAuth(req.body.username, req.body.externalAuthToken)
|
||||
}
|
||||
|
||||
return getBypassFromPasswordGrant(req.body.username, req.body.password)
|
||||
}
|
95
server/core/controllers/api/users/two-factor.ts
Normal file
95
server/core/controllers/api/users/two-factor.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
import express from 'express'
|
||||
import { generateOTPSecret, isOTPValid } from '@server/helpers/otp.js'
|
||||
import { encrypt } from '@server/helpers/peertube-crypto.js'
|
||||
import { CONFIG } from '@server/initializers/config.js'
|
||||
import { Redis } from '@server/lib/redis.js'
|
||||
import { asyncMiddleware, authenticate, usersCheckCurrentPasswordFactory } from '@server/middlewares/index.js'
|
||||
import {
|
||||
confirmTwoFactorValidator,
|
||||
disableTwoFactorValidator,
|
||||
requestOrConfirmTwoFactorValidator
|
||||
} from '@server/middlewares/validators/two-factor.js'
|
||||
import { HttpStatusCode, TwoFactorEnableResult } from '@peertube/peertube-models'
|
||||
|
||||
const twoFactorRouter = express.Router()
|
||||
|
||||
twoFactorRouter.post('/:id/two-factor/request',
|
||||
authenticate,
|
||||
asyncMiddleware(usersCheckCurrentPasswordFactory(req => req.params.id)),
|
||||
asyncMiddleware(requestOrConfirmTwoFactorValidator),
|
||||
asyncMiddleware(requestTwoFactor)
|
||||
)
|
||||
|
||||
twoFactorRouter.post('/:id/two-factor/confirm-request',
|
||||
authenticate,
|
||||
asyncMiddleware(requestOrConfirmTwoFactorValidator),
|
||||
confirmTwoFactorValidator,
|
||||
asyncMiddleware(confirmRequestTwoFactor)
|
||||
)
|
||||
|
||||
twoFactorRouter.post('/:id/two-factor/disable',
|
||||
authenticate,
|
||||
asyncMiddleware(usersCheckCurrentPasswordFactory(req => req.params.id)),
|
||||
asyncMiddleware(disableTwoFactorValidator),
|
||||
asyncMiddleware(disableTwoFactor)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
twoFactorRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function requestTwoFactor (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.user
|
||||
|
||||
const { secret, uri } = generateOTPSecret(user.email)
|
||||
|
||||
const encryptedSecret = await encrypt(secret, CONFIG.SECRETS.PEERTUBE)
|
||||
const requestToken = await Redis.Instance.setTwoFactorRequest(user.id, encryptedSecret)
|
||||
|
||||
return res.json({
|
||||
otpRequest: {
|
||||
requestToken,
|
||||
secret,
|
||||
uri
|
||||
}
|
||||
} as TwoFactorEnableResult)
|
||||
}
|
||||
|
||||
async function confirmRequestTwoFactor (req: express.Request, res: express.Response) {
|
||||
const requestToken = req.body.requestToken
|
||||
const otpToken = req.body.otpToken
|
||||
const user = res.locals.user
|
||||
|
||||
const encryptedSecret = await Redis.Instance.getTwoFactorRequestToken(user.id, requestToken)
|
||||
if (!encryptedSecret) {
|
||||
return res.fail({
|
||||
message: 'Invalid request token',
|
||||
status: HttpStatusCode.FORBIDDEN_403
|
||||
})
|
||||
}
|
||||
|
||||
if (await isOTPValid({ encryptedSecret, token: otpToken }) !== true) {
|
||||
return res.fail({
|
||||
message: 'Invalid OTP token',
|
||||
status: HttpStatusCode.FORBIDDEN_403
|
||||
})
|
||||
}
|
||||
|
||||
user.otpSecret = encryptedSecret
|
||||
await user.save()
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
async function disableTwoFactor (req: express.Request, res: express.Response) {
|
||||
const user = res.locals.user
|
||||
|
||||
user.otpSecret = null
|
||||
await user.save()
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue