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
|
config: BackupMutableConfig
|
||||||
) => hass.callWS({ type: "backup/config/update", ...config });
|
) => hass.callWS({ type: "backup/config/update", ...config });
|
||||||
|
|
||||||
export const getBackupDownloadUrl = (id: string, agentId: string) =>
|
export const getBackupDownloadUrl = (
|
||||||
`/api/backup/download/${id}?agent_id=${agentId}`;
|
id: string,
|
||||||
|
agentId: string,
|
||||||
|
password?: string | null
|
||||||
|
) =>
|
||||||
|
`/api/backup/download/${id}?agent_id=${agentId}${password ? `&password=${password}` : ""}`;
|
||||||
|
|
||||||
export const fetchBackupInfo = (hass: HomeAssistant): Promise<BackupInfo> =>
|
export const fetchBackupInfo = (hass: HomeAssistant): Promise<BackupInfo> =>
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
@ -246,6 +250,19 @@ export const getPreferredAgentForDownload = (agents: string[]) => {
|
|||||||
return agents[0];
|
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 CORE_LOCAL_AGENT = "backup.local";
|
||||||
export const HASSIO_LOCAL_AGENT = "hassio.local";
|
export const HASSIO_LOCAL_AGENT = "hassio.local";
|
||||||
export const CLOUD_AGENT = "cloud.cloud";
|
export const CLOUD_AGENT = "cloud.cloud";
|
||||||
|
@ -33,15 +33,12 @@ import "../../../components/ha-icon-next";
|
|||||||
import "../../../components/ha-icon-overflow-menu";
|
import "../../../components/ha-icon-overflow-menu";
|
||||||
import "../../../components/ha-list-item";
|
import "../../../components/ha-list-item";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import { getSignedPath } from "../../../data/auth";
|
|
||||||
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
||||||
import {
|
import {
|
||||||
computeBackupAgentName,
|
computeBackupAgentName,
|
||||||
deleteBackup,
|
deleteBackup,
|
||||||
generateBackup,
|
generateBackup,
|
||||||
generateBackupWithAutomaticSettings,
|
generateBackupWithAutomaticSettings,
|
||||||
getBackupDownloadUrl,
|
|
||||||
getPreferredAgentForDownload,
|
|
||||||
isLocalAgent,
|
isLocalAgent,
|
||||||
isNetworkMountAgent,
|
isNetworkMountAgent,
|
||||||
} from "../../../data/backup";
|
} from "../../../data/backup";
|
||||||
@ -60,10 +57,10 @@ import { haStyle } from "../../../resources/styles";
|
|||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { HomeAssistant, Route } from "../../../types";
|
||||||
import { brandsUrl } from "../../../util/brands-url";
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
import { bytesToString } from "../../../util/bytes-to-string";
|
import { bytesToString } from "../../../util/bytes-to-string";
|
||||||
import { fileDownload } from "../../../util/file_download";
|
|
||||||
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
||||||
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
||||||
import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
|
import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
|
||||||
|
import { downloadBackup } from "./helper/download_backup";
|
||||||
|
|
||||||
interface BackupRow extends DataTableRowData, BackupContent {
|
interface BackupRow extends DataTableRowData, BackupContent {
|
||||||
formatted_type: string;
|
formatted_type: string;
|
||||||
@ -487,12 +484,12 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _downloadBackup(backup: BackupContent): Promise<void> {
|
private async _downloadBackup(backup: BackupContent): Promise<void> {
|
||||||
const preferedAgent = getPreferredAgentForDownload(backup!.agent_ids!);
|
downloadBackup(
|
||||||
const signedUrl = await getSignedPath(
|
|
||||||
this.hass,
|
this.hass,
|
||||||
getBackupDownloadUrl(backup.backup_id, preferedAgent)
|
this,
|
||||||
|
backup,
|
||||||
|
this.config?.create_backup.password
|
||||||
);
|
);
|
||||||
fileDownload(signedUrl.path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _deleteBackup(backup: BackupContent): Promise<void> {
|
private async _deleteBackup(backup: BackupContent): Promise<void> {
|
||||||
|
@ -20,15 +20,16 @@ import "../../../components/ha-icon-button";
|
|||||||
import "../../../components/ha-list-item";
|
import "../../../components/ha-list-item";
|
||||||
import "../../../components/ha-md-list";
|
import "../../../components/ha-md-list";
|
||||||
import "../../../components/ha-md-list-item";
|
import "../../../components/ha-md-list-item";
|
||||||
import { getSignedPath } from "../../../data/auth";
|
import type {
|
||||||
import type { BackupContentExtended, BackupData } from "../../../data/backup";
|
BackupConfig,
|
||||||
|
BackupContentExtended,
|
||||||
|
BackupData,
|
||||||
|
} from "../../../data/backup";
|
||||||
import {
|
import {
|
||||||
compareAgents,
|
compareAgents,
|
||||||
computeBackupAgentName,
|
computeBackupAgentName,
|
||||||
deleteBackup,
|
deleteBackup,
|
||||||
fetchBackupDetails,
|
fetchBackupDetails,
|
||||||
getBackupDownloadUrl,
|
|
||||||
getPreferredAgentForDownload,
|
|
||||||
isLocalAgent,
|
isLocalAgent,
|
||||||
isNetworkMountAgent,
|
isNetworkMountAgent,
|
||||||
} from "../../../data/backup";
|
} from "../../../data/backup";
|
||||||
@ -37,11 +38,11 @@ import "../../../layouts/hass-subpage";
|
|||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { brandsUrl } from "../../../util/brands-url";
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
import { bytesToString } from "../../../util/bytes-to-string";
|
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 "./components/ha-backup-data-picker";
|
||||||
import { showRestoreBackupDialog } from "./dialogs/show-dialog-restore-backup";
|
import { showRestoreBackupDialog } from "./dialogs/show-dialog-restore-backup";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
|
import { downloadBackup } from "./helper/download_backup";
|
||||||
|
|
||||||
interface Agent {
|
interface Agent {
|
||||||
id: string;
|
id: string;
|
||||||
@ -67,6 +68,8 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: "backup-id" }) public backupId!: string;
|
@property({ attribute: "backup-id" }) public backupId!: string;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public config?: BackupConfig;
|
||||||
|
|
||||||
@state() private _backup?: BackupContentExtended | null;
|
@state() private _backup?: BackupContentExtended | null;
|
||||||
|
|
||||||
@state() private _agents: Agent[] = [];
|
@state() private _agents: Agent[] = [];
|
||||||
@ -377,13 +380,13 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _downloadBackup(agentId?: string): Promise<void> {
|
private async _downloadBackup(agentId?: string): Promise<void> {
|
||||||
const preferedAgent =
|
await downloadBackup(
|
||||||
agentId ?? getPreferredAgentForDownload(this._backup!.agent_ids!);
|
|
||||||
const signedUrl = await getSignedPath(
|
|
||||||
this.hass,
|
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> {
|
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": {
|
"show_encryption_key": {
|
||||||
"title": "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."
|
"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": {
|
"agents": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user