');
}
[$uid, $rel] = [$m[1], trim($m[2] ?? '', '/')];
@@ -127,7 +147,7 @@ class RestoreService {
if (!is_dir($directory) && !mkdir($directory, 0750, true) && !is_dir($directory)) {
throw new \RuntimeException("Cannot create $directory");
}
- return $this->targets->repository($target)->restore($snapshotId, $directory, trim($prefix, '/') === '' ? '' : trim($prefix, '/') . '/');
+ return $this->withSharedLock($target, fn () => $this->targets->repository($target)->restore($snapshotId, $directory, trim($prefix, '/') === '' ? '' : trim($prefix, '/') . '/'));
}
private function writeFile(Repository $repo, Folder $dest, string $relPath, array $entry): void {
diff --git a/src/api.js b/src/api.js
index 997b6ee..12c4873 100644
--- a/src/api.js
+++ b/src/api.js
@@ -24,7 +24,7 @@ export default {
trash: (targetId) => call('get', `/targets/${targetId}/trash`),
untrash: (targetId, snapshotId) => call('post', `/targets/${targetId}/trash/${snapshotId}`),
kitUrl: () => url('/key/kit'),
- confirmKit: (accepted, phrase) => call('post', '/key/confirm', { accepted, phrase }),
+ confirmKit: (accepted, phrase, code) => call('post', '/key/confirm', { accepted, phrase, code }),
backends: () => call('get', '/backends'),
addTarget: (target) => call('post', '/targets', target),
removeTarget: (id) => call('delete', `/targets/${id}`),
diff --git a/src/components/KeySetup.vue b/src/components/KeySetup.vue
index 8be90da..16085f0 100644
--- a/src/components/KeySetup.vue
+++ b/src/components/KeySetup.vue
@@ -63,8 +63,10 @@
: t('ng_backup', 'Before the first backup: download the recovery kit and store it, together with the passphrase, somewhere safe outside this server.') }}
{{ t('ng_backup', 'Download recovery kit') }}
+
{{ status.statement }}
- {{ status.confirmationPhrase }}
+ {{ status.confirmationPhrase }}
{{ t('ng_backup', 'Recovery kit confirmed by {user} on {date}.', { user: status.key.confirmation.uid, date: new Date(status.key.confirmation.time * 1000).toLocaleString() }) }}
@@ -99,6 +101,7 @@ const delay = ref(props.status.key.deleteDelayDays)
const slotForm = ref(null)
const downloaded = ref(false)
const accepted = ref(false)
+const code = ref('')
const busy = ref(false)
const error = ref('')
const kitUrl = api.kitUrl()
@@ -148,18 +151,10 @@ const removeSlot = (slot) => guarded(async () => {
downloaded.value = accepted.value = false
})
-async function confirm() {
- busy.value = true
- error.value = ''
- try {
- await api.confirmKit(accepted.value, props.status.confirmationPhrase)
- emit('changed')
- } catch (e) {
- error.value = e.message
- } finally {
- busy.value = false
- }
-}
+const confirm = () => guarded(async () => {
+ await api.confirmKit(accepted.value, props.status.confirmationPhrase, code.value)
+ code.value = ''
+})