Sécuriser le blog avec Phoenix

Dans le précédent article, nous avons montré comment créer un blog avec Phoenix. Nous avons besoin maintenant de securiser le blog avec Phoenix.

Pour sécuriser le blog avec Phoenix, nous devons :

  • associer les articles à leur auteur
  • donner des droits exclusifs aux auteurs pour la modification et la suppression des articles
  • créer une liste d’article par auteur pour aider les auteurs à administrer leur contenu

Nous mettons en place une sécurisation simple. Il est possible de compléter l’application en ajoutant la notion de rôle et d’attacher des rôles aux différents utilisateurs. Un article de leanpanda montre comment personnaliser les droits d’accès avec la notion de rôle à ajouter dans la table users.

Protéger l’accès aux articles pour sécuriser le blog Phoenix

Dans notre article précédentcomment créer un blog avec Phoenix, les articles sont tous accessibles sans avoir besoin de s’identifier.

Phoenix permet très simplement de protéger l’accès aux pages en forçant l’identification des visiteurs. Cela se fait au niveau de BLOGSET/lib/blogset_web/router.ex.

Pour l’instant, nous avons mis les chemins vers les pages des articles dans :

  • scope « / », BlogsetWeb do
  • pipe_through :browser
  • lignes 5 à 10 ci-dessous :
  scope "/", BlogsetWeb do
    pipe_through :browser

    get "/", PageController, :home
    live "/stories", StoryLive.Index, :index
    live "/stories/new", StoryLive.Index, :new
    live "/stories/:id/edit", StoryLive.Index, :edit

    live "/stories/:id", StoryLive.Show, :show
    live "/stories/:id/show/edit", StoryLive.Show, :edit
  end

Nous allons déplacer ces routes dans :

  • scope « / », BlogsetWeb do
  • pipe_through [:browser, :require_authenticated_user]

La définition de require_authenticated_user se trouve dans user_auth.ex.

BLOGSET/lib/blogset_web/user_auth.ex :

  @doc """
  Used for routes that require the user to be authenticated.

  If you want to enforce the user email is confirmed before
  they use the application at all, here would be a good place.
  """
  def require_authenticated_user(conn, _opts) do
    if conn.assigns[:current_user] do
      conn
    else
      conn
      |> put_flash(:error, "You must log in to access this page.")
      |> maybe_store_return_to()
      |> redirect(to: ~p"/users/log_in")
      |> halt()
    end
  end

Le principe est de tester si l’utilisateur est connecté dans la session (ligne 8) avec :

  • if conn.assigns[:current_user] do

Si c’est le cas, nous pouvons continuer et la fonction retourne la connection conn. Sinon, nous forçons une redirection vers la page d’identification (ligne 14) en modifiant conn.

Dans le cadre du projet, nous aurons besoin de deux types de vue en mode liste pour les articles :

  • la vue par auteur permettant à l’auteur de voir la liste de ses propres articles
  • la vue consultation publique permettant à tout le monde de voir les articles du blog

Seule la vue par auteur doit contenir les liens vers les actions de modification ou de suppression d’article.

Vérifier que nous avons bien sécurisé le blog Phoenix

Nous déplaçons les routes :

  • fichier : BLOGSET/lib/blogset_web/router.ex,
  • fonction : scope « / », BlogsetWeb do
  • filtrage : pipe_through [:browser, :require_authenticated_user]
  scope "/", BlogsetWeb do
    pipe_through [:browser, :require_authenticated_user]

    live_session :require_authenticated_user,
      on_mount: [{BlogsetWeb.UserAuth, :ensure_authenticated}] do
      live "/users/settings", UserSettingsLive, :edit
      live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email

      live "/stories", StoryLive.Index, :index
      live "/stories/new", StoryLive.Index, :new
      live "/stories/:id/edit", StoryLive.Index, :edit

      live "/stories/:id", StoryLive.Show, :show
      live "/stories/:id/show/edit", StoryLive.Show, :edit
    end
  end

Nous vérifions cette fonctionnalité :

  • cd C:\CarbonX1\Phoenix\Projets\blogset
  • iex -S mix phx.server ‘execution en mode interactif
  • http://localhost:4000/stories/ ‘aller sur la page de l’application depuis un navigateur

La page d’identification s’affiche lorsque l’utilisateur n’est pas identifé :

Sécuriser le blog avec Phoenix#1-Visualisation de la page d'identification
Sécuriser le blog avec Phoenix#1-Visualisation de la page d’identification

Après authentification, nous allons sur la page demandée :

Sécuriser le blog avec Phoenix#2-Accès à la liste des articles après identification
Sécuriser le blog avec Phoenix#2-Accès à la liste des articles après identification

Nous pouvons vérifier les conséquences du changement effectué sur les tests :

  • cd C:\CarbonX1\Phoenix\Projets\blogset
  • mix test
C:\CarbonX1\Phoenix\Projets\blogset>mix test
Compiling 1 file (.ex)
.........................................................................................................................

  1) test Show updates story within modal (BlogsetWeb.StoryLiveTest)
     test/blogset_web/live/story_live_test.exs:90
     ** (MatchError) no match of right hand side value: {:error, {:redirect, %{to: "/users/log_in", flash: %{"error" => "You must log in to access this page."}}}}
     code: {:ok, show_live, _html} = live(conn, ~p"/stories/#{story}")
     stacktrace:
       test/blogset_web/live/story_live_test.exs:91: (test)



  2) test Index saves new story (BlogsetWeb.StoryLiveTest)
     test/blogset_web/live/story_live_test.exs:26
     ** (MatchError) no match of right hand side value: {:error, {:redirect, %{to: "/users/log_in", flash: %{"error" => "You must log in to access this page."}}}}
     code: {:ok, index_live, _html} = live(conn, ~p"/stories")
     stacktrace:
       test/blogset_web/live/story_live_test.exs:27: (test)



  3) test Index deletes story in listing (BlogsetWeb.StoryLiveTest)
     test/blogset_web/live/story_live_test.exs:72
     ** (MatchError) no match of right hand side value: {:error, {:redirect, %{to: "/users/log_in", flash: %{"error" => "You must log in to access this page."}}}}
     code: {:ok, index_live, _html} = live(conn, ~p"/stories")
     stacktrace:
       test/blogset_web/live/story_live_test.exs:73: (test)



  4) test Show displays story (BlogsetWeb.StoryLiveTest)
     test/blogset_web/live/story_live_test.exs:83
     ** (MatchError) no match of right hand side value: {:error, {:redirect, %{to: "/users/log_in", flash: %{"error" => "You must log in to access this page."}}}}
     code: {:ok, _show_live, html} = live(conn, ~p"/stories/#{story}")
     stacktrace:
       test/blogset_web/live/story_live_test.exs:84: (test)



  5) test Index lists all stories (BlogsetWeb.StoryLiveTest)
     test/blogset_web/live/story_live_test.exs:19
     ** (MatchError) no match of right hand side value: {:error, {:redirect, %{to: "/users/log_in", flash: %{"error" => "You must log in to access this page."}}}}
     code: {:ok, _index_live, html} = live(conn, ~p"/stories")
     stacktrace:
       test/blogset_web/live/story_live_test.exs:20: (test)



  6) test Index updates story in listing (BlogsetWeb.StoryLiveTest)
     test/blogset_web/live/story_live_test.exs:49
     ** (MatchError) no match of right hand side value: {:error, {:redirect, %{to: "/users/log_in", flash: %{"error" => "You must log in to access this page."}}}}
     code: {:ok, index_live, _html} = live(conn, ~p"/stories")
     stacktrace:
       test/blogset_web/live/story_live_test.exs:50: (test)

...............
Finished in 2.2 seconds (1.1s async, 1.1s sync)
142 tests, 6 failures

Randomized with seed 731497

C:\CarbonX1\Phoenix\Projets\blogset>

Nous notons l’échec des tests avec 6 erreurs.

Pour corriger ce point, nous avons besoin de permettre aux tests de s’identifier afin que les tests puissent accéder aux pages protégées (lignes 7, 16, 25, 34, 43, 52) : « You must log in to access this page. »

Le fichier de test contenant l’erreur est indiqué juste au dessus avec les numéros de ligne : test/blogset_web/live/story_live_test.exs:49

Pour permettre aux tests de s’identifier, nous ajoutons au début du fichier story_live_test.exs :

  • setup :register_and_log_in_user ‘ajouté ligne 11 ci-dessous

BLOGSET/test/blogset_web/live/story_live_test.exs :

defmodule BlogsetWeb.StoryLiveTest do
  use BlogsetWeb.ConnCase

  import Phoenix.LiveViewTest
  import Blogset.StoriesFixtures

  @create_attrs %{title: "some title", body: "some body"}
  @update_attrs %{title: "some updated title", body: "some updated body"}
  @invalid_attrs %{title: nil, body: nil}

  setup :register_and_log_in_user

  defp create_story(_) do
    story = story_fixture()
    %{story: story}
  end

La fonction register_and_log_in_user est créé dans le fichier conn_case.ex

BLOGSET/test/support/conn_case.ex :

  @doc """
  Setup helper that registers and logs in users.

      setup :register_and_log_in_user

  It stores an updated connection and a registered user in the
  test context.
  """
  def register_and_log_in_user(%{conn: conn}) do
    user = Blogset.AccountsFixtures.user_fixture()
    %{conn: log_in_user(conn, user), user: user}
  end

Nous pouvons maintenant relancer les tests :

  • cd C:\CarbonX1\Phoenix\Projets\blogset
  • mix test

Les tests peuvent maintenant accéder à nos pages protégées :

C:\CarbonX1\Phoenix\Projets\blogset>mix test
..............................................................................................................................................
Finished in 2.7 seconds (1.3s async, 1.3s sync)
142 tests, 0 failures

Randomized with seed 820743

C:\CarbonX1\Phoenix\Projets\blogset>

Nous avons protégé les pages et permis à nos tests de fonctionner.

Modifier le template de la création des articles

Nous constatons que la création des articles a une zone body beaucoup trop petite. Nous avons besoin d’élargir cette zone à une taille suffisante pour créer un article.

La page de création d’article est définie dans form_component.ex. Nous devons modifier dans render l’input field body en textarea (ligne 23) :

defmodule BlogsetWeb.StoryLive.FormComponent do
  use BlogsetWeb, :live_component

  alias Blogset.Stories

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <.header>
        <%= @title %>
        <:subtitle>Use this form to manage story records in your database.</:subtitle>
      </.header>

      <.simple_form
        for={@form}
        id="story-form"
        phx-target={@myself}
        phx-change="validate"
        phx-submit="save"
      >
        <.input field={@form[:title]} type="text" label="Title" />
        <.input field={@form[:body]} type="textarea" label="Body" />
        <:actions>
          <.button phx-disable-with="Saving...">Save Story</.button>
        </:actions>
      </.simple_form>
    </div>
    """
  end

L’utilisation de textarea permet d’avoir une zone qui est ajustable par l’utilisateur grâce à la poignée en bas à droite de la zone :

Sécuriser le blog avec Phoenix#3-Modification de la page de création des articles
Sécuriser le blog avec Phoenix#3-Modification de la page de création des articles

Enregistrer l’identité des auteurs pour sécuriser le blog avec Phoenix

Nous avons vu que dans BLOGSET/lib/blogset_web/user_auth.ex, le current_user est dispoinible dans conn.assigns. C’est ce qui nous a permis de filtrer les utilisateurs identifiés.

Obtenir l’identifiant de l’utilisateur connecté

Nous pouvons donc prendre ce current_user pour affecter son identifiant id dans la fiche créé pour Story.

Dans la page avec la liste des Articles, nous pouvons tracer avec Logger le current_user.

Ouvrons VS Code :

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘dossier du projet
  • code . ‘ouvrir vs code
  • iex -S mix phx.server ‘execution en mode interactif
  • http://localhost:4000/stories ‘ page avec la liste des articles

BLOGSET/lib/blogset_web/live/index.ex :

defmodule BlogsetWeb.StoryLive.Index do
  use BlogsetWeb, :live_view

  require Logger

  alias Blogset.Stories
  alias Blogset.Stories.Story

  @impl true
  def mount(_params, _session, socket) do
    Logger.info(current_user_mount: socket.assigns[:current_user].id)
    {:ok, stream(socket, :stories, Stories.list_stories())}
  end

  @impl true
  def handle_params(params, _url, socket) do
    Logger.info(current_user_handle: socket.assigns[:current_user].id)
    Logger.info(params_handle: params)
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

Et lorsque nous executons la page en étant identifié, nous voyons dans le log les informations demandées en lignes 1, 8 et 9 ci-dessous :

[info] [current_user_mount: 1]
[debug] QUERY OK source="stories" db=0.2ms queue=0.1ms idle=990.9ms
SELECT s0."id", s0."title", s0."body", s0."user_id", s0."inserted_at", s0."updated_at" FROM "stories" AS s0 []
↳ BlogsetWeb.StoryLive.Index.mount/3, at: lib/blogset_web/live/story_live/index.ex:12
[debug] Replied in 5ms
[debug] HANDLE PARAMS in BlogsetWeb.StoryLive.Index
  Parameters: %{}
[info] [current_user_handle: 1]
[info] [params_handle: %{}]
[debug] Replied in 102µs
iex(1)>

Maintenant si nous allons dans la partie render de la page, nous voyons notre form_component utilisé dans la page en mode modal, déclenché sur les actions :new et :edit :

BLOGSET/lib/blogset_web/live/index.html.heex :

<.modal :if={@live_action in [:new, :edit]} id="story-modal" show on_cancel={JS.patch(~p"/stories")}>
  <.live_component
    module={BlogsetWeb.StoryLive.FormComponent}
    id={@story.id || :new}
    title={@page_title}
    action={@live_action}
    story={@story}
    user_id={@current_user.id}
    patch={~p"/stories"}
  />
</.modal>

Nous avons le form_component utilisé pour créer et éditer l’article.

Ajout de l’identifiant de l’utilisateur dans le form_component

BLOGSET/lib/blogset_web/live/story_live/form_component.ex :

defmodule BlogsetWeb.StoryLive.FormComponent do
  use BlogsetWeb, :live_component

 require Logger
 
  alias Blogset.Stories

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <.header>
        <%= @title %>
        <:subtitle>Use this form to manage story records in your database.</:subtitle>
      </.header>

      <.simple_form
        for={@form}
        id="story-form"
        phx-target={@myself}
        phx-change="validate"
        phx-submit="save"
      >
        <.input field={@form[:title]} type="text" label="Title" />
        <.input field={@form[:body]} type="textarea" label="Body" />
        <:actions>
          <.button phx-disable-with="Saving...">Save Story</.button>
        </:actions>
      </.simple_form>
    </div>
    """
  end
  ....
  def handle_event("save", %{"story" => story_params}, socket) do
    Logger.info(form_component_handle_event_save_user_id: socket.assigns[:user_id])
    Logger.info(form_component_handle_event_save_story_params: story_params)
  	save_story(socket, socket.assigns.action, story_params)
  end
  ....
  defp save_story(socket, :new, story_params) do
    case Stories.create_story(story_params) do
      {:ok, story} ->
        notify_parent({:saved, story})

        {:noreply,
         socket
         |> put_flash(:info, "Story created successfully")
         |> push_patch(to: socket.assigns.patch)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign_form(socket, changeset)}
    end
  end

La fonction render compose la page avec le formulaire. Lorsque nous activons le bouton save, la fonction handle_event (ligne 32) est activée et sauvegarde dans la fonction save_story(…, :new, …). Pour pouvoir enregistrer la référence de l’auteur, nous avons besoin de son id dans story_params.

Nous avons ajouter l’id de l’auteur dans l’appel de FormComponent [index.html.heex ligne 8].

  • user_id={@current_user.id}

Dans socket.params, nous pouvons récurpéré la valeur de l’id [form_component.ex ligne 35]

Pour que Logger soit actif, nous avons déclaré Logger dans [form_component.ex ligne 4].

  • cd C:\CarbonX1\Phoenix\Projets\blogset
  • iex -S mix phx.server ‘execution en mode interactif
  • http://localhost:4000/stories/ ‘aller sur la page de l’application depuis un navigateur
[debug] HANDLE EVENT "save" in BlogsetWeb.StoryLive.Index
  Component: BlogsetWeb.StoryLive.FormComponent
  Parameters: %{"story" => %{"body" => "body 5", "title" => "un article 5"}}
[info] [form_component_handle_event_save_user_id: 1]
[info] [form_component_handle_event_save_story_params: %{"body" => "body 5", "title" => "un article 5"}]
[debug] QUERY OK source="stories" db=1.8ms queue=0.5ms idle=1474.9ms
INSERT INTO "stories" ("title","body","inserted_at","updated_at") VALUES ($1,$2,$3,$4) RETURNING "id" ["un article 5", "body 5", ~U[2023-12-04 17:26:42Z], ~U[2023-12-04 17:26:42Z]]
↳ BlogsetWeb.StoryLive.FormComponent.save_story/3, at: lib/blogset_web/live/story_live/form_component.ex:77
[debug] Replied in 2ms
[info] [current_user_struct: nil]
[debug] HANDLE PARAMS in BlogsetWeb.StoryLive.Index
  Parameters: %{}
[info] [current_user_handle: 1]
[info] [params_handle: %{}]
[debug] Replied in 0µs
iex(1)>

Nous voyons la valeur user_id passé au composant grace au Log : [info] ligne 4 [form_component_handle_event_save_user_id: 1].

Et nous voyons en ligne 5 le contenu du formulaire.

En résumé, les valeurs que nous mettons dans le composant sont disponibles dans les fonctions du composant. Nous devons maintenant enregistré la valeur id de l’auteur dans la base de données.

Enregistrement de l’id de l’auteur dans la table pour sécuriser le blog avec Phoenix

Nous pouvons maintenant ajouter dans story_param la valeur user_id (form_component.ex ligne 4) :

  def handle_event("save", %{"story" => story_params}, socket) do
    Logger.info(form_component_handle_event_save_user_id: socket.assigns[:user_id])
    Logger.info(form_component_handle_event_save_story_params: story_params)
    story_params = Map.put(story_params,"user_id", socket.assigns[:user_id])
    Logger.info(form_component_handle_event_save_story_params2: story_params)
    save_story(socket, socket.assigns.action, story_params)
  end
  ....
  
  defp save_story(socket, :new, story_params) do
    case Stories.create_story(story_params) do
      {:ok, story} ->
        notify_parent({:saved, story})

        {:noreply,
         socket
         |> put_flash(:info, "Story created successfully")
         |> push_patch(to: socket.assigns.patch)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign_form(socket, changeset)}
    end
  end

Et nous voyons l’ajout dans la trace ligne 6 :

[debug] HANDLE EVENT "save" in BlogsetWeb.StoryLive.Index
  Component: BlogsetWeb.StoryLive.FormComponent
  Parameters: %{"story" => %{"body" => "body6", "title" => "article 6"}}
[info] [form_component_handle_event_save_user_id: 1]
[info] [form_component_handle_event_save_story_params: %{"body" => "body6", "title" => "article 6"}]
[info] [form_component_handle_event_save_story_params2: %{"body" => "body6", "title" => "article 6", "user_id" => 1}]
[debug] QUERY OK source="stories" db=2.4ms queue=0.8ms idle=1329.1ms
INSERT INTO "stories" ("title","body","inserted_at","updated_at") VALUES ($1,$2,$3,$4) RETURNING "id" ["article 6", "body6", ~U[2023-12-04 17:33:33Z], ~U[2023-12-04 17:33:33Z]]
↳ BlogsetWeb.StoryLive.FormComponent.save_story/3, at: lib/blogset_web/live/story_live/form_component.ex:79
[debug] Replied in 3ms
[info] [current_user_struct: nil]
[debug] HANDLE PARAMS in BlogsetWeb.StoryLive.Index
  Parameters: %{}
[info] [current_user_handle: 1]
[info] [params_handle: %{}]
[debug] Replied in 102µs
iex(1)>

Pour que la valeur soit enregistrée dans la base de données, nous devons suivre le parcours. Dans form_component.ex ligne 6, nous appelons la fonction save_story. En ligne 10, save_story est filtré avec :new, puis appelle en ligne 11 Stories.create_story(story_params).

BLOGSET/lib/blogset/stories/story.ex :

defmodule Blogset.Stories.Story do
  use Ecto.Schema
  import Ecto.Changeset

  schema "stories" do
    field :title, :string
    field :body, :string
    field :user_id, :id

    timestamps(type: :utc_datetime)
  end

  @doc false
  def changeset(story, attrs) do
    story
    |> cast(attrs, [:title, :body])
    |> validate_required([:title, :body])
  end
end

Nous devons ajouté user_id en ligne 16 :

  @doc false
  def changeset(story, attrs) do
    story
    |> cast(attrs, [:title, :body, :user_id])
    |> validate_required([:title, :body])
  end

Regardons dands la bese de données

  • cd C:\CarbonX1\Phoenix\Projets\blogset
  • psql -U postgres ‘ouvrir psql avec l’utilisateur postgres
  • \c blogset_dev ‘se connecter à la base blogset_dev
  • \d ‘afficher la liste des tables de la base
  • select * from stories; ‘afficher la table stories
  • \q ‘pour quitter psql

Nous avons du relancer le serveur pour que la compilation des modifications soient prises en compte. Finalement nous avons bien l’identifiant de l’auteur enregistré dans la base (ligne 15) :

blogset_dev=# select * from stories;
 id |      title       |                     body                      | user_id |     inserted_at     |     updated_at 
----+------------------+-----------------------------------------------+---------+---------------------+---------------------
  2 | Article numÚro 2 | mon deuxiÞme article                          |         | 2023-12-01 14:36:25 | 2023-12-01 14:36:25
  3 | autre article    | boy d'autre article                           |         | 2023-12-04 17:02:38 | 2023-12-04 17:02:38
  4 | autre article    | body d'autre article                          |         | 2023-12-04 17:04:43 | 2023-12-04 17:04:43
  5 | article 3        | body 3                                        |         | 2023-12-04 17:08:48 | 2023-12-04 17:08:48
  6 | article 4        | body 4                                        |         | 2023-12-04 17:13:45 | 2023-12-04 17:13:45
  7 | un article 5     | body 5                                        |         | 2023-12-04 17:26:42 | 2023-12-04 17:26:42
  8 | article 6        | body6                                         |         | 2023-12-04 17:33:33 | 2023-12-04 17:33:33
  9 | article complet  | est-ce que cela enregistre le user_id         |         | 2023-12-04 17:44:49 | 2023-12-04 17:44:49
 10 | encore un essai  | et maintenant est-ce que l'id est enregistrÚ? |         | 2023-12-04 17:51:11 | 2023-12-04 17:51:11
 11 | essai 10         | body 10                                       |         | 2023-12-04 17:54:31 | 2023-12-04 17:54:31
 12 | essai 11         | body 11                                       |         | 2023-12-04 18:00:14 | 2023-12-04 18:00:14
 13 | test 12          | body 12                                       |       1 | 2023-12-04 18:05:37 | 2023-12-04 18:05:37
(12 lignes)

-- Suite  --

Protéger les accès des pages articles en utilisant l’authentification

Les articles sont accessibles publiquement en mettant les liens dans la partie publique du routeur. Si nous souhaitons avoir une protection des pages articles, nous devons mettre les liens dans la partie require_authentificated_user.

Lorsque c’est fait, la demande des pages passe par une étape intermédiaire demandant à l’utilisateur de s’authentifier par son compte avant d’afficher la page.

le code de contrôle require_authentificated_user est défini dans user_auth.ex.

Maintenant que nous avons ajouté la référence à l’auteur qui a créé l’Article, nous pouvons filtrer les articles. La modification et la suppression d’articles peuvent être des actions réservées aux auteurs des articles.

Filtrer la liste avec le user_id pour sécuriser le blog avec Phoenix

Nous souhaitons avoir visible pour un utilisateur que ses propres articles. Nous réalisons le filtre dans :

  • list_stories dans le module stories.ex en ajoutant user_id en paramètre de la fonction
  • Repo.all(from s in Story, where: s.user_id == ^user_id)

Et pour l’appel nous devons fournir le user_id :

  • dans mount de index.ex
  • remplacer Stories.list_stories() par Stories.list_stories(socket.assigns[:current_user].id

Nous devons aussi protéger l’accès à chacun des articles par le get en passant le user_id en paramètre en plus de l’identifiant de l’article.

  • dans stories.ex, nous ajoutons get_story!(ide, user_id)
  • avec Repo.one(from s in Story, where: s.id == ^id and s.user_id == ^user_id)

L’appel est fait dans la page principale index.ex lors de l’action sur le bouton edit :

  • apply_action(socket, :edit, %(« id » => id)
  • on met Stories.get_story!(id, socket.assigns.current_user.id)

Conclusion, nous avons sécurisé le blog avec Phoenix

Dans cet article, nous avons montré comment ajouter la sécurisation du blog avec Phoenix mix.gen.auth. Nous avons réalisé :

  • une gestion de compte utilisateur permettant aux auteurs de gérer leurs articles
  • une gestion d’article avec un titre, un corps de texte et un identifiant d’auteur

Nous allons dans les prochains articles :

  • Ouvrir les commentaires pour le blog
  • Compléter la création d’article en ajoutant la gestion des images.
Si vous avez aimé l'article vous êtes libre de le partager :-)

Laisser un commentaire