# Magasin Auth

## Création du magasin Auth

Nous allons créer un magasin qui enregistrera les données de l'utilisateur authentifié (nom, token) et qui lui permettra de se connecter, se déconnecter ou créer un nouveau compte.

Créer un nouveau magasin `src/stores/store-auth.js` et y copier ce code.

{% code title="store/store-auth.js" %}

```javascript
import { defineStore } from 'pinia'

export const useAuthStore = defineStore('authStore', () => {
  return {}
})
```

{% endcode %}

## �Enregistrer un nouvel utilisateur

Commençons par créer une action `enregistrerUtilisateur(`*utilisateur*`)` qui sera appelée par `FormSignup.vue`. Cette action recevra en paramètre, *utilisateur*, les données du formulaire d'enregistrement.

{% code title="store-auth.js" %}

```javascript
/**
 * @param {{email: string, name: string, password: string, password_confirmation: string}} utilisateur
 */
const enregistrerUtilisateur = (utilisateur) => {
  console.log('enregistrerUtilisateur', utilisateur)
}

return { enregistrerUtilisateur }
```

{% endcode %}

{% hint style="info" %}
Le commentaire JavaDoc permet de définir un type sur le paramètre! Ainsi, on se souvient plus facilement quelles sont les données passées.
{% endhint %}

### Appel de l'action

Avant d'aller plus loin, nous allons mapper l'action dans `FormSignup.vue` et l'appeler en y passant les données pour nous assurer que tout fonctionne.

* 3 : importation du crochet du store `useAuthStore`
* 12 : initialisation du store `authStore`
* 15 : appel de l'action `enregistrerUtilisateur` avec les données du formulaire en paramètre.

{% code title="Connexion/FormSignup.vue" lineNumbers="true" %}

```markup
<script setup>
import { ref } from 'vue'
import { useAuthStore } from 'stores/store-auth'

const form = ref({
  email: '',
  password: '',
  password_confirmation: '',
  name: ''
})

const authStore = useAuthStore()

const submitForm = () => {
  authStore.enregistrerUtilisateur(form.value)
}

const validateEmail = (val) => {
  // Source : https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript
  const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
  return regex.test(String(val).toLowerCase())
}
</script>
```

{% endcode %}

Si tout est OK, les données du formulaire s'affichent dans la console.

### Envoi d'une requête POST à l'API

Pour créer un nouvel utilisateur avec l'API il faudra utiliser la route ci-après et y passer les paramètres demandés.

## Créer un utilisateur

<mark style="color:green;">`POST`</mark> `/register`

Permet la création d'un nouvel utilisateur pour l'API ToDo

#### Request Body

| Name                   | Type   | Description |
| ---------------------- | ------ | ----------- |
| name                   | string |             |
| email                  | string |             |
| password               | string |             |
| password\_confirmation | string |             |

{% tabs %}
{% tab title="200 " %}

```javascript
{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvdG9kby5rb2RlLmNoXC9hcGlcL3JlZ2lzdGVyIiwiaWF0IjoxNTkxMTE5ODc3LCJleHAiOjE1OTExMjM0NzcsIm5iZiI6MTU5MTExOTg3NywianRpIjoiVTBxM2JsUFJtQjZjc1RoMiIsInN1YiI6NSwicHJ2IjoiODdlMGFmMWVmOWZkMTU4MTJmZGVjOTcxNTNhMTRlMGIwNDc1NDZhYSJ9.5NTrgduM0gSWj7fADSvYv7V1va7f4WO0q988q4DXXao",
    "token_type": "bearer",
    "expires_in": 86400,
    "user": {
        "id": 5,
        "name": "user 4",
        "email": "user54@gmail.com"
    }
}
```

{% endtab %}

{% tab title="422 " %}

```javascript
{
    "email": [
        "The email has already been taken."
    ]
}
```

{% endtab %}
{% endtabs %}

### Axios

Pour dialoguer avec l'API, nous utiliserons la librairie JavaScript **axios** :

{% @github-files/github-code-block url="<https://github.com/axios/axios>" %}

Axios a été initialisé par Quasar et ajouté à notre projet dans `src/boot/axios.js`&#x20;

Si Axios et son boot n'existent pas vous pouvez le faire avec les commandes suivantes ;

```bash
# Ajouter Axios à votre projet
yarn add axios
# ou
npm install axios --save

# Création du boot Axios
quasar new boot axios
```

Modifier le contenu de ce fichier avec le code suivant qui préfixera automatiquement les routes de l'API avec l'URI de base de notre API `https://todo.kode.ch/api`.

{% code title="boot/axios.js" %}

```javascript
import { boot } from 'quasar/wrappers'
import axios from 'axios'

const api = axios.create({
  baseURL: 'https://todo.kode.ch/api',
  timeout: 3000,
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json'
  }
})

export default boot(({ app }) => {
  // for use inside Vue files (Options API) through this.$axios and this.$api

  app.config.globalProperties.$axios = axios
  // ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
  //       so you won't necessarily have to import axios in each vue file

  app.config.globalProperties.$api = api
  // ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
  //       so you can easily perform requests against your app's API
})

export { api }
```

{% endcode %}

Pour y accéder depuis des fichiers JavaScript sans instance Vue, comme nos magasins, il faudra l'importer :

```javascript
import { api } from 'boot/axios'
```

### Requête POST avec Axios

Créons une requête POST avec axios dans notre action `enregistrerUtilisateur` et affichons son résultat en cas de réussite `.then` ou en cas d'échec `.catch` dans la console.

{% hint style="danger" %}
Ne pas oublier d'importer axios dans le magasin *Auth*.
{% endhint %}

* 1 : importation d'axios
* 22 : envoi d'un requête  `POST /register` avec les données du formulaire en paramètre `payload`.
* 23-25 : en cas de succès, affiche la réponse de l'API (objet JS).
* 26-28 : en cas d'échec affiche la réponse de l'API (objet JS), stockée dans l'erreur JavaScript.
  * pour accéder à la réponse d'une erreur : `error.response`.

{% code title="src/store/store-auth.js" lineNumbers="true" %}

```javascript
import { defineStore } from 'pinia'
import { api } from 'boot/axios'

export const useAuthStore = defineStore('authStore', () => {
  /**
   * @param {{email: string, name: string, password: string, password_confirmation: string}} utilisateur
   */
  const enregistrerUtilisateur = async (utilisateur) => {
    let res
    try {
      res = await api.post('/register', utilisateur)
    } catch (e) {
      console.error(e)
    }

    console.log('Création OK', res)
  }

  return { enregistrerUtilisateur }
})

```

{% endcode %}

#### Réponse en cas de succès

![Exemple de réponse en cas de succès](https://140209345-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M60O5kdEkdIt2ATmwdy%2F-M8qVn7FSyPT5dGa48sp%2F-M8qWvAEhqrne-g7zkcV%2FQuasar-API-POST-ok-response-2.png?alt=media\&token=d75aa30f-9a1e-4154-b0b5-9749ded21d97)

#### Réponse en cas d'erreur

![Exemple de réponse en cas d'échec](https://140209345-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M60O5kdEkdIt2ATmwdy%2F-M8qVn7FSyPT5dGa48sp%2F-M8qWx7dwfzEGw1Szf1Q%2FQuasar-API-POST-error-response-2.png?alt=media\&token=726c93e5-c794-44a6-acb8-cef5dc5a54c9)

### Sauvegarder l'utilisateur dans le magasin

Que faire avec la réponse de l'API ? Et bien, on va récupérer les données de l'utilisateur et les sauvegarder dans notre magasin.

On pourra ainsi savoir si un utilisateur est connecté ou non et utiliser le token (clé de sécurité identifiante) pour appeler les routes protégées, notamment celles qui nous retourneront les tâches de l'utilisateur.

Pour ce faire nous allons :&#x20;

1. Créer un *ref* `user` et `token` initialisés à `null` dans le magasin `store-auth.js`
2. Modifier l'action `enregistrerUtilisateur` , en cas de succès de la requête de création
   1. Affecter le token et l'objet user au states
   2. Rediriger l'utilisateur vers la page d’accueil

#### Résultat :

* 7 : Création de la `ref` `user`
* 8 : Création de la `ref` `token`
* 24-25 : modification de `user` et `token`
* 26 : Redirige l'utilisateur vers la page d'accueil

{% code title="store/store-auth.js" lineNumbers="true" %}

```javascript
import { defineStore } from 'pinia'
import { api } from 'boot/axios'
import { ref } from 'vue'
import { useRouter } from 'vue-router'

export const useAuthStore = defineStore('authStore', () => {
  const user = ref(null)
  const token = ref(null)
  const router = useRouter()

  /**
   * @param {{email: string, name: string, password: string, password_confirmation: string}} utilisateur
   */
  const enregistrerUtilisateur = async (utilisateur) => {
    let res
    try {
      res = await api.post('/register', utilisateur)
    } catch (e) {
      console.error(e)
      return
    }

    console.log('Création OK', res)
    token.value = res.data.access_token
    user.value = res.data.user
    await router.push('/')
  }

  return {
    enregistrerUtilisateur,
    user,
    token
  }
})

```

{% endcode %}

## Connexion d'un utilisateur

## Connecter un utilisateur

<mark style="color:green;">`POST`</mark> `/login`

#### Request Body

| Name                                       | Type   | Description |
| ------------------------------------------ | ------ | ----------- |
| email<mark style="color:red;">\*</mark>    | string |             |
| password<mark style="color:red;">\*</mark> | string |             |

{% tabs %}
{% tab title="200 " %}

```javascript
{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvdG9kby5rb2RlLmNoXC9hcGlcL2xvZ2luIiwiaWF0IjoxNTkxMTI5OTI1LCJleHAiOjE1OTExMzM1MjUsIm5iZiI6MTU5MTEyOTkyNSwianRpIjoiSVRjOHA1NVF5aFJFZUkyeCIsInN1YiI6MiwicHJ2IjoiODdlMGFmMWVmOWZkMTU4MTJmZGVjOTcxNTNhMTRlMGIwNDc1NDZhYSJ9.pnPAv0ta9mmbCU8jibst2rzqoc2XeEWfYCXN7BbRy2o",
    "token_type": "bearer",
    "expires_in": 86400,
    "user": {
        "id": 2,
        "name": "user 2",
        "email": "user2@gmail.com"
    }
}
```

{% endtab %}

{% tab title="302 " %}

```
```

{% endtab %}

{% tab title="422 " %}

```
{
    "email": [
        "The email field is required."
    ],
    "password": [
        "The password field is required."
    ]
}
```

{% endtab %}
{% endtabs %}

Mêmes opérations que pour la création d'un compte :

1. Création d'une action `connecterUtilisateur`
   1. Envoyer la requête `POST /login` avec *axios* et y passer les données de connexion
   2. Récupérer les données et modifier les *ref* `user` et `token`
   3. Redirige l'utilisateur vers la page d'accueil
2. Modifier le formulaire de connexion dans `ConnexionForm.vue`
   1. appeler `connecterUtilisateur` et y passer les données du formulaire

#### Résultat

* **store-auth.js**
  * 32-44 : Création de l'action `connecterUtilisateur`
* **FormLogin.vue**
  * 31 : Importation de `mapActions`
  * 43  : Mappage de `connecterUtilisateur`
  * 45 : Appelle  `connecterUtilisateur` et y passer les données du formulaire

{% tabs %}
{% tab title="store-auth.js" %}
{% code title="store/store-auth.js" lineNumbers="true" %}

```javascript
import { defineStore } from 'pinia'
import { api } from 'boot/axios'
import { ref } from 'vue'
import { useRouter } from 'vue-router'

export const useAuthStore = defineStore('authStore', () => {
  const user = ref(null)
  const token = ref(null)
  const router = useRouter()

  /**
   * @param {{email: string, name: string, password: string, password_confirmation: string}} utilisateur
   */
  const enregistrerUtilisateur = async (utilisateur) => {
    let res
    try {
      res = await api.post('/register', utilisateur)
    } catch (e) {
      console.error(e)
      return
    }

    console.log('Création OK', res)
    token.value = res.data.access_token
    user.value = res.data.user
    await router.push('/')
  }

  /**
   * @param {{email:string, password:string}} utilisateur
   */
  const connecterUtilisateur = async (utilisateur) => {
    let res
    try {
      res = await api.post('/login', utilisateur)
    } catch (e) {
      console.error(e)
    }

    console.log('Connexion OK', res)
    token.value = res.data.access_token
    user.value = res.data.user
    await router.push('/')
  }

  return {
    enregistrerUtilisateur,
    connecterUtilisateur,
    user,
    token
  }
})

```

{% endcode %}
{% endtab %}

{% tab title="ConnexionForm.vue" %}
{% code title="src/components/Connexion/ConnexionForm.vue" %}

```markup
<template>
  <q-form @submit.prevent="submitForm">
    <q-input
      v-model="form.email"
      :rules="[val => validateEmail(val) || 'Email invalide']"
      class="q-my-md"
      label="E-mail"
      lazy-rules
      outlined
    />

    <q-input
      v-model="form.password"
      :rules="[ val => val.length >= 4 || 'Minimum 4 caractère']"
      class="q-my-md"
      label="Mot de passe"
      lazy-rules
      outlined
      type="password"
    />

    <q-btn
      color="primary"
      label="Se connecter"
      type="submit"
    />
  </q-form>
</template>

<script setup>
import { ref } from 'vue'
import { useAuthStore } from 'stores/store-auth'

const auth = useAuthStore()

const form = ref({
  email: '',
  password: ''
})

const submitForm = () => {
  auth.connecterUtilisateur(form.value)
}

const validateEmail = (val) => {
  // Source : https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript
  const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
  return regex.test(String(val).toLowerCase())
}
</script>

<style lang="scss" scoped>

</style>

```

{% endcode %}
{% endtab %}
{% endtabs %}

## Amélioration du code, *refactoring*

Le code des méthodes `enregistrerUtilisateur` et `connecterUtilisateur` se ressemble beaucoup. Afin d'améliorer notre code, nous allons créer une nouvelle méthode `setUser`, qui regroupe le code identique de nos deux méthodes.

L'action `setUser` va sauvegarder les données de l'utilisateur dans le magasin *Auth* et rediriger l'utilisateur vers la page des tâches.

Pour appeler une action depuis une autre action, il suffit de l'appeler. Vu que le code est interne à notre magasin, il ne faut pas le retourner dans notre setup!

{% code title="store/store-auth.js" %}

```javascript
import { defineStore } from 'pinia'
import { api } from 'boot/axios'
import { ref } from 'vue'
import { useRouter } from 'vue-router'

export const useAuthStore = defineStore('authStore', () => {
  const user = ref(null)
  const token = ref(null)
  const router = useRouter()

  /**
   * @param {{data:{user:any,token:string}}} res
   */
  const setUser = async (res) => {
    token.value = res.data.access_token
    user.value = res.user
    await router.push('/')
  }

  /**
   * @param {{email: string, name: string, password: string, password_confirmation: string}} utilisateur
   */
  const enregistrerUtilisateur = async (utilisateur) => {
    let res
    try {
      res = await api.post('/register', utilisateur)
      setUser(res)
    } catch (e) {
      console.error(e)
    }
  }

  /**
   * @param {{email:string, password:string}} utilisateur
   */
  const connecterUtilisateur = async (utilisateur) => {
    let res
    try {
      res = await api.post('/login', utilisateur)
      setUser(res)
    } catch (e) {
      console.error(e)
    }
  }

  return {
    enregistrerUtilisateur,
    connecterUtilisateur,
    user,
    token
  }
})

```

{% endcode %}

## �Gestion des erreurs

Si l'utilisateur entre un mauvais login ou mot de passe, rien ne s'affiche.

Nous allons créer une fonction qui s'occupera d'afficher les message d'erreurs pour toute notre application. Pour que cette fonction soit utilisable par l'ensemble de l'application, nous allons la créer dans un fichier séparer qui sera importer par les fichiers qui en ont besoin.

Pour afficher les messages d'erreurs, nous utiliserons le plugin Dialog de Quasar :

{% embed url="<https://quasar.dev/quasar-plugins/dialog>" %}

Créer un le fichier `src/fonctions/message-erreur.js` et y copier le code suivant.

{% code title="fonctions/message-erreur.js" %}

```javascript
import { Dialog } from 'quasar'

/**
 * @param {string} message
 * @param {string[] | undefined} erreurs
 */
export const afficherMessageErreur = (message, erreurs) => {
  if (Array.isArray(erreurs) && erreurs.length) {
    message += '<ul><li>' + erreurs.join('</li><li>') + '</li></ul>'
  }

  Dialog.create({
    title: 'Erreur',
    message,
    html: true
  })
}

```

{% endcode %}

Il faut maintenant importer cette fonction dans le magasin *Auth* et l'appeler en cas d'échec dans les actions `enregistrerUtilisateur` et `connecterUtilisateur`.

* 2 : Importation de la fonction `afficherMessageErreur`
* 34-37 et 47-50 : affiche un message d'erreur et y passe les données d'erreur retournée par l'API
* 38 et 51 : L'instruction `throw` permet de lever une exception définie par l'utilisateur. L'exécution de la fonction courante sera stoppée et une erreur sera visible dans la console.

{% code title="src/store/store-auth.js" %}

```javascript
import { defineStore } from 'pinia'
import { api } from 'boot/axios'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { afficherMessageErreur } from 'src/fonctions/message-erreur'

export const useAuthStore = defineStore('authStore', () => {
  const user = ref(null)
  const token = ref(null)
  const router = useRouter()

  /**
   * @param {{data:{user:any,token:string}}} res
   */
  const setUser = async (res) => {
    token.value = res.data.access_token
    user.value = res.user
    await router.push('/')
  }

  /**
   * @param {{email: string, name: string, password: string, password_confirmation: string}} utilisateur
   */
  const enregistrerUtilisateur = async (utilisateur) => {
    let res
    try {
      res = await api.post('/register', utilisateur)
      setUser(res)
    } catch (e) {
      afficherMessageErreur(
        'Impossible de créer le compte',
        Object.values(e.response.data)
      )
    }
  }

  /**
   * @param {{email:string, password:string}} utilisateur
   */
  const connecterUtilisateur = async (utilisateur) => {
    let res
    try {
      res = await api.post('/login', utilisateur)
      setUser(res)
    } catch (e) {
      afficherMessageErreur(
        'Impossible de se connecter',
        Object.values(e.response.data)
      )
    }
  }

  return {
    enregistrerUtilisateur,
    connecterUtilisateur,
    user,
    token
  }
})
```

{% endcode %}

{% hint style="info" %}
**Idée :** Faire la même chose pour la **validation d'une adresse e-mail** en créant un fichier `src/functions/valider-email.js`
{% endhint %}

## Loader...

Le temps de réponse de l'API varie et peut parfois prendre quelques secondes.

Afin d'indiquer à l'utilisateur qu'un traitement est en cours, nous allons afficher un loader en utilisant le plugin *Loading* de Quasar.

{% embed url="<https://quasar.dev/quasar-plugins/loading>" %}

Pour utiliser ce plugin, il suffit de l'importer et d'utiliser les méthodes `Loading.show()` et `Loading.hide()` pour afficher et cacher le *loader*.

### Importation du plugin *Loader*&#x20;

Activer le plugin *Loading* en l'ajoutant dans `quasar.config.js` :

{% code title="quasar.config.js" %}

```javascript
// ...
module.exports = configure(function (/* ctx */) {
  return {
    // ...
    framework: {
      // ...
      // Quasar plugins
      plugins: [
        'Dialog',
        'Loading'
      ]
      // ...
    }
    // ...
  }
})
```

{% endcode %}

Il faudra afficher le *loader* lorsque l'utilisateur démarre les action `enregistrerUtilisateur` et `connecterUtilisateur`et les cacher après avoir reçu une réponse.

* 6 : importation du plugin *Loading*
* 27 et 45:  Affiche le loader lorsqu'on se connecte ou crée un compte utilisateur
* 33 et 51 : Cache le loader si il y a une erreur, avant d'afficher le message
* 20 : Cache le loader après la connexion de l’utilisateur

{% code title="src/store/store-auth.js" lineNumbers="true" %}

```javascript
import { defineStore } from 'pinia'
import { api } from 'boot/axios'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { afficherMessageErreur } from 'src/fonctions/message-erreur'
import { Loading } from 'quasar'

export const useAuthStore = defineStore('authStore', () => {
  const user = ref(null)
  const token = ref(null)
  const router = useRouter()

  /**
   * @param {{data:{user:any,token:string}}} res
   */
  const setUser = async (res) => {
    token.value = res.data.access_token
    user.value = res.user
    await router.push('/')
    Loading.hide()
  }

  /**
   * @param {{email: string, name: string, password: string, password_confirmation: string}} utilisateur
   */
  const enregistrerUtilisateur = async (utilisateur) => {
    Loading.show()
    let res
    try {
      res = await api.post('/register', utilisateur)
      setUser(res)
    } catch (e) {
      Loading.hide()
      afficherMessageErreur(
        'Impossible de créer le compte',
        Object.values(e.response.data)
      )
    }
  }

  /**
   * @param {{email:string, password:string}} utilisateur
   */
  const connecterUtilisateur = async (utilisateur) => {
    Loading.show()
    let res
    try {
      res = await api.post('/login', utilisateur)
      setUser(res)
    } catch (e) {
      Loading.hide()
      afficherMessageErreur(
        'Impossible de se connecter',
        Object.values(e.response.data)
      )
    }
  }

  return {
    enregistrerUtilisateur,
    connecterUtilisateur,
    user,
    token
  }
})
```

{% endcode %}
