Reputation: 57
I followed the complete tutorial explaining how to handle context & authentication/authorization by @chrismccord ...and I took advantage of his post about decoupling auth from dockyard blog :slight_smile: auth for phoenix context and from Adding CMS functions in phoenix context
According to this, In my case( CMS is ENR ) all created students need an enroller, the enroller is linked to a user(authenticated) with its own session : Student ->belongs_to Enroller, Enroller ->belongs_to User, users have their own credentials.
the resources "/students", StudentController
is protected that's fine.
But Now I want to expose some fact: what about if we want to allow student registering himself/herself? how the code will look like?
for self-enrollment or self-registration instead of doing it by the admin's folk... it'll be good.
here is what I did but I'm always redirect to the authentication page, the result: "you have to be logged in".
when the Admin is logged in/registered.... S/he can achieve administrative tasks (create: Students, Enrollers, pages, etc.) that's allowed by:
scope "/enr", HelloWeb.ENR, as: :enr do
pipe_through [:browser, :authenticate_user]
resources "/admissions", AdmissionController
end
for the admission's tasks
CMS
or (ENR
) is the admin part and we want to allow students to register themselves through public part I did route like that: scope "/", InsWeb do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/users", UserController
resources "/sessions", SessionController, only: [:new, :create, :delete],
singleton: true
resources "/admissions", AdmissionController, only: [:new, :create, :show]
end
scope "/ENR", InsWeb.ENR, as: :enr do
pipe_through [:browser, :authenticate_user]
resources "/admissions", AdmissionController
end
defp authenticate_user(conn, _) do
case get_session(conn, :user_id) do
nil ->
conn
|> Phoenix.Controller.put_flash(:error, "Login required")
|> Phoenix.Controller.redirect(to: "/")
|> halt()
user_id ->
assign(conn, :current_user, Ins.Accounts.get_user!(user_id))
end
end
all seems good I reached the admission page through public part but when I submitted the form ==== nothing..I always got "Login required" so it's not possible to allow student self-registration...
the AdmissionController.ex
in the CMS(ENRin my case) scope looks like:
defmodule InsWeb.ENR.AdmissionController do
use InsWeb, :controller
plug :require_existing_enroller
plug :authorize_admission when action in [:edit, :update, :delete]
alias Ins.ENR
alias Ins.ENR.Admission
def index(conn, _params) do
admissions = ENR.list_admissions()
render(conn, "index.html", admissions: admissions)
end
def new(conn, _params) do
changeset = ENR.change_admission(%Admission{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"admission" => admission_params}) do
case ENR.create_admission(conn.assigns.current_enroller, admission_params) do
{:ok, admission} ->
conn
|> put_flash(:info, "Admission created successfully.")
|> redirect(to: enr_admission_path(conn, :show, admission))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
admission =
id
|> ENR.get_admission!()
|> ENR.inc_admission_views()
render(conn, "show.html", admission: admission)
end
def edit(conn, %{"id" => id}) do
admission = ENR.get_admission!(id)
changeset = ENR.change_admission(admission)
render(conn, "edit.html", admission: admission, changeset: changeset)
end
def update(conn, %{"id" => id, "admission" => admission_params}) do
admission = ENR.get_admission!(id)
case ENR.update_admission(conn.assigns.admission, admission_params) do
{:ok, admission} ->
conn
|> put_flash(:info, "Admission updated successfully.")
|> redirect(to: enr_admission_path(conn, :show, admission))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "edit.html", admission: admission, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
admission = ENR.get_admission!(id)
{:ok, _admission} = ENR.delete_admission(conn.assigns.admission)
conn
|> put_flash(:info, "Admission deleted successfully.")
|> redirect(to: enr_admission_path(conn, :index))
end
defp require_existing_enroller(conn, _) do
enroller = ENR.ensure_enroller_exists(conn.assigns.current_user)
assign(conn, :current_enroller, enroller)
end
defp authorize_admission(conn, _) do
admission = ENR.get_admission!(conn.params["id"])
if conn.assigns.current_enroller.id == admission.enroller_id do
assign(conn, :admission, admission)
else
conn
|> put_flash(:error, "You can't modify that admission page")
|> redirect(to: enr_admission_path(conn, :index))
|> halt()
end
end
end
And the other one out of CMS
defmodule InsWeb.AdmissionController do
use InsWeb, :controller
alias Ins.ENR
alias Ins.ENR.Admission
def new(conn, _params) do
changeset = ENR.change_admission(%Admission{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"admission" => admission_params}) do
case ENR.create_admission(conn.assigns.current_enroller, admission_params) do
{:ok, admission} ->
conn
|> put_flash(:info, "Admission created successfully.")
|> redirect(to: enr_admission_path(conn, :show, admission))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
admission =
id
|> ENR.get_admission!()
render(conn, "show.html", admission: admission)
end
end
Any help would be appreciated! thank you!
/templates/enr/admission/form.html.eex
<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<div class="form-group">
<%= label f, :first_name, class: "control-label" %>
<%= text_input f, :first_name, class: "form-control" %>
<%= error_tag f, :first_name %>
</div>
<div class="form-group">
<%= label f, :last_name, class: "control-label" %>
<%= text_input f, :last_name, class: "form-control" %>
<%= error_tag f, :last_name %>
</div>
<div class="form-group">
<%= label f, :views, class: "control-label" %>
<%= number_input f, :views, class: "form-control" %>
<%= error_tag f, :views %>
</div>
<div class="form-group">
<%= submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>
/templates/enr/admission/new.html.eex
<h2>New Admission</h2>
<%= render "form.html", Map.put(assigns, :action, enr_admission_path(@conn, :create)) %>
<span><%= link "Back", to: enr_admission_path(@conn, :index) %></span>
Upvotes: 1
Views: 955
Reputation: 3310
With your definition there should be two methods to your AdmissionController.create
action:
admission_path(conn, :create)
- a POST route to /admissions
enr_admission_path(conn, :create)
- a POST route to /ENR/admissions
I could imagine, in your new.eex template there's the second one used in the form_for
method, so every user needs to be logged in.
I would use two controllers instead. If it's not an option for you, you would need a decision, which route should be used. That depends wether there's a user logged in or not.
Upvotes: 2