Implemented TOTP in frontend
authorAdam Dullage <redacted>
Fri, 14 Oct 2022 12:20:58 +0000 (13:20 +0100)
committerAdam Dullage <redacted>
Fri, 14 Oct 2022 12:20:58 +0000 (13:20 +0100)
flatnotes/src/components/App.js
flatnotes/src/components/Login.vue
flatnotes/src/components/SearchInput.vue
flatnotes/src/constants.js

index fb7087fafd995b3e265470ba302598dd5ab84278..ececa960d9191c6bafe6cbdf3baa7d46754fd0bb 100644 (file)
@@ -47,9 +47,16 @@ export default {
   methods: {
     loadConfig: function() {
       let parent = this;
-      api.get("/api/config").then(function(response) {
-        parent.authType = constants.authTypes[response.data.authType];
-      });
+      api
+        .get("/api/config")
+        .then(function(response) {
+          parent.authType = response.data.authType;
+        })
+        .catch(function(error) {
+          if (!error.handled) {
+            parent.unhandledServerErrorToast();
+          }
+        });
     },
 
     route: function() {
index 11a3f6872219c4b99b36a3ae36e5dca0570a9e75..c34b8877418fc1b297f9a4ea9c5c4057c6058bf2 100644 (file)
@@ -1,55 +1,77 @@
 <template>
   <div class="d-flex flex-column justify-content-center align-items-center">
     <!-- Logo -->
-    <Logo class="mb-3"></Logo>
-
-    <form
-      class="d-flex flex-column align-items-center"
-      v-on:submit.prevent="login"
+    <Logo class="mb-5"></Logo>
+    <div
+      v-if="authType != null && authType != constants.authTypes.none"
+      class="d-flex flex-column justify-content-center align-items-center"
     >
-      <!-- Username -->
-      <div class="mb-1">
-        <input
-          type="text"
-          placeholder="Username"
-          class="form-control"
-          id="username"
-          autocomplete="username"
-          v-model="usernameInput"
-          autofocus
-          required
-        />
-      </div>
+      <form
+        v-show="authType != null"
+        class="d-flex flex-column align-items-center"
+        v-on:submit.prevent="login"
+      >
+        <div class="mb-1">
+          <!-- Username -->
+          <div class="mb-1">
+            <input
+              type="text"
+              placeholder="Username"
+              class="form-control"
+              id="username"
+              autocomplete="username"
+              v-model="usernameInput"
+              autofocus
+              required
+            />
+          </div>
+
+          <!-- Password -->
+          <div class="mb-1">
+            <input
+              type="password"
+              placeholder="Password"
+              class="form-control"
+              id="password"
+              autocomplete="current-password"
+              v-model="passwordInput"
+              required
+            />
+          </div>
 
-      <!-- Password -->
-      <div class="mb-2">
-        <input
-          type="password"
-          placeholder="Password"
-          class="form-control"
-          id="password"
-          autocomplete="current-password"
-          v-model="passwordInput"
-          required
-        />
-      </div>
+          <!-- 2FA -->
+          <div v-if="authType == constants.authTypes.totp" class="mb-1">
+            <input
+              type="text"
+              inputmode="numeric"
+              pattern="[0-9]*"
+              placeholder="2FA Code"
+              class="form-control"
+              id="totp"
+              autocomplete="one-time-code"
+              v-model="totpInput"
+              required
+            />
+          </div>
+        </div>
 
-      <!-- Remember Me -->
-      <div class="mb-3 form-check">
-        <input
-          type="checkbox"
-          class="form-check-input"
-          id="rememberMe"
-          v-model="rememberMeInput"
-        />
-        <label class="form-check-label" for="rememberMe">Remember Me</label>
-      </div>
+        <!-- Remember Me -->
+        <div class="mb-3 form-check">
+          <input
+            type="checkbox"
+            class="form-check-input"
+            id="rememberMe"
+            v-model="rememberMeInput"
+          />
+          <label class="form-check-label" for="rememberMe">Remember Me</label>
+        </div>
 
-      <!-- Button -->
-      <button type="submit" class="bttn">
-        <b-icon icon="box-arrow-in-right"></b-icon> Log In
-      </button>
-    </form>
+        <!-- Button -->
+        <button type="submit" class="bttn">
+          <b-icon icon="box-arrow-in-right"></b-icon> Log In
+        </button>
+      </form>
+    </div>
   </div>
 </template>
 
@@ -67,13 +89,14 @@ export default {
   },
 
   props: {
-    authType: { type: Number, default: null },
+    authType: { type: String, default: null },
   },
 
   data: function () {
     return {
       usernameInput: null,
       passwordInput: null,
+      totpInput: null,
       rememberMeInput: false,
     };
   },
@@ -97,7 +120,10 @@ export default {
       api
         .post("/api/token", {
           username: this.usernameInput,
-          password: this.passwordInput,
+          password:
+            this.authType == constants.authTypes.totp
+              ? this.passwordInput + this.totpInput
+              : this.passwordInput,
         })
         .then(function (response) {
           sessionStorage.setItem("token", response.data.access_token);
@@ -114,7 +140,7 @@ export default {
             typeof error.response !== "undefined" &&
             [400, 422].includes(error.response.status)
           ) {
-            parent.$bvToast.toast("Incorrect Username or Password ✘", {
+            parent.$bvToast.toast("Incorrect login credentials ✘", {
               variant: "danger",
               noCloseButton: true,
               toaster: "b-toaster-bottom-right",
@@ -126,12 +152,14 @@ export default {
         .finally(function () {
           parent.usernameInput = null;
           parent.passwordInput = null;
+          parent.totpInput = null;
           parent.rememberMeInput = false;
         });
     },
   },
 
   created: function () {
+    this.constants = constants;
     this.skipIfNoneAuthType();
   },
 };
index dec533a915bf313df7c1900fdb02ab39b91ae39a..8c4965738d87c9cff9aea8b8c36109ef47fa0ff3 100644 (file)
@@ -4,6 +4,7 @@
       <input
         id="search-input"
         type="text"
+        inputmode="search"
         class="form-control"
         :class="{ highlight: includeHighlightClass }"
         placeholder="Search"
index 5934fd45f3cf9cec99fe48a7233cc6c7a9348cd5..34574665c0373007a8119932b9c841b9b6ce5ea8 100644 (file)
@@ -47,4 +47,4 @@ export const alphabet = [
 
 export const searchSortOptions = { score: 0, title: 1, lastModified: 2 };
 
-export const authTypes = { none: 0, password: 1 };
+export const authTypes = { none: "none", password: "password", totp: "totp" };
git clone https://git.99rst.org/PROJECT