mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-05-30 06:49:11 +02:00
Add the logo fetching feature to the Create/Edit forms
This commit is contained in:
parent
9b634dd55f
commit
3d7607cb53
@ -5,11 +5,13 @@ namespace App\Api\v1\Controllers;
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Services\LogoService;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
|
||||||
|
|
||||||
class IconController extends Controller
|
class IconController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Handle uploaded icon image
|
* Handle uploaded icon image
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
@ -21,15 +23,35 @@ class IconController extends Controller
|
|||||||
'icon' => 'required|image',
|
'icon' => 'required|image',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$path = $request->file('icon')->store('public/icons');
|
|
||||||
$path = $request->file('icon')->store('', 'icons');
|
$path = $request->file('icon')->store('', 'icons');
|
||||||
$response['filename'] = pathinfo($path)['basename'];
|
$response['filename'] = pathinfo($path)['basename'];
|
||||||
|
|
||||||
return response()->json($response, 201);
|
return response()->json($response, 201);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a logo
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function fetch(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'service' => 'string|regex:/^[^:]+$/i',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$logoService = App::make(LogoService::class);
|
||||||
|
$icon = $logoService->getIcon($request->service);
|
||||||
|
|
||||||
|
return $icon
|
||||||
|
? response()->json(['filename' => $icon], 201)
|
||||||
|
: response()->json(null, 204);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* delete an icon
|
* delete an icon
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
:type="nativeType"
|
:type="nativeType"
|
||||||
:disabled="isLoading"
|
:disabled="isLoading || isDisabled"
|
||||||
:class="{
|
:class="{
|
||||||
'button': true,
|
'button': true,
|
||||||
[`${color}`]: true,
|
[`${color}`]: true,
|
||||||
'is-loading': isLoading,
|
'is-loading': isLoading,
|
||||||
}">
|
}"
|
||||||
|
v-on:click="$emit('click')">
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
@ -30,6 +31,11 @@
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="field" :class="{ 'with-offset' : hasOffset }">
|
<div class="field" :class="{ 'pt-3' : hasOffset }">
|
||||||
<label class="label" v-html="label"></label>
|
<label class="label" v-html="label"></label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input :disabled="isDisabled" :id="fieldName" :type="inputType" class="input" v-model="form[fieldName]" :placeholder="placeholder" v-bind="$attrs" />
|
<input :disabled="isDisabled" :id="fieldName" :type="inputType" class="input" v-model="form[fieldName]" :placeholder="placeholder" v-bind="$attrs" v-on:change="$emit('field-changed', form[fieldName])"/>
|
||||||
</div>
|
</div>
|
||||||
<field-error :form="form" :field="fieldName" />
|
<field-error :form="form" :field="fieldName" />
|
||||||
<p class="help" v-html="help" v-if="help"></p>
|
<p class="help" v-html="help" v-if="help"></p>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="field" :class="{ 'with-offset' : hasOffset }">
|
<div class="field" :class="{ 'pt-3' : hasOffset }">
|
||||||
<label class="label" v-html="label"></label>
|
<label class="label" v-html="label"></label>
|
||||||
<div class="is-toggle buttons">
|
<div class="is-toggle buttons">
|
||||||
<label class="button is-dark" :disabled="isDisabled" v-for="choice in choices" :class="{ 'is-link' : form[fieldName] === choice.value }">
|
<label class="button is-dark" :disabled="isDisabled" v-for="choice in choices" :class="{ 'is-link' : form[fieldName] === choice.value }">
|
||||||
|
4
resources/js/packages/fontawesome.js
vendored
4
resources/js/packages/fontawesome.js
vendored
@ -25,6 +25,8 @@ import {
|
|||||||
faTh,
|
faTh,
|
||||||
faList,
|
faList,
|
||||||
faTimesCircle,
|
faTimesCircle,
|
||||||
|
faUpload,
|
||||||
|
faGlobe,
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -54,6 +56,8 @@ library.add(
|
|||||||
faTh,
|
faTh,
|
||||||
faList,
|
faList,
|
||||||
faTimesCircle,
|
faTimesCircle,
|
||||||
|
faUpload,
|
||||||
|
faGlobe,
|
||||||
);
|
);
|
||||||
|
|
||||||
Vue.component('font-awesome-icon', FontAwesomeIcon)
|
Vue.component('font-awesome-icon', FontAwesomeIcon)
|
@ -60,27 +60,42 @@
|
|||||||
<!-- account -->
|
<!-- account -->
|
||||||
<form-field :form="form" fieldName="account" inputType="text" :label="$t('twofaccounts.account')" :placeholder="$t('twofaccounts.forms.account.placeholder')" />
|
<form-field :form="form" fieldName="account" inputType="text" :label="$t('twofaccounts.account')" :placeholder="$t('twofaccounts.forms.account.placeholder')" />
|
||||||
<!-- icon upload -->
|
<!-- icon upload -->
|
||||||
<div class="field">
|
<label class="label">{{ $t('twofaccounts.icon') }}</label>
|
||||||
<label class="label">{{ $t('twofaccounts.icon') }}</label>
|
<div class="field is-grouped">
|
||||||
<div class="file is-dark">
|
<!-- i'm lucky button -->
|
||||||
<label class="file-label">
|
<div class="control">
|
||||||
<input class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
|
<v-button @click="fetchLogo" :color="'is-dark'" :nativeType="'button'" :isDisabled="form.service.length < 3">
|
||||||
<span class="file-cta">
|
<span class="icon is-small">
|
||||||
<span class="file-icon">
|
<font-awesome-icon :icon="['fas', 'globe']" />
|
||||||
<font-awesome-icon :icon="['fas', 'image']" />
|
|
||||||
</span>
|
|
||||||
<span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</label>
|
<span>{{ $t('twofaccounts.forms.i_m_lucky') }}</span>
|
||||||
<span class="tag is-black is-large" v-if="tempIcon">
|
</v-button>
|
||||||
<img class="icon-preview" :src="'/storage/icons/' + tempIcon" >
|
</div>
|
||||||
<button class="delete is-small" @click.prevent="deleteIcon"></button>
|
<!-- upload button -->
|
||||||
</span>
|
<div class="control">
|
||||||
|
<div class="file is-dark">
|
||||||
|
<label class="file-label">
|
||||||
|
<input class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
|
||||||
|
<span class="file-cta">
|
||||||
|
<span class="file-icon">
|
||||||
|
<font-awesome-icon :icon="['fas', 'upload']" />
|
||||||
|
</span>
|
||||||
|
<span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span class="tag is-black is-large" v-if="tempIcon">
|
||||||
|
<img class="icon-preview" :src="'/storage/icons/' + tempIcon" >
|
||||||
|
<button class="delete is-small" @click.prevent="deleteIcon"></button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<field-error :form="form" field="icon" class="help-for-file" />
|
<div class="field">
|
||||||
|
<field-error :form="form" field="icon" class="help-for-file" />
|
||||||
|
<p class="help" v-html="$t('twofaccounts.forms.i_m_lucky_legend')"></p>
|
||||||
|
</div>
|
||||||
<!-- otp type -->
|
<!-- otp type -->
|
||||||
<form-toggle @otp_type="SetFormState" class="has-uppercased-button" :form="form" :choices="otp_types" fieldName="otp_type" :label="$t('twofaccounts.forms.otp_type.label')" :help="$t('twofaccounts.forms.otp_type.help')" :hasOffset="true" />
|
<form-toggle @otp_type="setFormState" class="has-uppercased-button" :form="form" :choices="otp_types" fieldName="otp_type" :label="$t('twofaccounts.forms.otp_type.label')" :help="$t('twofaccounts.forms.otp_type.help')" :hasOffset="true" />
|
||||||
<div v-if="form.otp_type">
|
<div v-if="form.otp_type">
|
||||||
<!-- secret -->
|
<!-- secret -->
|
||||||
<label class="label" v-html="$t('twofaccounts.forms.secret.label')"></label>
|
<label class="label" v-html="$t('twofaccounts.forms.secret.label')"></label>
|
||||||
@ -345,7 +360,21 @@
|
|||||||
const { data } = await this.form.upload('/api/v1/icons', imgdata)
|
const { data } = await this.form.upload('/api/v1/icons', imgdata)
|
||||||
|
|
||||||
this.tempIcon = data.filename;
|
this.tempIcon = data.filename;
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchLogo() {
|
||||||
|
|
||||||
|
this.axios.post('/api/v1/icons/default', {service: this.form.service}, {returnError: true}).then(response => {
|
||||||
|
if (response.status === 201) {
|
||||||
|
// clean possible already uploaded temp icon
|
||||||
|
this.deleteIcon()
|
||||||
|
this.tempIcon = response.data.filename;
|
||||||
|
}
|
||||||
|
else this.$notify({type: 'is-warning', text: this.$t('errors.no_logo_found_for_x', {service: this.form.service}) })
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.$notify({type: 'is-warning', text: this.$t('errors.no_logo_found_for_x', {service: this.form.service}) })
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteIcon(event) {
|
deleteIcon(event) {
|
||||||
@ -376,7 +405,7 @@
|
|||||||
console.log('error', value)
|
console.log('error', value)
|
||||||
},
|
},
|
||||||
|
|
||||||
SetFormState (event) {
|
setFormState (event) {
|
||||||
this.form.otp_type = event
|
this.form.otp_type = event
|
||||||
this.form.service = event === 'steamtotp' ? 'Steam' : ''
|
this.form.service = event === 'steamtotp' ? 'Steam' : ''
|
||||||
this.secretIsBase32Encoded = event === 'steamtotp' ? 1 : this.secretIsBase32Encoded
|
this.secretIsBase32Encoded = event === 'steamtotp' ? 1 : this.secretIsBase32Encoded
|
||||||
|
@ -5,26 +5,41 @@
|
|||||||
<form-field :isDisabled="form.otp_type === 'steamtotp'" :form="form" fieldName="service" inputType="text" :label="$t('twofaccounts.service')" :placeholder="$t('twofaccounts.forms.service.placeholder')" autofocus />
|
<form-field :isDisabled="form.otp_type === 'steamtotp'" :form="form" fieldName="service" inputType="text" :label="$t('twofaccounts.service')" :placeholder="$t('twofaccounts.forms.service.placeholder')" autofocus />
|
||||||
<!-- account -->
|
<!-- account -->
|
||||||
<form-field :form="form" fieldName="account" inputType="text" :label="$t('twofaccounts.account')" :placeholder="$t('twofaccounts.forms.account.placeholder')" />
|
<form-field :form="form" fieldName="account" inputType="text" :label="$t('twofaccounts.account')" :placeholder="$t('twofaccounts.forms.account.placeholder')" />
|
||||||
<!-- icon -->
|
<!-- icon upload -->
|
||||||
<div class="field">
|
<label class="label">{{ $t('twofaccounts.icon') }}</label>
|
||||||
<label class="label">{{ $t('twofaccounts.icon') }}</label>
|
<div class="field is-grouped">
|
||||||
<div class="file is-dark">
|
<!-- i'm lucky button -->
|
||||||
<label class="file-label">
|
<div class="control">
|
||||||
<input class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
|
<v-button @click="fetchLogo" :color="'is-dark'" :nativeType="'button'" :isDisabled="form.service.length < 3">
|
||||||
<span class="file-cta">
|
<span class="icon is-small">
|
||||||
<span class="file-icon">
|
<font-awesome-icon :icon="['fas', 'globe']" />
|
||||||
<font-awesome-icon :icon="['fas', 'image']" />
|
|
||||||
</span>
|
|
||||||
<span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</label>
|
<span>{{ $t('twofaccounts.forms.i_m_lucky') }}</span>
|
||||||
<span class="tag is-black is-large" v-if="tempIcon">
|
</v-button>
|
||||||
<img class="icon-preview" :src="'/storage/icons/' + tempIcon" >
|
</div>
|
||||||
<button class="delete is-small" @click.prevent="deleteIcon"></button>
|
<!-- upload button -->
|
||||||
</span>
|
<div class="control">
|
||||||
|
<div class="file is-dark">
|
||||||
|
<label class="file-label">
|
||||||
|
<input class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
|
||||||
|
<span class="file-cta">
|
||||||
|
<span class="file-icon">
|
||||||
|
<font-awesome-icon :icon="['fas', 'upload']" />
|
||||||
|
</span>
|
||||||
|
<span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span class="tag is-black is-large" v-if="tempIcon">
|
||||||
|
<img class="icon-preview" :src="'/storage/icons/' + tempIcon" >
|
||||||
|
<button class="delete is-small" @click.prevent="deleteIcon"></button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<field-error :form="form" field="icon" class="help-for-file" />
|
<div class="field">
|
||||||
|
<field-error :form="form" field="icon" class="help-for-file" />
|
||||||
|
<p class="help" v-html="$t('twofaccounts.forms.i_m_lucky_legend')"></p>
|
||||||
|
</div>
|
||||||
<!-- otp type -->
|
<!-- otp type -->
|
||||||
<form-toggle class="has-uppercased-button" :isDisabled="true" :form="form" :choices="otp_types" fieldName="otp_type" :label="$t('twofaccounts.forms.otp_type.label')" :help="$t('twofaccounts.forms.otp_type.help')" :hasOffset="true" />
|
<form-toggle class="has-uppercased-button" :isDisabled="true" :form="form" :choices="otp_types" fieldName="otp_type" :label="$t('twofaccounts.forms.otp_type.label')" :help="$t('twofaccounts.forms.otp_type.help')" :hasOffset="true" />
|
||||||
<div v-if="form.otp_type">
|
<div v-if="form.otp_type">
|
||||||
@ -256,6 +271,21 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fetchLogo() {
|
||||||
|
|
||||||
|
this.axios.post('/api/v1/icons/default', {service: this.form.service}, {returnError: true}).then(response => {
|
||||||
|
if (response.status === 201) {
|
||||||
|
// clean possible already uploaded temp icon
|
||||||
|
this.deleteIcon()
|
||||||
|
this.tempIcon = response.data.filename;
|
||||||
|
}
|
||||||
|
else this.$notify({type: 'is-warning', text: this.$t('errors.no_logo_found_for_x', {service: this.form.service}) })
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.$notify({type: 'is-warning', text: this.$t('errors.no_logo_found_for_x', {service: this.form.service}) })
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
deleteIcon(event) {
|
deleteIcon(event) {
|
||||||
|
|
||||||
if( this.tempIcon && this.tempIcon !== this.form.icon ) {
|
if( this.tempIcon && this.tempIcon !== this.form.icon ) {
|
||||||
|
@ -41,4 +41,5 @@ return [
|
|||||||
'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.',
|
'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',
|
'invalid_google_auth_migration' => 'Invalid or unreadable Google Authenticator data',
|
||||||
'unsupported_otp_type' => 'Unsupported OTP type',
|
'unsupported_otp_type' => 'Unsupported OTP type',
|
||||||
|
'no_logo_found_for_x' => 'No logo available for {service}'
|
||||||
];
|
];
|
@ -25,7 +25,7 @@ return [
|
|||||||
'no_service' => '- no service -',
|
'no_service' => '- no service -',
|
||||||
'forms' => [
|
'forms' => [
|
||||||
'service' => [
|
'service' => [
|
||||||
'placeholder' => 'example.com',
|
'placeholder' => 'Google, Twitter, Apple',
|
||||||
],
|
],
|
||||||
'account' => [
|
'account' => [
|
||||||
'placeholder' => 'John DOE',
|
'placeholder' => 'John DOE',
|
||||||
@ -49,7 +49,9 @@ return [
|
|||||||
'val' => 'Lock',
|
'val' => 'Lock',
|
||||||
'title' => 'Lock it',
|
'title' => 'Lock it',
|
||||||
],
|
],
|
||||||
'choose_image' => 'Choose an image…',
|
'choose_image' => 'Upload',
|
||||||
|
'i_m_lucky' => 'I\'m lucky',
|
||||||
|
'i_m_lucky_legend' => 'The "I\'m lucky" button try to get the official icon of the given service. Enter actual service name without ".xyz" extension and try to avoid typo. (beta feature)',
|
||||||
'test' => 'Test',
|
'test' => 'Test',
|
||||||
'secret' => [
|
'secret' => [
|
||||||
'label' => 'Secret',
|
'label' => 'Secret',
|
||||||
|
@ -43,6 +43,7 @@ Route::group(['middleware' => 'auth:api-guard'], function () {
|
|||||||
|
|
||||||
Route::post('qrcode/decode', 'QrCodeController@decode')->name('qrcode.decode');
|
Route::post('qrcode/decode', 'QrCodeController@decode')->name('qrcode.decode');
|
||||||
|
|
||||||
|
Route::post('icons/default', 'IconController@fetch')->name('icons.fetch');
|
||||||
Route::post('icons', 'IconController@upload')->name('icons.upload');
|
Route::post('icons', 'IconController@upload')->name('icons.upload');
|
||||||
Route::delete('icons/{icon}', 'IconController@delete')->name('icons.delete');
|
Route::delete('icons/{icon}', 'IconController@delete')->name('icons.delete');
|
||||||
});
|
});
|
Loading…
x
Reference in New Issue
Block a user