mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2025-08-06 20:00:08 +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
251 lines
8.1 KiB
JavaScript
251 lines
8.1 KiB
JavaScript
const Settings = require('@overleaf/settings')
|
|
const mongoose = require('../infrastructure/Mongoose')
|
|
const TokenGenerator = require('../Features/TokenGenerator/TokenGenerator')
|
|
const { Schema } = mongoose
|
|
const { ObjectId } = Schema
|
|
|
|
// See https://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address/574698#574698
|
|
const MAX_EMAIL_LENGTH = 254
|
|
const MAX_NAME_LENGTH = 255
|
|
|
|
const UserSchema = new Schema(
|
|
{
|
|
email: { type: String, default: '', maxlength: MAX_EMAIL_LENGTH },
|
|
emails: [
|
|
{
|
|
email: { type: String, default: '', maxlength: MAX_EMAIL_LENGTH },
|
|
reversedHostname: { type: String, default: '' },
|
|
createdAt: {
|
|
type: Date,
|
|
default() {
|
|
return new Date()
|
|
},
|
|
},
|
|
confirmedAt: { type: Date },
|
|
samlProviderId: { type: String },
|
|
affiliationUnchecked: { type: Boolean },
|
|
reconfirmedAt: { type: Date },
|
|
},
|
|
],
|
|
first_name: {
|
|
type: String,
|
|
default: '',
|
|
maxlength: MAX_NAME_LENGTH,
|
|
},
|
|
last_name: {
|
|
type: String,
|
|
default: '',
|
|
maxlength: MAX_NAME_LENGTH,
|
|
},
|
|
role: { type: String, default: '' },
|
|
institution: { type: String, default: '' },
|
|
hashedPassword: String,
|
|
enrollment: {
|
|
sso: [
|
|
{
|
|
groupId: {
|
|
type: ObjectId,
|
|
ref: 'Subscription',
|
|
},
|
|
linkedAt: Date,
|
|
primary: { type: Boolean, default: false },
|
|
},
|
|
],
|
|
managedBy: {
|
|
type: ObjectId,
|
|
ref: 'Subscription',
|
|
},
|
|
enrolledAt: { type: Date },
|
|
},
|
|
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() {
|
|
return new Date()
|
|
},
|
|
},
|
|
loginEpoch: { type: Number },
|
|
lastActive: { type: Date },
|
|
lastFailedLogin: { type: Date },
|
|
lastLoggedIn: { type: Date },
|
|
lastLoginIp: { type: String, default: '' },
|
|
lastPrimaryEmailCheck: { type: Date },
|
|
lastTrial: { type: Date },
|
|
loginCount: { type: Number, default: 0 },
|
|
holdingAccount: { type: Boolean, default: false },
|
|
ace: {
|
|
mode: { type: String, default: 'none' },
|
|
theme: { type: String, default: 'textmate' },
|
|
overallTheme: { type: String, default: '' },
|
|
fontSize: { type: Number, default: '12' },
|
|
autoComplete: { type: Boolean, default: true },
|
|
autoPairDelimiters: { type: Boolean, default: true },
|
|
spellCheckLanguage: { type: String, default: 'en' },
|
|
pdfViewer: { type: String, default: 'pdfjs' },
|
|
syntaxValidation: { type: Boolean },
|
|
fontFamily: { type: String },
|
|
lineHeight: { type: String },
|
|
mathPreview: { type: Boolean, default: true },
|
|
breadcrumbs: { type: Boolean, default: true },
|
|
referencesSearchMode: { type: String, default: 'advanced' }, // 'advanced' or 'simple'
|
|
enableNewEditor: { type: Boolean },
|
|
},
|
|
features: {
|
|
collaborators: {
|
|
type: Number,
|
|
default: Settings.defaultFeatures.collaborators,
|
|
},
|
|
versioning: {
|
|
type: Boolean,
|
|
default: Settings.defaultFeatures.versioning,
|
|
},
|
|
dropbox: { type: Boolean, default: Settings.defaultFeatures.dropbox },
|
|
github: { type: Boolean, default: Settings.defaultFeatures.github },
|
|
gitBridge: { type: Boolean, default: Settings.defaultFeatures.gitBridge },
|
|
compileTimeout: {
|
|
type: Number,
|
|
default: Settings.defaultFeatures.compileTimeout,
|
|
},
|
|
compileGroup: {
|
|
type: String,
|
|
default: Settings.defaultFeatures.compileGroup,
|
|
},
|
|
references: {
|
|
type: Boolean,
|
|
default: Settings.defaultFeatures.references,
|
|
},
|
|
trackChanges: {
|
|
type: Boolean,
|
|
default: Settings.defaultFeatures.trackChanges,
|
|
},
|
|
mendeley: { type: Boolean, default: Settings.defaultFeatures.mendeley },
|
|
zotero: { type: Boolean, default: Settings.defaultFeatures.zotero },
|
|
papers: { type: Boolean, default: Settings.defaultFeatures.papers },
|
|
referencesSearch: {
|
|
type: Boolean,
|
|
default: Settings.defaultFeatures.referencesSearch,
|
|
},
|
|
symbolPalette: {
|
|
type: Boolean,
|
|
default: Settings.defaultFeatures.symbolPalette,
|
|
},
|
|
aiErrorAssistant: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
},
|
|
featuresOverrides: [
|
|
{
|
|
createdAt: {
|
|
type: Date,
|
|
default() {
|
|
return new Date()
|
|
},
|
|
},
|
|
expiresAt: { type: Date },
|
|
note: { type: String },
|
|
features: {
|
|
aiErrorAssistant: { type: Boolean },
|
|
collaborators: { type: Number },
|
|
versioning: { type: Boolean },
|
|
dropbox: { type: Boolean },
|
|
github: { type: Boolean },
|
|
gitBridge: { type: Boolean },
|
|
compileTimeout: { type: Number },
|
|
compileGroup: { type: String },
|
|
templates: { type: Boolean },
|
|
trackChanges: { type: Boolean },
|
|
mendeley: { type: Boolean },
|
|
papers: { type: Boolean },
|
|
zotero: { type: Boolean },
|
|
referencesSearch: { type: Boolean },
|
|
symbolPalette: { type: Boolean },
|
|
},
|
|
},
|
|
],
|
|
featuresUpdatedAt: { type: Date },
|
|
featuresEpoch: {
|
|
type: String,
|
|
},
|
|
must_reconfirm: { type: Boolean, default: false },
|
|
referal_id: {
|
|
type: String,
|
|
default() {
|
|
return TokenGenerator.generateReferralId()
|
|
},
|
|
},
|
|
refered_users: [{ type: ObjectId, ref: 'User' }],
|
|
refered_user_count: { type: Number, default: 0 },
|
|
refProviders: {
|
|
// The actual values are managed by third-party-references.
|
|
mendeley: Schema.Types.Mixed,
|
|
zotero: Schema.Types.Mixed,
|
|
papers: Schema.Types.Mixed,
|
|
},
|
|
writefull: {
|
|
enabled: { type: Boolean, default: null },
|
|
autoCreatedAccount: { type: Boolean, default: false },
|
|
isPremium: { type: Boolean, default: false },
|
|
premiumSource: { type: String, default: null },
|
|
},
|
|
aiErrorAssistant: {
|
|
enabled: { type: Boolean, default: true },
|
|
},
|
|
alphaProgram: { type: Boolean, default: false }, // experimental features
|
|
betaProgram: { type: Boolean, default: false },
|
|
labsProgram: { type: Boolean, default: false },
|
|
labsExperiments: { type: Array, default: [] },
|
|
overleaf: {
|
|
id: { type: Number },
|
|
accessToken: { type: String },
|
|
refreshToken: { type: String },
|
|
},
|
|
awareOfV2: { type: Boolean, default: false },
|
|
samlIdentifiers: { type: Array, default: [] },
|
|
thirdPartyIdentifiers: { type: Array, default: [] },
|
|
migratedAt: { type: Date },
|
|
twoFactorAuthentication: {
|
|
createdAt: { type: Date },
|
|
enrolledAt: { type: Date },
|
|
secretEncrypted: { type: String },
|
|
},
|
|
onboardingEmailSentAt: { type: Date },
|
|
splitTests: Schema.Types.Mixed,
|
|
analyticsId: { type: String },
|
|
completedTutorials: Schema.Types.Mixed,
|
|
suspended: { type: Boolean },
|
|
dsMobileApp: {
|
|
subscribed: { type: Boolean },
|
|
},
|
|
},
|
|
{ minimize: false }
|
|
)
|
|
|
|
function formatSplitTestsSchema(next) {
|
|
if (this.splitTests) {
|
|
for (const splitTestKey of Object.keys(this.splitTests)) {
|
|
for (const variantIndex in this.splitTests[splitTestKey]) {
|
|
this.splitTests[splitTestKey][variantIndex].assignedAt = new Date(
|
|
this.splitTests[splitTestKey][variantIndex].assignedAt
|
|
)
|
|
}
|
|
}
|
|
}
|
|
next()
|
|
}
|
|
UserSchema.pre('save', formatSplitTestsSchema)
|
|
|
|
exports.User = mongoose.model('User', UserSchema)
|
|
exports.UserSchema = UserSchema
|