mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2025-07-29 05:00:06 +02:00

* Create AdminCapabilities in admin-panel module * Add `adminRolesEnabled` setting * Use `PermissionsController.requirePermission` in admin-panel routes * Update `adminCapabilities` to be an array * Update frontend tests * Rename `defaultAdminCapabilities` to `fullAdminCapabilities` Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com> * Add tests to PermissionsManagerTests.js * Get admin roles and capabilities from the database * Add tests to admin-panel * Fixup PermissionsManagerTests.js without admin-panel module * Revert "Use `PermissionsController.requirePermission` in admin-panel routes" This reverts commit ccbf3e3e3bca9239b786c662cba2ac6bd2f4117a. * Revert "Fixup PermissionsManagerTests.js without admin-panel module" This reverts commit 6d7ad207bb17c5ca4c12c489d4636a02c608926d. * Revert "Add tests to PermissionsManagerTests.js" This reverts commit 8f9cc911750911e1c4b74b631d8c8a1b1ca86630. * Fix tests after the reverts * Replace capabilities to more sensible examples ('modify-user-email' and 'view-project') * Set `adminRolesEnabled: false` for now * Return `[]` capabilities for non-admins * Misc: types, test description, settings ordering * Small refactor of AdminPermissions.mjs: Reuse code with `getMissingCapabilities` Throw when `requiredCapabilities` is empty * Update tests after update * Rename `checkAdminPermissions` to `hasAdminPermissions` * Change role permissions to array instead of object * Remove admin capabilities when `!Settings.adminPrivilegeAvailable` * Return `[]` if there is no user id * Throw if `user?._id` is missing * Update services/web/modules/admin-panel/app/src/AdminPermissions.mjs Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com> * Adjust to ForbiddenError constructor syntax * Give empty capabilities for unknown role, update tests --------- Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com> GitOrigin-RevId: 1eec4f6a45e1cc3ae76a3a4603cec1ceba1c2322
341 lines
12 KiB
TypeScript
341 lines
12 KiB
TypeScript
import { User, Features } from '../../../types/user'
|
|
import { User as MinimalUser } from '../../../types/admin/user'
|
|
import { User as ManagedUser } from '../../../types/group-management/user'
|
|
import { UserSettings } from '../../../types/user-settings'
|
|
import { OAuthProviders } from '../../../types/oauth-providers'
|
|
import { ExposedSettings } from '../../../types/exposed-settings'
|
|
import {
|
|
type AllowedImageName,
|
|
OverallThemeMeta,
|
|
type SpellCheckLanguage,
|
|
} from '../../../types/project-settings'
|
|
import { CurrencyCode } from '../../../types/subscription/currency'
|
|
import { PricingFormState } from '../../../types/subscription/payment-context-value'
|
|
import { Plan } from '../../../types/subscription/plan'
|
|
import { Affiliation } from '../../../types/affiliation'
|
|
import type { PortalTemplate } from '../../../types/portal-template'
|
|
import { UserEmailData } from '../../../types/user-email'
|
|
import {
|
|
GroupsAndEnterpriseBannerVariant,
|
|
Institution as InstitutionType,
|
|
Notification as NotificationType,
|
|
PendingGroupSubscriptionEnrollment,
|
|
USGovBannerVariant,
|
|
} from '../../../types/project/dashboard/notification'
|
|
import { Survey } from '../../../types/project/dashboard/survey'
|
|
import { GetProjectsResponseBody } from '../../../types/project/dashboard/api'
|
|
import { Tag } from '../../../app/src/Features/Tags/types'
|
|
import { Institution } from '../../../types/institution'
|
|
import {
|
|
GroupPolicy,
|
|
ManagedGroupSubscription,
|
|
MemberGroupSubscription,
|
|
} from '../../../types/subscription/dashboard/subscription'
|
|
import { SplitTestInfo } from '../../../types/split-test'
|
|
import { ValidationStatus } from '../../../types/group-management/validation'
|
|
import { ManagedInstitution } from '../../../types/subscription/dashboard/managed-institution'
|
|
import { OnboardingFormData } from '../../../types/onboarding'
|
|
import { GroupSSOTestResult } from '../../../modules/group-settings/frontend/js/utils/types'
|
|
import {
|
|
AccessToken,
|
|
InstitutionLink,
|
|
SAMLError,
|
|
} from '../../../types/settings-page'
|
|
import { SuggestedLanguage } from '../../../types/system-message'
|
|
import type { TeamInvite } from '../../../types/team-invite'
|
|
import { GroupPlans } from '../../../types/subscription/dashboard/group-plans'
|
|
import {
|
|
GroupSSOLinkingStatus,
|
|
SSOConfig,
|
|
} from '../../../types/subscription/sso'
|
|
import { PasswordStrengthOptions } from '../../../types/password-strength-options'
|
|
import { Subscription as ProjectDashboardSubscription } from '../../../types/project/dashboard/subscription'
|
|
import { ThirdPartyIds } from '../../../types/third-party-ids'
|
|
import { Publisher } from '../../../types/subscription/dashboard/publisher'
|
|
import { SubscriptionChangePreview } from '../../../types/subscription/subscription-change-preview'
|
|
import { DefaultNavbarMetadata } from '@/features/ui/components/types/default-navbar-metadata'
|
|
import { FooterMetadata } from '@/features/ui/components/types/footer-metadata'
|
|
import type { ScriptLogType } from '../../../modules/admin-panel/frontend/js/features/script-logs/script-log'
|
|
import { ActiveExperiment } from './labs-utils'
|
|
import { Subscription as AdminSubscription } from '../../../types/admin/subscription'
|
|
import { AdminCapability } from '../../../types/admin-capabilities'
|
|
|
|
export interface Meta {
|
|
'ol-ExposedSettings': ExposedSettings
|
|
'ol-addonPrices': Record<
|
|
string,
|
|
{ annual: string; monthly: string; annualDividedByTwelve: string }
|
|
>
|
|
'ol-adminCapabilities': AdminCapability[]
|
|
'ol-adminSubscription': AdminSubscription
|
|
'ol-aiAssistViaWritefullSource': string
|
|
'ol-allInReconfirmNotificationPeriods': UserEmailData[]
|
|
'ol-allowedExperiments': string[]
|
|
'ol-allowedImageNames': AllowedImageName[]
|
|
'ol-anonymous': boolean
|
|
'ol-baseAssetPath': string
|
|
'ol-bootstrapVersion': 3 | 5
|
|
'ol-brandVariation': Record<string, any>
|
|
|
|
// dynamic keys based on permissions
|
|
'ol-canUseAddSeatsFeature': boolean
|
|
'ol-canUseFlexibleLicensing': boolean
|
|
'ol-cannot-add-secondary-email': boolean
|
|
'ol-cannot-change-password': boolean
|
|
'ol-cannot-delete-own-account': boolean
|
|
'ol-cannot-join-subscription': boolean
|
|
'ol-cannot-leave-group-subscription': boolean
|
|
'ol-cannot-link-google-sso': boolean
|
|
'ol-cannot-link-other-third-party-sso': boolean
|
|
'ol-cannot-reactivate-subscription': boolean
|
|
'ol-cannot-use-ai': boolean
|
|
'ol-capabilities': Array<'dropbox' | 'chat' | 'use-ai'>
|
|
'ol-compileSettings': {
|
|
reducedTimeoutWarning: string
|
|
compileTimeout: number
|
|
}
|
|
'ol-compilesUserContentDomain': string
|
|
'ol-countryCode': PricingFormState['country']
|
|
'ol-couponCode': PricingFormState['coupon']
|
|
'ol-createdAt': Date
|
|
'ol-csrfToken': string
|
|
'ol-currentInstitutionsWithLicence': Institution[]
|
|
'ol-currentManagedUserAdminEmail': string
|
|
'ol-currentUrl': string
|
|
'ol-customerIoEnabled': boolean
|
|
'ol-debugPdfDetach': boolean
|
|
'ol-detachRole': 'detached' | 'detacher' | ''
|
|
'ol-dictionariesRoot': 'string'
|
|
'ol-dropbox': { error: boolean; registered: boolean }
|
|
'ol-editorThemes': string[]
|
|
'ol-email': string
|
|
'ol-emailAddressLimit': number
|
|
'ol-error': { name: string } | undefined
|
|
'ol-expired': boolean
|
|
'ol-features': Features
|
|
'ol-footer': FooterMetadata
|
|
'ol-fromPlansPage': boolean
|
|
'ol-galleryTagName': string
|
|
'ol-gitBridgeEnabled': boolean
|
|
'ol-gitBridgePublicBaseUrl': string
|
|
'ol-github': { enabled: boolean; error: boolean }
|
|
'ol-groupAuditLogs': []
|
|
'ol-groupId': string
|
|
'ol-groupName': string
|
|
'ol-groupPlans': GroupPlans
|
|
'ol-groupPolicy': GroupPolicy
|
|
'ol-groupSSOActive': boolean
|
|
'ol-groupSSOConfig'?: SSOConfig
|
|
'ol-groupSSOTestResult': GroupSSOTestResult
|
|
'ol-groupSettingsAdvertisedFor': string[]
|
|
'ol-groupSettingsEnabledFor': string[]
|
|
'ol-groupSize': number
|
|
'ol-groupSsoSetupSuccess': boolean
|
|
'ol-groupSubscriptionsPendingEnrollment': PendingGroupSubscriptionEnrollment[]
|
|
'ol-groupsAndEnterpriseBannerVariant': GroupsAndEnterpriseBannerVariant
|
|
'ol-hasAiAssistViaWritefull': boolean
|
|
'ol-hasGroupSSOFeature': boolean
|
|
'ol-hasIndividualPaidSubscription': boolean
|
|
'ol-hasManagedUsersFeature': boolean
|
|
'ol-hasPassword': boolean
|
|
'ol-hasSplitTestWriteAccess': boolean
|
|
'ol-hasSubscription': boolean
|
|
'ol-hasTrackChangesFeature': boolean
|
|
'ol-hideLinkingWidgets': boolean // CI only
|
|
'ol-i18n': { currentLangCode: string }
|
|
'ol-inactiveTutorials': string[]
|
|
'ol-institutionEmailNonCanonical': string | undefined
|
|
'ol-institutionLinked': InstitutionLink | undefined
|
|
'ol-inviteToken': string
|
|
'ol-inviterName': string
|
|
'ol-isCollectionMethodManual': boolean
|
|
'ol-isExternalAuthenticationSystemUsed': boolean
|
|
'ol-isManagedAccount': boolean
|
|
'ol-isProfessional': boolean
|
|
'ol-isRegisteredViaGoogle': boolean
|
|
'ol-isRestrictedTokenMember': boolean
|
|
'ol-isSaas': boolean
|
|
'ol-isUserGroupManager': boolean
|
|
'ol-itm_campaign': string
|
|
'ol-itm_content': string
|
|
'ol-itm_referrer': string
|
|
'ol-labs': boolean
|
|
'ol-labsExperiments': ActiveExperiment[] | undefined
|
|
'ol-languages': SpellCheckLanguage[]
|
|
'ol-learnedWords': string[]
|
|
'ol-legacyEditorThemes': string[]
|
|
'ol-licenseQuantity'?: number
|
|
'ol-loadingText': string
|
|
'ol-managedGroupSubscriptions': ManagedGroupSubscription[]
|
|
'ol-managedInstitutions': ManagedInstitution[]
|
|
'ol-managedPublishers': Publisher[]
|
|
'ol-managedUsersActive': boolean
|
|
'ol-managedUsersEnabled': boolean
|
|
'ol-managers': MinimalUser[]
|
|
'ol-mathJaxPath': string
|
|
'ol-maxDocLength': number
|
|
'ol-maxReconnectGracefullyIntervalMs': number
|
|
'ol-memberGroupSubscriptions': MemberGroupSubscription[]
|
|
'ol-memberOfSSOEnabledGroups': GroupSSOLinkingStatus[]
|
|
'ol-members': MinimalUser[]
|
|
'ol-navbar': DefaultNavbarMetadata
|
|
'ol-no-single-dollar': boolean
|
|
'ol-notifications': NotificationType[]
|
|
'ol-notificationsInstitution': InstitutionType[]
|
|
'ol-oauthProviders': OAuthProviders
|
|
'ol-odcData': OnboardingFormData
|
|
'ol-overallThemes': OverallThemeMeta[]
|
|
'ol-pages': number
|
|
'ol-passwordStrengthOptions': PasswordStrengthOptions
|
|
'ol-paywallPlans': { [key: string]: string }
|
|
'ol-personalAccessTokens': AccessToken[] | undefined
|
|
'ol-plan': Plan
|
|
'ol-planCode': string
|
|
'ol-planCodesChangingAtTermEnd': string[] | undefined
|
|
'ol-plans': Plan[]
|
|
'ol-portalTemplates': PortalTemplate[]
|
|
'ol-postCheckoutRedirect': string
|
|
'ol-postUrl': string
|
|
'ol-prefetchedProjectsBlob': GetProjectsResponseBody | undefined
|
|
'ol-preventCompileOnLoad'?: boolean
|
|
'ol-primaryEmail': { email: string; confirmed: boolean }
|
|
'ol-project': any // TODO
|
|
'ol-projectEntityCounts': { files: number; docs: number }
|
|
'ol-projectHistoryBlobsEnabled': boolean
|
|
'ol-projectName': string
|
|
'ol-projectOwnerHasPremiumOnPageLoad': boolean
|
|
'ol-projectSyncSuccessMessage': string
|
|
'ol-projectTags': Tag[]
|
|
'ol-project_id': string
|
|
'ol-purchaseReferrer': string
|
|
'ol-recommendedCurrency': CurrencyCode
|
|
'ol-reconfirmationRemoveEmail': string
|
|
'ol-reconfirmedViaSAML': string
|
|
'ol-recurlyAccount':
|
|
| {
|
|
code: string
|
|
error?: undefined
|
|
}
|
|
| {
|
|
error: boolean
|
|
code?: undefined
|
|
}
|
|
| undefined
|
|
'ol-recurlyApiKey': string
|
|
'ol-recurlySubdomain': string
|
|
'ol-ro-mirror-on-client-no-local-storage': boolean
|
|
'ol-samlError': SAMLError | undefined
|
|
'ol-script-log': ScriptLogType
|
|
'ol-script-logs': ScriptLogType[]
|
|
'ol-settingsGroupSSO': { enabled: boolean } | undefined
|
|
'ol-settingsPlans': Plan[]
|
|
'ol-shouldAllowEditingDetails': boolean
|
|
'ol-shouldLoadHotjar': boolean
|
|
'ol-showAiAssistNotification': boolean
|
|
'ol-showAiErrorAssistant': boolean
|
|
'ol-showBrlGeoBanner': boolean
|
|
'ol-showCouponField': boolean
|
|
'ol-showGroupDiscount': boolean
|
|
'ol-showGroupsAndEnterpriseBanner': boolean
|
|
'ol-showInrGeoBanner': boolean
|
|
'ol-showLATAMBanner': boolean
|
|
'ol-showSupport': boolean
|
|
'ol-showSymbolPalette': boolean
|
|
'ol-showTemplatesServerPro': boolean
|
|
'ol-showUSGovBanner': boolean
|
|
'ol-showUpgradePrompt': boolean
|
|
'ol-skipUrl': string
|
|
'ol-splitTestInfo': { [name: string]: SplitTestInfo }
|
|
'ol-splitTestName': string
|
|
'ol-splitTestVariants': { [name: string]: string }
|
|
'ol-ssoDisabled': boolean
|
|
'ol-ssoErrorMessage': string
|
|
'ol-stripeAccountId': string
|
|
'ol-stripeCustomerId': string
|
|
'ol-subscription': any // TODO: mixed types, split into two fields
|
|
'ol-subscriptionChangePreview': SubscriptionChangePreview
|
|
'ol-subscriptionId': string
|
|
'ol-suggestedLanguage': SuggestedLanguage | undefined
|
|
'ol-survey': Survey | undefined
|
|
'ol-symbolPaletteAvailable': boolean
|
|
'ol-tags': Tag[]
|
|
'ol-teamInvites': TeamInvite[]
|
|
'ol-thirdPartyIds': ThirdPartyIds
|
|
'ol-totalLicenses': number
|
|
'ol-translationIoNotLoaded': string
|
|
'ol-translationLoadErrorMessage': string
|
|
'ol-translationMaintenance': string
|
|
'ol-translationUnableToJoin': string
|
|
'ol-usGovBannerVariant': USGovBannerVariant
|
|
'ol-useShareJsHash': boolean
|
|
'ol-user': User
|
|
'ol-userAffiliations': Affiliation[]
|
|
'ol-userCanExtendTrial': boolean
|
|
'ol-userCanNotStartRequestedTrial': boolean
|
|
'ol-userEmails': UserEmailData[]
|
|
'ol-userSettings': UserSettings
|
|
'ol-user_id': string | undefined
|
|
'ol-users': ManagedUser[]
|
|
'ol-usersBestSubscription': ProjectDashboardSubscription | undefined
|
|
'ol-usersEmail': string | undefined
|
|
'ol-usersSubscription': { personal: boolean; group: boolean }
|
|
'ol-validationStatus': ValidationStatus
|
|
'ol-wikiEnabled': boolean
|
|
'ol-writefullCssUrl': string
|
|
'ol-writefullEnabled': boolean
|
|
'ol-writefullJsUrl': string
|
|
'ol-wsUrl': string
|
|
}
|
|
|
|
type DeepPartial<T> =
|
|
T extends Record<string, any> ? { [P in keyof T]?: DeepPartial<T[P]> } : T
|
|
|
|
export type PartialMeta = DeepPartial<Meta>
|
|
|
|
export type MetaAttributesCache<
|
|
K extends keyof PartialMeta = keyof PartialMeta,
|
|
> = Map<K, PartialMeta[K]>
|
|
|
|
export type MetaTag = {
|
|
[K in keyof Meta]: {
|
|
name: K
|
|
value: Meta[K]
|
|
}
|
|
}[keyof Meta]
|
|
|
|
// cache for parsed values
|
|
window.metaAttributesCache = window.metaAttributesCache || new Map()
|
|
|
|
export default function getMeta<T extends keyof Meta>(name: T): Meta[T] {
|
|
if (window.metaAttributesCache.has(name)) {
|
|
return window.metaAttributesCache.get(name)
|
|
}
|
|
const element = document.head.querySelector(
|
|
`meta[name="${name}"]`
|
|
) as HTMLMetaElement
|
|
if (!element) {
|
|
return undefined!
|
|
}
|
|
const plainTextValue = element.content
|
|
let value
|
|
switch (element.dataset.type) {
|
|
case 'boolean':
|
|
// in pug: content=false -> no content field
|
|
// in pug: content=true -> empty content field
|
|
value = element.hasAttribute('content')
|
|
break
|
|
case 'json':
|
|
case 'number':
|
|
if (!plainTextValue) {
|
|
// JSON.parse('') throws
|
|
value = undefined
|
|
} else {
|
|
value = JSON.parse(plainTextValue)
|
|
}
|
|
break
|
|
default:
|
|
value = plainTextValue
|
|
}
|
|
window.metaAttributesCache.set(name, value)
|
|
return value
|
|
}
|