replace staff access with role based sytem (#30004)

- remove references to staff access

GitOrigin-RevId: 5d7df3ae8bc78aa02b65ec0dac0a323520c3df15
This commit is contained in:
Anna Claire Fields
2026-01-16 15:21:48 +01:00
committed by Copybot
parent 37b8bb15e6
commit ff8f77d85c
17 changed files with 76 additions and 386 deletions

View File

@@ -62,20 +62,6 @@ function checkCredentials(userDetailsMap, user, password) {
return isValid
}
function reduceStaffAccess(staffAccess) {
const reducedStaffAccess = {}
for (const field in staffAccess) {
if (staffAccess[field]) {
reducedStaffAccess[field] = true
}
}
return reducedStaffAccess
}
function userHasStaffAccess(user) {
return user.staffAccess && Object.values(user.staffAccess).includes(true)
}
// TODO: Finish making these methods async
const AuthenticationController = {
serializeUser(user, callback) {
@@ -100,9 +86,7 @@ const AuthenticationController = {
}
if (user.isAdmin) {
lightUser.isAdmin = true
}
if (userHasStaffAccess(user)) {
lightUser.staffAccess = reduceStaffAccess(user.staffAccess)
lightUser.adminRoles = user.adminRoles
}
callback(null, lightUser)

View File

@@ -1,16 +0,0 @@
import { UserSchema } from '../../models/User.mjs'
export default {
hasAnyStaffAccess,
}
function hasAnyStaffAccess(user) {
if (!user.staffAccess) {
return false
}
for (const key of Object.keys(UserSchema.obj.staffAccess)) {
if (user.staffAccess[key]) return true
}
return false
}

View File

@@ -172,39 +172,6 @@ function formatGroupPlansDataForDash() {
}
}
/**
* Trim the staffAccess object to only include allowed fields
* @param {Object} user - The user object with mongoose object fields
* @returns {Object} - User object with trimmed staffAccess
*/
function _trimStaffAccess(user) {
if (!user || !user.staffAccess) return user
const allowedFields = [
'publisherMetrics',
'publisherManagement',
'institutionMetrics',
'institutionManagement',
'groupMetrics',
'groupManagement',
'adminMetrics',
'splitTestMetrics',
'splitTestManagement',
]
const trimmedStaffAccess = allowedFields.reduce((acc, key) => {
if (key in user.staffAccess) {
acc[key] = user.staffAccess[key]
}
return acc
}, {})
return {
...user,
staffAccess: trimmedStaffAccess,
}
}
async function userSubscriptionPage(req, res) {
const user = SessionManager.getSessionUser(req.session)
await SplitTestHandler.promises.getAssignment(req, res, 'pause-subscription')
@@ -337,7 +304,7 @@ async function userSubscriptionPage(req, res) {
title: 'your_subscriptions',
plans: plansData?.plans,
planCodesChangingAtTermEnd: plansData?.planCodesChangingAtTermEnd,
user: _trimStaffAccess(user),
user,
hasSubscription,
fromPlansPage,
redirectedPaymentErrorCode,

View File

@@ -1,34 +1,17 @@
import AdminAuthorizationHelper from '../Helpers/AdminAuthorizationHelper.mjs'
import SessionManager from '../Authentication/SessionManager.mjs'
import Settings from '@overleaf/settings'
const { hasAdminCapability, hasAdminAccess } = AdminAuthorizationHelper
const UserMembershipAuthorization = {
hasStaffAccess(requiredStaffAccess) {
return req => {
if (!req.user) {
return false
}
return (
requiredStaffAccess &&
req.user.staffAccess &&
req.user.staffAccess[requiredStaffAccess]
)
}
},
hasAdminCapability: AdminAuthorizationHelper.hasAdminCapability,
hasAdminCapability,
hasAnyAdminRole(req) {
return (
Settings.adminRolesEnabled &&
hasAdminAccess(SessionManager.getSessionUser(req.session))
hasAdminAccess(req) {
return AdminAuthorizationHelper.hasAdminAccess(
SessionManager.getSessionUser(req.session)
)
},
hasModifyGroupMemberCapability(req, res) {
return hasAdminCapability(
return AdminAuthorizationHelper.hasAdminCapability(
req.entity.managedUsersEnabled
? 'modify-managed-group-member'
: 'modify-group-member',

View File

@@ -23,7 +23,7 @@ const UserMembershipMiddleware = {
requireEntity(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('groupMetrics'),
UserMembershipAuthorization.hasAdminAccess,
]),
],
@@ -36,7 +36,7 @@ const UserMembershipMiddleware = {
requireEntity(),
],
requireEntityAccess: ({ entityName, staffAccess, adminCapability }) => [
requireEntityAccess: ({ entityName, adminCapability }) => [
AuthenticationController.requireLogin(),
fetchEntityConfig(entityName),
fetchEntity(),
@@ -44,7 +44,6 @@ const UserMembershipMiddleware = {
allowAccessIfAny(
[
UserMembershipAuthorization.hasEntityAccess(),
staffAccess && UserMembershipAuthorization.hasStaffAccess(staffAccess),
adminCapability &&
UserMembershipAuthorization.hasAdminCapability(adminCapability),
].filter(Boolean)
@@ -58,9 +57,7 @@ const UserMembershipMiddleware = {
requireEntity(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('groupManagement'),
// allow to all admins when `adminRolesEnabled` is true
UserMembershipAuthorization.hasAnyAdminRole,
UserMembershipAuthorization.hasAdminCapability('modify-group'),
]),
],
@@ -72,7 +69,6 @@ const UserMembershipMiddleware = {
useAdminCapabilities,
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('groupManagement'),
UserMembershipAuthorization.hasModifyGroupMemberCapability,
]),
],
@@ -84,7 +80,7 @@ const UserMembershipMiddleware = {
requireEntity(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('groupMetrics'),
UserMembershipAuthorization.hasAdminAccess,
]),
],
@@ -92,10 +88,10 @@ const UserMembershipMiddleware = {
AuthenticationController.requireLogin(),
fetchEntityConfig('institution'),
fetchEntity(),
requireEntityOrCreate('institutionManagement'),
requireEntityOrCreate(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('institutionMetrics'),
UserMembershipAuthorization.hasAdminAccess,
]),
],
@@ -103,31 +99,40 @@ const UserMembershipMiddleware = {
AuthenticationController.requireLogin(),
fetchEntityConfig('institution'),
fetchEntity(),
requireEntityOrCreate('institutionManagement'),
requireEntityOrCreate(),
useAdminCapabilities,
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('institutionManagement'),
UserMembershipAuthorization.hasAdminCapability(
'modify-institution-manager'
),
]),
],
requireInstitutionManagementStaffAccess: [
requireInstitutionAIAccess: [
AuthenticationController.requireLogin(),
allowAccessIfAny([
UserMembershipAuthorization.hasStaffAccess('institutionManagement'),
]),
fetchEntityConfig('institution'),
fetchEntity(),
requireEntityOrCreate('institutionManagement'),
requireEntityOrCreate(),
allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]),
],
requireInstitutionStaffHubAccess: [
AuthenticationController.requireLogin(),
fetchEntityConfig('institution'),
fetchEntity(),
requireEntityOrCreate(),
allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]),
],
requirePublisherMetricsAccess: [
AuthenticationController.requireLogin(),
fetchEntityConfig('publisher'),
fetchEntity(),
requireEntityOrCreate('publisherManagement'),
requireEntityOrCreate(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('publisherMetrics'),
UserMembershipAuthorization.hasAdminAccess,
]),
],
@@ -135,10 +140,13 @@ const UserMembershipMiddleware = {
AuthenticationController.requireLogin(),
fetchEntityConfig('publisher'),
fetchEntity(),
requireEntityOrCreate('publisherManagement'),
requireEntityOrCreate(),
useAdminCapabilities,
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('publisherManagement'),
UserMembershipAuthorization.hasAdminCapability(
'modify-publisher-manager'
),
]),
],
@@ -146,18 +154,16 @@ const UserMembershipMiddleware = {
AuthenticationController.requireLogin(),
fetchEntityConfig('publisher'),
fetchEntity(),
requireEntityOrCreate('publisherManagement'),
requireEntityOrCreate(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('publisherMetrics'),
UserMembershipAuthorization.hasAdminAccess,
]),
],
requireAdminMetricsAccess: [
AuthenticationController.requireLogin(),
allowAccessIfAny([
UserMembershipAuthorization.hasStaffAccess('adminMetrics'),
]),
allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]),
],
requireTemplateMetricsAccess: [
@@ -168,23 +174,19 @@ const UserMembershipMiddleware = {
fetchPublisherFromTemplate(),
allowAccessIfAny([
UserMembershipAuthorization.hasEntityAccess(),
UserMembershipAuthorization.hasStaffAccess('publisherMetrics'),
UserMembershipAuthorization.hasAdminAccess,
]),
],
requirePublisherCreationAccess: [
AuthenticationController.requireLogin(),
allowAccessIfAny([
UserMembershipAuthorization.hasStaffAccess('publisherManagement'),
]),
allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]),
fetchEntityConfig('publisher'),
],
requireInstitutionCreationAccess: [
AuthenticationController.requireLogin(),
allowAccessIfAny([
UserMembershipAuthorization.hasStaffAccess('institutionManagement'),
]),
allowAccessIfAny([UserMembershipAuthorization.hasAdminAccess]),
fetchEntityConfig('institution'),
],
@@ -192,8 +194,6 @@ const UserMembershipMiddleware = {
AuthenticationController.requireLogin(),
useAdminCapabilities,
allowAccessIfAny([
UserMembershipAuthorization.hasStaffAccess('splitTestMetrics'),
UserMembershipAuthorization.hasStaffAccess('splitTestManagement'),
UserMembershipAuthorization.hasAdminCapability('view-split-test'),
]),
],
@@ -202,7 +202,6 @@ const UserMembershipMiddleware = {
AuthenticationController.requireLogin(),
useAdminCapabilities,
allowAccessIfAny([
UserMembershipAuthorization.hasStaffAccess('splitTestManagement'),
UserMembershipAuthorization.hasAdminCapability('modify-split-test'),
]),
],
@@ -325,13 +324,13 @@ function requireEntity() {
// ensure an entity was found or redirect to entity creation page if the user
// has permissions to create the entity, or fail with 404
function requireEntityOrCreate(creationStaffAccess) {
function requireEntityOrCreate() {
return (req, res, next) => {
if (req.entity) {
return next()
}
if (UserMembershipAuthorization.hasStaffAccess(creationStaffAccess)(req)) {
if (UserMembershipAuthorization.hasAdminAccess(req)) {
res.redirect(`/entities/${req.entityName}/create/${req.params.id}`)
return
}

View File

@@ -58,7 +58,6 @@ export default {
'/manage/groups/:id/managers',
UserMembershipMiddleware.requireEntityAccess({
entityName: 'groupManagers',
staffAccess: 'groupManagement',
adminCapability: 'view-group-manager',
}),
UserMembershipController.manageGroupManagers
@@ -67,7 +66,6 @@ export default {
'/manage/groups/:id/managers',
UserMembershipMiddleware.requireEntityAccess({
entityName: 'groupManagers',
staffAccess: 'groupManagement',
adminCapability: 'modify-group-manager',
}),
UserMembershipController.add
@@ -76,7 +74,6 @@ export default {
'/manage/groups/:id/managers/:userId',
UserMembershipMiddleware.requireEntityAccess({
entityName: 'groupManagers',
staffAccess: 'groupManagement',
adminCapability: 'modify-group-manager',
}),
UserMembershipController.remove

View File

@@ -59,17 +59,6 @@ export const UserSchema = new Schema(
},
isAdmin: { type: Boolean, default: false },
adminRoles: { type: Array },
staffAccess: {
publisherMetrics: { type: Boolean, default: false },
publisherManagement: { type: Boolean, default: false },
institutionMetrics: { type: Boolean, default: false },
institutionManagement: { type: Boolean, default: false },
groupMetrics: { type: Boolean, default: false },
groupManagement: { type: Boolean, default: false },
adminMetrics: { type: Boolean, default: false },
splitTestMetrics: { type: Boolean, default: false },
splitTestManagement: { type: Boolean, default: false },
},
signUpDate: {
type: Date,
default() {

View File

@@ -11,9 +11,8 @@ block append meta
- const canDisplayAdminMenu = hasAdminAccess()
- const canDisplayAdminRedirect = canRedirectToAdminDomain()
- const sessionUser = getSessionUser()
- const staffAccess = sessionUser?.staffAccess
- const canDisplayProjectUrlLookup = settings.adminPrivilegeAvailable && canDisplayAdminMenu && hasAdminCapability('view-project-setting', false)
- const canDisplaySplitTestMenu = hasFeature('saas') && ((canDisplayAdminMenu && hasAdminCapability('view-split-test')) || staffAccess?.splitTestMetrics || staffAccess?.splitTestManagement)
- const canDisplaySplitTestMenu = hasFeature('saas') && canDisplayAdminMenu && hasAdminCapability('view-split-test')
- const canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu && hasAdminCapability('manage-survey', false)
- const canDisplayScriptLogMenu = hasFeature('saas') && hasAdminCapability('view-script-log', false) && canDisplayAdminMenu
- const enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on')

View File

@@ -31,7 +31,7 @@ nav.navbar.navbar-default.navbar-main.navbar-expand-lg(
- var canDisplayAdminMenu = hasAdminAccess()
- var canDisplayAdminRedirect = canRedirectToAdminDomain()
- var canDisplayProjectUrlLookup = settings.adminPrivilegeAvailable && canDisplayAdminMenu && hasAdminCapability('view-project-setting', false)
- var canDisplaySplitTestMenu = hasFeature('saas') && ((canDisplayAdminMenu && hasAdminCapability('view-split-test')) || (getSessionUser() && getSessionUser().staffAccess && (getSessionUser().staffAccess.splitTestMetrics || getSessionUser().staffAccess.splitTestManagement)))
- var canDisplaySplitTestMenu = hasFeature('saas') && canDisplayAdminMenu && hasAdminCapability('view-split-test')
- var canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu && hasAdminCapability('manage-survey', false)
- var canDisplayScriptLogMenu = hasFeature('saas') && hasAdminCapability('view-script-log', false) && canDisplayAdminMenu

View File

@@ -4,7 +4,7 @@ block entrypointVar
- entrypoint = 'pages/user/subscription/group-management/group-managers'
block append meta
- var hasWriteAccess = entityAccess || (hasAdminAccess() && hasAdminCapability('modify-group-manager')) || (getSessionUser().staffAccess && getSessionUser().staffAccess.groupManagement)
- var hasWriteAccess = entityAccess || (hasAdminAccess() && hasAdminCapability('modify-group-manager'))
meta(name='ol-user' data-type='json' content=user)
meta(name='ol-users' data-type='json' content=users)
meta(name='ol-groupId' data-type='string' content=groupId)

View File

@@ -4,7 +4,7 @@ block entrypointVar
- entrypoint = 'pages/user/subscription/group-management/group-members'
block append meta
- var hasWriteAccess = entityAccess || (hasAdminAccess() && hasAdminCapability(managedUsersActive ? 'modify-managed-group-member' : 'modify-group-member')) || (getSessionUser().staffAccess && getSessionUser().staffAccess.groupManagement)
- var hasWriteAccess = entityAccess || (hasAdminAccess() && hasAdminCapability(managedUsersActive ? 'modify-managed-group-member' : 'modify-group-member'))
meta(name='ol-user' data-type='json' content=user)
meta(name='ol-users' data-type='json' content=users)
meta(name='ol-groupId' data-type='string' content=groupId)

View File

@@ -1,94 +0,0 @@
import {
db,
READ_PREFERENCE_SECONDARY,
} from '../app/src/infrastructure/mongodb.mjs'
import UserSessionsManager from '../app/src/Features/User/UserSessionsManager.mjs'
import { scriptRunner } from './lib/ScriptRunner.mjs'
const COMMIT = process.argv.includes('--commit')
const KEEP_SESSIONS = process.argv.includes('--keep-sessions')
const FULL_STAFF_ACCESS = {
publisherMetrics: true,
publisherManagement: true,
institutionMetrics: true,
institutionManagement: true,
groupMetrics: true,
groupManagement: true,
adminMetrics: true,
splitTestMetrics: true,
splitTestManagement: true,
}
function doesNotHaveFullStaffAccess(user) {
if (!user.staffAccess) {
return true
}
for (const field of Object.keys(FULL_STAFF_ACCESS)) {
if (!user.staffAccess[field]) {
return true
}
}
return false
}
function formatUser(user) {
user = Object.assign({}, user, user.staffAccess)
delete user.staffAccess
return user
}
async function main() {
const adminUsers = await db.users
.find(
{ isAdmin: true },
{
projection: {
_id: 1,
email: 1,
staffAccess: 1,
},
readPreference: READ_PREFERENCE_SECONDARY,
}
)
.toArray()
console.log('All Admin users:')
console.table(adminUsers.map(formatUser))
const incompleteUsers = adminUsers.filter(doesNotHaveFullStaffAccess)
if (incompleteUsers.length === 0) {
console.warn('All Admin users have full staff access.')
return
}
console.log()
console.log('Incomplete staff access:')
console.table(incompleteUsers.map(formatUser))
if (COMMIT) {
for (const user of incompleteUsers) {
console.error(
`Granting ${user.email} (${user._id.toString()}) full staff access`
)
await db.users.updateOne(
{ _id: user._id, isAdmin: true },
{ $set: { staffAccess: FULL_STAFF_ACCESS } }
)
if (!KEEP_SESSIONS) {
await UserSessionsManager.promises.removeSessionsFromRedis(user)
}
}
} else {
console.warn('Use --commit to grant missing staff access.')
}
}
try {
await scriptRunner(main)
console.error('Done.')
process.exit(0)
} catch (error) {
console.error({ error })
process.exit(1)
}

View File

@@ -6,6 +6,7 @@ import Subscription from './helpers/Subscription.mjs'
import Publisher from './helpers/Publisher.mjs'
import sinon from 'sinon'
import RecurlyClient from '../../../app/src/Features/Subscription/RecurlyClient.mjs'
import Settings from '@overleaf/settings'
import Features from '../../../app/src/infrastructure/Features.mjs'
describe('UserMembershipAuthorization', function () {
@@ -16,6 +17,16 @@ describe('UserMembershipAuthorization', function () {
this.user = new User()
sinon.stub(RecurlyClient.promises, 'getSubscription').resolves({})
this.adminRolesEnabledStub = sinon.stub(Settings, 'adminRolesEnabled')
this.adminRolesEnabledStub.value(true)
this.adminPrivilegeAvailableStub = sinon.stub(
Settings,
'adminPrivilegeAvailable'
)
this.adminPrivilegeAvailableStub.value(true)
async.series([this.user.ensureUserExists.bind(this.user)], done)
})
@@ -25,6 +36,8 @@ describe('UserMembershipAuthorization', function () {
}
RecurlyClient.promises.getSubscription.restore()
this.adminRolesEnabledStub.restore()
this.adminPrivilegeAvailableStub.restore()
})
describe('group', function () {
@@ -92,13 +105,14 @@ describe('UserMembershipAuthorization', function () {
})
describe('creation', function () {
it('should allow staff only', function (done) {
it('should allow admin only', function (done) {
const url = `/entities/institution/create/foo`
async.series(
[
this.user.login.bind(this.user),
expectAccess(this.user, url, 403),
cb => this.user.ensureStaffAccess('institutionManagement', cb),
cb => this.user.ensureAdmin(cb),
cb => this.user.ensureAdminRole('engineering', cb),
this.user.login.bind(this.user),
expectAccess(this.user, url, 200),
],
@@ -135,13 +149,14 @@ describe('UserMembershipAuthorization', function () {
})
describe('creation', function () {
it('should redirect staff only', function (done) {
it('should redirect admin only', function (done) {
const url = `/manage/publishers/foo/managers`
async.series(
[
this.user.login.bind(this.user),
expectAccess(this.user, url, 404),
cb => this.user.ensureStaffAccess('publisherManagement', cb),
cb => this.user.ensureAdmin(cb),
cb => this.user.ensureAdminRole('engineering', cb),
this.user.login.bind(this.user),
expectAccess(this.user, url, 302, /\/create/),
],
@@ -149,12 +164,13 @@ describe('UserMembershipAuthorization', function () {
)
})
it('should allow staff only', function (done) {
it('should allow admin only', function (done) {
const url = `/entities/publisher/create/foo`
async.series(
[
expectAccess(this.user, url, 403),
cb => this.user.ensureStaffAccess('publisherManagement', cb),
cb => this.user.ensureAdmin(cb),
cb => this.user.ensureAdminRole('engineering', cb),
this.user.login.bind(this.user),
expectAccess(this.user, url, 200),
],

View File

@@ -580,13 +580,7 @@ class User {
}
ensureAdminRole(role, callback) {
this.mongoUpdate({ $addToSet: { adminRoles: 'engineering' } }, callback)
}
ensureStaffAccess(flag, callback) {
const update = { $set: {} }
update.$set[`staffAccess.${flag}`] = true
this.mongoUpdate(update, callback)
this.mongoUpdate({ $addToSet: { adminRoles: role } }, callback)
}
upgradeSomeFeatures(callback) {

View File

@@ -35,34 +35,6 @@ describe('AuthenticationController', function () {
referal_id: 1234,
isAdmin: false,
}
ctx.staffUser = {
...ctx.user,
staffAccess: {
publisherMetrics: true,
publisherManagement: false,
institutionMetrics: true,
institutionManagement: false,
groupMetrics: true,
groupManagement: false,
adminMetrics: true,
splitTestMetrics: false,
splitTestManagement: true,
},
}
ctx.noStaffAccessUser = {
...ctx.user,
staffAccess: {
publisherMetrics: false,
publisherManagement: false,
institutionMetrics: false,
institutionManagement: false,
groupMetrics: false,
groupManagement: false,
adminMetrics: false,
splitTestMetrics: false,
splitTestManagement: false,
},
}
ctx.password = 'banana'
ctx.req = new MockRequest(vi)
ctx.res = new MockResponse(vi)
@@ -325,41 +297,6 @@ describe('AuthenticationController', function () {
expect(ctx.callback).to.have.been.calledWith(null, isAdminMatcher)
})
})
describe('when staffAccess fields are provided', function () {
it('only returns the fields set to true', function (ctx) {
const expectedStaffAccess = {
publisherMetrics: true,
institutionMetrics: true,
groupMetrics: true,
adminMetrics: true,
splitTestManagement: true,
}
const staffAccessMatcher = sinon.match(value => {
return (
Object.keys(value.staffAccess).length ===
Object.keys(expectedStaffAccess).length
)
})
ctx.AuthenticationController.serializeUser(ctx.staffUser, ctx.callback)
expect(ctx.callback).to.have.been.calledWith(null, staffAccessMatcher)
})
})
describe('when all staffAccess fields are false', function () {
it('no staffAccess attribute is set', function (ctx) {
const staffAccessMatcher = sinon.match(value => {
return !('staffAccess' in value)
})
ctx.AuthenticationController.serializeUser(
ctx.noStaffAccessUser,
ctx.callback
)
expect(ctx.callback).to.have.been.calledWith(null, staffAccessMatcher)
})
})
})
describe('passportLogin', function () {

View File

@@ -1,67 +0,0 @@
import { vi, expect } from 'vitest'
const modulePath = '../../../../app/src/Features/Helpers/AuthorizationHelper'
describe('AuthorizationHelper', function () {
beforeEach(async function (ctx) {
vi.doMock('../../../../app/src/models/User', () => ({
UserSchema: {
obj: {
staffAccess: {
publisherMetrics: {},
publisherManagement: {},
institutionMetrics: {},
institutionManagement: {},
groupMetrics: {},
groupManagement: {},
adminMetrics: {},
},
},
},
}))
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
default: (ctx.ProjectGetter = { promises: {} }),
}))
vi.doMock(
'../../../../app/src/Features/SplitTests/SplitTestHandler',
() => ({
default: (ctx.SplitTestHandler = {
promises: {},
}),
})
)
ctx.AuthorizationHelper = (await import(modulePath)).default
})
describe('hasAnyStaffAccess', function () {
it('with empty user', function (ctx) {
const user = {}
expect(ctx.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
})
it('with no access user', function (ctx) {
const user = { isAdmin: false, staffAccess: { adminMetrics: false } }
expect(ctx.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
})
it('with admin user', function (ctx) {
const user = { isAdmin: true }
expect(ctx.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
})
it('with staff user', function (ctx) {
const user = { staffAccess: { adminMetrics: true, somethingElse: false } }
expect(ctx.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.true
})
it('with non-staff user with extra attributes', function (ctx) {
// make sure that staffAccess attributes not declared on the model don't
// give user access
const user = { staffAccess: { adminMetrics: false, somethingElse: true } }
expect(ctx.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
})
})
})

View File

@@ -11,25 +11,27 @@ export type AdminCapability =
| 'modify-group-manager'
| 'modify-group-member'
| 'modify-group-setting'
| 'modify-institution-manager'
| 'modify-managed-group'
| 'modify-managed-group-member'
| 'modify-user-account-status'
| 'modify-project-content'
| 'modify-project-setting'
| 'modify-publisher-manager'
| 'manage-survey'
| 'modify-split-test'
| 'modify-user-account-status'
| 'modify-user-email'
| 'modify-user-name'
| 'update-stripe-customer-segment'
| 'view-audit-log'
| 'view-group-manager'
| 'view-project-content'
| 'view-project-setting'
| 'view-session'
| 'view-script-log'
| 'view-session'
| 'view-split-test'
| 'view-user-additional-info'
| 'create-stripe-account'
| 'update-stripe-customer-segment'
export type AdminRole =
| 'engagement'