Skip to content

Commit

Permalink
Merge pull request #46 from camino-school/better-login-flow
Browse files Browse the repository at this point in the history
Better login flow
  • Loading branch information
endoooo authored Jan 24, 2024
2 parents e0b1814 + 89c7f57 commit 01128c3
Show file tree
Hide file tree
Showing 36 changed files with 1,724 additions and 1,369 deletions.
39 changes: 39 additions & 0 deletions lib/lanttern/identity.ex
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,26 @@ defmodule Lanttern.Identity do
User.email_changeset(user, attrs, validate_email: false)
end

@doc """
Updates an user email.
This function should be use only in admin (and maybe it should be removed
or refactored in the near future, adding more security guards).
## Examples
iex> admin_update_user_email(user, %{"email => "[email protected]"})
{:ok, %User{}}
iex> admin_update_user_email(user, %{"email => "[email protected]"})
{:error, %Ecto.Changeset{}}
"""
def admin_update_user_email(user, params) do
user
|> User.email_changeset(params)
|> Repo.update()
end

@doc """
Emulates that the email will change without actually changing
it in the database.
Expand Down Expand Up @@ -422,6 +442,25 @@ defmodule Lanttern.Identity do
end
end

@doc """
Deletes a user.
This function should be used only in admin (and maybe it should be removed
or refactored in the near future, adding more security guards).
## Examples
iex> admin_delete_user(user)
{:ok, %User{}}
iex> admin_delete_user(user)
{:error, %Ecto.Changeset{}}
"""
def admin_delete_user(%User{} = user) do
Repo.delete(user)
end

@doc """
Returns the list of profiles.
Expand Down
1 change: 1 addition & 0 deletions lib/lanttern_web/controllers/admin_html/home.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<h1 class="font-display font-black text-3xl">Admin</h1>
<div class="grid gap-4 grid-cols-3 mt-12">
<.link_list title="Identity">
<:item link={~p"/admin/users"}>Users</:item>
<:item link={~p"/admin/profiles"}>Profiles</:item>
</.link_list>
<.link_list title="Assessments">
Expand Down
10 changes: 9 additions & 1 deletion lib/lanttern_web/controllers/page_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ defmodule LantternWeb.PageController do
use LantternWeb, :controller

def home(conn, _params) do
google_client_id =
Application.fetch_env!(:lanttern, LantternWeb.UserAuth)
|> Keyword.get(:google_client_id)

# The home page is often custom made,
# so skip the default app layout.
render(conn, :home, layout: false, page_title: "Lanttern: visualizing learning patterns")
render(conn, :home,
layout: false,
page_title: "Lanttern: visualizing learning patterns",
google_client_id: google_client_id
)
end
end
49 changes: 40 additions & 9 deletions lib/lanttern_web/controllers/page_html/home.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,46 @@
</strong>
</p>
</hgroup>
<a
href="/dashboard"
class="absolute top-4 right-4 md:top-10 md:right-10 group flex gap-2 items-center p-2 rounded bg-white shadow-xl hover:bg-slate-100"
>
<span class="font-display font-bold text-sm text-ltrn-subtle group-hover:text-ltrn-dark">
Go to app
</span>
<div class="w-6 h-6 rounded-full bg-ltrn-mesh-primary blur-sm group-hover:blur-none transition-[filter]" />
</a>
<%= if @current_user do %>
<a
href="/dashboard"
class="absolute top-4 right-4 md:top-10 md:right-10 group flex gap-2 items-center p-2 rounded bg-white shadow-xl hover:bg-slate-100"
>
<span class="font-display font-bold text-sm text-ltrn-subtle group-hover:text-ltrn-dark">
Go to app
</span>
<div class="w-6 h-6 rounded-full bg-ltrn-mesh-primary blur-sm group-hover:blur-none transition-[filter]" />
</a>
<% else %>
<div
id="g_id_signin_container"
class="absolute top-4 right-4 md:top-10 md:right-10"
phx-update="ignore"
>
<script src="https://accounts.google.com/gsi/client" async>
</script>
<div
id="g_id_onload"
data-client_id={@google_client_id}
data-context="signin"
data-ux_mode="popup"
data-login_uri={~p"/users/google_sign_in"}
data-nonce=""
data-auto_prompt="false"
>
</div>
<div
class="g_id_signin"
data-type="standard"
data-shape="pill"
data-theme="outline"
data-text="signin_with"
data-size="large"
data-logo_alignment="left"
>
</div>
</div>
<% end %>
</header>
<div id="active-learning" class="relative px-10 py-40">
<div class="max-w-3xl mx-auto">
Expand Down
68 changes: 68 additions & 0 deletions lib/lanttern_web/controllers/user_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
defmodule LantternWeb.UserController do
use LantternWeb, :controller

alias Lanttern.Identity
alias Lanttern.Identity.User

def index(conn, _params) do
users = Identity.list_users()
render(conn, :index, users: users)
end

def new(conn, _params) do
changeset = Identity.change_user_email(%User{})

render(conn, :new, changeset: changeset)
end

def create(conn, %{"user" => user_params}) do
# the form doesn't have the password field because we're only using
# Google Sign In, but we need a password in order to create the user
user_params = Map.put(user_params, "password", Ecto.UUID.generate())

case Identity.register_user(user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "User created successfully.")
|> redirect(to: ~p"/admin/users/#{user}")

{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset)
end
end

def show(conn, %{"id" => id}) do
user = Identity.get_user!(id)
render(conn, :show, user: user)
end

def edit(conn, %{"id" => id}) do
user = Identity.get_user!(id)
changeset = Identity.change_user_email(user)

render(conn, :edit, user: user, changeset: changeset)
end

def update(conn, %{"id" => id, "user" => user_params}) do
user = Identity.get_user!(id)

case Identity.admin_update_user_email(user, user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "User updated successfully.")
|> redirect(to: ~p"/admin/users/#{user}")

{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :edit, user: user, changeset: changeset)
end
end

def delete(conn, %{"id" => id}) do
user = Identity.get_user!(id)
{:ok, _user} = Identity.admin_delete_user(user)

conn
|> put_flash(:info, "User deleted successfully.")
|> redirect(to: ~p"/admin/users")
end
end
13 changes: 13 additions & 0 deletions lib/lanttern_web/controllers/user_html.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule LantternWeb.UserHTML do
use LantternWeb, :html

embed_templates "user_html/*"

@doc """
Renders a user form.
"""
attr :changeset, Ecto.Changeset, required: true
attr :action, :string, required: true

def user_form(assigns)
end
8 changes: 8 additions & 0 deletions lib/lanttern_web/controllers/user_html/edit.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<.header>
Edit User <%= @user.id %>
<:subtitle>Use this form to manage user records in your database.</:subtitle>
</.header>

<.user_form changeset={@changeset} action={~p"/admin/users/#{@user}"} />

<.back navigate={~p"/admin/users"}>Back to users</.back>
23 changes: 23 additions & 0 deletions lib/lanttern_web/controllers/user_html/index.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<.header>
Listing Users
<:actions>
<.link href={~p"/admin/users/new"}>
<.button>New User</.button>
</.link>
</:actions>
</.header>

<.table id="users" rows={@users} row_click={&JS.navigate(~p"/admin/users/#{&1}")}>
<:col :let={user} label="User"><%= user.email %></:col>
<:action :let={user}>
<div class="sr-only">
<.link navigate={~p"/admin/users/#{user}"}>Show</.link>
</div>
<.link navigate={~p"/admin/users/#{user}/edit"}>Edit</.link>
</:action>
<:action :let={user}>
<.link href={~p"/admin/users/#{user}"} method="delete" data-confirm="Are you sure?">
Delete
</.link>
</:action>
</.table>
8 changes: 8 additions & 0 deletions lib/lanttern_web/controllers/user_html/new.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<.header>
New User
<:subtitle>Use this form to manage user records in your database.</:subtitle>
</.header>

<.user_form changeset={@changeset} action={~p"/admin/users"} />

<.back navigate={~p"/admin/users"}>Back to users</.back>
15 changes: 15 additions & 0 deletions lib/lanttern_web/controllers/user_html/show.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<.header>
User <%= @user.id %>
<:subtitle>This is a user record from your database.</:subtitle>
<:actions>
<.link href={~p"/admin/users/#{@user}/edit"}>
<.button>Edit user</.button>
</.link>
</:actions>
</.header>

<.list>
<:item title="User"><%= @user.email %></:item>
</.list>

<.back navigate={~p"/admin/users"}>Back to users</.back>
9 changes: 9 additions & 0 deletions lib/lanttern_web/controllers/user_html/user_form.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<.simple_form :let={f} for={@changeset} action={@action}>
<.error :if={@changeset.action}>
Oops, something went wrong! Please check the errors below.
</.error>
<.input field={f[:email]} type="text" label="Email" />
<:actions>
<.button>Save User</.button>
</:actions>
</.simple_form>
69 changes: 36 additions & 33 deletions lib/lanttern_web/controllers/user_session_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,38 @@ defmodule LantternWeb.UserSessionController do
use LantternWeb, :controller

alias Lanttern.Identity
alias Lanttern.Identity.User
alias LantternWeb.UserAuth

def create(conn, %{"_action" => "registered"} = params) do
create(conn, params, "Account created successfully!")
end
# def create(conn, %{"_action" => "registered"} = params) do
# create(conn, params, "Account created successfully!")
# end

def create(conn, %{"_action" => "password_updated"} = params) do
conn
|> put_session(:user_return_to, ~p"/users/settings")
|> create(params, "Password updated successfully!")
end
# def create(conn, %{"_action" => "password_updated"} = params) do
# conn
# |> put_session(:user_return_to, ~p"/users/settings")
# |> create(params, "Password updated successfully!")
# end

def create(conn, params) do
create(conn, params, "Welcome back!")
end
# def create(conn, params) do
# create(conn, params, "Welcome back!")
# end

defp create(conn, %{"user" => user_params}, info) do
%{"email" => email, "password" => password} = user_params
# defp create(conn, %{"user" => user_params}, info) do
# %{"email" => email, "password" => password} = user_params

if user = Identity.get_user_by_email_and_password(email, password) do
conn
|> put_flash(:info, info)
|> UserAuth.log_in_user(user, user_params)
else
# In order to prevent user enumeration attacks, don't disclose whether the email is registered.
conn
|> put_flash(:error, "Invalid email or password")
|> put_flash(:email, String.slice(email, 0, 160))
|> redirect(to: ~p"/users/log_in")
end
end
# if user = Identity.get_user_by_email_and_password(email, password) do
# conn
# |> put_flash(:info, info)
# |> UserAuth.log_in_user(user, user_params)
# else
# # In order to prevent user enumeration attacks, don't disclose whether the email is registered.
# conn
# |> put_flash(:error, "Invalid email or password")
# |> put_flash(:email, String.slice(email, 0, 160))
# |> redirect(to: ~p"/users/log_in")
# end
# end

def google_sign_in(conn, %{"credential" => credential}) do
handle_google_sign_in_response(
Expand All @@ -44,14 +45,16 @@ defmodule LantternWeb.UserSessionController do
defp handle_google_sign_in_response(conn, {:ok, claims}) do
%{"email" => email, "name" => name} = claims

if user = Identity.get_user_by_email(email) do
conn
|> put_flash(:info, "Welcome back #{name}!")
|> UserAuth.log_in_user(user)
else
conn
|> put_flash(:error, "User not registered in Lanttern")
|> redirect(to: ~p"/users/log_in")
case Identity.get_user_by_email(email) do
%User{} = user ->
conn
|> put_flash(:info, "Welcome back #{name}!")
|> UserAuth.log_in_user(user, %{"remember_me" => "true"})

nil ->
conn
|> put_flash(:error, "User not registered in Lanttern")
|> redirect(to: ~p"/users/log_in")
end
end

Expand Down
Loading

0 comments on commit 01128c3

Please sign in to comment.