Przeglądaj źródła

TOTP period and timer based on T0

Bubka 6 lat temu
rodzic
commit
e58d7ac864

+ 8 - 0
app/Http/Controllers/TwoFAccountController.php

@@ -67,8 +67,16 @@ class TwoFAccountController extends Controller
             ], 500);
         }
 
+        $currentPosition = time();
+        $PeriodCount = floor($currentPosition / 30); //nombre de période de 30s depuis T0
+        $currentPeriodStartAt = $PeriodCount * 30;
+        $currentPeriodendAt = $currentPeriodStartAt + 30;
+        $positionInCurrentPeriod = $currentPosition - $currentPeriodStartAt;
+
+
         return response()->json([
                 'totp' => $otp->now(),
+                'position' => $positionInCurrentPeriod
             ], 200);
 
     }

+ 10 - 3
resources/js/components/Modal.vue

@@ -1,10 +1,10 @@
 <template>
     <div class="modal modal-otp" v-bind:class="{ 'is-active': isActive }">
-      <div class="modal-background" @click.stop="isActive = false"></div>
+      <div class="modal-background" @click.stop="closeModal"></div>
       <div class="modal-content">
         <slot></slot>
       </div>
-      <button class="modal-close is-large" aria-label="close" @click.stop="isActive = false"></button>
+      <button class="modal-close is-large" aria-label="close" @click.stop="closeModal"></button>
     </div>
 </template>
 
@@ -13,7 +13,6 @@ export default {
     name: 'Modal',
     props: {
         value: Boolean,
-
     },
     computed: {
         isActive: {
@@ -24,6 +23,14 @@ export default {
                 this.$emit('input', value)
             }
         }
+    },
+    methods: {
+        closeModal: function(event) {
+            if (event) {
+                this.isActive = false
+                this.$parent.$emit('modalClose')
+            }
+        }
     }
 }
 </script>

+ 70 - 31
resources/js/components/OneTimePassword.vue

@@ -2,7 +2,7 @@
     <div>
         <p id="otp" title="refresh" class="is-size-1 has-text-white">{{ totp }}</p>
         <ul class="dots">
-            <li data-is-active style="display:none"></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li>
+            <li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li>
         </ul>
     </div>
 </template>
@@ -12,53 +12,92 @@
         data() {
             return {
                 totp : '',
-                componentKey: 0
-            }
-        },
-        props: ['AccountId'],
-        watch: {
-            AccountId: function(newVal, oldVal) {
-                this.getOTP()
+                componentKey: 0,
+                timerID: null,
+                position: null,
+                AccountId : null
             }
         },
+        //props: ['AccountId'],
+        // watch: {
+        //     AccountId: function(newVal, oldVal) {
+        //         this.getOTP()
+        //     }
+        // },
         methods: {
             getOTP: function () {
+
+                // if( !this.$props.AccountId ) {
+                //     console.log("AccountId is undefined")
+                //     return
+                // }
+
                 let token = localStorage.getItem('jwt')
 
                 axios.defaults.headers.common['Content-Type'] = 'application/json'
                 axios.defaults.headers.common['Authorization'] = 'Bearer ' + token
 
-                axios.get('api/twofaccounts/' + this.$props.AccountId + '/totp').then(response => {
+                axios.get('api/twofaccounts/' + this.AccountId + '/totp').then(response => {
                     this.totp = response.data.totp.substr(0, 3) + " " + response.data.totp.substr(3);
-                })
+                    this.position = response.data.position;
 
-                var self = this;
-                var timer = setInterval(function() {
-                    let dots = self.$el.querySelector('.dots');
-                    let active = dots.querySelector('[data-is-active]');
-                    let lastdot = dots.querySelector('li:last-child');
+                    let dots = this.$el.querySelector('.dots');
+                    //let lastdot = dots.querySelector('li:last-child');
 
-                    if (active === null) {
-                        self.$el.querySelector('.dots li:first-child').setAttribute('data-is-active', true);
-                        active = dots.querySelector('[data-is-active]');
-                    }
-                    else if(active === lastdot) {
-                        clearInterval(timer);
-                        active.removeAttribute('data-is-active');
-                        self.$el.querySelector('.dots li:first-child').setAttribute('data-is-active', true);
-                        self.getOTP();
+                    // clear active dots
+                    while (dots.querySelector('[data-is-active]')) {
+                        dots.querySelector('[data-is-active]').removeAttribute('data-is-active');
                     }
-                    else
-                    {
+
+                    // set dot at given position as the active one
+                    let active = dots.querySelector('li:nth-child(' + (this.position + 1 ) + ')');
+                    active.setAttribute('data-is-active', true);
+
+                    // if (active === null) {
+                    //     this.$el.querySelector('.dots li:first-child').setAttribute('data-is-active', true);
+                    //     active = dots.querySelector('[data-is-active]');
+                    // }
+
+                    let self = this;
+
+                    this.timerID = setInterval(function() {
+
                         let sibling = active.nextSibling;
-                        if (sibling === null) return;
 
-                        active.removeAttribute('data-is-active');
-                        sibling.setAttribute('data-is-active', true);
-                    }
-                },3000);
+                        // axios.get('api/twofaccounts/' + self.AccountId + '/totp').then(response => {
+                            // console.log(response.data.totp.substr(0, 3) + " " + response.data.totp.substr(3))
+                            // console.log(response.data.position);
 
+                            if(active.nextSibling === null) {
+                                console.log('no more sibling to activate, we refresh the TOTP')
+                                self.stopLoop()
+                                self.getOTP();
+                            }
+                            else
+                            {
+                                active.removeAttribute('data-is-active');
+                                sibling.setAttribute('data-is-active', true);
+                                active = sibling
+                            }
+                        // })
+
+
+                    }, 1000);
+                })
+            },
+            clearOTP: function() {
+                this.stopLoop()
+                this.timerID = null
+                this.totp = '... ...'
+                this.$el.querySelector('[data-is-active]').removeAttribute('data-is-active');
+                this.$el.querySelector('.dots li:first-child').setAttribute('data-is-active', true);
+            },
+            stopLoop: function() {
+                clearInterval(this.timerID)
             }
+        },
+        beforeDestroy () {
+            this.stopLoop()
         }
     }
 </script>

+ 9 - 27
resources/js/views/Accounts.vue

@@ -14,7 +14,7 @@
                 :name='twofaccount.name'
                 :icon='twofaccount.icon'
                 :email='twofaccount.email'>
-                <one-time-password :AccountId='twofaccount.id' ></one-time-password>
+                <one-time-password  ref="OneTimePassword"></one-time-password>
             </modal-twofaccount>
         </modal>
     </div>
@@ -28,31 +28,6 @@
     import ModalTwofaccount from '../components/ModalTwofaccount'
     import OneTimePassword from '../components/OneTimePassword'
 
-    // const ModalTwofaccount = {
-    //     props: ['id', 'name', 'email', 'icon', 'totp'],
-    //     template: `
-    //         <section class="section">
-    //             <div class="columns is-centered">
-    //                 <div class="column is-three-quarters">
-    //                     <div class="box has-text-centered has-background-black-ter ">
-    //                         <figure class="image is-64x64" style="display: inline-block">
-    //                             <img :src="icon">
-    //                         </figure>
-    //                         <p class="is-size-4 has-text-grey-light">{{ name }}</p>
-    //                         <p class="is-size-6 has-text-grey">{{ email }}</p>
-    //                         <p id="otp" title="refresh" class="is-size-1 has-text-white">{{ totp }}</p>
-    //                         <ul class="dots">
-    //                             <li data-is-active style="display:none"></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li><li></li>
-    //                         </ul>
-    //                     </div>
-    //                 </div>
-    //             </div>
-    //         </section>
-    //     `
-    // }
-
-
-
     export default {
         data(){
             return {
@@ -76,8 +51,12 @@
                         icon : data.icon
                     })
                 })
-                // this.loadTasks()
             })
+
+            this.$on('modalClose', function() {
+                console.log('modalClose triggered')
+                this.$refs.OneTimePassword.clearOTP()
+            });
         },
         components: {
             Modal,
@@ -93,11 +72,14 @@
                 axios.defaults.headers.common['Authorization'] = 'Bearer ' + token
 
                 axios.get('api/twofaccounts/' + id).then(response => {
+
                     this.twofaccount.id = response.data.id
                     this.twofaccount.name = response.data.name
                     this.twofaccount.email = response.data.email
                     this.twofaccount.icon = response.data.icon
 
+                    this.$refs.OneTimePassword.AccountId = response.data.id
+                    this.$refs.OneTimePassword.getOTP()
                     this.ModalIsActive = true;
 
                 })

+ 7 - 2
resources/sass/app.scss

@@ -52,10 +52,15 @@ nav.level {
   background: hsl(0, 0%, 7%);  /* grey */
 }
 
-.dots li:nth-child(-n+9) {
+.dots li:nth-child(-n+27) {
   background: hsl(48, 100%, 67%); /* yellow */
 }
 
-.dots li:nth-child(-n+7) {
+.dots li:nth-child(-n+21) {
   background: hsl(141, 71%, 48%); /* green */
+}
+
+.dots li:nth-child(3n+1), .dots li:nth-child(3n+2) {
+    // background-color: black;
+    display:none;
 }