diff --git a/services/web/app/src/Features/Authorization/PermissionsController.mjs b/services/web/app/src/Features/Authorization/PermissionsController.mjs index bbfbdf9754..41dea73cdc 100644 --- a/services/web/app/src/Features/Authorization/PermissionsController.mjs +++ b/services/web/app/src/Features/Authorization/PermissionsController.mjs @@ -26,9 +26,9 @@ const { */ function useCapabilities() { /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ const middleware = async function (req, res, next) { // attach the user's capabilities to the request object @@ -36,7 +36,7 @@ function useCapabilities() { // provide a function to assert that a capability is present /** @param {any} capability */ req.assertPermission = capability => { - if (!req.capabilitySet.has(capability)) { + if (!req.capabilitySet?.has(capability)) { throw new ForbiddenError( `user does not have permission for ${capability}` ) @@ -64,15 +64,17 @@ function useCapabilities() { const combinedGroupPolicy = combineGroupPolicies(groupPolicies) // attach the new capabilities to the request object for (const cap of getUserCapabilities(combinedGroupPolicy)) { - req.capabilitySet.add(cap) + req.capabilitySet?.add(cap) } // also attach the user's restrictions (the capabilities they don't have) req.userRestrictions = getUserRestrictions(combinedGroupPolicy) // attach allowed properties to the request object const allowedProperties = combineAllowedProperties(results) + /** @type {Record} */ + const reqRecord = req for (const [prop, value] of Object.entries(allowedProperties)) { - req[prop] = value + reqRecord[prop] = value } } next() diff --git a/services/web/app/src/Features/FileStore/FileStoreController.mjs b/services/web/app/src/Features/FileStore/FileStoreController.mjs index 174b4f057b..79fb327746 100644 --- a/services/web/app/src/Features/FileStore/FileStoreController.mjs +++ b/services/web/app/src/Features/FileStore/FileStoreController.mjs @@ -10,6 +10,12 @@ import Errors from '../Errors/Errors.js' import { preparePlainTextResponse } from '../../infrastructure/Response.mjs' import { z, zz, parseReq } from '../../infrastructure/Validation.mjs' +/** + * @typedef {import('express').Request} Request + * @typedef {import('express').Response} Response + * @typedef {import('express').NextFunction} NextFunction + */ + const getFileSchema = z.object({ params: z.strictObject({ Project_id: zz.objectId(), @@ -28,8 +34,8 @@ const getFileHeadSchema = z.object({ }) /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function getFile(req, res) { const { params, query } = parseReq(req, getFileSchema, { logOnly: true }) @@ -125,8 +131,8 @@ async function getFile(req, res) { } /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function getFileHead(req, res) { const { params } = parseReq(req, getFileHeadSchema, { logOnly: true }) diff --git a/services/web/app/src/Features/History/HistoryController.mjs b/services/web/app/src/Features/History/HistoryController.mjs index 057cfc8878..65f0b3cee1 100644 --- a/services/web/app/src/Features/History/HistoryController.mjs +++ b/services/web/app/src/Features/History/HistoryController.mjs @@ -35,6 +35,12 @@ import { z, zz, parseReq } from '../../infrastructure/Validation.mjs' /** @type {any} */ import ProjectAuditLogHandler from '../Project/ProjectAuditLogHandler.mjs' +/** + * @typedef {import('express').Request} Request + * @typedef {import('express').Response} Response + * @typedef {import('express').NextFunction} NextFunction + */ + // Number of seconds after which the browser should send a request to revalidate // blobs const REVALIDATE_BLOB_AFTER_SECONDS = 86400 // 1 day @@ -46,16 +52,16 @@ const STALE_WHILE_REVALIDATE_SECONDS = 365 * 86400 // 1 year const MAX_HISTORY_ZIP_ATTEMPTS = 40 /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function getBlob(req, res) { await requestBlob('GET', req, res) } /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function headBlob(req, res) { await requestBlob('HEAD', req, res) @@ -73,8 +79,8 @@ const requestBlobSchema = z.object({ /** * @param {any} method - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function requestBlob(method, req, res) { const { params } = parseReq(req, requestBlobSchema) @@ -124,7 +130,7 @@ async function requestBlob(method, req, res) { } /** - * @param {any} res + * @param {Response} res * @param {any} etag */ function setBlobCacheHeaders(res, etag) { @@ -159,9 +165,9 @@ const proxyToHistoryApiFallbackSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function proxyToHistoryApi(req, res, next) { parseReq(req, proxyToHistoryApiSchema, { @@ -210,9 +216,9 @@ const proxyToHistoryApiAndInjectUserDetailsFallbackSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function proxyToHistoryApiAndInjectUserDetails(req, res, next) { parseReq(req, proxyToHistoryApiAndInjectUserDetailsSchema, { @@ -251,9 +257,9 @@ const resyncProjectHistoryFallbackSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function resyncProjectHistory(req, res, next) { // increase timeout to 6 minutes @@ -306,9 +312,9 @@ const restoreFileFromV2FallbackSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function restoreFileFromV2(req, res, next) { const { params, body } = parseReq(req, restoreFileFromV2Schema, { @@ -367,9 +373,9 @@ const revertFileFallbackSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function revertFile(req, res, next) { const { params, body } = parseReq(req, revertFileSchema, { @@ -427,9 +433,9 @@ const revertProjectFallbackSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function revertProject(req, res, next) { const { params, body } = parseReq(req, revertProjectSchema, { @@ -475,9 +481,9 @@ const getLabelsFallbackSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function getLabels(req, res, next) { const { params } = parseReq(req, getLabelsSchema, { @@ -516,9 +522,9 @@ const createLabelFallbackSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function createLabel(req, res, next) { const { params, body } = parseReq(req, createLabelSchema, { @@ -636,9 +642,9 @@ const deleteLabelFallbackSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function deleteLabel(req, res, next) { const { params } = parseReq(req, deleteLabelSchema, { @@ -672,9 +678,9 @@ const downloadZipOfVersionSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function downloadZipOfVersion(req, res, next) { const { params } = parseReq(req, downloadZipOfVersionSchema) @@ -718,8 +724,8 @@ async function downloadZipOfVersion(req, res, next) { * @param {any} v1ProjectId * @param {any} version * @param {any} name - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function _pipeHistoryZipToResponse(v1ProjectId, version, name, req, res) { if (req.destroyed) { @@ -837,9 +843,9 @@ const getLatestHistorySchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function getLatestHistory(req, res, next) { const { params } = parseReq(req, getLatestHistorySchema) @@ -859,9 +865,9 @@ const getChangesSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function getChanges(req, res, next) { const { params, query } = parseReq(req, getChangesSchema) diff --git a/services/web/app/src/Features/Project/ProjectListController.mjs b/services/web/app/src/Features/Project/ProjectListController.mjs index 6b42b9fc7c..5acb9e724d 100644 --- a/services/web/app/src/Features/Project/ProjectListController.mjs +++ b/services/web/app/src/Features/Project/ProjectListController.mjs @@ -131,7 +131,7 @@ const _buildPortalTemplatesList = affiliations => { } /** - * @param {any} req + * @param {import("express").Request} req */ function cleanupSession(req) { // cleanup redirects at the end of the redirect chain diff --git a/services/web/app/src/Features/Spelling/SpellingController.mjs b/services/web/app/src/Features/Spelling/SpellingController.mjs index d55fb79edd..7780802c7c 100644 --- a/services/web/app/src/Features/Spelling/SpellingController.mjs +++ b/services/web/app/src/Features/Spelling/SpellingController.mjs @@ -4,6 +4,12 @@ import SessionManager from '../Authentication/SessionManager.mjs' import LearnedWordsManager from './LearnedWordsManager.mjs' import { z, parseReq } from '../../infrastructure/Validation.mjs' +/** + * @typedef {import('express').Request} Request + * @typedef {import('express').Response} Response + * @typedef {import('express').NextFunction} NextFunction + */ + const learnSchema = z.object({ body: z.strictObject({ word: z.string().min(1), @@ -32,9 +38,9 @@ const unlearnFallbackSchema = z.object({ export default { /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ learn(req, res, next) { const { body } = parseReq(req, learnSchema, { @@ -53,9 +59,9 @@ export default { }, /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ unlearn(req, res, next) { const { body } = parseReq(req, unlearnSchema, { diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.mjs b/services/web/app/src/Features/Subscription/SubscriptionController.mjs index e8d7f9cb72..d24bf6bb69 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.mjs +++ b/services/web/app/src/Features/Subscription/SubscriptionController.mjs @@ -41,6 +41,9 @@ const SUBSCRIPTION_PAUSED_REDIRECT_PATH = * @typedef {import('../../../../types/subscription/currency').CurrencyCode} CurrencyCode * @typedef {import('./PaymentProviderEntities.mjs').PaymentProviderSubscription} PaymentProviderSubscription * @typedef {import('../../../../types/subscription/plan').Plan} Plan + * @typedef {import('express').Request} Request + * @typedef {import('express').Response} Response + * @typedef {import('express').NextFunction} NextFunction */ /** @@ -161,8 +164,8 @@ const userSubscriptionPageSchema = z.object({ }) /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function userSubscriptionPage(req, res) { const { query } = parseReq(req, userSubscriptionPageSchema, { @@ -378,8 +381,8 @@ const successfulSubscriptionFallbackSchema = z.object({ }) /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function successfulSubscription(req, res) { const { query } = parseReq(req, successfulSubscriptionSchema, { @@ -431,9 +434,9 @@ const pauseSubscriptionSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function pauseSubscription(req, res, next) { const user = SessionManager.getSessionUser(req.session) @@ -487,9 +490,9 @@ async function pauseSubscription(req, res, next) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function resumeSubscription(req, res, next) { const user = SessionManager.getSessionUser(req.session) @@ -508,9 +511,9 @@ async function resumeSubscription(req, res, next) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function cancelSubscription(req, res, next) { const user = SessionManager.getSessionUser(req.session) @@ -527,12 +530,10 @@ async function cancelSubscription(req, res, next) { } /** - * @param {any} req - * @param {any} res - * @param {any} next - * @returns {Promise} + * @param {Request} req + * @param {Response} res */ -async function canceledSubscription(req, res, next) { +async function canceledSubscription(req, res) { return res.render('subscriptions/canceled-subscription-react', { title: 'subscription_canceled', user: sanitizeSessionUserForFrontEnd( @@ -542,9 +543,9 @@ async function canceledSubscription(req, res, next) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ function cancelV1Subscription(req, res, next) { const userId = SessionManager.getLoggedInUserId(req.session) @@ -570,8 +571,8 @@ const previewAddonPurchaseSchema = z.object({ }) /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function previewAddonPurchase(req, res) { const { params } = parseReq(req, previewAddonPurchaseSchema, { @@ -589,11 +590,10 @@ async function previewAddonPurchase(req, res) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res */ -async function purchaseAddon(req, res, next) { +async function purchaseAddon(req, res) { return res.sendStatus(404) } @@ -604,9 +604,9 @@ const removeAddonSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function removeAddon(req, res, next) { const user = SessionManager.getSessionUser(req.session) @@ -662,8 +662,8 @@ const reactivateAddonSchema = z.object({ * Reactivate an add-on pending cancellation * * This "cancels" the cancellation. - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function reactivateAddon(req, res) { const user = SessionManager.getSessionUser(req.session) @@ -701,11 +701,10 @@ const previewSubscriptionSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res */ -async function previewSubscription(req, res, next) { +async function previewSubscription(req, res) { const { query } = parseReq(req, previewSubscriptionSchema, { logOnly: true, }) @@ -768,9 +767,9 @@ async function previewSubscription(req, res, next) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ function cancelPendingSubscriptionChange(req, res, next) { const user = SessionManager.getSessionUser(req.session) @@ -794,9 +793,9 @@ function cancelPendingSubscriptionChange(req, res, next) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function updateAccountEmailAddress(req, res, next) { const user = SessionManager.getSessionUser(req.session) @@ -813,9 +812,9 @@ async function updateAccountEmailAddress(req, res, next) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ function reactivateSubscription(req, res, next) { const user = SessionManager.getSessionUser(req.session) @@ -859,9 +858,9 @@ const recurlyCallbackSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ function recurlyCallback(req, res, next) { const { body } = parseReq(req, recurlyCallbackSchema, { logOnly: true }) @@ -914,8 +913,8 @@ function recurlyCallback(req, res, next) { } /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function extendTrial(req, res) { const user = SessionManager.getSessionUser(req.session) @@ -943,9 +942,9 @@ async function extendTrial(req, res) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ function recurlyNotificationParser(req, res, next) { let xml = '' @@ -975,8 +974,8 @@ const refreshUserFeaturesSchema = z.object({ }) /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function refreshUserFeatures(req, res) { const { params } = parseReq(req, refreshUserFeaturesSchema, { @@ -1003,8 +1002,8 @@ const getRecommendedCurrencySchema = z.object({ }) /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res * @returns {Promise<{currency: CurrencyCode, recommendedCurrency: CurrencyCode, countryCode: string|undefined}>} */ async function getRecommendedCurrency(req, res) { @@ -1049,8 +1048,8 @@ const getLatamCountryBannerDetailsSchema = z.object({ }) /** - * @param {any} req - * @param {any} res + * @param {Request} req + * @param {Response} res */ async function getLatamCountryBannerDetails(req, res) { const { query } = parseReq(req, getLatamCountryBannerDetailsSchema, { diff --git a/services/web/app/src/Features/Subscription/SubscriptionHandler.mjs b/services/web/app/src/Features/Subscription/SubscriptionHandler.mjs index b0591528f8..da8a4efeca 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHandler.mjs +++ b/services/web/app/src/Features/Subscription/SubscriptionHandler.mjs @@ -236,7 +236,7 @@ async function syncSubscription(recurlySubscription, requesterData) { /** * attempt to collect past due invoice for customer. Only do that when a) the * customer is using Paypal and b) there is only one past due invoice. - * This is used because Recurly doesn't always attempt collection of paast due + * This is used because Recurly doesn't always attempt collection of past due * invoices after Paypal billing info were updated. * * @param {any} recurlyAccountCode diff --git a/services/web/app/src/Features/Subscription/TeamInvitesController.mjs b/services/web/app/src/Features/Subscription/TeamInvitesController.mjs index 5c47d92348..da140816d2 100644 --- a/services/web/app/src/Features/Subscription/TeamInvitesController.mjs +++ b/services/web/app/src/Features/Subscription/TeamInvitesController.mjs @@ -19,6 +19,12 @@ import UserAuditLogHandler from '../User/UserAuditLogHandler.mjs' import { sanitizeSessionUserForFrontEnd } from '../../infrastructure/FrontEndUser.mjs' import { z, zz, parseReq } from '../../infrastructure/Validation.mjs' +/** + * @typedef {import('express').Request} Request + * @typedef {import('express').Response} Response + * @typedef {import('express').NextFunction} NextFunction + */ + const rateLimiters = { resendGroupInvite: new RateLimiter('resend-group-invite', { points: 1, @@ -33,9 +39,9 @@ const createInviteSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function createInvite(req, res, next) { const { body } = parseReq(req, createInviteSchema, { logOnly: true }) @@ -96,9 +102,9 @@ const viewInviteSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function viewInvite(req, res, next) { const { params, query } = parseReq(req, viewInviteSchema, { @@ -220,9 +226,9 @@ async function viewInvite(req, res, next) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function viewInvites(req, res, next) { const user = SessionManager.getSessionUser(req.session) @@ -246,9 +252,9 @@ const acceptInviteSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function acceptInvite(req, res, next) { const { params } = parseReq(req, acceptInviteSchema, { logOnly: true }) @@ -294,9 +300,9 @@ const revokeInviteSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ function revokeInvite(req, res, next) { const { params } = parseReq(req, revokeInviteSchema, { logOnly: true }) @@ -331,9 +337,9 @@ const resendInviteSchema = z.object({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async function resendInvite(req, res, next) { const { body } = parseReq(req, resendInviteSchema, { logOnly: true }) diff --git a/services/web/app/src/Features/Uploads/ProjectUploadController.mjs b/services/web/app/src/Features/Uploads/ProjectUploadController.mjs index 0402ef97ac..55acb5054b 100644 --- a/services/web/app/src/Features/Uploads/ProjectUploadController.mjs +++ b/services/web/app/src/Features/Uploads/ProjectUploadController.mjs @@ -23,6 +23,12 @@ import AnalyticsManager from '../Analytics/AnalyticsManager.mjs' import { multer, multerUploadHandler } from '../../infrastructure/Multer.mjs' import { parseReq, z, zz } from '../../infrastructure/Validation.mjs' +/** + * @typedef {import('express').Request} Request + * @typedef {import('express').Response} Response + * @typedef {import('express').NextFunction} NextFunction + */ + const uploadMetaTypeSchema = z.string().optional() const uploadProjectSchema = z.object({ @@ -77,11 +83,10 @@ const upload = multerUploadHandler({ }) /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res */ -function uploadProject(req, res, next) { +function uploadProject(req, res) { const timer = new metrics.Timer('project-upload') const userId = SessionManager.getLoggedInUserId(req.session) const { body, file } = parseReq(req, uploadProjectSchema, { @@ -120,11 +125,10 @@ function uploadProject(req, res, next) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res */ -async function uploadFile(req, res, next) { +async function uploadFile(req, res) { const timer = new metrics.Timer('file-upload') const { params, query, body, file } = parseReq(req, uploadFileSchema, { logOnly: true, @@ -226,11 +230,10 @@ async function uploadFile(req, res, next) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res */ -async function importDocument(req, res, next) { +async function importDocument(req, res) { const userId = SessionManager.getLoggedInUserId(req.session) const { query, body, file } = parseReq(req, importDocumentSchema, { logOnly: true, @@ -313,9 +316,9 @@ async function importDocument(req, res, next) { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ function multerMiddleware(req, res, next) { if (upload == null) { diff --git a/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.mjs b/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.mjs index 80f253a278..d6f00100d9 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.mjs +++ b/services/web/app/src/Features/UserMembership/UserMembershipMiddleware.mjs @@ -17,6 +17,12 @@ import { } from '../../infrastructure/Validation.mjs' import AdminAuthorizationHelper from '../Helpers/AdminAuthorizationHelper.mjs' +/** + * @typedef {import('express').Request} Request + * @typedef {import('express').Response} Response + * @typedef {import('express').NextFunction} NextFunction + */ + const { useAdminCapabilities, useNonAdminDomainCapabilities } = AdminAuthorizationHelper // set of middleware arrays or functions that checks user access to an entity @@ -310,9 +316,9 @@ const requireGraphAccessSchema = z.object({ // - the entity name is in `req.query.resource_type` and is used to find the // require middleware depending on the entity name /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ function requireGraphAccess(req, res, next) { const { query, params } = parseReq(req, requireGraphAccessSchema, { diff --git a/services/web/app/src/infrastructure/HttpPermissionsPolicy.mjs b/services/web/app/src/infrastructure/HttpPermissionsPolicy.mjs index 30a4d7c359..846add880f 100644 --- a/services/web/app/src/infrastructure/HttpPermissionsPolicy.mjs +++ b/services/web/app/src/infrastructure/HttpPermissionsPolicy.mjs @@ -3,7 +3,10 @@ import Settings from '@overleaf/settings' /** - * @import { HttpPermissionsPolicy } from './types' + * @typedef {import('express').Request} Request + * @typedef {import('express').Response} Response + * @typedef {import('express').NextFunction} NextFunction + * @typedef {import('./types').HttpPermissionsPolicy} HttpPermissionsPolicy */ class HttpPermissionsPolicyMiddleware { @@ -73,16 +76,17 @@ class HttpPermissionsPolicyMiddleware { } /** - * @param {any} req - * @param {any} res - * @param {any} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ middleware(req, res, next) { - if (this.policy && Settings.useHttpPermissionsPolicy) { + const policy = this.policy + if (policy && Settings.useHttpPermissionsPolicy) { const originalRender = res.render res.render = (/** @type {any} */ ...args) => { - res.setHeader('Permissions-Policy', this.policy) + res.setHeader('Permissions-Policy', policy) originalRender.apply(res, args) } } diff --git a/services/web/app/src/infrastructure/rate-limiters/TokenUsageRateLimiter.mjs b/services/web/app/src/infrastructure/rate-limiters/TokenUsageRateLimiter.mjs index b51f7578d1..e9f3960c69 100644 --- a/services/web/app/src/infrastructure/rate-limiters/TokenUsageRateLimiter.mjs +++ b/services/web/app/src/infrastructure/rate-limiters/TokenUsageRateLimiter.mjs @@ -3,8 +3,10 @@ import { UserFeatureUsage } from '../../models/UserFeatureUsage.mjs' import { TooManyRequestsError } from '../../Features/Errors/Errors.js' import AnalyticsManager from '../../Features/Analytics/AnalyticsManager.mjs' import UserAuditLogHandler from '../../Features/User/UserAuditLogHandler.mjs' + /** @typedef {{usage?: number | null, periodStart?: Date | null}} FeatureUsage */ /** @typedef {{remainingTokens?: number | null, periodStart?: Date | null}} RemainingTokens */ +/** @typedef {import('express').Response} Response */ const PERIOD = 24 // hours const PERIOD_IN_MILLISECONDS = PERIOD * 60 * 60 * 1000 @@ -60,7 +62,7 @@ export default class TokenUsageRateLimiter { /** * @param {any} userId - * @param {any} res + * @param {Response} res * @param {any} amount * @param {{ auditLogTool?: string }} [options] - if `auditLogTool` is set, an `ai-quota-breach` audit log entry is written with `{ tool }` in the info payload when this recording crosses or sits past the allowance */ diff --git a/services/web/types/backend/express/request.d.ts b/services/web/types/backend/express/request.d.ts index 69d23d581a..bfac639aea 100644 --- a/services/web/types/backend/express/request.d.ts +++ b/services/web/types/backend/express/request.d.ts @@ -1,6 +1,7 @@ import 'express' import OAuth2Server from '@node-oauth/oauth2-server' import type SessionData from 'express-session' +import type { Capability } from '../../capabilities' // Request-scoped logger attached by @overleaf/metrics http.monitor() middleware. // See libraries/metrics/http.js RequestLogger class. @@ -20,11 +21,17 @@ declare module 'express' { oauth_user?: OAuth2Server.User logger: RequestLogger i18n: { + language: string translate( key: string, vars?: Record, components?: any ): string } + // Set by PermissionMiddleware + managedBy?: any + isManagedGroupAdmin?: boolean + capabilitySet?: Set + assertPermission(capability: string): void } } diff --git a/services/web/types/backend/express/response.d.ts b/services/web/types/backend/express/response.d.ts new file mode 100644 index 0000000000..d8b2a4a3be --- /dev/null +++ b/services/web/types/backend/express/response.d.ts @@ -0,0 +1,13 @@ +import 'express' + +declare module 'express' { + // eslint-disable-next-line no-unused-vars + interface Response { + // Set per-request by the addSetContentDisposition middleware in + // app/src/infrastructure/ExpressLocals.mjs (web/private-api/public-api routers only). + setContentDisposition( + type: 'attachment' | 'inline', + opts: { filename: string } + ): void + } +}