فهرست منبع

Complete the release radar to notify new 2Fauth releases - Close #127

Bubka 2 سال پیش
والد
کامیت
8d3a97a701

+ 3 - 8
app/Events/ReleaseRadarActivated.php → app/Events/ScanForNewReleaseCalled.php

@@ -5,24 +5,19 @@ namespace App\Events;
 use Illuminate\Broadcasting\InteractsWithSockets;
 use Illuminate\Foundation\Events\Dispatchable;
 use Illuminate\Queue\SerializesModels;
+use Illuminate\Support\Facades\Log;
 
-class ReleaseRadarActivated
+class ScanForNewReleaseCalled
 {
     use Dispatchable, InteractsWithSockets, SerializesModels;
 
-    /**
-     * @var \App\Models\Group
-     */
-    // public $group;
-
     /**
      * Create a new event instance.
      *
-     * @param  \App\Models\Group  $group
      * @return void
      */
     public function __construct()
     {
-        // $this->group = $group;
+        Log::info('ReleaseRadarActivated event dispatched');
     }
 }

+ 6 - 0
app/Helpers/Helpers.php

@@ -17,4 +17,10 @@ class Helpers
     {
         return Str::random(40).'.'.$extension;
     }
+
+
+    public static function cleanVersionNumber(?string $release): string|false
+    {
+        return preg_match('/([[0-9][0-9\.]*[0-9])/', $release, $version) ? $version[0] : false;
+    }
 }

+ 3 - 0
app/Http/Controllers/SinglePageController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
 
 use App\Facades\Settings;
 use Illuminate\Support\Facades\App;
+use App\Events\ScanForNewReleaseCalled;
 
 class SinglePageController extends Controller
 {
@@ -15,6 +16,8 @@ class SinglePageController extends Controller
      */
     public function index()
     {
+        event(new ScanForNewReleaseCalled());
+
         return view('landing')->with([
             'appSettings' => Settings::all()->toJson(),
             'appConfig' => collect([

+ 18 - 21
app/Http/Controllers/SystemController.php

@@ -2,9 +2,7 @@
 
 namespace App\Http\Controllers;
 
-use App\Events\ReleaseRadarActivated;
 use App\Services\ReleaseRadarService;
-use Illuminate\Support\Facades\App;
 use App\Http\Controllers\Controller;
 use App\Facades\Settings;
 use Illuminate\Http\Request;
@@ -20,23 +18,23 @@ class SystemController extends Controller
     public function infos(Request $request)
     {
         $infos = array();
-        $infos['Date']               = date(DATE_RFC2822);
-        $infos['userAgent']        = $request->header('user-agent');
+        $infos['Date']              = date(DATE_RFC2822);
+        $infos['userAgent']         = $request->header('user-agent');
         // App info
-        $infos['Version']          = config('2fauth.version');
-        $infos['Environment']          = config('app.env');
-        $infos['Debug']        = var_export(config('app.debug'), true);
-        $infos['Cache driver']     = config('cache.default');
-        $infos['Log channel']      = config('logging.default');
-        $infos['Log level']     = env('LOG_LEVEL');
-        $infos['DB driver']    = DB::getDriverName();
+        $infos['Version']           = config('2fauth.version');
+        $infos['Environment']       = config('app.env');
+        $infos['Debug']             = var_export(config('app.debug'), true);
+        $infos['Cache driver']      = config('cache.default');
+        $infos['Log channel']       = config('logging.default');
+        $infos['Log level']         = env('LOG_LEVEL');
+        $infos['DB driver']         = DB::getDriverName();
         // PHP info
-        $infos['PHP version'] = PHP_VERSION;
-        $infos['Operating system']      = PHP_OS;
-        $infos['interface']       = PHP_SAPI;
+        $infos['PHP version']       = PHP_VERSION;
+        $infos['Operating system']  = PHP_OS;
+        $infos['interface']         = PHP_SAPI;
         // Auth info
         if ($request->user()) {
-            $infos['Auth guard']        = config('auth.defaults.guard');
+            $infos['Auth guard']    = config('auth.defaults.guard');
             if ($infos['Auth guard'] === 'reverse-proxy-guard') {
                 $infos['Auth proxy header for user'] = config('auth.auth_proxy_headers.user');
                 $infos['Auth proxy header for email'] = config('auth.auth_proxy_headers.email');
@@ -46,7 +44,7 @@ class SystemController extends Controller
         }
         // User info
         if ($request->user()) {
-            $infos['options']     = Settings::all()->toArray();
+            $infos['options'] = Settings::all()->toArray();
         }
 
         return response()->json($infos);
@@ -58,11 +56,10 @@ class SystemController extends Controller
      * 
      * @return \Illuminate\Http\JsonResponse
      */
-    public function latestRelease(Request $request)
+    public function latestRelease(Request $request, ReleaseRadarService $releaseRadar)
     {
-        $releaseRadarService = App::make(ReleaseRadarService::class);
-        $release = $releaseRadarService->scanForRelease();
+        $release = $releaseRadar->manualScan();
 
-        return response()->json($release);
+        return response()->json(['newRelease' => $release]);
     }
-}
+}

+ 16 - 9
app/Listeners/ReleaseRadar.php

@@ -2,34 +2,41 @@
 
 namespace App\Listeners;
 
-use App\Events\ReleaseRadarActivated;
+use App\Events\ScanForNewReleaseCalled;
 use App\Services\ReleaseRadarService;
 use Illuminate\Support\Facades\App;
 use Illuminate\Support\Facades\Log;
 
 class ReleaseRadar
 {
+    /**
+     * @var ReleaseRadarService $releaseRadar
+     */
+    protected $releaseRadar;
+
+
     /**
      * Create the event listener.
+     * 
+     * @param  \App\Services\ReleaseRadarService  $releaseRadar
      *
      * @return void
      */
-    public function __construct()
+    public function __construct(ReleaseRadarService $releaseRadar)
     {
-        //
+        $this->releaseRadar = $releaseRadar;
     }
 
+
     /**
      * Handle the event.
      *
-     * @param  \App\Events\ReleaseRadarActivated  $event
+     * @param  \App\Events\ScanForNewReleaseCalled  $event
      * @return void
      */
-    public function handle(ReleaseRadarActivated $event)
+    public function handle(ScanForNewReleaseCalled $event)
     {
-        Log::info('Release radar activated');
-
-        $releaseRadarService = App::make(ReleaseRadarService::class);
-        $releaseRadarService->scanForNewRelease();
+        $this->releaseRadar->scheduledScan();
+        Log::info('Scheduled release scan complete');
     }
 }

+ 2 - 2
app/Providers/EventServiceProvider.php

@@ -4,7 +4,7 @@ namespace App\Providers;
 
 use App\Events\GroupDeleting;
 use App\Events\TwoFAccountDeleted;
-use App\Events\ReleaseRadarActivated;
+use App\Events\ScanForNewReleaseCalled;
 use App\Listeners\ReleaseRadar;
 use App\Listeners\CleanIconStorage;
 use App\Listeners\DissociateTwofaccountFromGroup;
@@ -29,7 +29,7 @@ class EventServiceProvider extends ServiceProvider
         GroupDeleting::class => [
             DissociateTwofaccountFromGroup::class,
         ],
-        ReleaseRadarActivated::class => [
+        ScanForNewReleaseCalled::class => [
             ReleaseRadar::class,
         ],
     ];

+ 1 - 1
app/Providers/TwoFAuthServiceProvider.php

@@ -56,7 +56,7 @@ class TwoFAuthServiceProvider extends ServiceProvider implements DeferrableProvi
     {
         return [
             LogoService::class,
-            LogoSeReleaseRadarServicervice::class,
+            ReleaseRadarService::class,
         ];
     }
 }

+ 43 - 17
app/Services/ReleaseRadarService.php

@@ -3,33 +3,60 @@
 namespace App\Services;
 
 use App\Facades\Settings;
+use App\Helpers\Helpers;
 use Illuminate\Support\Facades\Http;
 use Illuminate\Support\Facades\Log;
 
 class ReleaseRadarService
 {
     /**
+     * Run a scheduled release scan
      * 
+     * @return void
      */
-    public function scanForNewRelease() : void
+    public function scheduledScan() : void
     {
-        // Only if the last check is old enough
-        // if ((Settings::get('lastRadarScan') + 604800) < time()) {
-            if ($latestReleaseData = json_decode($this->GetLatestReleaseData()))
-            {
-                if ($latestReleaseData->prerelease == false && $latestReleaseData->draft == false) {
-
-                    Settings::set('lastRadarScan', time());
-                    Settings::set('lastRadarScan', time());
-                }
+        if ((Settings::get('lastRadarScan') + 604800) < time()) {
+            $this->newRelease();
+        }
+    }
+
+
+    /**
+     * Run a manual release scan
+     * 
+     * @return false|string False if no new release, the new release number otherwise
+     */
+    public function manualScan() : false|string
+    {
+        return $this->newRelease();
+    }
+
+    /**
+     * Run a release scan
+     * 
+     * @return false|string False if no new release, the new release number otherwise
+     */
+    protected function newRelease() : false|string
+    {
+        if ($latestReleaseData = json_decode($this->getLatestReleaseData()))
+        {
+            $githubVersion = Helpers::cleanVersionNumber($latestReleaseData->tag_name);
+            $installedVersion = Helpers::cleanVersionNumber(config('2fauth.version'));
+
+            if ($githubVersion > $installedVersion && $latestReleaseData->prerelease == false && $latestReleaseData->draft == false) {
+                Settings::set('latestRelease', $latestReleaseData->tag_name);
+                
+                return $latestReleaseData->tag_name;
+            }
+            else {
+                Settings::delete('latestRelease');
             }
 
-            return $latestReleaseData;
-        // }
+            Settings::set('lastRadarScan', time());
+        }
 
-        // tag_name
-        // prerelease
-        // draft
+        return false;
     }
 
 
@@ -38,9 +65,8 @@ class ReleaseRadarService
      * 
      * @return string|null
      */
-    protected function GetLatestReleaseData() : string|null
+    protected function getLatestReleaseData() : string|null
     {
-        return null;
         try {
             $response = Http::retry(3, 100)
                 ->get(config('2fauth.latestReleaseUrl'));

+ 1 - 1
config/2fauth.php

@@ -67,7 +67,7 @@ return [
         'getOfficialIcons' => true,
         'checkForUpdate' => true,
         'lastRadarScan' => 0,
-        'latestRelease' => '',
+        'latestRelease' => false,
     ],
 
 ];

+ 3 - 1
resources/js/components/Footer.vue

@@ -13,7 +13,9 @@
             </router-link>
         </div>
         <div v-else class="content has-text-centered">
-            <router-link id="lnkSettings"  :to="{ name: 'settings.options' }" class="has-text-grey">{{ $t('settings.settings') }}</router-link>
+            <router-link id="lnkSettings"  :to="{ name: 'settings.options' }" class="has-text-grey">
+                {{ $t('settings.settings') }}<span v-if="$root.appSettings.latestRelease && $root.appSettings.checkForUpdate" class="release-flag"></span>
+            </router-link>
             <span v-if="!this.$root.appConfig.proxyAuth || (this.$root.appConfig.proxyAuth && this.$root.appConfig.proxyLogoutUrl)">
                  - <button id="lnkSignOut" class="button is-text is-like-text has-text-grey" @click="logout">{{ $t('auth.sign_out') }}</button>
             </span>

+ 42 - 0
resources/js/components/VersionChecker.vue

@@ -0,0 +1,42 @@
+<template>
+    <div class="columns is-mobile is-vcentered">
+        <div class="column is-narrow">
+            <button type="button" :class="isScanning ? 'is-loading' : ''" class="button is-link is-rounded is-small" @click="getLatestRelease">Check now</button>
+        </div>
+        <div class="column">
+            <span v-if="$root.appSettings.latestRelease" class="mt-2 has-text-warning">
+                <span class="release-flag"></span>{{ $root.appSettings.latestRelease }} is available <a class="is-size-7" href="https://github.com/Bubka/2FAuth/releases">View on Github</a>
+            </span>
+            <span v-if="isUpToDate" class="has-text-grey">
+                {{ $t('commons.you_are_up_to_date') }}
+            </span>
+        </div>
+    </div>
+</template>
+
+<script>
+    export default {
+        name: 'VersionChecker',
+
+        data() {
+            return {
+                isScanning: false,
+                isUpToDate: null,
+            }
+        },
+
+        methods: {
+
+            async getLatestRelease() {
+                this.isScanning = true;
+
+                await this.axios.get('/latestRelease').then(response => {
+                    this.$root.appSettings['latestRelease'] = response.data.newRelease
+                    this.isUpToDate = response.data.newRelease === false
+                })
+
+                this.isScanning = false;
+            },
+        }
+    }
+</script>

+ 4 - 46
resources/js/views/About.vue

@@ -7,22 +7,9 @@
                 {{ $t('commons.2fauth_teaser')}}
             </p>
             <img src="logo.svg" style="height: 32px" alt="2FAuth logo" />
-            <p class="block mb-6">
+            <p class="block" :class="showUserOptions ? 'mb-5' : '' ">
                 ©Bubka <a class="is-size-7" href="https://github.com/Bubka/2FAuth/blob/master/LICENSE">AGPL-3.0 license</a>
             </p>
-            <div v-if="showUserOptions" class="block">
-                <form>
-                    <form-checkbox 
-                        v-on:checkForUpdate="saveSetting('checkForUpdate', $event)" 
-                        :form="form" 
-                        fieldName="checkForUpdate" 
-                        :label="$t('commons.check_for_update')" 
-                        :help="$t('commons.check_for_update_help')"
-                        labelClass="has-text-weight-normal"
-                    />
-                </form>
-                <button type="button" class="button is-link" @click="getLatestRelease">{{ $t('commons.cancel') }}</button>
-            </div>
             <h2 class="title is-5 has-text-grey-light">
                 {{ $t('commons.resources') }}
             </h2>
@@ -66,7 +53,7 @@
                 {{ $t('commons.environment') }}
             </h2>
             <div class="box has-background-black-bis is-family-monospace is-size-7">
-                <button class="button copy-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listInfos.innerText" v-clipboard:success="clipboardSuccessHandler">
+                <button class="button is-like-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listInfos.innerText" v-clipboard:success="clipboardSuccessHandler">
                     <font-awesome-icon :icon="['fas', 'copy']" />
                 </button>
                 <ul ref="listInfos">
@@ -78,7 +65,7 @@
                     {{ $t('settings.user_options') }}
                 </h2>
                 <div class="box has-background-black-bis is-family-monospace is-size-7">
-                    <button class="button copy-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listUserOptions.innerText" v-clipboard:success="clipboardSuccessHandler">
+                    <button class="button is-like-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listUserOptions.innerText" v-clipboard:success="clipboardSuccessHandler">
                         <font-awesome-icon :icon="['fas', 'copy']" />
                     </button>
                     <ul ref="listUserOptions">
@@ -98,17 +85,12 @@
 </template>
 
 <script>
-    import Form from './../components/Form'
-
     export default {
         data() {
             return {
                 infos : null,
                 options : null,
                 showUserOptions: false,
-                form: new Form({
-                    checkForUpdate: null,
-                }),
             }
         },
 
@@ -122,34 +104,10 @@
                     this.showUserOptions = true
                 }
             })
-
-            await this.form.get('/api/v1/settings/checkForUpdate', {returnError: true}).then(response => {
-                if (response.status === 200) {
-                    this.form.fillWithKeyValueObject(response.data)
-                }
-            })
-            .catch(error => {
-                // do nothing
-            })
         },
 
         methods: {
-
-            saveSetting(settingName, event) {
-
-                this.axios.put('/api/v1/settings/' + settingName, { value: event }).then(response => {
-                    this.$notify({ type: 'is-success', text: this.$t('settings.forms.setting_saved') })
-                    this.$root.appSettings[response.data.key] = response.data.value
-                })
-            },
-
-            async getLatestRelease() {
-
-                await this.axios.get('latestRelease').then(response => {
-                    console.log(response.data)
-                })
-            },
-
+            
             clipboardSuccessHandler ({ value, event }) {
                 this.$notify({ type: 'is-success', text: this.$t('commons.copied_to_clipboard') })
             },

+ 9 - 2
resources/js/views/settings/Options.vue

@@ -6,6 +6,9 @@
                 <!-- <form @submit.prevent="handleSubmit" @change="handleSubmit" @keydown="form.onKeydown($event)"> -->
                 <form>
                     <h4 class="title is-4 has-text-grey-light">{{ $t('settings.general') }}</h4>
+                    <!-- Check for update -->
+                    <form-checkbox v-on:checkForUpdate="saveSetting('checkForUpdate', $event)" :form="form" fieldName="checkForUpdate" :label="$t('commons.check_for_update')" :help="$t('commons.check_for_update_help')" />
+                    <version-checker></version-checker>
                     <!-- Language -->
                     <form-select v-on:lang="saveSetting('lang', $event)" :options="langs" :form="form" fieldName="lang" :label="$t('settings.forms.language.label')" :help="$t('settings.forms.language.help')" />
                     <div class="field help">
@@ -22,7 +25,6 @@
                     <!-- Official icons -->
                     <form-checkbox v-on:getOfficialIcons="saveSetting('getOfficialIcons', $event)" :form="form" fieldName="getOfficialIcons" :label="$t('settings.forms.get_official_icons.label')" :help="$t('settings.forms.get_official_icons.help')" />
 
-
                     <h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('groups.groups') }}</h4>
                     <!-- default group -->
                     <form-select v-on:defaultGroup="saveSetting('defaultGroup', $event)" :options="groups" :form="form" fieldName="defaultGroup" :label="$t('settings.forms.default_group.label')" :help="$t('settings.forms.default_group.help')" />
@@ -41,7 +43,6 @@
                     <!-- copy otp on get -->
                     <form-checkbox v-on:copyOtpOnDisplay="saveSetting('copyOtpOnDisplay', $event)" :form="form" fieldName="copyOtpOnDisplay" :label="$t('settings.forms.copy_otp_on_display.label')" :help="$t('settings.forms.copy_otp_on_display.help')" />
 
-
                     <h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('settings.data_input') }}</h4>
                     <!-- basic qrcode -->
                     <form-checkbox v-on:useBasicQrcodeReader="saveSetting('useBasicQrcodeReader', $event)" :form="form" fieldName="useBasicQrcodeReader" :label="$t('settings.forms.use_basic_qrcode_reader.label')" :help="$t('settings.forms.use_basic_qrcode_reader.help')" />
@@ -82,6 +83,7 @@
      */
 
     import Form from './../../components/Form'
+    import VersionChecker from './../../components/VersionChecker'
 
     export default {
         data(){
@@ -101,6 +103,7 @@
                     defaultCaptureMode: '',
                     rememberActiveGroup: true,
                     getOfficialIcons: null,
+                    checkForUpdate: null,
                 }),
                 layouts: [
                     { text: this.$t('settings.forms.grid'), value: 'grid', icon: 'th' },
@@ -129,6 +132,10 @@
             }
         },
 
+        components: {
+            VersionChecker,
+        },
+
         computed : {
             langs: function() {
                 let locales = [{

+ 2 - 1
resources/lang/en/commons.php

@@ -60,8 +60,9 @@ return [
     'logos_by' => 'Logos by',
     'search' => 'Search',
     'resources' => 'Resources',
-    'check_for_update' => 'Check regularly for new version',
+    'check_for_update' => 'Check for new version',
     'check_for_update_help' => 'Automatically check (once a week) and warn when a new release of 2FAuth is published on Github',
+    'you_are_up_to_date' => 'This instance is up-to-date',
     '2fauth_description' => 'A web app to manage your Two-Factor Authentication (2FA) accounts and generate their security codes',
     'image_of_qrcode_to_scan' => 'Image of a QR code to scan',
     'file' => 'File',

+ 11 - 0
resources/sass/app.scss

@@ -762,6 +762,17 @@ footer .field.is-grouped {
   overflow-wrap: break-word;
 }
 
+.release-flag {
+  height: 0.5rem;
+  width: 0.5rem;
+  display: inline-block;
+  border: none;
+  border-radius: 50%;
+  background-color: $warning;
+  vertical-align: middle;
+  margin: 0 5px;
+}
+
 
 .fadeInOut-enter-active {
     animation: fadeIn 500ms