From 61d177aecd4a13e632a19ca66d82c34ace991f3b Mon Sep 17 00:00:00 2001 From: Bubka <858858+Bubka@users.noreply.github.com> Date: Fri, 2 Sep 2022 14:28:57 +0200 Subject: [PATCH] Handle icon & qrcode upload failure gracefully --- app/Api/v1/Controllers/IconController.php | 10 +++-- app/Api/v1/Controllers/QrCodeController.php | 4 +- resources/js/components/Form.js | 2 +- resources/js/views/Start.vue | 17 ++++---- resources/js/views/twofaccounts/Create.vue | 45 ++++++++++++--------- resources/js/views/twofaccounts/Edit.vue | 10 +++-- resources/lang/en/errors.php | 3 +- 7 files changed, 55 insertions(+), 36 deletions(-) diff --git a/app/Api/v1/Controllers/IconController.php b/app/Api/v1/Controllers/IconController.php index 7bd65732..e3f06e82 100644 --- a/app/Api/v1/Controllers/IconController.php +++ b/app/Api/v1/Controllers/IconController.php @@ -22,11 +22,13 @@ public function upload(Request $request) $this->validate($request, [ 'icon' => 'required|image', ]); - - $path = $request->file('icon')->store('', 'icons'); - $response = array( "filename" => pathinfo($path)['basename']); - return response()->json($response, 201); + $icon = $request->file('icon'); + $path = $icon instanceof \Illuminate\Http\UploadedFile ? $icon->store('', 'icons') : false; + + return $path + ? response()->json(['filename' => pathinfo($path)['basename']], 201) + : response()->json(['message' => __('errors.file_upload_failed')], 500); } diff --git a/app/Api/v1/Controllers/QrCodeController.php b/app/Api/v1/Controllers/QrCodeController.php index 8e4209ec..68822e7b 100644 --- a/app/Api/v1/Controllers/QrCodeController.php +++ b/app/Api/v1/Controllers/QrCodeController.php @@ -35,7 +35,9 @@ public function decode(QrCodeDecodeRequest $request) { $file = $request->file('qrcode'); - return response()->json(['data' => QrCode::decode($file)], 200); + return $file instanceof \Illuminate\Http\UploadedFile + ? response()->json(['data' => QrCode::decode($file)], 200) + : response()->json(['message' => __('errors.file_upload_failed')], 500); } } \ No newline at end of file diff --git a/resources/js/components/Form.js b/resources/js/components/Form.js index 383849d6..ba2323ca 100644 --- a/resources/js/components/Form.js +++ b/resources/js/components/Form.js @@ -213,7 +213,7 @@ class Form { return new Promise((resolve, reject) => { // (Form.axios || axios).request({ url: this.route(url), method, data, ...config }) - Vue.axios.request({ url: this.route(url), method: 'post', data: formData, header: {'Content-Type' : 'multipart/form-data'} }) + Vue.axios.request({ url: this.route(url), method: 'post', data: formData, header: {'Content-Type' : 'multipart/form-data'}, ...config }) .then(response => { this.finishProcessing() diff --git a/resources/js/views/Start.vue b/resources/js/views/Start.vue index 593b1acc..c6b7456f 100644 --- a/resources/js/views/Start.vue +++ b/resources/js/views/Start.vue @@ -110,18 +110,21 @@ * Upload the submitted QR code file to the backend for decoding, then route the user * to the Create or Import form with decoded URI to prefill the form */ - async submitQrCode() { + submitQrCode() { let imgdata = new FormData(); imgdata.append('qrcode', this.$refs.qrcodeInput.files[0]); imgdata.append('inputFormat', 'fileUpload'); - const { data } = await this.form.upload('/api/v1/qrcode/decode', imgdata) - - if( data.data.slice(0, 33).toLowerCase() === "otpauth-migration://offline?data=" ) { - this.$router.push({ name: 'importAccounts', params: { migrationUri: data.data } }); - } - else this.$router.push({ name: 'createAccount', params: { decodedUri: data.data } }); + this.form.upload('/api/v1/qrcode/decode', imgdata, {returnError: true}).then(response => { + if( response.data.data.slice(0, 33).toLowerCase() === "otpauth-migration://offline?data=" ) { + this.$router.push({ name: 'importAccounts', params: { migrationUri: response.data.data } }); + } + else this.$router.push({ name: 'createAccount', params: { decodedUri: response.data.data } }); + }) + .catch(error => { + this.$notify({type: 'is-danger', text: this.$t(error.response.data.message) }) + }); }, /** diff --git a/resources/js/views/twofaccounts/Create.vue b/resources/js/views/twofaccounts/Create.vue index 6cff70d9..2f635739 100644 --- a/resources/js/views/twofaccounts/Create.vue +++ b/resources/js/views/twofaccounts/Create.vue @@ -340,32 +340,38 @@ this.$router.push({name: 'accounts'}); }, - async uploadQrcode(event) { + uploadQrcode(event) { let imgdata = new FormData(); imgdata.append('qrcode', this.$refs.qrcodeInput.files[0]); imgdata.append('inputFormat', 'fileUpload'); // First we get the uri encoded in the qrcode - const { data } = await this.form.upload('/api/v1/qrcode/decode', imgdata) - this.uri = data.data - - // Then the otp described by the uri - this.axios.post('/api/v1/twofaccounts/preview', { uri: data.data }).then(response => { - this.form.fill(response.data) - this.secretIsBase32Encoded = 1 - this.tempIcon = response.data.icon ? response.data.icon : null + this.form.upload('/api/v1/qrcode/decode', imgdata, {returnError: true}).then(response => { + this.uri = response.data.data + + // Then the otp described by the uri + this.axios.post('/api/v1/twofaccounts/preview', { uri: this.uri }).then(response => { + this.form.fill(response.data) + this.secretIsBase32Encoded = 1 + this.tempIcon = response.data.icon ? response.data.icon : null + }) + .catch(error => { + if( error.response.status === 422 ) { + if( error.response.data.errors.uri ) { + this.showAlternatives = true + } + } + }); }) .catch(error => { - if( error.response.status === 422 ) { - if( error.response.data.errors.uri ) { - this.showAlternatives = true - } - } + this.$notify({type: 'is-danger', text: this.$t(error.response.data.message) }) + return false }); + }, - async uploadIcon(event) { + uploadIcon(event) { // clean possible already uploaded temp icon this.deleteIcon() @@ -373,9 +379,12 @@ let imgdata = new FormData(); imgdata.append('icon', this.$refs.iconInput.files[0]); - const { data } = await this.form.upload('/api/v1/icons', imgdata) - - this.tempIcon = data.filename; + this.form.upload('/api/v1/icons', imgdata, {returnError: true}).then(response => { + this.tempIcon = response.data.filename; + }) + .catch(error => { + this.$notify({type: 'is-danger', text: this.$t(error.response.data.message) }) + }); }, fetchLogo() { diff --git a/resources/js/views/twofaccounts/Edit.vue b/resources/js/views/twofaccounts/Edit.vue index 6b46888d..250855d1 100644 --- a/resources/js/views/twofaccounts/Edit.vue +++ b/resources/js/views/twofaccounts/Edit.vue @@ -265,10 +265,12 @@ let imgdata = new FormData(); imgdata.append('icon', this.$refs.iconInput.files[0]); - const { data } = await this.form.upload('/api/v1/icons', imgdata) - - this.tempIcon = data.filename; - + this.form.upload('/api/v1/icons', imgdata, {returnError: true}).then(response => { + this.tempIcon = response.data.filename; + }) + .catch(error => { + this.$notify({type: 'is-danger', text: this.$t(error.response.data.message) }) + }); }, fetchLogo() { diff --git a/resources/lang/en/errors.php b/resources/lang/en/errors.php index b3a91e4b..ba43bccf 100644 --- a/resources/lang/en/errors.php +++ b/resources/lang/en/errors.php @@ -41,5 +41,6 @@ 'auth_proxy_failed_legend' => '2Fauth is configured to run behind an authentication proxy but your proxy does not return the expected header. Check your configuration and try again.', 'invalid_google_auth_migration' => 'Invalid or unreadable Google Authenticator data', 'unsupported_otp_type' => 'Unsupported OTP type', - 'no_logo_found_for_x' => 'No logo available for {service}' + 'no_logo_found_for_x' => 'No logo available for {service}', + 'file_upload_failed' => 'File upload failed' ]; \ No newline at end of file