Add:Server setting to allow iframe & update UI to differentiate web client settings #3684

This commit is contained in:
advplyr 2024-12-08 08:57:45 -06:00
parent 726adbb3bf
commit 57906540fe
6 changed files with 49 additions and 27 deletions

View File

@ -42,11 +42,6 @@
</div> </div>
</div> </div>
<div class="flex items-center py-2 mb-2">
<ui-toggle-switch labeledBy="settings-chromecast-support" v-model="newServerSettings.chromecastEnabled" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('chromecastEnabled', val)" />
<p class="pl-4" id="settings-chromecast-support">{{ $strings.LabelSettingsChromecastSupport }}</p>
</div>
<div class="pt-4"> <div class="pt-4">
<h2 class="font-semibold">{{ $strings.HeaderSettingsScanner }}</h2> <h2 class="font-semibold">{{ $strings.HeaderSettingsScanner }}</h2>
</div> </div>
@ -94,6 +89,20 @@
</p> </p>
</ui-tooltip> </ui-tooltip>
</div> </div>
<div class="pt-4">
<h2 class="font-semibold">{{ $strings.HeaderSettingsWebClient }}</h2>
</div>
<div class="flex items-center py-2">
<ui-toggle-switch labeledBy="settings-chromecast-support" v-model="newServerSettings.chromecastEnabled" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('chromecastEnabled', val)" />
<p class="pl-4" id="settings-chromecast-support">{{ $strings.LabelSettingsChromecastSupport }}</p>
</div>
<div class="flex items-center py-2 mb-2">
<ui-toggle-switch labeledBy="settings-allow-iframe" v-model="newServerSettings.allowIframe" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('allowIframe', val)" />
<p class="pl-4" id="settings-allow-iframe">{{ $strings.LabelSettingsAllowIframe }}</p>
</div>
</div> </div>
<div class="flex-1"> <div class="flex-1">
@ -324,21 +333,21 @@ export default {
}, },
updateServerSettings(payload) { updateServerSettings(payload) {
this.updatingServerSettings = true this.updatingServerSettings = true
this.$store this.$store.dispatch('updateServerSettings', payload).then((response) => {
.dispatch('updateServerSettings', payload) this.updatingServerSettings = false
.then(() => {
this.updatingServerSettings = false
if (payload.language) { if (response.error) {
// Updating language after save allows for re-rendering console.error('Failed to update server settins', response.error)
this.$setLanguageCode(payload.language) this.$toast.error(response.error)
} this.initServerSettings()
}) return
.catch((error) => { }
console.error('Failed to update server settings', error)
this.updatingServerSettings = false if (payload.language) {
this.$toast.error(this.$strings.ToastFailedToUpdate) // Updating language after save allows for re-rendering
}) this.$setLanguageCode(payload.language)
}
})
}, },
initServerSettings() { initServerSettings() {
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {} this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}

View File

@ -72,16 +72,17 @@ export const actions = {
return this.$axios return this.$axios
.$patch('/api/settings', updatePayload) .$patch('/api/settings', updatePayload)
.then((result) => { .then((result) => {
if (result.success) { if (result.serverSettings) {
commit('setServerSettings', result.serverSettings) commit('setServerSettings', result.serverSettings)
return true
} else {
return false
} }
return result
}) })
.catch((error) => { .catch((error) => {
console.error('Failed to update server settings', error) console.error('Failed to update server settings', error)
return false const errorMsg = error.response?.data || 'Unknown error'
return {
error: errorMsg
}
}) })
}, },
checkForUpdate({ commit }) { checkForUpdate({ commit }) {

View File

@ -190,6 +190,7 @@
"HeaderSettingsExperimental": "Experimental Features", "HeaderSettingsExperimental": "Experimental Features",
"HeaderSettingsGeneral": "General", "HeaderSettingsGeneral": "General",
"HeaderSettingsScanner": "Scanner", "HeaderSettingsScanner": "Scanner",
"HeaderSettingsWebClient": "Web Client",
"HeaderSleepTimer": "Sleep Timer", "HeaderSleepTimer": "Sleep Timer",
"HeaderStatsLargestItems": "Largest Items", "HeaderStatsLargestItems": "Largest Items",
"HeaderStatsLongestItems": "Longest Items (hrs)", "HeaderStatsLongestItems": "Longest Items (hrs)",
@ -542,6 +543,7 @@
"LabelServerYearReview": "Server Year in Review ({0})", "LabelServerYearReview": "Server Year in Review ({0})",
"LabelSetEbookAsPrimary": "Set as primary", "LabelSetEbookAsPrimary": "Set as primary",
"LabelSetEbookAsSupplementary": "Set as supplementary", "LabelSetEbookAsSupplementary": "Set as supplementary",
"LabelSettingsAllowIframe": "Allow embedding in an iframe",
"LabelSettingsAudiobooksOnly": "Audiobooks only", "LabelSettingsAudiobooksOnly": "Audiobooks only",
"LabelSettingsAudiobooksOnlyHelp": "Enabling this setting will ignore ebook files unless they are inside an audiobook folder in which case they will be set as supplementary ebooks", "LabelSettingsAudiobooksOnlyHelp": "Enabling this setting will ignore ebook files unless they are inside an audiobook folder in which case they will be set as supplementary ebooks",
"LabelSettingsBookshelfViewHelp": "Skeumorphic design with wooden shelves", "LabelSettingsBookshelfViewHelp": "Skeumorphic design with wooden shelves",

View File

@ -53,7 +53,6 @@ class Server {
global.RouterBasePath = ROUTER_BASE_PATH global.RouterBasePath = ROUTER_BASE_PATH
global.XAccel = process.env.USE_X_ACCEL global.XAccel = process.env.USE_X_ACCEL
global.AllowCors = process.env.ALLOW_CORS === '1' global.AllowCors = process.env.ALLOW_CORS === '1'
global.AllowIframe = process.env.ALLOW_IFRAME === '1'
global.DisableSsrfRequestFilter = process.env.DISABLE_SSRF_REQUEST_FILTER === '1' global.DisableSsrfRequestFilter = process.env.DISABLE_SSRF_REQUEST_FILTER === '1'
if (!fs.pathExistsSync(global.ConfigPath)) { if (!fs.pathExistsSync(global.ConfigPath)) {
@ -195,7 +194,7 @@ class Server {
const app = express() const app = express()
app.use((req, res, next) => { app.use((req, res, next) => {
if (!global.AllowIframe) { if (!global.ServerSettings.allowIframe) {
// Prevent clickjacking by disallowing iframes // Prevent clickjacking by disallowing iframes
res.setHeader('Content-Security-Policy', "frame-ancestors 'self'") res.setHeader('Content-Security-Policy', "frame-ancestors 'self'")
} }

View File

@ -126,6 +126,10 @@ class MiscController {
if (!isObject(settingsUpdate)) { if (!isObject(settingsUpdate)) {
return res.status(400).send('Invalid settings update object') return res.status(400).send('Invalid settings update object')
} }
if (settingsUpdate.allowIframe == false && process.env.ALLOW_IFRAME === '1') {
Logger.warn('Cannot disable iframe when ALLOW_IFRAME is enabled in environment')
return res.status(400).send('Cannot disable iframe when ALLOW_IFRAME is enabled in environment')
}
const madeUpdates = Database.serverSettings.update(settingsUpdate) const madeUpdates = Database.serverSettings.update(settingsUpdate)
if (madeUpdates) { if (madeUpdates) {
@ -137,7 +141,6 @@ class MiscController {
} }
} }
return res.json({ return res.json({
success: true,
serverSettings: Database.serverSettings.toJSONForBrowser() serverSettings: Database.serverSettings.toJSONForBrowser()
}) })
} }

View File

@ -24,6 +24,7 @@ class ServerSettings {
// Security/Rate limits // Security/Rate limits
this.rateLimitLoginRequests = 10 this.rateLimitLoginRequests = 10
this.rateLimitLoginWindow = 10 * 60 * 1000 // 10 Minutes this.rateLimitLoginWindow = 10 * 60 * 1000 // 10 Minutes
this.allowIframe = false
// Backups // Backups
this.backupPath = Path.join(global.MetadataPath, 'backups') this.backupPath = Path.join(global.MetadataPath, 'backups')
@ -99,6 +100,7 @@ class ServerSettings {
this.rateLimitLoginRequests = !isNaN(settings.rateLimitLoginRequests) ? Number(settings.rateLimitLoginRequests) : 10 this.rateLimitLoginRequests = !isNaN(settings.rateLimitLoginRequests) ? Number(settings.rateLimitLoginRequests) : 10
this.rateLimitLoginWindow = !isNaN(settings.rateLimitLoginWindow) ? Number(settings.rateLimitLoginWindow) : 10 * 60 * 1000 // 10 Minutes this.rateLimitLoginWindow = !isNaN(settings.rateLimitLoginWindow) ? Number(settings.rateLimitLoginWindow) : 10 * 60 * 1000 // 10 Minutes
this.allowIframe = !!settings.allowIframe
this.backupPath = settings.backupPath || Path.join(global.MetadataPath, 'backups') this.backupPath = settings.backupPath || Path.join(global.MetadataPath, 'backups')
this.backupSchedule = settings.backupSchedule || false this.backupSchedule = settings.backupSchedule || false
@ -190,6 +192,11 @@ class ServerSettings {
Logger.info(`[ServerSettings] Using backup path from environment variable ${process.env.BACKUP_PATH}`) Logger.info(`[ServerSettings] Using backup path from environment variable ${process.env.BACKUP_PATH}`)
this.backupPath = process.env.BACKUP_PATH this.backupPath = process.env.BACKUP_PATH
} }
if (process.env.ALLOW_IFRAME === '1' && !this.allowIframe) {
Logger.info(`[ServerSettings] Using allowIframe from environment variable`)
this.allowIframe = true
}
} }
toJSON() { toJSON() {
@ -207,6 +214,7 @@ class ServerSettings {
metadataFileFormat: this.metadataFileFormat, metadataFileFormat: this.metadataFileFormat,
rateLimitLoginRequests: this.rateLimitLoginRequests, rateLimitLoginRequests: this.rateLimitLoginRequests,
rateLimitLoginWindow: this.rateLimitLoginWindow, rateLimitLoginWindow: this.rateLimitLoginWindow,
allowIframe: this.allowIframe,
backupPath: this.backupPath, backupPath: this.backupPath,
backupSchedule: this.backupSchedule, backupSchedule: this.backupSchedule,
backupsToKeep: this.backupsToKeep, backupsToKeep: this.backupsToKeep,