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.
|
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,
|
If the array is populated but do not have a zero value,
|
||||||
permanent uploads will be rejected.
|
attempts to set permanent upload age will be rejected.
|
||||||
This only applies to new files uploaded after enabling the option.
|
|
||||||
|
|
||||||
If the array is empty or is set to falsy value, temporary uploads
|
This only applies to new files uploaded AFTER enabling the option.
|
||||||
feature will be disabled, and all uploads will be permanent (original behavior).
|
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
|
DEPRECATED: Please use "retentionPeriods" option below instead.
|
||||||
will not ever be automatically deleted, since the safe will not start the
|
|
||||||
periodical checkup task.
|
|
||||||
*/
|
*/
|
||||||
temporaryUploadAges: [
|
// temporaryUploadAges: [],
|
||||||
0, // permanent
|
|
||||||
|
/*
|
||||||
|
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 * 15, // 15 minutes
|
||||||
1 / 60 * 30, // 30 minutes
|
1 / 60 * 30, // 30 minutes
|
||||||
1, // 1 hour
|
1, // 1 hour
|
||||||
6, // 6 hours
|
6, // 6 hours
|
||||||
12, // 12 hours
|
12 // 12 hours
|
||||||
24, // 24 hours (1 day)
|
],
|
||||||
|
/*
|
||||||
|
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 * 2, // 48 hours (2 days)
|
||||||
24 * 3, // 72 hours (3 days)
|
24 * 3, // 72 hours (3 days)
|
||||||
24 * 4, // 96 hours (4 days)
|
24 * 4, // 96 hours (4 days)
|
||||||
24 * 5, // 120 hours (5 days)
|
24 * 5, // 120 hours (5 days)
|
||||||
24 * 6, // 144 hours (6 days)
|
24 * 6 // 144 hours (6 days)
|
||||||
24 * 7 // 168 hours (7 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).
|
Interval of the periodical check up tasks for temporary uploads (in milliseconds).
|
||||||
|
@ -2,6 +2,8 @@ const self = {}
|
|||||||
|
|
||||||
self.permissions = Object.freeze({
|
self.permissions = Object.freeze({
|
||||||
user: 0, // Upload & delete own files, create & delete albums
|
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
|
moderator: 50, // Delete other user's files
|
||||||
admin: 80, // Manage users (disable accounts) & create moderators
|
admin: 80, // Manage users (disable accounts) & create moderators
|
||||||
superadmin: 100 // Create admins
|
superadmin: 100 // Create admins
|
||||||
|
@ -59,6 +59,15 @@ self.verify = async (req, res, next) => {
|
|||||||
permissions: perms.mapPermissions(user)
|
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) {
|
if (utils.clientVersion) {
|
||||||
obj.version = utils.clientVersion
|
obj.version = utils.clientVersion
|
||||||
}
|
}
|
||||||
|
@ -48,8 +48,6 @@ const extensionsFilter = Array.isArray(config.extensionsFilter) &&
|
|||||||
config.extensionsFilter.length
|
config.extensionsFilter.length
|
||||||
const urlExtensionsFilter = Array.isArray(config.uploads.urlExtensionsFilter) &&
|
const urlExtensionsFilter = Array.isArray(config.uploads.urlExtensionsFilter) &&
|
||||||
config.uploads.urlExtensionsFilter.length
|
config.uploads.urlExtensionsFilter.length
|
||||||
const temporaryUploads = Array.isArray(config.uploads.temporaryUploadAges) &&
|
|
||||||
config.uploads.temporaryUploadAges.length
|
|
||||||
|
|
||||||
/** Chunks helper class & function **/
|
/** Chunks helper class & function **/
|
||||||
|
|
||||||
@ -244,17 +242,29 @@ self.getUniqueRandomName = async (length, extension) => {
|
|||||||
throw new ServerError('Failed to allocate a unique name for the upload. Try again?')
|
throw new ServerError('Failed to allocate a unique name for the upload. Try again?')
|
||||||
}
|
}
|
||||||
|
|
||||||
self.parseUploadAge = age => {
|
self.assertRetentionPeriod = (user, age) => {
|
||||||
if (age === undefined || age === null) {
|
if (!utils.retentions.enabled) return null
|
||||||
return config.uploads.temporaryUploadAges[0]
|
|
||||||
|
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)
|
let parsed = null
|
||||||
if (config.uploads.temporaryUploadAges.includes(parsed)) {
|
if (age === undefined || age === null) {
|
||||||
return parsed
|
parsed = utils.retentions.default[group]
|
||||||
} else {
|
} 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 => {
|
self.parseStripTags = stripTags => {
|
||||||
@ -287,13 +297,7 @@ self.upload = async (req, res, next) => {
|
|||||||
let albumid = parseInt(req.headers.albumid || req.params.albumid)
|
let albumid = parseInt(req.headers.albumid || req.params.albumid)
|
||||||
if (isNaN(albumid)) albumid = null
|
if (isNaN(albumid)) albumid = null
|
||||||
|
|
||||||
let age = null
|
const age = self.assertRetentionPeriod(user, req.headers.age)
|
||||||
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 func = req.body.urls ? self.actuallyUploadUrls : self.actuallyUploadFiles
|
const func = req.body.urls ? self.actuallyUploadUrls : self.actuallyUploadFiles
|
||||||
await func(req, res, user, albumid, age)
|
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.`)
|
throw new ClientError(`${file.extname ? `${file.extname.substr(1).toUpperCase()} files` : 'Files with no extension'} are not permitted.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (temporaryUploads) {
|
file.age = self.assertRetentionPeriod(user, file.age)
|
||||||
file.age = self.parseUploadAge(file.age)
|
|
||||||
if (!file.age && !config.uploads.temporaryUploadAges.includes(0)) {
|
|
||||||
throw new ClientError('Permanent uploads are not permitted.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file.size = chunksData[file.uuid].stream.bytesWritten
|
file.size = chunksData[file.uuid].stream.bytesWritten
|
||||||
if (config.filterEmptyFile && file.size === 0) {
|
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)
|
else if (offset < 0) offset = Math.max(0, Math.ceil(count / 25) + offset)
|
||||||
|
|
||||||
const columns = ['id', 'name', 'original', 'userid', 'size', 'timestamp']
|
const columns = ['id', 'name', 'original', 'userid', 'size', 'timestamp']
|
||||||
if (temporaryUploads) columns.push('expirydate')
|
if (utils.retentions.enabled) columns.push('expirydate')
|
||||||
if (!all ||
|
if (!all ||
|
||||||
filterObj.queries.albumid ||
|
filterObj.queries.albumid ||
|
||||||
filterObj.queries.exclude.albumid ||
|
filterObj.queries.exclude.albumid ||
|
||||||
|
@ -52,7 +52,13 @@ const self = {
|
|||||||
ffprobe: promisify(ffmpeg.ffprobe),
|
ffprobe: promisify(ffmpeg.ffprobe),
|
||||||
|
|
||||||
albumsCache: {},
|
albumsCache: {},
|
||||||
timezoneOffset: new Date().getTimezoneOffset()
|
timezoneOffset: new Date().getTimezoneOffset(),
|
||||||
|
|
||||||
|
retentions: {
|
||||||
|
enabled: false,
|
||||||
|
periods: {},
|
||||||
|
default: {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember old renderer, if overridden, or proxy to default renderer
|
// 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)
|
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 = {
|
const statsData = {
|
||||||
system: {
|
system: {
|
||||||
title: '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)
|
// Initiate internal periodical check ups of temporary uploads if required
|
||||||
if (Array.isArray(config.uploads.temporaryUploadAges) &&
|
if (utils.retentions && utils.retentions.enabled && config.uploads.temporaryUploadsInterval > 0) {
|
||||||
config.uploads.temporaryUploadAges.length &&
|
|
||||||
config.uploads.temporaryUploadsInterval) {
|
|
||||||
let temporaryUploadsInProgress = false
|
let temporaryUploadsInProgress = false
|
||||||
const temporaryUploadCheck = async () => {
|
const temporaryUploadCheck = async () => {
|
||||||
if (temporaryUploadsInProgress) return
|
if (temporaryUploadsInProgress) return
|
||||||
|
@ -12,11 +12,16 @@ routes.get('/check', (req, res, next) => {
|
|||||||
enableUserAccounts: config.enableUserAccounts,
|
enableUserAccounts: config.enableUserAccounts,
|
||||||
maxSize: config.uploads.maxSize,
|
maxSize: config.uploads.maxSize,
|
||||||
chunkSize: config.uploads.chunkSize,
|
chunkSize: config.uploads.chunkSize,
|
||||||
temporaryUploadAges: config.uploads.temporaryUploadAges,
|
|
||||||
fileIdentifierLength: config.uploads.fileIdentifierLength,
|
fileIdentifierLength: config.uploads.fileIdentifierLength,
|
||||||
stripTags: config.uploads.stripTags
|
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)
|
return res.json(obj)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ const page = {
|
|||||||
maxSize: null,
|
maxSize: null,
|
||||||
chunkSizeConfig: null,
|
chunkSizeConfig: null,
|
||||||
temporaryUploadAges: null,
|
temporaryUploadAges: null,
|
||||||
|
defaultTemporaryUploadAge: null,
|
||||||
fileIdentifierLength: null,
|
fileIdentifierLength: null,
|
||||||
stripTagsConfig: null,
|
stripTagsConfig: null,
|
||||||
|
|
||||||
@ -182,6 +183,7 @@ page.checkIfPublic = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
page.temporaryUploadAges = response.data.temporaryUploadAges
|
page.temporaryUploadAges = response.data.temporaryUploadAges
|
||||||
|
page.defaultTemporaryUploadAge = response.data.defaultTemporaryUploadAge || null
|
||||||
page.fileIdentifierLength = response.data.fileIdentifierLength
|
page.fileIdentifierLength = response.data.fileIdentifierLength
|
||||||
page.stripTagsConfig = response.data.stripTags
|
page.stripTagsConfig = response.data.stripTags
|
||||||
|
|
||||||
@ -210,6 +212,13 @@ page.verifyToken = token => {
|
|||||||
return axios.post('api/tokens/verify', { token }).then(response => {
|
return axios.post('api/tokens/verify', { token }).then(response => {
|
||||||
localStorage[lsKeys.token] = token
|
localStorage[lsKeys.token] = token
|
||||||
page.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()
|
return page.prepareUpload()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
return swal({
|
return swal({
|
||||||
@ -917,11 +926,14 @@ page.prepareUploadConfig = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (temporaryUploadAges) {
|
if (temporaryUploadAges) {
|
||||||
|
const _default = page.defaultTemporaryUploadAge === null
|
||||||
|
? page.temporaryUploadAges[0]
|
||||||
|
: page.defaultTemporaryUploadAge
|
||||||
const stored = parseFloat(localStorage[lsKeys.uploadAge])
|
const stored = parseFloat(localStorage[lsKeys.uploadAge])
|
||||||
for (let i = 0; i < page.temporaryUploadAges.length; i++) {
|
for (let i = 0; i < page.temporaryUploadAges.length; i++) {
|
||||||
const age = page.temporaryUploadAges[i]
|
const age = page.temporaryUploadAges[i]
|
||||||
config.uploadAge.select.push({
|
config.uploadAge.select.push({
|
||||||
value: i === 0 ? 'default' : String(age),
|
value: age === _default ? 'default' : String(age),
|
||||||
text: page.getPrettyUploadAge(age)
|
text: page.getPrettyUploadAge(age)
|
||||||
})
|
})
|
||||||
if (age === stored) {
|
if (age === stored) {
|
||||||
|
Loading…
Reference in New Issue
Block a user