Skip to content
Snippets Groups Projects
Login.vue 15.61 KiB
<template>
  <div id="login-page">
    <div class="row">
      <div class="fourteen wide column">
        <img
          class="ui centered small image"
          :src="appLogo"
          alt="Logo de l'application"
        >
        <h2 class="ui center aligned icon header">
          <div class="content">
            {{ appName }}
            <div class="sub header">
              {{ appAbstract }}
            </div>
          </div>
        </h2>
      </div>
    </div>
    <div class="row">
      <div
        v-if="$route.name === 'login'"
        class="six wide column"
      >
        <h3 class="ui horizontal divider header">
          CONNEXION
        </h3>

        <div :class="['ui warning message', {'closed': !errors.global}]">
          <div class="header">
            Les informations d'identification sont incorrectes.
          </div>
          NB: Seuls les comptes actifs peuvent se connecter.
        </div>

        <form
          class="ui form"
          role="form"
          type="post"
          @submit.prevent="login"
        >
          <div class="ui secondary segment">
            <div class="six field">
              <div class="ui left icon input">
                <i
                  class="user icon"
                  aria-hidden="true"
                />
                <input
                  v-model="loginForm.username"
                  type="text"
                  name="username"
                  placeholder="Utilisateur"
                >
              </div>
            </div>
            <div class="six field">
              <div class="ui left icon input">
                <i
                  class="lock icon"
                  aria-hidden="true"
                />
                <input
                  v-model="loginForm.password"
                  type="password"
                  name="password"
                  placeholder="Mot de passe"
                >
              </div>
            </div>
            <button
              class="ui fluid large teal submit button"
              type="submit"
            >
              Login
            </button>
          </div>
        </form>
      </div>
      <div
        v-else-if="$route.name === 'signup'"
        class="six wide column"
      >
        <h3 class="ui horizontal divider header">
          INSCRIPTION
        </h3>

        <div :class="['ui warning message', {'closed': !error}]">
          {{ error }}
        </div>

        <form
          class="ui form"
          role="form"
          type="post"
          @submit.prevent="signup"
        >
          <div class="ui secondary segment">
            <div class="six field">
              <div class="ui left icon input">
                <i
                  class="user outline icon"
                  aria-hidden="true"
                />
                <input
                  v-model="signupForm.first_name"
                  type="text"
                  name="first_name"
                  placeholder="Prénom"
                  required
                >
              </div>
            </div>

            <div class="six field">
              <div class="ui left icon input">
                <i
                  class="id card icon"
                  aria-hidden="true"
                />
                <input
                  v-model="signupForm.last_name"
                  type="text"
                  name="last_name"
                  placeholder="Nom"
                  required
                >
              </div>
            </div>

            <div class="six field">
              <div class="ui left icon input">
                <i
                  class="envelope icon"
                  aria-hidden="true"
                />
                <input
                  v-model="signupForm.email"
                  type="email"
                  name="email"
                  placeholder="Adresse courriel"
                  required
                >
              </div>
            </div>

            <div class="six field">
              <div class="ui left icon input">
                <i
                  class="user icon"
                  aria-hidden="true"
                />
                <input
                  v-model="signupForm.username"
                  type="text"
                  name="username"
                  placeholder="Utilisateur"
                  disabled
                >
              </div>
            </div>

            <div :class="['six field', {'error': errors.passwd}]">
              <div class="ui action left icon input">
                <i
                  class="lock icon"
                  aria-hidden="true"
                />
                <input
                  v-model="signupForm.password"
                  :type="showPwd ? 'text' : 'password'"
                  name="password"
                  placeholder="Mot de passe"
                  required
                  @blur="isValidPwd"
                >
                <button
                  class="ui icon button"
                  @click="showPwd = !showPwd"
                >
                  <i :class="[showPwd ? 'eye slash' : 'eye', 'icon']" />
                </button>
              </div>
            </div>

            <div :class="['six field', {'error': errors.comments}]">
              <div class="ui left icon input">
                <i
                  class="pencil icon"
                  aria-hidden="true"
                />
                <input
                  v-model="signupForm.comments"
                  type="text"
                  name="comments"
                  :placeholder="commentsFieldLabel || `Commentaires`"
                  :required="commentsFieldRequired"
                >
              </div>
            </div>
            <div
              v-if="usersGroupsOptions.length > 0"
              class="six field"
            >
              <div class="ui divider" />
              <Multiselect
                v-model="usersGroupsSelections"
                :options="usersGroupsOptions"
                :multiple="true"
                track-by="value"
                label="name"
                select-label=""
                selected-label=""
                deselect-label=""
                :searchable="false"
                :placeholder="'Sélectionez un ou plusieurs groupe de la liste ...'"
              />
              <p v-if="adminMail">
                Si le groupe d'utilisateurs recherché n'apparaît pas, vous pouvez demander à
                <a :href="'mailto:'+adminMail">{{ adminMail }}</a> de le créer
              </p>
            </div>

            <button
              :class="['ui fluid large teal submit button']"
              type="submit"
            >
              Valider
            </button>
          </div>
        </form>
      </div>
      <div
        v-else-if="$route.name === 'sso-signup-success'"
        class="six wide column"
      >
        <h3 class="ui horizontal divider header">
          INSCRIPTION RÉUSSIE
        </h3>
        <h4 class="ui center aligned icon header">
          <div class="content">
            <p
              v-if="username"
              class="sub header"
            >
              Le compte pour le nom d'utilisateur <strong>{{ username }}</strong> a été créé
            </p>
            <p>
              Un e-mail de confirmation vient d'être envoyé à l'adresse indiquée.
            </p>
            <p class="sub header">
              Merci de bien vouloir suivre les instructions données afin de finaliser la création de votre compte.
            </p>
          </div>
        </h4>
      </div>
    </div>
  </div>
</template>

<script>
import { mapState, mapGetters, mapMutations } from 'vuex';
import Multiselect from 'vue-multiselect';
import userAPI from '../services/user-api';

export default {
  name: 'Login',

  components: {
    Multiselect
  },

  props: {
    username: {
      type: String,
      default: null
    }
  },

  data() {
    return {
      logged: false,
      loginForm: {
        username: null,
        password: null,
      },
      signupForm: {
        username: null,
        password: null,
        first_name: null,
        last_name: null,
        email: null,
        comments: null,
        usersgroups: [],
      },
      errors: {
        global: null,
        passwd: null,
        comments: null,
      },
      showPwd: false,
    };
  },

  computed: {
    ...mapState({
      appLogo: state => state.configuration.VUE_APP_LOGO_PATH,
      appName: state => state.configuration.VUE_APP_APPLICATION_NAME,
      appAbstract: state => state.configuration.VUE_APP_APPLICATION_ABSTRACT,
      adminMail: state => state.configuration.VUE_APP_ADMIN_MAIL,
      ssoSignupUrl: state => state.configuration.VUE_APP_SSO_SIGNUP_URL,
      commentsFieldLabel: state => state.configuration.VUE_APP_SIGNUP_COMMENTS_FIELD_LABEL,
      commentsFieldRequired: state => state.configuration.VUE_APP_SIGNUP_COMMENTS_FIELD_REQUIRED,
    }),
    ...mapGetters(['usersGroupsOptions']),

    usersGroupsSelections: {
      get() {
        return this.usersGroupsOptions.filter((el) => this.signupForm.usersgroups?.includes(el.value));
      },
      set(newValue) {
        this.signupForm.usersgroups = newValue.map(el => el.value);
      }
    },

    error() {
      return this.errors.global || this.errors.passwd || this.errors.comments;
    }
  },

  watch: {
    'signupForm.first_name': function (newValue, oldValue) {
      if (newValue !== oldValue) {
        this.signupForm.username = `${newValue.charAt(0)}${this.signupForm.last_name}`.toLowerCase().replace(/\s/g, '');
      }
    },
    'signupForm.last_name': function (newValue, oldValue) {
      if (newValue !== oldValue) {
        this.signupForm.username = `${this.signupForm.first_name.charAt(0)}${newValue}`.toLowerCase().replace(/\s/g, '');
      }
    },
    'signupForm.password': function (newValue, oldValue) {
      if (newValue.length >= 8) {
        if (newValue !== oldValue) {
          this.isValidPwd();
        }
      } else {
        this.errors.passwd = null;
      }
    },
    username(newValue, oldValue) {
      if (newValue !== oldValue) {
        this.loginForm.username = newValue; 
      }
    }
  },

  created() {
    if (this.$route.name === 'signup') {
      this.$store.dispatch('GET_USERS_GROUPS'); // récupére les groupes d'utilisateurs pour extra_forms
    }
  },

  mounted() {
    if (this.$route.name === 'login') {
      if (this.$store.state.user) {
        this.DISPLAY_MESSAGE({ header: 'Vous êtes déjà connecté', comment: 'Vous allez être redirigé vers la page précédente.' });
        setTimeout(() => this.$store.dispatch('REDIRECT_AFTER_LOGIN'), 3100);
      }
    }
  },

  methods: {
    ...mapMutations(['DISPLAY_MESSAGE']),

    login() {
      this.$store
        .dispatch('LOGIN', {
          username: this.loginForm.username,
          password: this.loginForm.password,
        })
        .then((status) => {
          if (status === 200) {
            this.errors.global = null;
          } else if (status === 'error') {
            this.errors.global = status;
          }
        })
        .catch();
    },

    async signup() {
      if (this.hasUnvalidFields()) return;
      
      // Étape 1 : Création de l'utilisateur auprès du service d'authentification SSO si nécessaire
      if (this.ssoSignupUrl) {
        const ssoResponse = await userAPI.signup({
          ...this.signupForm,
          // Ajout du label personnalisé pour affichage plus précis dans admin OGS
          comments: `{"${this.commentsFieldLabel}":"${this.signupForm.comments}"}`,
          // Pour permettre la visualisation dans OGS Maps, l'utilisateur doit être ajouté à un groupe OGS, mis en dur pour aller vite pour l'instant
          usergroup_roles:[{ organisation: { id: 1 } }]
        }, this.ssoSignupUrl);
        
        if (ssoResponse.status !== 201) {
          if (ssoResponse.status === 400) {
            this.errors.global = 'Un compte associé à ce courriel existe déjà';
          } else {
            this.errors.global = `Erreur lors de l'inscription: ${ssoResponse.data?.detail || 'Problème inconnu'}`;
          }
          return;  // Stoppe la fonction si l'inscription SSO échoue
        } else {
          this.signupForm.username = ssoResponse.data.username;
          this.signupForm.first_name = ssoResponse.data.first_name;
          this.signupForm.last_name = ssoResponse.data.last_name;
        }
      }

      // Étape 2 : Création de l'utilisateur dans Geocontrib
      const response = await userAPI.signup(this.signupForm);

      if (response.status !== 201) {
        const errorMessage = response.data
          ? Object.values(response.data)?.[0]?.[0] || 'Problème inconnu'
          : 'Problème inconnu';
        this.errors.global = `Erreur lors de l'inscription: ${errorMessage}`;
        return;
      }

      this.DISPLAY_MESSAGE({ header: 'Inscription réussie !', comment: `Bienvenue sur la plateforme ${this.signupForm.username}.`, level: 'positive' });
      if (this.ssoSignupUrl) {
        setTimeout(() => {
          this.$router.push({ name: 'sso-signup-success', params: { username: this.signupForm.username } });
        }, 3100);
      } else {
        setTimeout(() => {
          this.$router.push({ name: 'login', params: { username: this.signupForm.username } });
        }, 3100);
      }
    },


    isValidPwd() {
      const regPwd = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d/$&+,:;=?#|'<>.^*()%!-]{8,}$/;

      if (!regPwd.test(this.signupForm.password)) {
        this.errors.passwd = `Le mot de passe doit comporter au moins 8 caractères, dont 1 majuscule, 1 minuscule et 1 chiffre.
        Vous pouvez utiliser les caractères spéciaux suivants : /$ & + , : ; = ? # | ' < > . ^ * ( ) % ! -.`;
        return false;
      }
      
      this.errors.passwd = null;
      return true;
    },

    hasUnvalidFields() {
      const { last_name, email, first_name, comments } = this.signupForm;

      if (this.commentsFieldRequired && !comments) {
        this.errors.comments = `Le champ ${ this.commentsFieldLabel || 'Commentaires'} est requis`;
        return true;
      } else {
        this.errors.comments = null;
      }

      if (email && last_name && first_name) {
        this.errors.global = null;
      } else {
        this.errors.global = 'Certains champs requis ne sont pas renseignés';
        return true;
      }

      return !this.isValidPwd();   
    }
  },
};
</script>

<style lang="less" scoped>
#login-page {
  max-width: 500px;
  min-width: 200px;
  margin: 3em auto;

  .ui.message {
    min-height: 0px;

    &.closed {
      overflow: hidden;
      opacity: 0;
      padding: 0;
      max-height: 0px;
    }
  }

  input[required] {
    background-image: linear-gradient(45deg, transparent, transparent 50%, rgb(209, 0, 0) 50%, rgb(209, 0, 0) 100%);
    background-position: top right;
    background-size: .5em .5em;
    background-repeat: no-repeat;
  }
}
p {
  margin: 1em 0 !important; 
}
</style>

<style>
.multiselect__placeholder {
  position: absolute;
  width: calc(100% - 48px);
  overflow: hidden;
  text-overflow: ellipsis;
}

.multiselect__tags {
  position: relative;
}

/* keep font-weight from overide of semantic classes */
.multiselect__placeholder,
.multiselect__content,
.multiselect__tags {
  font-weight: initial !important;
}

/* keep placeholder eigth */
.multiselect .multiselect__placeholder {
  margin-bottom: 9px !important;
  padding-top: 1px;
}

/* keep placeholder height when opening dropdown without selection */
input.multiselect__input {
  padding: 3px 0 0 0 !important;
}

/* keep placeholder height when opening dropdown with already a value selected */
.multiselect__tags .multiselect__single {
  padding: 1px 0 0 0 !important;
  margin-bottom: 9px;
}
</style>