mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-01-18 17:21:33 +00:00
feat: usergroup-based file retention periods
this supersedes the old temporaryUploadAges, while maintaining full backwards-compatibility. please consult config.sample.js if you want to start using this
This commit is contained in:
parent
4d4dffc5ae
commit
2d147e748b
@ -369,31 +369,82 @@ module.exports = {
|
||||
|
||||
Default age will be the value at the very top of the array.
|
||||
If the array is populated but do not have a zero value,
|
||||
permanent uploads will be rejected.
|
||||
This only applies to new files uploaded after enabling the option.
|
||||
attempts to set permanent upload age will be rejected.
|
||||
|
||||
If the array is empty or is set to falsy value, temporary uploads
|
||||
feature will be disabled, and all uploads will be permanent (original behavior).
|
||||
This only applies to new files uploaded AFTER enabling the option.
|
||||
If disabled, any existing temporary uploads will not ever be automatically deleted,
|
||||
since the safe assumes all uploads are permanent,
|
||||
and thus will not start the periodical check up task.
|
||||
|
||||
When temporary uploads feature is disabled, any existing temporary uploads
|
||||
will not ever be automatically deleted, since the safe will not start the
|
||||
periodical checkup task.
|
||||
DEPRECATED: Please use "retentionPeriods" option below instead.
|
||||
*/
|
||||
temporaryUploadAges: [
|
||||
0, // permanent
|
||||
// temporaryUploadAges: [],
|
||||
|
||||
/*
|
||||
Usergroup-based file retention periods (temporary uploads ages).
|
||||
|
||||
You need to at least configure the default group (_), or any one group, to enable this.
|
||||
If this is enabled, "temporaryUploadAges" option above will be completely ignored.
|
||||
|
||||
It's safe to disable and remove that option completely if you plan to only use this one.
|
||||
The support for it was only kept as backwards-compatibility for older installations.
|
||||
|
||||
This only applies to new files uploaded AFTER enabling the option.
|
||||
If disabled, any existing temporary uploads will not ever be automatically deleted,
|
||||
since the safe assumes all uploads are permanent,
|
||||
and thus will not start the periodical check up task.
|
||||
|
||||
Please refer to the examples below about inheritances
|
||||
and how to set default retention for each groups.
|
||||
*/
|
||||
retentionPeriods: {
|
||||
// Defaults that also apply to non-registered users
|
||||
_: [
|
||||
24, // 24 hours (1 day) -- first value is the group's default retention
|
||||
1 / 60 * 15, // 15 minutes
|
||||
1 / 60 * 30, // 30 minutes
|
||||
1, // 1 hour
|
||||
6, // 6 hours
|
||||
12, // 12 hours
|
||||
24, // 24 hours (1 day)
|
||||
12 // 12 hours
|
||||
],
|
||||
/*
|
||||
Inheritance is based on each group's 'values' in permissionController.js.
|
||||
Basically groups with higher 'value' will inherit retention periods
|
||||
of any groups with lower 'values'.
|
||||
You may remove all the groups below to apply the defaults above for everyone.
|
||||
*/
|
||||
user: [
|
||||
24 * 7, // 168 hours (7 days) -- group's default
|
||||
24 * 2, // 48 hours (2 days)
|
||||
24 * 3, // 72 hours (3 days)
|
||||
24 * 4, // 96 hours (4 days)
|
||||
24 * 5, // 120 hours (5 days)
|
||||
24 * 6, // 144 hours (6 days)
|
||||
24 * 7 // 168 hours (7 days)
|
||||
24 * 6 // 144 hours (6 days)
|
||||
],
|
||||
vip: [
|
||||
24 * 30, // 720 hours (30 days) -- group's default
|
||||
24 * 14, // 336 hours (14 days)
|
||||
24 * 21, // 504 hours (21 days)
|
||||
24 * 91 // 2184 hours (91 days)
|
||||
],
|
||||
vvip: [
|
||||
null, // -- if null, use previous group's default as this group's default
|
||||
0, // permanent
|
||||
24 * 183 // 4392 hours (183 days)
|
||||
],
|
||||
moderator: [
|
||||
0 // -- group's default
|
||||
/*
|
||||
vvip group also have 0 (permanent) in its retention periods,
|
||||
but duplicates are perfectly fine and will be safely 'uniquified',
|
||||
while still properly maintaining defaults when required.
|
||||
*/
|
||||
]
|
||||
/*
|
||||
Missing groups will follow the inheritance rules.
|
||||
Following the example above, admin and superadmin will have the same retention periods as moderator.
|
||||
*/
|
||||
},
|
||||
|
||||
/*
|
||||
Interval of the periodical check up tasks for temporary uploads (in milliseconds).
|
||||
|
@ -2,6 +2,8 @@ const self = {}
|
||||
|
||||
self.permissions = Object.freeze({
|
||||
user: 0, // Upload & delete own files, create & delete albums
|
||||
vip: 5, // If used with "retentionPeriods" in config, may have additional retention period options
|
||||
vvip: 10, // If used with "retentionPeriods" in config, may have additional retention period options
|
||||
moderator: 50, // Delete other user's files
|
||||
admin: 80, // Manage users (disable accounts) & create moderators
|
||||
superadmin: 100 // Create admins
|
||||
|
@ -59,6 +59,15 @@ self.verify = async (req, res, next) => {
|
||||
permissions: perms.mapPermissions(user)
|
||||
}
|
||||
|
||||
const group = perms.group(user)
|
||||
if (group) {
|
||||
obj.group = group
|
||||
if (utils.retentions.enabled) {
|
||||
obj.retentionPeriods = utils.retentions.periods[group]
|
||||
obj.defaultRetentionPeriod = utils.retentions.default[group]
|
||||
}
|
||||
}
|
||||
|
||||
if (utils.clientVersion) {
|
||||
obj.version = utils.clientVersion
|
||||
}
|
||||
|
@ -48,8 +48,6 @@ const extensionsFilter = Array.isArray(config.extensionsFilter) &&
|
||||
config.extensionsFilter.length
|
||||
const urlExtensionsFilter = Array.isArray(config.uploads.urlExtensionsFilter) &&
|
||||
config.uploads.urlExtensionsFilter.length
|
||||
const temporaryUploads = Array.isArray(config.uploads.temporaryUploadAges) &&
|
||||
config.uploads.temporaryUploadAges.length
|
||||
|
||||
/** Chunks helper class & function **/
|
||||
|
||||
@ -244,19 +242,31 @@ self.getUniqueRandomName = async (length, extension) => {
|
||||
throw new ServerError('Failed to allocate a unique name for the upload. Try again?')
|
||||
}
|
||||
|
||||
self.parseUploadAge = age => {
|
||||
if (age === undefined || age === null) {
|
||||
return config.uploads.temporaryUploadAges[0]
|
||||
self.assertRetentionPeriod = (user, age) => {
|
||||
if (!utils.retentions.enabled) return null
|
||||
|
||||
const group = user ? perms.group(user) : '_'
|
||||
if (!group || !utils.retentions.periods[group]) {
|
||||
throw new ClientError('You are not eligible for any file retention periods.', { statusCode: 403 })
|
||||
}
|
||||
|
||||
const parsed = parseFloat(age)
|
||||
if (config.uploads.temporaryUploadAges.includes(parsed)) {
|
||||
return parsed
|
||||
let parsed = null
|
||||
if (age === undefined || age === null) {
|
||||
parsed = utils.retentions.default[group]
|
||||
} else {
|
||||
return null
|
||||
parsed = parseFloat(age)
|
||||
if (!utils.retentions.periods[group].includes(parsed)) {
|
||||
throw new ClientError('You are not eligible for the specified file retention period.', { statusCode: 403 })
|
||||
}
|
||||
}
|
||||
|
||||
if (!parsed && !utils.retentions.periods[group].includes(0)) {
|
||||
throw new ClientError('Permanent uploads are not permitted.', { statusCode: 403 })
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
self.parseStripTags = stripTags => {
|
||||
if (!config.uploads.stripTags) return false
|
||||
|
||||
@ -287,13 +297,7 @@ self.upload = async (req, res, next) => {
|
||||
let albumid = parseInt(req.headers.albumid || req.params.albumid)
|
||||
if (isNaN(albumid)) albumid = null
|
||||
|
||||
let age = null
|
||||
if (temporaryUploads) {
|
||||
age = self.parseUploadAge(req.headers.age)
|
||||
if (!age && !config.uploads.temporaryUploadAges.includes(0)) {
|
||||
throw new ClientError('Permanent uploads are not permitted.', { statusCode: 403 })
|
||||
}
|
||||
}
|
||||
const age = self.assertRetentionPeriod(user, req.headers.age)
|
||||
|
||||
const func = req.body.urls ? self.actuallyUploadUrls : self.actuallyUploadFiles
|
||||
await func(req, res, user, albumid, age)
|
||||
@ -539,12 +543,7 @@ self.actuallyFinishChunks = async (req, res, user) => {
|
||||
throw new ClientError(`${file.extname ? `${file.extname.substr(1).toUpperCase()} files` : 'Files with no extension'} are not permitted.`)
|
||||
}
|
||||
|
||||
if (temporaryUploads) {
|
||||
file.age = self.parseUploadAge(file.age)
|
||||
if (!file.age && !config.uploads.temporaryUploadAges.includes(0)) {
|
||||
throw new ClientError('Permanent uploads are not permitted.')
|
||||
}
|
||||
}
|
||||
file.age = self.assertRetentionPeriod(user, file.age)
|
||||
|
||||
file.size = chunksData[file.uuid].stream.bytesWritten
|
||||
if (config.filterEmptyFile && file.size === 0) {
|
||||
@ -1455,7 +1454,7 @@ self.list = async (req, res, next) => {
|
||||
else if (offset < 0) offset = Math.max(0, Math.ceil(count / 25) + offset)
|
||||
|
||||
const columns = ['id', 'name', 'original', 'userid', 'size', 'timestamp']
|
||||
if (temporaryUploads) columns.push('expirydate')
|
||||
if (utils.retentions.enabled) columns.push('expirydate')
|
||||
if (!all ||
|
||||
filterObj.queries.albumid ||
|
||||
filterObj.queries.exclude.albumid ||
|
||||
|
@ -52,7 +52,13 @@ const self = {
|
||||
ffprobe: promisify(ffmpeg.ffprobe),
|
||||
|
||||
albumsCache: {},
|
||||
timezoneOffset: new Date().getTimezoneOffset()
|
||||
timezoneOffset: new Date().getTimezoneOffset(),
|
||||
|
||||
retentions: {
|
||||
enabled: false,
|
||||
periods: {},
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
|
||||
// Remember old renderer, if overridden, or proxy to default renderer
|
||||
@ -71,6 +77,75 @@ self.md.instance.renderer.rules.link_open = function (tokens, idx, options, env,
|
||||
return self.md.defaultRenderers.link_open(tokens, idx, options, env, that)
|
||||
}
|
||||
|
||||
if (typeof config.uploads.retentionPeriods === 'object' &&
|
||||
Object.keys(config.uploads.retentionPeriods).length) {
|
||||
// Build a temporary index of group values
|
||||
const _retentionPeriods = Object.assign({}, config.uploads.retentionPeriods)
|
||||
const _groups = { _: -1 }
|
||||
Object.assign(_groups, perms.permissions)
|
||||
|
||||
// Sanitize config values
|
||||
const names = Object.keys(_groups)
|
||||
for (const name of names) {
|
||||
if (Array.isArray(_retentionPeriods[name]) && _retentionPeriods[name].length) {
|
||||
_retentionPeriods[name] = _retentionPeriods[name]
|
||||
.filter((v, i, a) => (Number.isFinite(v) && v >= 0) || v === null)
|
||||
} else {
|
||||
_retentionPeriods[name] = []
|
||||
}
|
||||
}
|
||||
|
||||
if (!_retentionPeriods._.length && !config.private) {
|
||||
logger.error('Guests\' retention periods are missing, yet this installation is not set to private.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Create sorted array of group names based on their values
|
||||
const _sorted = Object.keys(_groups)
|
||||
.sort((a, b) => _groups[a] - _groups[b])
|
||||
|
||||
// Build retention periods array for each groups
|
||||
for (let i = 0; i < _sorted.length; i++) {
|
||||
const current = _sorted[i]
|
||||
const _periods = [..._retentionPeriods[current]]
|
||||
self.retentions.default[current] = _periods.length ? _periods[0] : null
|
||||
|
||||
if (i > 0) {
|
||||
// Inherit retention periods of lower-valued groups
|
||||
for (let j = i - 1; j >= 0; j--) {
|
||||
const lower = _sorted[j]
|
||||
if (_groups[lower] < _groups[current]) {
|
||||
_periods.unshift(..._retentionPeriods[lower])
|
||||
if (self.retentions.default[current] === null) {
|
||||
self.retentions.default[current] = self.retentions.default[lower]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.retentions.periods[current] = _periods
|
||||
.filter((v, i, a) => v !== null && a.indexOf(v) === i) // re-sanitize & uniquify
|
||||
.sort((a, b) => a - b) // sort from lowest to highest (zero/permanent will always be first)
|
||||
|
||||
// Mark the feature as enabled, if at least one group was configured
|
||||
if (self.retentions.periods[current].length) {
|
||||
self.retentions.enabled = true
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(config.uploads.temporaryUploadAges) &&
|
||||
config.uploads.temporaryUploadAges.length) {
|
||||
self.retentions.periods._ = config.uploads.temporaryUploadAges
|
||||
.filter((v, i, a) => Number.isFinite(v) && v >= 0)
|
||||
self.retentions.default._ = self.retentions.periods._[0]
|
||||
|
||||
for (const name of Object.keys(perms.permissions)) {
|
||||
self.retentions.periods[name] = self.retentions.periods._
|
||||
self.retentions.default[name] = self.retentions.default._
|
||||
}
|
||||
|
||||
self.retentions.enabled = true
|
||||
}
|
||||
|
||||
const statsData = {
|
||||
system: {
|
||||
title: 'System',
|
||||
|
@ -372,10 +372,8 @@ safe.use('/api', api)
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary uploads (only check for expired uploads if config.uploads.temporaryUploadsInterval is also set)
|
||||
if (Array.isArray(config.uploads.temporaryUploadAges) &&
|
||||
config.uploads.temporaryUploadAges.length &&
|
||||
config.uploads.temporaryUploadsInterval) {
|
||||
// Initiate internal periodical check ups of temporary uploads if required
|
||||
if (utils.retentions && utils.retentions.enabled && config.uploads.temporaryUploadsInterval > 0) {
|
||||
let temporaryUploadsInProgress = false
|
||||
const temporaryUploadCheck = async () => {
|
||||
if (temporaryUploadsInProgress) return
|
||||
|
@ -12,11 +12,16 @@ routes.get('/check', (req, res, next) => {
|
||||
enableUserAccounts: config.enableUserAccounts,
|
||||
maxSize: config.uploads.maxSize,
|
||||
chunkSize: config.uploads.chunkSize,
|
||||
temporaryUploadAges: config.uploads.temporaryUploadAges,
|
||||
fileIdentifierLength: config.uploads.fileIdentifierLength,
|
||||
stripTags: config.uploads.stripTags
|
||||
}
|
||||
if (utilsController.clientVersion) obj.version = utilsController.clientVersion
|
||||
if (utilsController.retentions.enabled && utilsController.retentions.periods._) {
|
||||
obj.temporaryUploadAges = utilsController.retentions.periods._
|
||||
obj.defaultTemporaryUploadAge = utilsController.retentions.default._
|
||||
}
|
||||
if (utilsController.clientVersion) {
|
||||
obj.version = utilsController.clientVersion
|
||||
}
|
||||
return res.json(obj)
|
||||
})
|
||||
|
||||
|
@ -22,6 +22,7 @@ const page = {
|
||||
maxSize: null,
|
||||
chunkSizeConfig: null,
|
||||
temporaryUploadAges: null,
|
||||
defaultTemporaryUploadAge: null,
|
||||
fileIdentifierLength: null,
|
||||
stripTagsConfig: null,
|
||||
|
||||
@ -182,6 +183,7 @@ page.checkIfPublic = () => {
|
||||
}
|
||||
|
||||
page.temporaryUploadAges = response.data.temporaryUploadAges
|
||||
page.defaultTemporaryUploadAge = response.data.defaultTemporaryUploadAge || null
|
||||
page.fileIdentifierLength = response.data.fileIdentifierLength
|
||||
page.stripTagsConfig = response.data.stripTags
|
||||
|
||||
@ -210,6 +212,13 @@ page.verifyToken = token => {
|
||||
return axios.post('api/tokens/verify', { token }).then(response => {
|
||||
localStorage[lsKeys.token] = token
|
||||
page.token = token
|
||||
|
||||
// If user has its own retention periods array, override defaults
|
||||
if (Array.isArray(response.data.retentionPeriods)) {
|
||||
page.temporaryUploadAges = response.data.retentionPeriods
|
||||
page.defaultTemporaryUploadAge = response.data.defaultRetentionPeriod
|
||||
}
|
||||
|
||||
return page.prepareUpload()
|
||||
}).catch(error => {
|
||||
return swal({
|
||||
@ -917,11 +926,14 @@ page.prepareUploadConfig = () => {
|
||||
}
|
||||
|
||||
if (temporaryUploadAges) {
|
||||
const _default = page.defaultTemporaryUploadAge === null
|
||||
? page.temporaryUploadAges[0]
|
||||
: page.defaultTemporaryUploadAge
|
||||
const stored = parseFloat(localStorage[lsKeys.uploadAge])
|
||||
for (let i = 0; i < page.temporaryUploadAges.length; i++) {
|
||||
const age = page.temporaryUploadAges[i]
|
||||
config.uploadAge.select.push({
|
||||
value: i === 0 ? 'default' : String(age),
|
||||
value: age === _default ? 'default' : String(age),
|
||||
text: page.getPrettyUploadAge(age)
|
||||
})
|
||||
if (age === stored) {
|
||||
|
Loading…
Reference in New Issue
Block a user