mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-04 22:59:01 +02:00
a7591bcdab
* Add types on existing Capabilities code * Add ts-expect-error comments * Minor code changes to satisfy types * Remove ts-check because of unrelated errors * Remove some ts-expect-error comments * Revert "Remove some ts-expect-error comments" This reverts commit 76cc0a073710eecf4f8b88f8579405838607f4d5. * Remove the `@ts-check`s for now It looks like typescript is somewhat flaky. We can re-enable this later * Remove the `@ts-expect-error`s * Remove return type GitOrigin-RevId: 57bbd370654592c0662047e72e61f91bf38e0949
119 lines
4.0 KiB
JavaScript
119 lines
4.0 KiB
JavaScript
const { ForbiddenError, UserNotFoundError } = require('../Errors/Errors')
|
|
const {
|
|
getUserCapabilities,
|
|
getUserRestrictions,
|
|
combineGroupPolicies,
|
|
combineAllowedProperties,
|
|
} = require('./PermissionsManager')
|
|
const { assertUserPermissions } = require('./PermissionsManager').promises
|
|
const Modules = require('../../infrastructure/Modules')
|
|
const { expressify } = require('@overleaf/promise-utils')
|
|
const Features = require('../../infrastructure/Features')
|
|
|
|
/**
|
|
* @typedef {(import('express').Request)} Request
|
|
* @typedef {(import('express').Response)} Response
|
|
* @typedef {(import('express').NextFunction)} NextFunction
|
|
* @typedef {import('./PermissionsManager').Capability} Capability
|
|
*/
|
|
|
|
/**
|
|
* Function that returns middleware to add an `assertPermission` function to the request object to check if the user has a specific capability.
|
|
* @returns {() => (req: Request, res: Response, next: NextFunction) => void} The middleware function that adds the `assertPermission` function to the request object.
|
|
*/
|
|
function useCapabilities() {
|
|
const middleware = async function (req, res, next) {
|
|
// attach the user's capabilities to the request object
|
|
req.capabilitySet = new Set()
|
|
// provide a function to assert that a capability is present
|
|
req.assertPermission = capability => {
|
|
if (!req.capabilitySet.has(capability)) {
|
|
throw new ForbiddenError(
|
|
`user does not have permission for ${capability}`
|
|
)
|
|
}
|
|
}
|
|
if (!req.user) {
|
|
return next()
|
|
}
|
|
try {
|
|
/**
|
|
* @type {{groupPolicy: Record<string, boolean>}[][]}
|
|
*/
|
|
const hookResponses = await Modules.promises.hooks.fire(
|
|
'getGroupPolicyForUser',
|
|
req.user
|
|
)
|
|
// merge array of all results from all modules
|
|
const results = hookResponses.flat()
|
|
|
|
if (results.length > 0) {
|
|
// get the combined group policy applying to the user
|
|
const groupPolicies = results.map(result => result.groupPolicy)
|
|
const combinedGroupPolicy = combineGroupPolicies(groupPolicies)
|
|
// attach the new capabilities to the request object
|
|
for (const cap of getUserCapabilities(combinedGroupPolicy)) {
|
|
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)
|
|
for (const [prop, value] of Object.entries(allowedProperties)) {
|
|
req[prop] = value
|
|
}
|
|
}
|
|
next()
|
|
} catch (error) {
|
|
if (error instanceof UserNotFoundError) {
|
|
// the user is logged in but doesn't exist in the database
|
|
// this can happen if the user has just deleted their account
|
|
return next()
|
|
} else {
|
|
next(error)
|
|
}
|
|
}
|
|
}
|
|
return expressify(middleware)
|
|
}
|
|
|
|
/**
|
|
* Function that returns middleware to check if the user has permission to access a resource.
|
|
* @param {...Capability} requiredCapabilities - the capabilities required to access the resource.
|
|
* @returns {(req: Request, res: Response, next: NextFunction) => void} The middleware function that checks if the user has the required capabilities.
|
|
*/
|
|
function requirePermission(...requiredCapabilities) {
|
|
if (
|
|
requiredCapabilities.length === 0 ||
|
|
requiredCapabilities.some(capability => typeof capability !== 'string')
|
|
) {
|
|
throw new Error('invalid required capabilities')
|
|
}
|
|
/**
|
|
* @param {Request} req
|
|
* @param {Response} res
|
|
* @param {NextFunction} next
|
|
*/
|
|
const doRequest = async function (req, res, next) {
|
|
if (!Features.hasFeature('saas')) {
|
|
return next()
|
|
}
|
|
if (!req.user) {
|
|
return next(new Error('no user'))
|
|
}
|
|
try {
|
|
await assertUserPermissions(req.user, requiredCapabilities)
|
|
next()
|
|
} catch (error) {
|
|
next(error)
|
|
}
|
|
}
|
|
return doRequest
|
|
}
|
|
|
|
module.exports = {
|
|
requirePermission,
|
|
useCapabilities,
|
|
}
|