mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Improve encrypted backup dialog (#23991)
* Improve encrypted backup dialog * Remove unused code
This commit is contained in:
parent
15f33e1f19
commit
f0a56e75f5
@ -0,0 +1,225 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-icon-next";
|
||||
import "../../../../components/ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-password-field";
|
||||
import "../../../../components/ha-alert";
|
||||
import {
|
||||
canDecryptBackupOnDownload,
|
||||
getPreferredAgentForDownload,
|
||||
} from "../../../../data/backup";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { downloadBackupFile } from "../helper/download_backup";
|
||||
import type { DownloadDecryptedBackupDialogParams } from "./show-dialog-download-decrypted-backup";
|
||||
|
||||
@customElement("ha-dialog-download-decrypted-backup")
|
||||
class DialogDownloadDecryptedBackup extends LitElement implements HassDialog {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _params?: DownloadDecryptedBackupDialogParams;
|
||||
|
||||
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
||||
|
||||
@state() private _encryptionKey = "";
|
||||
|
||||
@state() private _error = "";
|
||||
|
||||
public showDialog(params: DownloadDecryptedBackupDialogParams): void {
|
||||
this._opened = true;
|
||||
this._params = params;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._dialog?.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
private _dialogClosed() {
|
||||
if (this._opened) {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
this._opened = false;
|
||||
this._params = undefined;
|
||||
this._encryptionKey = "";
|
||||
this._error = "";
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._opened || !this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-md-dialog open @closed=${this._dialogClosed} disable-cancel-action>
|
||||
<ha-dialog-header slot="headline">
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
@click=${this.closeDialog}
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
<span slot="title">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.title"
|
||||
)}
|
||||
</span>
|
||||
</ha-dialog-header>
|
||||
|
||||
<div slot="content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.description"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.download_backup_encrypted",
|
||||
{
|
||||
download_it_encrypted: html`<button
|
||||
class="link"
|
||||
@click=${this._downloadEncrypted}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.download_it_encrypted"
|
||||
)}
|
||||
</button>`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
|
||||
<ha-password-field
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.encryption_key"
|
||||
)}
|
||||
@input=${this._keyChanged}
|
||||
></ha-password-field>
|
||||
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<ha-button @click=${this._cancel}>
|
||||
${this.hass.localize("ui.dialogs.generic.cancel")}
|
||||
</ha-button>
|
||||
|
||||
<ha-button @click=${this._submit}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.download"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _cancel() {
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private async _submit() {
|
||||
if (this._encryptionKey === "") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await canDecryptBackupOnDownload(
|
||||
this.hass,
|
||||
this._params!.backup.backup_id,
|
||||
this._agentId,
|
||||
this._encryptionKey
|
||||
);
|
||||
downloadBackupFile(
|
||||
this.hass,
|
||||
this._params!.backup.backup_id,
|
||||
this._agentId,
|
||||
this._encryptionKey
|
||||
);
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
if (err?.code === "password_incorrect") {
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.incorrect_encryption_key"
|
||||
);
|
||||
} else if (err?.code === "decrypt_not_supported") {
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.decryption_not_supported"
|
||||
);
|
||||
} else {
|
||||
alert(err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _keyChanged(ev) {
|
||||
this._encryptionKey = ev.currentTarget.value;
|
||||
this._error = "";
|
||||
}
|
||||
|
||||
private get _agentId() {
|
||||
if (this._params?.agentId) {
|
||||
return this._params.agentId;
|
||||
}
|
||||
return getPreferredAgentForDownload(
|
||||
Object.keys(this._params!.backup.agents)
|
||||
);
|
||||
}
|
||||
|
||||
private async _downloadEncrypted() {
|
||||
downloadBackupFile(
|
||||
this.hass,
|
||||
this._params!.backup.backup_id,
|
||||
this._agentId
|
||||
);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-md-dialog {
|
||||
--dialog-content-padding: 8px 24px;
|
||||
max-width: 500px;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-md-dialog {
|
||||
max-width: none;
|
||||
}
|
||||
div[slot="content"] {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button.link {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
color: var(--primary-color);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-dialog-download-decrypted-backup": DialogDownloadDecryptedBackup;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { BackupContent } from "../../../../data/backup";
|
||||
|
||||
export interface DownloadDecryptedBackupDialogParams {
|
||||
backup: BackupContent;
|
||||
agentId?: string;
|
||||
}
|
||||
|
||||
export const loadDownloadDecryptedBackupDialog = () =>
|
||||
import("./dialog-download-decrypted-backup");
|
||||
|
||||
export const showDownloadDecryptedBackupDialog = (
|
||||
element: HTMLElement,
|
||||
params: DownloadDecryptedBackupDialogParams
|
||||
) => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "ha-dialog-download-decrypted-backup",
|
||||
dialogImport: loadDownloadDecryptedBackupDialog,
|
||||
dialogParams: params,
|
||||
});
|
||||
};
|
@ -531,12 +531,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _downloadBackup(backup: BackupContent): Promise<void> {
|
||||
downloadBackup(
|
||||
this.hass,
|
||||
this,
|
||||
backup,
|
||||
this.config?.create_backup.password
|
||||
);
|
||||
downloadBackup(this.hass, this, backup, this.config);
|
||||
}
|
||||
|
||||
private async _deleteBackup(backup: BackupContent): Promise<void> {
|
||||
|
@ -401,13 +401,7 @@ class HaConfigBackupDetails extends LitElement {
|
||||
}
|
||||
|
||||
private async _downloadBackup(agentId?: string): Promise<void> {
|
||||
await downloadBackup(
|
||||
this.hass,
|
||||
this,
|
||||
this._backup!,
|
||||
this.config?.create_backup.password,
|
||||
agentId
|
||||
);
|
||||
await downloadBackup(this.hass, this, this._backup!, this.config, agentId);
|
||||
}
|
||||
|
||||
private async _deleteBackup(): Promise<void> {
|
||||
|
@ -1,20 +1,17 @@
|
||||
import type { LitElement } from "lit";
|
||||
import { getSignedPath } from "../../../../data/auth";
|
||||
import type { BackupConfig, BackupContent } from "../../../../data/backup";
|
||||
import {
|
||||
canDecryptBackupOnDownload,
|
||||
getBackupDownloadUrl,
|
||||
getPreferredAgentForDownload,
|
||||
type BackupContent,
|
||||
} from "../../../../data/backup";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../lovelace/custom-card-helpers";
|
||||
import { getSignedPath } from "../../../../data/auth";
|
||||
import { fileDownload } from "../../../../util/file_download";
|
||||
import { showAlertDialog } from "../../../lovelace/custom-card-helpers";
|
||||
import { showDownloadDecryptedBackupDialog } from "../dialogs/show-dialog-download-decrypted-backup";
|
||||
|
||||
const triggerDownload = async (
|
||||
export const downloadBackupFile = async (
|
||||
hass: HomeAssistant,
|
||||
backupId: string,
|
||||
preferedAgent: string,
|
||||
@ -27,120 +24,79 @@ const triggerDownload = async (
|
||||
fileDownload(signedUrl.path);
|
||||
};
|
||||
|
||||
const downloadEncryptedBackup = async (
|
||||
hass: HomeAssistant,
|
||||
element: LitElement,
|
||||
backup: BackupContent,
|
||||
agentId?: string
|
||||
) => {
|
||||
if (
|
||||
await showConfirmationDialog(element, {
|
||||
title: "Encryption key incorrect",
|
||||
text: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.incorrect_entered_encryption_key"
|
||||
),
|
||||
confirmText: "Download encrypted",
|
||||
})
|
||||
) {
|
||||
const agentIds = Object.keys(backup.agents);
|
||||
const preferedAgent = agentId ?? getPreferredAgentForDownload(agentIds);
|
||||
|
||||
triggerDownload(hass, backup.backup_id, preferedAgent);
|
||||
}
|
||||
};
|
||||
|
||||
const requestEncryptionKey = async (
|
||||
hass: HomeAssistant,
|
||||
element: LitElement,
|
||||
backup: BackupContent,
|
||||
agentId?: string
|
||||
): Promise<void> => {
|
||||
const encryptionKey = await showPromptDialog(element, {
|
||||
title: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.show_encryption_key.title"
|
||||
),
|
||||
text: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.incorrect_current_encryption_key"
|
||||
),
|
||||
inputLabel: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.show_encryption_key.title"
|
||||
),
|
||||
inputType: "password",
|
||||
confirmText: hass.localize("ui.common.download"),
|
||||
});
|
||||
if (encryptionKey === null) {
|
||||
return;
|
||||
}
|
||||
downloadBackup(hass, element, backup, encryptionKey, agentId, true);
|
||||
};
|
||||
|
||||
export const downloadBackup = async (
|
||||
hass: HomeAssistant,
|
||||
element: LitElement,
|
||||
backup: BackupContent,
|
||||
encryptionKey?: string | null,
|
||||
agentId?: string,
|
||||
userProvided = false
|
||||
backupConfig?: BackupConfig,
|
||||
agentId?: string
|
||||
): Promise<void> => {
|
||||
const agentIds = Object.keys(backup.agents);
|
||||
const preferedAgent = agentId ?? getPreferredAgentForDownload(agentIds);
|
||||
const isProtected = backup.agents[preferedAgent]?.protected;
|
||||
|
||||
if (isProtected) {
|
||||
if (encryptionKey) {
|
||||
try {
|
||||
await canDecryptBackupOnDownload(
|
||||
hass,
|
||||
backup.backup_id,
|
||||
preferedAgent,
|
||||
encryptionKey
|
||||
);
|
||||
} catch (err: any) {
|
||||
if (err?.code === "password_incorrect") {
|
||||
if (userProvided) {
|
||||
downloadEncryptedBackup(hass, element, backup, agentId);
|
||||
} else {
|
||||
requestEncryptionKey(hass, element, backup, agentId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (err?.code === "decrypt_not_supported") {
|
||||
showAlertDialog(element, {
|
||||
title: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.decryption_unsupported_title"
|
||||
),
|
||||
text: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.decryption_unsupported"
|
||||
),
|
||||
confirm() {
|
||||
triggerDownload(hass, backup.backup_id, preferedAgent);
|
||||
},
|
||||
});
|
||||
encryptionKey = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
showAlertDialog(element, {
|
||||
title: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.error_check_title",
|
||||
{
|
||||
error: err.message,
|
||||
}
|
||||
),
|
||||
text: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.error_check_description",
|
||||
{
|
||||
error: err.message,
|
||||
}
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
requestEncryptionKey(hass, element, backup, agentId);
|
||||
return;
|
||||
}
|
||||
if (!isProtected) {
|
||||
downloadBackupFile(hass, backup.backup_id, preferedAgent);
|
||||
}
|
||||
|
||||
await triggerDownload(hass, backup.backup_id, preferedAgent, encryptionKey);
|
||||
const encryptionKey = backupConfig?.create_backup?.password;
|
||||
|
||||
if (!encryptionKey) {
|
||||
showDownloadDecryptedBackupDialog(element, {
|
||||
backup,
|
||||
agentId: preferedAgent,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if we can decrypt it
|
||||
await canDecryptBackupOnDownload(
|
||||
hass,
|
||||
backup.backup_id,
|
||||
preferedAgent,
|
||||
encryptionKey
|
||||
);
|
||||
downloadBackupFile(hass, backup.backup_id, preferedAgent, encryptionKey);
|
||||
} catch (err: any) {
|
||||
// If encryption key is incorrect, ask for encryption key
|
||||
if (err?.code === "password_incorrect") {
|
||||
showDownloadDecryptedBackupDialog(element, {
|
||||
backup,
|
||||
agentId: preferedAgent,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// If decryption is not supported, ask for confirmation and download it encrypted
|
||||
if (err?.code === "decrypt_not_supported") {
|
||||
showAlertDialog(element, {
|
||||
title: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.decryption_unsupported_title"
|
||||
),
|
||||
text: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.decryption_unsupported"
|
||||
),
|
||||
confirm() {
|
||||
downloadBackupFile(hass, backup.backup_id, preferedAgent);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Else, show generic error
|
||||
showAlertDialog(element, {
|
||||
title: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.error_check_title",
|
||||
{
|
||||
error: err.message,
|
||||
}
|
||||
),
|
||||
text: hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.error_check_description",
|
||||
{
|
||||
error: err.message,
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -2392,11 +2392,16 @@
|
||||
"download": {
|
||||
"decryption_unsupported_title": "Decryption unsupported",
|
||||
"decryption_unsupported": "Decryption is not supported for this backup. The downloaded backup will remain encrypted and can't be opened. To restore it, you will need the encryption key.",
|
||||
"incorrect_entered_encryption_key": "The entered encryption key was incorrect, try again or download the encrypted backup. The encrypted backup can't be opened. To restore it, you will need the encryption key.",
|
||||
"download_encrypted": "Download encrypted",
|
||||
"incorrect_current_encryption_key": "This backup is encrypted with a different encryption key than the current one, please enter the encryption key of this backup.",
|
||||
"error_check_title": "Error checking backup",
|
||||
"error_check_description": "An error occurred while checking the backup, please try again. Error message: {error}"
|
||||
"error_check_description": "An error occurred while checking the backup, please try again. Error message: {error}",
|
||||
"title": "Download backup",
|
||||
"description": "This backup is encrypted with a different encryption key than the current one, please enter the encryption key of this backup.",
|
||||
"download_backup_encrypted": "You can still {download_it_encrypted}. To restore it, you will need the encryption key.",
|
||||
"download_it_encrypted": "download the backup encrypted",
|
||||
"encryption_key": "Encryption key",
|
||||
"incorrect_encryption_key": "Incorrect encryption key",
|
||||
"decryption_not_supported": "Decryption not supported",
|
||||
"download": "Download"
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user