2FAuth/resources/js/views/Start.vue

236 lines
9.1 KiB
Vue

<template>
<div id="quick-uploader">
<!-- static landing UI -->
<div v-show="!(showStream && canStream)" class="container has-text-centered">
<div class="columns quick-uploader">
<!-- trailer phrase that invite to add an account -->
<div class="column is-full quick-uploader-header" :class="{ 'is-invisible' : accountCount > 0 }">
{{ $t('twofaccounts.no_account_here') }}<br>
{{ $t('twofaccounts.add_first_account') }}
</div>
<!-- Livescan button -->
<div class="column is-full quick-uploader-button" >
<div class="quick-uploader-centerer">
<!-- upload a qr code (with basic file field and backend decoding) -->
<label v-if="$root.appSettings.useBasicQrcodeReader" class="button is-link is-medium is-rounded is-focused" >
<input class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput">
{{ $t('twofaccounts.forms.upload_qrcode') }}
</label>
<!-- scan button that launch camera stream -->
<label v-else class="button is-link is-medium is-rounded is-focused" @click="enableStream()">
{{ $t('twofaccounts.forms.scan_qrcode') }}
</label>
</div>
</div>
<!-- alternative methods -->
<div class="column is-full">
<div class="block has-text-light">Alternative methods</div>
<!-- upload a qr code (with qrcode-capture component) -->
<div class="block has-text-link" v-if="!$root.appSettings.useBasicQrcodeReader">
<form @submit.prevent="createAccount" @keydown="form.onKeydown($event)">
<label :class="{'is-loading' : form.isBusy}" class="button is-link is-outlined is-rounded">
<qrcode-capture @decode="submitUri" class="file-input" ref="qrcodeInput" />
{{ $t('twofaccounts.forms.upload_qrcode') }}
</label>
<field-error :form="form" field="qrcode" />
<field-error :form="form" field="uri" />
</form>
</div>
<!-- link to advanced form -->
<div class="block has-text-link">
<router-link class="button is-link is-outlined is-rounded" :to="{ name: 'createAccount' }" >
{{ $t('twofaccounts.forms.use_advanced_form') }}
</router-link>
</div>
</div>
</div>
<!-- Footer -->
<vue-footer :showButtons="true">
<!-- back button -->
<p class="control">
<router-link class="button is-dark is-rounded" :to="{ name: '/' }" >
{{ $t('commons.back') }}
</router-link>
</p>
</vue-footer>
</div>
<!-- camera stream fullscreen scanner -->
<div v-show="showStream && canStream">
<div class="fullscreen-alert has-text-centered">
<span class="is-size-4 has-text-light">
<font-awesome-icon :icon="['fas', 'spinner']" size="2x" spin />
</span>
</div>
<div class="fullscreen-streamer">
<qrcode-stream @decode="submitUri" @init="onStreamerInit" :camera="camera" />
</div>
<div class="fullscreen-footer">
<!-- Cancel button -->
<label class="button is-large is-warning is-rounded" @click="disableStream()">
{{ $t('commons.cancel') }}
</label>
</div>
</div>
</div>
</template>
<script>
import Form from './../components/Form'
export default {
name: 'Start',
data(){
return {
form: new Form({
qrcode: null,
uri: '',
}),
errorName: '',
errorText: '',
showStream: false,
canStream: true,
camera: 'auto',
}
},
props: ['accountCount'],
created() {
if( this.$root.appSettings.useBasicQrcodeReader ) {
// User has set the basic QR code reader (run by backend) so we disable the other one (run by js)
this.canStream = this.showStream = false
}
else {
if( this.accountCount > 0 && this.$root.appSettings.useDirectCapture ) {
this.enableStream()
}
}
},
beforeDestroy() {
this.form.clear()
},
methods: {
async enableStream() {
this.setUploader()
this.camera = 'auto'
this.showStream = true
console.log('stream enabled')
},
async disableStream() {
this.camera = 'off'
this.showStream = false
this.$parent.$emit('stopStreaming')
console.log('stream disabled')
},
async onStreamerInit (promise) {
this.errorText = ''
this.errorName = ''
try {
await promise
}
catch (error) {
this.errorName = error.name
if (error.name === 'NotAllowedError') {
this.errorText = this.$t('twofaccounts.stream.need_grant_permission')
} else if (error.name === 'NotReadableError') {
this.errorText = this.$t('twofaccounts.stream.not_readable')
} else if (error.name === 'NotFoundError') {
this.errorText = this.$t('twofaccounts.stream.no_cam_on_device')
} else if (error.name === 'NotSupportedError' || error.name === 'InsecureContextError') {
this.errorText = this.$t('twofaccounts.stream.secured_context_required')
} else if (error.name === 'OverconstrainedError') {
this.errorText = this.$t('twofaccounts.stream.camera_not_suitable')
} else if (error.name === 'StreamApiNotSupportedError') {
this.errorText = this.$t('twofaccounts.stream.stream_api_not_supported')
}
}
},
setUploader() {
if( this.errorName ) {
console.log('fail to stream : ' + this.errorText)
this.canStream = false
this.$notify({ type: 'is-warning', text: this.errorText })
}
else
{
if( !this.errorName && !this.showStream ) {
this.camera = 'off'
console.log('stream stopped')
}
console.log('stream started')
}
},
/**
* the basicQRcodeReader option is On, so qrcode decoding has to be done by the backend, which in return
* send the corresponding URI
*/
async submitQrCode() {
let imgdata = new FormData();
imgdata.append('qrcode', this.$refs.qrcodeInput.files[0]);
imgdata.append('inputFormat', 'fileUpload');
const { data } = await this.form.upload('/api/qrcode/decode', imgdata)
this.pushUriToCreateForm(data.uri)
},
/**
* The basicQRcodeReader option is Off, so qrcode decoding has already be done by vue-qrcode-reader, whether
* from livescan or file input.
* We simply check the uri validity to prevent useless push to the Create form, but the form will check uri validity too.
*/
async submitUri(event) {
// We post the URI automatically decoded by vue-qrcode-reader
this.form.uri = event
if( !this.form.uri ) {
this.$notify({type: 'is-warning', text: this.$t('errors.qrcode_cannot_be_read') })
}
else if( this.form.uri.slice(0, 15 ).toLowerCase() !== "otpauth://totp/" && this.form.uri.slice(0, 15 ).toLowerCase() !== "otpauth://hotp/" ) {
this.$notify({type: 'is-warning', text: this.$t('errors.no_valid_otp') })
}
else {
this.pushUriToCreateForm(this.form.uri)
}
},
pushUriToCreateForm(data) {
this.$router.push({ name: 'createAccount', params: { decodedUri: data } });
}
}
};
</script>