mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 07:16:39 +00:00
Check if we can decrypt backup on download (#23756)
Co-authored-by: Kevin Cathcart <kevincathcart@gmail.com> Co-authored-by: Wendelin <w@pe8.at> Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
This commit is contained in:
parent
87907b98bd
commit
fcb6da55d8
@ -152,8 +152,12 @@ export const updateBackupConfig = (
|
||||
config: BackupMutableConfig
|
||||
) => hass.callWS({ type: "backup/config/update", ...config });
|
||||
|
||||
export const getBackupDownloadUrl = (id: string, agentId: string) =>
|
||||
`/api/backup/download/${id}?agent_id=${agentId}`;
|
||||
export const getBackupDownloadUrl = (
|
||||
id: string,
|
||||
agentId: string,
|
||||
password?: string | null
|
||||
) =>
|
||||
`/api/backup/download/${id}?agent_id=${agentId}${password ? `&password=${password}` : ""}`;
|
||||
|
||||
export const fetchBackupInfo = (hass: HomeAssistant): Promise<BackupInfo> =>
|
||||
hass.callWS({
|
||||
@ -246,6 +250,19 @@ export const getPreferredAgentForDownload = (agents: string[]) => {
|
||||
return agents[0];
|
||||
};
|
||||
|
||||
export const canDecryptBackupOnDownload = (
|
||||
hass: HomeAssistant,
|
||||
backup_id: string,
|
||||
agent_id: string,
|
||||
password: string
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "backup/can_decrypt_on_download",
|
||||
backup_id,
|
||||
agent_id,
|
||||
password,
|
||||
});
|
||||
|
||||
export const CORE_LOCAL_AGENT = "backup.local";
|
||||
export const HASSIO_LOCAL_AGENT = "hassio.local";
|
||||
export const CLOUD_AGENT = "cloud.cloud";
|
||||
|
@ -33,15 +33,12 @@ import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-icon-overflow-menu";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
||||
import {
|
||||
computeBackupAgentName,
|
||||
deleteBackup,
|
||||
generateBackup,
|
||||
generateBackupWithAutomaticSettings,
|
||||
getBackupDownloadUrl,
|
||||
getPreferredAgentForDownload,
|
||||
isLocalAgent,
|
||||
isNetworkMountAgent,
|
||||
} from "../../../data/backup";
|
||||
@ -60,10 +57,10 @@ import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { bytesToString } from "../../../util/bytes-to-string";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
||||
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
||||
import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
|
||||
import { downloadBackup } from "./helper/download_backup";
|
||||
|
||||
interface BackupRow extends DataTableRowData, BackupContent {
|
||||
formatted_type: string;
|
||||
@ -487,12 +484,12 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _downloadBackup(backup: BackupContent): Promise<void> {
|
||||
const preferedAgent = getPreferredAgentForDownload(backup!.agent_ids!);
|
||||
const signedUrl = await getSignedPath(
|
||||
downloadBackup(
|
||||
this.hass,
|
||||
getBackupDownloadUrl(backup.backup_id, preferedAgent)
|
||||
this,
|
||||
backup,
|
||||
this.config?.create_backup.password
|
||||
);
|
||||
fileDownload(signedUrl.path);
|
||||
}
|
||||
|
||||
private async _deleteBackup(backup: BackupContent): Promise<void> {
|
||||
|
@ -20,15 +20,16 @@ import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import type { BackupContentExtended, BackupData } from "../../../data/backup";
|
||||
import type {
|
||||
BackupConfig,
|
||||
BackupContentExtended,
|
||||
BackupData,
|
||||
} from "../../../data/backup";
|
||||
import {
|
||||
compareAgents,
|
||||
computeBackupAgentName,
|
||||
deleteBackup,
|
||||
fetchBackupDetails,
|
||||
getBackupDownloadUrl,
|
||||
getPreferredAgentForDownload,
|
||||
isLocalAgent,
|
||||
isNetworkMountAgent,
|
||||
} from "../../../data/backup";
|
||||
@ -37,11 +38,11 @@ import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { bytesToString } from "../../../util/bytes-to-string";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
import { showConfirmationDialog } from "../../lovelace/custom-card-helpers";
|
||||
import "./components/ha-backup-data-picker";
|
||||
import { showRestoreBackupDialog } from "./dialogs/show-dialog-restore-backup";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { downloadBackup } from "./helper/download_backup";
|
||||
|
||||
interface Agent {
|
||||
id: string;
|
||||
@ -67,6 +68,8 @@ class HaConfigBackupDetails extends LitElement {
|
||||
|
||||
@property({ attribute: "backup-id" }) public backupId!: string;
|
||||
|
||||
@property({ attribute: false }) public config?: BackupConfig;
|
||||
|
||||
@state() private _backup?: BackupContentExtended | null;
|
||||
|
||||
@state() private _agents: Agent[] = [];
|
||||
@ -377,13 +380,13 @@ class HaConfigBackupDetails extends LitElement {
|
||||
}
|
||||
|
||||
private async _downloadBackup(agentId?: string): Promise<void> {
|
||||
const preferedAgent =
|
||||
agentId ?? getPreferredAgentForDownload(this._backup!.agent_ids!);
|
||||
const signedUrl = await getSignedPath(
|
||||
await downloadBackup(
|
||||
this.hass,
|
||||
getBackupDownloadUrl(this._backup!.backup_id, preferedAgent)
|
||||
this,
|
||||
this._backup!,
|
||||
this.config?.create_backup.password,
|
||||
agentId
|
||||
);
|
||||
fileDownload(signedUrl.path);
|
||||
}
|
||||
|
||||
private async _deleteBackup(): Promise<void> {
|
||||
|
146
src/panels/config/backup/helper/download_backup.ts
Normal file
146
src/panels/config/backup/helper/download_backup.ts
Normal file
@ -0,0 +1,146 @@
|
||||
import type { LitElement } from "lit";
|
||||
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";
|
||||
|
||||
const triggerDownload = async (
|
||||
hass: HomeAssistant,
|
||||
backupId: string,
|
||||
preferedAgent: string,
|
||||
encryptionKey?: string | null
|
||||
) => {
|
||||
const signedUrl = await getSignedPath(
|
||||
hass,
|
||||
getBackupDownloadUrl(backupId, preferedAgent, encryptionKey)
|
||||
);
|
||||
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",
|
||||
})
|
||||
) {
|
||||
triggerDownload(
|
||||
hass,
|
||||
backup.backup_id,
|
||||
agentId ?? getPreferredAgentForDownload(backup.agent_ids!)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
): Promise<void> => {
|
||||
const preferedAgent =
|
||||
agentId ?? getPreferredAgentForDownload(backup.agent_ids!);
|
||||
|
||||
if (backup.protected) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
await triggerDownload(hass, backup.backup_id, preferedAgent, encryptionKey);
|
||||
};
|
@ -2375,6 +2375,15 @@
|
||||
"show_encryption_key": {
|
||||
"title": "Encryption key",
|
||||
"description": "Make sure you save the encryption key in a secure place so you always have access to your backups."
|
||||
},
|
||||
"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}"
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user