Source: views/Users/Login/Login.vue

<template>
  <v-form
    id="loginPage"
    ref="loginPage"
    v-model="formValid"
    class="login mb-9 elevation-10 rounded"
    style="background: white"
  >
    <v-container>
      <!-- forms -->
      <v-row justify="center">
        <v-col
          :lg="!popUp ? '8' : '12'"
          :md="!popUp ? '8' : '12'"
          :xl="!popUp ? '5' : '12'"
          cols="12"
          sm="12"
        >
          <v-card :flat="popUp">
            <v-card-title
              :class="{
                'bg-blue text-white mb-5 text-center': !popUp,
                'py-0 mb-5': popUp,
              }"
            >
              <h2 class="ma-0">
                {{ currentPanel }}
              </h2>
            </v-card-title>

            <v-card-text>
              <!-- message handler -->
              <MessageHandler field="login" />

              <!-- button to re-send confirmation if login failed -->
              <div v-if="resendButton" class="d-flex flex-row justify-center">
                <v-btn
                  class="text-center bg-teal text-white px-2"
                  href="/users/resendConfirmation"
                  @click="
                    () => {
                      $emit('ClosePopup', true);
                    }
                  "
                >
                  Resend me the confirmation email
                </v-btn>
              </div>
              <v-divider v-if="resendButton" class="pb-0 mb-0" opacity="0.9" />

              <!-- OAUTH -->
              <v-list
                v-if="checkEndpoint()"
                class="d-flex flex-column align-center"
              >
                <v-list-item
                  v-for="(provider, providerIndex) in oauthLogin"
                  :key="'provider_' + providerIndex"
                >
                  <v-btn
                    :class="provider.color"
                    :href="provider.callback + getCurrentLocation()"
                    class="text-left"
                    elevation="3"
                    width="250px"
                  >
                    <v-layout width="100%">
                      <v-icon class="mr-5" start>
                        {{ "fab fa-" + provider.name.toLowerCase() }}
                      </v-icon>
                      <v-layout>with {{ provider.name }}</v-layout>
                    </v-layout>
                  </v-btn>
                </v-list-item>
              </v-list>
            </v-card-text>

            <!-- card content // Form -->
            <v-card-text v-if="currentPanel === 'Login'">
              <v-form id="loginForm" ref="loginForm" v-model="formValid">
                <!-- account -->
                <v-text-field
                  v-model="loginData.name"
                  :rules="[rules.isRequired()]"
                  label="Username or email"
                  required
                  variant="outlined"
                  @keyup.enter="logUser()"
                />

                <!-- password -->
                <v-text-field
                  v-model="loginData.password"
                  :append-inner-icon="show1 ? 'fas fa-eye' : 'fas fa-eye-slash'"
                  :rules="[rules.isRequired()]"
                  :type="show1 ? 'text' : 'password'"
                  autocomplete="off"
                  counter
                  label="Password"
                  required
                  variant="outlined"
                  @click:append-inner="show1 = !show1"
                  @keyup.enter="logUser()"
                />

                <v-card-text class="text-center py-1">
                  <router-link to="/accounts/forgotPassword">
                    <span
                      @click="
                        () => {
                          $emit('ClosePopup', true);
                        }
                      "
                      >Forgotten your password?</span
                    >
                  </router-link>
                  <v-divider opacity="0.9" />
                  <router-link to="/accounts/signup">
                    <span
                      @click="
                        () => {
                          $emit('ClosePopup', true);
                        }
                      "
                      >Need to create a new account?</span
                    >
                  </router-link>
                  <v-divider opacity="0.9" />
                  <a
                    href="https://fairsharing.gitbook.io/fairsharing/#accessing-fairsharing-through-3rd-party-accounts"
                    target="_blank"
                  >
                    <span
                      @click="
                        () => {
                          $emit('ClosePopup', true);
                        }
                      "
                      >Can't login with ORCID or Github?</span
                    >
                  </a>
                </v-card-text>

                <v-card-actions class="mt-2 justify-center">
                  <v-btn
                    :disabled="!formValid"
                    class="px-4 bg-primary"
                    elevation="2"
                    @click="logUser()"
                  >
                    LOGIN
                  </v-btn>
                </v-card-actions>
              </v-form>
            </v-card-text>
          </v-card>
        </v-col>
      </v-row>
    </v-container>
  </v-form>
</template>

<script>
import { mapActions, mapState } from "vuex";

import MessageHandler from "@/components/Users/MessageHandler";
import { isRequired } from "@/utils/rules.js";
import stringUtils from "@/utils/stringUtils";

/** This component handles the login page
 *
 */
export default {
  name: "Login",
  components: { MessageHandler },
  mixins: [stringUtils],
  props: {
    redirect: {
      type: Boolean,
      default: true,
    },
    popUp: {
      type: Boolean,
      default: false,
    },
  },
  emits: ["ClosePopup"],
  data: () => {
    return {
      show1: false,
      resendButton: false,
      currentPanel: "Login",
      loginData: {},
      oauthLogin: [
        {
          name: "ORCID",
          color: "bg-green text-white",
          callback: import.meta.env.VITE_API_ENDPOINT + "/users/auth/orcid",
        },
        // See: https://github.com/FAIRsharing/fairsharing.github.io/issues/2184
        /*
        {
          name: "Twitter",
          color: "blue text-white",
          callback: import.meta.env.VITE_API_ENDPOINT + "/users/auth/twitter",
        },
         */
        {
          name: "GitHub",
          color: "bg-black text-white",
          callback: import.meta.env.VITE_API_ENDPOINT + "/users/auth/github",
        },
      ],
      rules: {
        isRequired: function () {
          return isRequired();
        },
      },
      formValid: false,
    };
  },
  computed: {
    ...mapState("users", ["messages", "user"]),
  },
  methods: {
    ...mapActions("users", ["login", "logout"]),
    async logUser() {
      const _module = this;
      const user = {
        name: _module.loginData.name,
        password: _module.loginData.password,
      };
      _module.$emit("ClosePopup", false);
      await _module.login(user);

      if (_module.messages().login.error) {
        const confirmationError =
          "You have to confirm your email address before continuing.";
        if (_module.messages().login.message === confirmationError) {
          _module.resendButton = true;
        }
      } else {
        const goTo = _module.$route.query.goTo;
        let target = {};
        if (_module.redirect) {
          if (goTo) {
            //Added if condition as path was trimming query params in path in vue-router 4
            if (goTo.includes("?")) {
              const url = goTo.split("?");
              const queryURLArr = url[1].split("&");
              queryURLArr.forEach((pair) => {
                if (pair !== "") {
                  let splitpair = pair.split("=");
                  let key = splitpair[0];
                  target[key] = splitpair[1];

                  //For advancedSearch only
                  if (url[0] === "/advancedsearch" && pair.includes("fields")) {
                    const [key, ...rest] = pair.split("=");
                    const value = rest.join("=");
                    target[key] = decodeURIComponent(value);
                  }
                }
              });
              _module.$router.push({
                path: url[0],
                query: target,
              });
            } else {
              _module.$router.push({
                path: goTo,
              });
            }
          } else {
            _module.$router.push({
              path: "/accounts/profile",
            });
          }
        }
      }
    },
    returnTo() {
      const _module = this;
      const goTo = _module.$route.query.goTo;
      if (goTo) {
        return `?return_to=${goTo}`;
      }
      return "";
    },
    getCurrentLocation() {
      let loc = this.$router.currentRoute.path;
      let params = this.$route.query;
      let query = Object.keys(params)
        .map((k) => `${k}=${params[k]}`)
        .join("&")
        .replace("next=", "");
      let origin;
      if (params.length > 0) {
        origin = encodeURI(`${loc}`);
      } else {
        origin = encodeURI(`${loc}?${query}`);
      }
      return `?origin=${origin}`;
    },
    checkEndpoint() {
      if (
        import.meta.env.VITE_API_ENDPOINT === "https://api.fairsharing.org" ||
        import.meta.env.VITE_API_ENDPOINT === "http://127.0.0.1:3000"
      ) {
        return true;
      }
      return false;
    },
  },
};
</script>

<style lang="scss" scoped>
#loginPage a {
  text-decoration: none !important;
}

.v-card__text {
  width: auto;
}
</style>