Преглед на файлове

refactor(otp): generate enroll QR code in front-end

Hintay преди 2 месеца
родител
ревизия
aedf631254
променени са 4 файла, в които са добавени 36 реда и са изтрити 55 реда
  1. 5 24
      api/user/otp.go
  2. 1 0
      app/components.d.ts
  3. 1 1
      app/src/api/otp.ts
  4. 29 30
      app/src/views/preference/components/TOTP.vue

+ 5 - 24
api/user/otp.go

@@ -3,9 +3,11 @@ package user
 import (
 	"bytes"
 	"crypto/sha1"
-	"encoding/base64"
 	"encoding/hex"
 	"fmt"
+	"net/http"
+	"strings"
+
 	"github.com/0xJacky/Nginx-UI/api"
 	"github.com/0xJacky/Nginx-UI/internal/crypto"
 	"github.com/0xJacky/Nginx-UI/query"
@@ -14,9 +16,6 @@ import (
 	"github.com/pquerna/otp"
 	"github.com/pquerna/otp/totp"
 	"github.com/uozi-tech/cosy"
-	"image/jpeg"
-	"net/http"
-	"strings"
 )
 
 func GenerateTOTP(c *gin.Context) {
@@ -38,27 +37,9 @@ func GenerateTOTP(c *gin.Context) {
 		return
 	}
 
-	qrCode, err := otpKey.Image(512, 512)
-	if err != nil {
-		api.ErrHandler(c, err)
-		return
-	}
-
-	// Encode the image to a buffer
-	var buf []byte
-	buffer := bytes.NewBuffer(buf)
-	err = jpeg.Encode(buffer, qrCode, nil)
-	if err != nil {
-		fmt.Println("Error encoding image:", err)
-		return
-	}
-
-	// Convert the buffer to a base64 string
-	base64Str := "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(buffer.Bytes())
-
 	c.JSON(http.StatusOK, gin.H{
-		"secret":  otpKey.Secret(),
-		"qr_code": base64Str,
+		"secret": otpKey.Secret(),
+		"url":    otpKey.URL(),
 	})
 }
 

+ 1 - 0
app/components.d.ts

@@ -49,6 +49,7 @@ declare module 'vue' {
     APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
     APopover: typeof import('ant-design-vue/es')['Popover']
     AProgress: typeof import('ant-design-vue/es')['Progress']
+    AQrcode: typeof import('ant-design-vue/es')['QRCode']
     ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
     ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
     AResult: typeof import('ant-design-vue/es')['Result']

+ 1 - 1
app/src/api/otp.ts

@@ -2,7 +2,7 @@ import http from '@/lib/http'
 
 export interface OTPGenerateSecretResponse {
   secret: string
-  qr_code: string
+  url: string
 }
 
 const otp = {

+ 29 - 30
app/src/views/preference/components/TOTP.vue

@@ -10,7 +10,7 @@ import { message } from 'ant-design-vue'
 const status = ref(false)
 const enrolling = ref(false)
 const resetting = ref(false)
-const qrCode = ref('')
+const generatedUrl = ref('')
 const secret = ref('')
 const passcode = ref('')
 const refOtp = useTemplateRef('refOtp')
@@ -25,7 +25,7 @@ function clickEnable2FA() {
 function generateSecret() {
   otp.generate_secret().then(r => {
     secret.value = r.secret
-    qrCode.value = r.qr_code
+    generatedUrl.value = r.url
     refOtp.value?.clearInput()
   })
 }
@@ -70,9 +70,7 @@ function reset2FA() {
     <p>{{ $gettext('TOTP is a two-factor authentication method that uses a time-based one-time password algorithm.') }}</p>
     <p>{{ $gettext('To enable it, you need to install the Google or Microsoft Authenticator app on your mobile phone.') }}</p>
     <p>{{ $gettext('Scan the QR code with your mobile phone to add the account to the app.') }}</p>
-    <p v-if="!status">
-      {{ $gettext('Current account is not enabled TOTP.') }}
-    </p>
+    <AAlert v-if="!status" type="warning" :message="$gettext('Current account is not enabled TOTP.')" show-icon />
     <div v-else>
       <p><CheckCircleOutlined class="mr-2 text-green-600" />{{ $gettext('Current account is enabled TOTP.') }}</p>
     </div>
@@ -112,33 +110,34 @@ function reset2FA() {
     </AButton>
 
     <template v-if="enrolling">
-      <div class="mt-4 mb-2">
-        <img
-          v-if="qrCode"
-          class="w-64 h-64"
-          :src="qrCode"
-          alt="qr code"
-        >
-        <div class="w-64 flex justify-center">
-          <UseClipboard v-slot="{ copy, copied }">
-            <a
-              class="mr-2"
-              @click="() => copy(secret)"
-            >
-              {{ copied ? $gettext('Secret has been copied')
-                : $gettext('Can\'t scan? Use text key binding') }}
-            </a>
-          </UseClipboard>
+      <div class="flex flex-col items-center">
+        <div class="mt-4 mb-2">
+          <AQrcode
+            v-if="generatedUrl"
+            :value="generatedUrl"
+            :size="256"
+          />
+          <div class="w-64 flex justify-center">
+            <UseClipboard v-slot="{ copy, copied }">
+              <a
+                class="mr-2"
+                @click="() => copy(secret)"
+              >
+                {{ copied ? $gettext('Secret has been copied')
+                  : $gettext('Can\'t scan? Use text key binding') }}
+              </a>
+            </UseClipboard>
+          </div>
         </div>
-      </div>
 
-      <div>
-        <p>{{ $gettext('Input the code from the app:') }}</p>
-        <OTPInput
-          ref="refOtp"
-          v-model="passcode"
-          @on-complete="enroll"
-        />
+        <div>
+          <p>{{ $gettext('Input the code from the app:') }}</p>
+          <OTPInput
+            ref="refOtp"
+            v-model="passcode"
+            @on-complete="enroll"
+          />
+        </div>
       </div>
     </template>