Skip to content

Commit

Permalink
fix(settings): Clarify peculiarities of enabling encryption
Browse files Browse the repository at this point in the history
- Clarify that enabling server side encryption will not encrypt
  existing files but only new or changed files.
- Clarify that server side encryption can only be disabled using OCC
- Ensure there is accessible information of encryption state (`disabled`
  input will not be announced so make it `aria-disabled` instead)
- Make warning more prominent by moving it into a dialog

Signed-off-by: Ferdinand Thiessen <[email protected]>
  • Loading branch information
susnux committed Jan 27, 2025
1 parent 510d897 commit 94e1323
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,30 @@
<NcSettingsSection :name="t('settings', 'Server-side encryption')"
:description="t('settings', 'Server-side encryption makes it possible to encrypt files which are uploaded to this server. This comes with limitations like a performance penalty, so enable this only if needed.')"
:doc-url="encryptionAdminDoc">
<NcCheckboxRadioSwitch :checked="encryptionEnabled || shouldDisplayWarning"
:disabled="encryptionEnabled"
<NcNoteCard v-if="encryptionEnabled" type="info">
<p>
{{ textExistingFilesNotEncrypted }}
{{ t('settings', 'To encrypt all existing files run this OCC command:') }}
</p>
<code>
<pre>occ encryption:encrypt-all</pre>
</code>
</NcNoteCard>

<NcCheckboxRadioSwitch :class="{ disabled: encryptionEnabled }"
:checked="encryptionEnabled"
:aria-disabled="encryptionEnabled ? 'true' : undefined"
:aria-describedby="encryptionEnabled ? 'server-side-encryption-disable-hint' : undefined"
:loading="loadingEncryptionState"
type="switch"
@update:checked="displayWarning">
{{ t('settings', 'Enable server-side encryption') }}
</NcCheckboxRadioSwitch>
<p v-if="encryptionEnabled" id="server-side-encryption-disable-hint" class="disable-hint">
{{ t('settings', 'Disabling server side encryption is only possible using OCC, please refer to the documentation.') }}
</p>

<div v-if="shouldDisplayWarning && !encryptionEnabled" class="notecard warning" role="alert">
<p>{{ t('settings', 'Please read carefully before activating server-side encryption:') }}</p>
<ul>
<li>{{ t('settings', 'Once encryption is enabled, all files uploaded to the server from that point forward will be encrypted at rest on the server. It will only be possible to disable encryption at a later date if the active encryption module supports that function, and all pre-conditions (e.g. setting a recover key) are met.') }}</li>
<li>{{ t('settings', 'Encryption alone does not guarantee security of the system. Please see documentation for more information about how the encryption app works, and the supported use cases.') }}</li>
<li>{{ t('settings', 'Be aware that encryption always increases the file size.') }}</li>
<li>{{ t('settings', 'It is always good to create regular backups of your data, in case of encryption make sure to backup the encryption keys along with your data.') }}</li>
</ul>

<p class="margin-bottom">
{{ t('settings', 'This is the final warning: Do you really want to enable encryption?') }}
</p>
<NcButton type="primary"
@click="enableEncryption()">
{{ t('settings', "Enable encryption") }}
</NcButton>
</div>

<div v-if="encryptionEnabled">
<template v-if="encryptionEnabled">
<div v-if="encryptionReady">
<p v-if="encryptionModules.length === 0">
{{ t('settings', 'No encryption module loaded, please enable an encryption module in the app menu.') }}
Expand Down Expand Up @@ -62,31 +60,42 @@
)
}}
</div>
</div>
</template>
</NcSettingsSection>
</template>

<script>
import { showError } from '@nextcloud/dialogs'
import { showError, spawnDialog } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import { confirmPassword } from '@nextcloud/password-confirmation'
import { textExistingFilesNotEncrypted } from './sharedTexts'

import axios from '@nextcloud/axios'
import logger from '../../logger.ts'

import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'

import logger from '../logger'

import '@nextcloud/password-confirmation/dist/style.css'
import EncryptionWarningDialog from './EncryptionWarningDialog.vue'

export default {
name: 'Encryption',
name: 'EncryptionSettings',

components: {
NcCheckboxRadioSwitch,
NcNoteCard,
NcSettingsSection,
NcButton,
},

setup() {
return {
t,
textExistingFilesNotEncrypted,
}
},

data() {
const encryptionModules = loadState('settings', 'encryption-modules')
return {
Expand All @@ -95,20 +104,31 @@ export default {
externalBackendsEnabled: loadState('settings', 'external-backends-enabled'),
encryptionAdminDoc: loadState('settings', 'encryption-admin-doc'),
encryptionModules,
shouldDisplayWarning: false,
migrating: false,
defaultCheckedModule: Object.entries(encryptionModules).find((module) => module[1].default)?.[0],

loadingEncryptionState: false,
}
},

methods: {
displayWarning() {
if (!this.encryptionEnabled) {
this.shouldDisplayWarning = !this.shouldDisplayWarning
} else {
this.encryptionEnabled = false
this.shouldDisplayWarning = false
displayWarning(checked) {
if (this.loadingEncryptionState || checked === false) {
return
}

this.loadingEncryptionState = true
spawnDialog(EncryptionWarningDialog, {}, async (confirmed) => {
try {
if (confirmed) {
await this.enableEncryption()
}
} finally {
this.loadingEncryptionState = false
}
})
},

async update(key, value) {
await confirmPassword()

Expand All @@ -129,14 +149,15 @@ export default {
errorMessage: t('settings', 'Unable to update server side encryption config'),
error: e,
})
return false
}
return true
},
async checkDefaultModule() {
await this.update('default_encryption_module', this.defaultCheckedModule)
},
async enableEncryption() {
this.encryptionEnabled = true
await this.update('encryption_enabled', 'yes')
this.encryptionEnabled = await this.update('encryption_enabled', 'yes')
},
async handleResponse({ status, errorMessage, error }) {
if (status !== 'ok') {
Expand All @@ -148,42 +169,27 @@ export default {
}
</script>

<style lang="scss" scoped>

.notecard.success {
--note-background: rgba(var(--color-success-rgb), 0.2);
--note-theme: var(--color-success);
}

.notecard.error {
--note-background: rgba(var(--color-error-rgb), 0.2);
--note-theme: var(--color-error);
}
<style scoped>
code {
background-color: var(--color-background-dark);
color: var(--color-main-text);

.notecard.warning {
--note-background: rgba(var(--color-warning-rgb), 0.2);
--note-theme: var(--color-warning);
display: block;
margin-block-start: 0.5rem;
padding: .25lh .5lh;
width: fit-content;
}

#body-settings .notecard {
color: var(--color-text-light);
background-color: var(--note-background);
border: 1px solid var(--color-border);
border-inline-start: 4px solid var(--note-theme);
border-radius: var(--border-radius);
box-shadow: rgba(43, 42, 51, 0.05) 0px 1px 2px 0px;
margin: 1rem 0;
margin-top: 1rem;
padding: 1rem;
.disabled {
opacity: .75;
}

li {
list-style-type: initial;
margin-inline-start: 1rem;
padding: 0.25rem 0;
.disabled :deep(*) {
cursor: not-allowed !important;
}

.margin-bottom {
margin-bottom: 0.75rem;
.disable-hint {
color: var(--color-text-maxcontrast);
padding-inline-start: 10px;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import type { IDialogButton } from '@nextcloud/dialogs'

import { t } from '@nextcloud/l10n'
import { textExistingFilesNotEncrypted } from './sharedTexts.ts'
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'

const emit = defineEmits<{
(e: 'close', encrypt: boolean): void
}>()

const buttons: IDialogButton[] = [
{
label: t('settings', 'Cancel encryption'),
// @ts-expect-error Needs to be fixed in the dialogs library - value is allowed but missing from the types
type: 'tertiary',
callback: () => emit('close', false),
},
{
label: t('settings', 'Enable encryption'),
type: 'error',
callback: () => emit('close', true),
},
]

/**
* When closed we need to emit the close event
* @param isOpen open state of the dialog
*/
function onUpdateOpen(isOpen: boolean) {
if (!isOpen) {
emit('close', false)
}
}
</script>

<template>
<NcDialog :buttons="buttons"
:name="t('settings', 'Confirm enabling encryption')"
size="normal"
@update:open="onUpdateOpen">
<NcNoteCard type="warning">
<p>
{{ t('settings', 'Please read carefully before activating server-side encryption:') }}
<ul>
<li>
{{ t('settings', 'Once encryption is enabled, all files uploaded to the server from that point forward will be encrypted at rest on the server. It will only be possible to disable encryption at a later date if the active encryption module supports that function, and all pre-conditions (e.g. setting a recover key) are met.') }}
</li>
<li>
{{ t('settings', 'Encryption alone does not guarantee security of the system. Please see documentation for more information about how the encryption app works, and the supported use cases.') }}
</li>
<li>
{{ t('settings', 'Be aware that encryption always increases the file size.') }}
</li>
<li>
{{ t('settings', 'It is always good to create regular backups of your data, in case of encryption make sure to backup the encryption keys along with your data.') }}
</li>
<li>
{{ textExistingFilesNotEncrypted }}
{{ t('settings', 'Refer to the admin documentation on how to manually also encrypt existing files.') }}
</li>
</ul>
</p>
</NcNoteCard>
<p>
{{ t('settings', 'This is the final warning: Do you really want to enable encryption?') }}
</p>
</NcDialog>
</template>

<style scoped>
li {
list-style-type: initial;
margin-inline-start: 1rem;
padding: 0.25rem 0;
}

p + p,
div + p {
margin-block: 0.75rem;
}
</style>
7 changes: 7 additions & 0 deletions apps/settings/src/components/Encryption/sharedTexts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { t } from '@nextcloud/l10n'

export const textExistingFilesNotEncrypted = t('settings', 'For performance reasons, when you enable encryption on a Nextcloud server only new and changed files are encrypted.')
4 changes: 2 additions & 2 deletions apps/settings/src/main-admin-security.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { loadState } from '@nextcloud/initial-state'
import Vue from 'vue'

import AdminTwoFactor from './components/AdminTwoFactor.vue'
import Encryption from './components/Encryption.vue'
import EncryptionSettings from './components/Encryption/EncryptionSettings.vue'
import store from './store/admin-security.js'

// eslint-disable-next-line camelcase
Expand All @@ -28,5 +28,5 @@ new View({
store,
}).$mount('#two-factor-auth-settings')

const EncryptionView = Vue.extend(Encryption)
const EncryptionView = Vue.extend(EncryptionSettings)
new EncryptionView().$mount('#vue-admin-encryption')

0 comments on commit 94e1323

Please sign in to comment.