mirror of
https://github.com/plausible/analytics.git
synced 2024-12-01 03:48:35 +03:00
dd44bc803d
* Do not error when activating an account that already exists * Add changelog entry
332 lines
9.9 KiB
Elixir
332 lines
9.9 KiB
Elixir
defmodule PlausibleWeb.AuthController do
|
|
use PlausibleWeb, :controller
|
|
use Plausible.Repo
|
|
alias Plausible.Auth
|
|
require Logger
|
|
|
|
plug PlausibleWeb.RequireLoggedOutPlug
|
|
when action in [:register_form, :register, :login_form, :login]
|
|
|
|
plug PlausibleWeb.RequireAccountPlug
|
|
when action in [:user_settings, :save_settings, :delete_me, :password_form, :set_password]
|
|
|
|
def register_form(conn, _params) do
|
|
if Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_registration) do
|
|
conn
|
|
|> redirect(to: "/login")
|
|
else
|
|
changeset = Plausible.Auth.User.changeset(%Plausible.Auth.User{})
|
|
|
|
render(conn, "register_form.html",
|
|
changeset: changeset,
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
end
|
|
|
|
def register(conn, params) do
|
|
user = Plausible.Auth.User.changeset(%Plausible.Auth.User{}, params["user"])
|
|
|
|
if PlausibleWeb.Captcha.verify(params["h-captcha-response"]) do
|
|
case Ecto.Changeset.apply_action(user, :insert) do
|
|
{:ok, user} ->
|
|
token = Auth.Token.sign_activation(user.name, user.email)
|
|
url = PlausibleWeb.Endpoint.url() <> "/claim-activation?token=#{token}"
|
|
Logger.info(url)
|
|
email_template = PlausibleWeb.Email.activation_email(user, url)
|
|
Plausible.Mailer.send_email(email_template)
|
|
|
|
conn
|
|
|> render("register_success.html",
|
|
email: user.email,
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
|
|
{:error, changeset} ->
|
|
render(conn, "register_form.html",
|
|
changeset: changeset,
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
else
|
|
render(conn, "register_form.html",
|
|
changeset: user,
|
|
captcha_error: "Please complete the captcha to register",
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
end
|
|
|
|
def claim_activation_link(conn, %{"token" => token}) do
|
|
case Auth.Token.verify_activation(token) do
|
|
{:ok, %{name: name, email: email}} ->
|
|
case Auth.create_user(name, email) do
|
|
{:ok, user} ->
|
|
PlausibleWeb.Email.welcome_email(user)
|
|
|> Plausible.Mailer.send_email()
|
|
|
|
user_activated_account(conn, user)
|
|
{:error, %Ecto.Changeset{errors: [email: {"has already been taken", _}]}} ->
|
|
user = Auth.find_user_by(email: email)
|
|
user_activated_account(conn, user)
|
|
{:error, changeset} ->
|
|
send_resp(conn, 400, inspect(changeset.errors))
|
|
end
|
|
|
|
{:error, :expired} ->
|
|
render_error(conn, 401, "Your token has expired. Please request another activation link.")
|
|
|
|
{:error, _} ->
|
|
render_error(conn, 400, "Your token is invalid. Please request another activation link.")
|
|
end
|
|
end
|
|
|
|
defp user_activated_account(conn, user) do
|
|
conn
|
|
|> put_session(:current_user_id, user.id)
|
|
|> put_resp_cookie("logged_in", "true", http_only: false)
|
|
|> redirect(to: "/password")
|
|
end
|
|
|
|
def password_reset_request_form(conn, _) do
|
|
render(conn, "password_reset_request_form.html",
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
|
|
def password_reset_request(conn, %{"email" => ""}) do
|
|
render(conn, "password_reset_request_form.html",
|
|
error: "Please enter an email address",
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
|
|
def password_reset_request(conn, %{"email" => email} = params) do
|
|
if PlausibleWeb.Captcha.verify(params["h-captcha-response"]) do
|
|
user = Repo.get_by(Plausible.Auth.User, email: email)
|
|
|
|
if user do
|
|
token = Auth.Token.sign_password_reset(email)
|
|
url = PlausibleWeb.Endpoint.url() <> "/password/reset?token=#{token}"
|
|
Logger.debug("PASSWORD RESET LINK: " <> url)
|
|
email_template = PlausibleWeb.Email.password_reset_email(email, url)
|
|
Plausible.Mailer.deliver_now(email_template)
|
|
|
|
render(conn, "password_reset_request_success.html",
|
|
email: email,
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
else
|
|
render(conn, "password_reset_request_success.html",
|
|
email: email,
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
else
|
|
render(conn, "password_reset_request_form.html",
|
|
error: "Please complete the captcha to reset your password",
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
end
|
|
|
|
def password_reset_form(conn, %{"token" => token}) do
|
|
case Auth.Token.verify_password_reset(token) do
|
|
{:ok, _} ->
|
|
render(conn, "password_reset_form.html",
|
|
token: token,
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
|
|
{:error, :expired} ->
|
|
render_error(
|
|
conn,
|
|
401,
|
|
"Your token has expired. Please request another password reset link."
|
|
)
|
|
|
|
{:error, _} ->
|
|
render_error(
|
|
conn,
|
|
401,
|
|
"Your token is invalid. Please request another password reset link."
|
|
)
|
|
end
|
|
end
|
|
|
|
def password_reset(conn, %{"token" => token, "password" => pw}) do
|
|
case Auth.Token.verify_password_reset(token) do
|
|
{:ok, %{email: email}} ->
|
|
user = Repo.get_by(Auth.User, email: email)
|
|
changeset = Auth.User.set_password(user, pw)
|
|
|
|
case Repo.update(changeset) do
|
|
{:ok, _updated} ->
|
|
conn
|
|
|> put_flash(:login_title, "Password updated successfully")
|
|
|> put_flash(:login_instructions, "Please log in with your new credentials")
|
|
|> put_session(:current_user_id, nil)
|
|
|> delete_resp_cookie("logged_in")
|
|
|> redirect(to: "/login")
|
|
|
|
{:error, changeset} ->
|
|
render(conn, "password_reset_form.html",
|
|
changeset: changeset,
|
|
token: token,
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
|
|
{:error, :expired} ->
|
|
render_error(
|
|
conn,
|
|
401,
|
|
"Your token has expired. Please request another password reset link."
|
|
)
|
|
|
|
{:error, _} ->
|
|
render_error(
|
|
conn,
|
|
401,
|
|
"Your token is invalid. Please request another password reset link."
|
|
)
|
|
end
|
|
end
|
|
|
|
def login(conn, %{"email" => email, "password" => password}) do
|
|
alias Plausible.Auth.Password
|
|
|
|
user =
|
|
Repo.one(
|
|
from u in Plausible.Auth.User,
|
|
where: u.email == ^email
|
|
)
|
|
|
|
if user do
|
|
if Password.match?(password, user.password_hash || "") do
|
|
login_dest = get_session(conn, :login_dest) || "/sites"
|
|
|
|
conn
|
|
|> put_session(:current_user_id, user.id)
|
|
|> put_resp_cookie("logged_in", "true", http_only: false)
|
|
|> put_session(:login_dest, nil)
|
|
|> redirect(to: login_dest)
|
|
else
|
|
conn
|
|
|> render("login_form.html",
|
|
error: "Wrong email or password. Please try again.",
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
else
|
|
Password.dummy_calculation()
|
|
|
|
conn
|
|
|> render("login_form.html",
|
|
error: "Wrong email or password. Please try again.",
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
end
|
|
|
|
def login_form(conn, _params) do
|
|
render(conn, "login_form.html", layout: {PlausibleWeb.LayoutView, "focus.html"})
|
|
end
|
|
|
|
def password_form(conn, _params) do
|
|
render(conn, "password_form.html",
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"},
|
|
skip_plausible_tracking: true
|
|
)
|
|
end
|
|
|
|
def set_password(conn, %{"password" => pw}) do
|
|
changeset = Auth.User.set_password(conn.assigns[:current_user], pw)
|
|
|
|
case Repo.update(changeset) do
|
|
{:ok, _user} ->
|
|
redirect(conn, to: "/sites/new")
|
|
|
|
{:error, changeset} ->
|
|
render(conn, "password_form.html",
|
|
changeset: changeset,
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
end
|
|
|
|
def user_settings(conn, _params) do
|
|
changeset = Auth.User.changeset(conn.assigns[:current_user])
|
|
|
|
render(conn, "user_settings.html",
|
|
changeset: changeset,
|
|
subscription: conn.assigns[:current_user].subscription
|
|
)
|
|
end
|
|
|
|
def save_settings(conn, %{"user" => user_params}) do
|
|
changes = Auth.User.changeset(conn.assigns[:current_user], user_params)
|
|
|
|
case Repo.update(changes) do
|
|
{:ok, _user} ->
|
|
conn
|
|
|> put_flash(:success, "Account settings saved succesfully")
|
|
|> redirect(to: "/settings")
|
|
|
|
{:error, changeset} ->
|
|
render(conn, "user_settings.html",
|
|
changeset: changeset,
|
|
subscription: conn.assigns[:current_user].subscription
|
|
)
|
|
end
|
|
end
|
|
|
|
def delete_me(conn, params) do
|
|
user =
|
|
conn.assigns[:current_user]
|
|
|> Repo.preload(:sites)
|
|
|> Repo.preload(:subscription)
|
|
|
|
for site_membership <- user.site_memberships do
|
|
Repo.delete!(site_membership)
|
|
end
|
|
|
|
for site <- user.sites do
|
|
Repo.delete!(site)
|
|
end
|
|
|
|
if user.subscription, do: Repo.delete!(user.subscription)
|
|
Repo.delete!(user)
|
|
|
|
logout(conn, params)
|
|
end
|
|
|
|
def logout(conn, _params) do
|
|
conn
|
|
|> configure_session(drop: true)
|
|
|> delete_resp_cookie("logged_in")
|
|
|> redirect(to: "/")
|
|
end
|
|
|
|
def google_auth_callback(conn, %{"code" => code, "state" => site_id}) do
|
|
res = Plausible.Google.Api.fetch_access_token(code)
|
|
id_token = res["id_token"]
|
|
[_, body, _] = String.split(id_token, ".")
|
|
id = body |> Base.decode64!(padding: false) |> Jason.decode!()
|
|
|
|
Plausible.Site.GoogleAuth.changeset(%Plausible.Site.GoogleAuth{}, %{
|
|
email: id["email"],
|
|
refresh_token: res["refresh_token"],
|
|
access_token: res["access_token"],
|
|
expires: NaiveDateTime.utc_now() |> NaiveDateTime.add(res["expires_in"]),
|
|
user_id: conn.assigns[:current_user].id,
|
|
site_id: site_id
|
|
})
|
|
|> Repo.insert!()
|
|
|
|
site = Repo.get(Plausible.Site, site_id)
|
|
|
|
redirect(conn, to: "/#{URI.encode_www_form(site.domain)}/settings#google-auth")
|
|
end
|
|
end
|