| defmodule PlausibleWeb.SSOController do |
| use PlausibleWeb, :controller |
|
|
| require Logger |
|
|
| alias Plausible.Auth |
| alias Plausible.Auth.SSO |
| alias PlausibleWeb.LoginPreference |
|
|
| alias PlausibleWeb.Router.Helpers, as: Routes |
|
|
| plug Plausible.Plugs.AuthorizeTeamAccess, |
| [:owner] when action in [:sso_settings] |
|
|
| plug Plausible.Plugs.AuthorizeTeamAccess, |
| [:owner, :admin] when action in [:team_sessions, :delete_session] |
|
|
| def login_form(conn, params) do |
| login_preference = LoginPreference.get(conn) |
| error = Phoenix.Flash.get(conn.assigns.flash, :login_error) |
|
|
| case {login_preference, params["prefer"], error} do |
| {nil, nil, nil} -> |
| redirect(conn, to: Routes.auth_path(conn, :login_form, return_to: params["return_to"])) |
|
|
| _ -> |
| render_auth_page(conn, "login_form.html", |
| autosubmit: params["autosubmit"] != nil, |
| heading: "Sign in with SSO" |
| ) |
| end |
| end |
|
|
| def login(conn, %{"email" => email} = params) do |
| with :ok <- Auth.rate_limit(:login_ip, conn), |
| {:ok, %{sso_integration: integration}} <- SSO.Domains.lookup(email) do |
| redirect(conn, |
| to: |
| Routes.sso_path( |
| conn, |
| :saml_signin, |
| integration.identifier, |
| email: email, |
| return_to: params["return_to"] |
| ) |
| ) |
| else |
| {:error, :not_found} -> |
| conn |
| |> put_flash( |
| :login_error, |
| "We couldn't find a Single Sign-On account for that email." |
| ) |
| |> redirect(to: Routes.sso_path(conn, :login_form)) |
|
|
| {:error, {:rate_limit, _}} -> |
| Auth.log_failed_login_attempt("too many login attempts for #{email}") |
|
|
| render_error( |
| conn, |
| 429, |
| "Too many login attempts. Wait a minute before trying again." |
| ) |
| end |
| end |
|
|
| def provision_notice(conn, _params) do |
| render_auth_page(conn, "provision_notice.html", heading: "Single Sign-On required") |
| end |
|
|
| def provision_issue(conn, params) do |
| issue = |
| case params["issue"] do |
| "not_a_member" -> :not_a_member |
| "multiple_memberships" -> :multiple_memberships |
| "multiple_memberships_noforce" -> :multiple_memberships_noforce |
| "active_personal_team" -> :active_personal_team |
| "active_personal_team_noforce" -> :active_personal_team_noforce |
| _ -> :unknown |
| end |
|
|
| render_auth_page(conn, "provision_issue.html", |
| heading: "Single Sign-On required", |
| issue: issue |
| ) |
| end |
|
|
| def saml_signin(conn, params) do |
| saml_adapter().signin(conn, params) |
| end |
|
|
| def saml_consume(conn, params) do |
| saml_adapter().consume(conn, params) |
| end |
|
|
| def csp_report(conn, _params) do |
| {:ok, body, conn} = Plug.Conn.read_body(conn) |
| Logger.error(body) |
| conn |> send_resp(200, "OK") |
| end |
|
|
| def cta(conn, _params) do |
| render(conn, :cta, layout: {PlausibleWeb.LayoutView, :settings}) |
| end |
|
|
| def sso_settings(conn, _params) do |
| if Plausible.Teams.setup?(conn.assigns.current_team) and |
| Plausible.Billing.Feature.SSO.check_availability(conn.assigns.current_team) == :ok do |
| render(conn, :sso_settings, |
| layout: {PlausibleWeb.LayoutView, :settings}, |
| connect_live_socket: true |
| ) |
| else |
| conn |
| |> redirect(to: Routes.site_path(conn, :index)) |
| end |
| end |
|
|
| def team_sessions(conn, _params) do |
| sso_sessions = Auth.UserSessions.list_sso_for_team(conn.assigns.current_team) |
|
|
| render(conn, :team_sessions, |
| layout: {PlausibleWeb.LayoutView, :settings}, |
| sso_sessions: sso_sessions |
| ) |
| end |
|
|
| def delete_session(conn, %{"session_id" => session_id}) do |
| current_team = conn.assigns.current_team |
| Auth.UserSessions.revoke_sso_by_id(current_team, session_id) |
|
|
| conn |
| |> put_flash(:success, "Session logged out successfully") |
| |> redirect(to: Routes.sso_path(conn, :team_sessions)) |
| end |
|
|
| defp saml_adapter() do |
| Application.fetch_env!(:plausible, :sso_saml_adapter) |
| end |
| end |
|
|