defmodule FrenzyWeb.Fervor.OauthController do use FrenzyWeb, :controller alias Frenzy.{Repo, FervorClient, User, ApprovedClient} alias FrenzyWeb.Endpoint def authorize_get(conn, params) do user_token = get_session(conn, :user_token) case Phoenix.Token.verify(Endpoint, "user token", user_token, max_age: 24 * 60 * 60) do {:error, _reason} -> continue = "#{conn.request_path}?#{conn.query_string}" redirect(conn, to: Routes.login_path(Endpoint, :login, continue: continue)) {:ok, user_id} -> case Repo.get(User, user_id) do nil -> continue = "#{conn.request_path}?#{conn.query_string}" redirect(conn, to: Routes.login_path(Endpoint, :login, continue: continue)) user -> conn |> assign(:user, user) |> try_render_authorize(params) end end end def try_render_authorize( conn, %{ "response_type" => "code", "client_id" => client_id, "redirect_uri" => redirect_uri } = params ) do case Repo.get_by(FervorClient, client_id: client_id) do nil -> conn client -> if redirect_uri == client.redirect_uri do render(conn, "authorize.html", %{ client: client, state: Map.get(params, "state") }) else conn |> put_status(400) |> json(%{error: "mismatched redirect uri"}) end end end def try_render_authorize(conn, _params) do conn |> put_status(400) |> json(%{error: "invalid parameters"}) end def authorize_post(conn, params) do user_token = get_session(conn, :user_token) case Phoenix.Token.verify(Endpoint, "user token", user_token, max_age: 24 * 60 * 60) do {:error, _reason} -> continue = "#{conn.request_path}?#{conn.query_string}" redirect(conn, to: Routes.login_path(Endpoint, :login, continue: continue)) {:ok, user_id} -> case Repo.get(User, user_id) do nil -> continue = "#{conn.request_path}?#{conn.query_string}" redirect(conn, to: Routes.login_path(Endpoint, :login, continue: continue)) user -> conn |> assign(:user, user) |> try_authorize(params) end end end def try_authorize(conn, %{"client_id" => client_id} = params) do user = conn.assigns[:user] client = Repo.get_by(FervorClient, client_id: client_id) state = Map.get(params, "state") auth_code = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) changeset = Ecto.build_assoc(user, :approved_clients, %{ auth_code: auth_code, client_id: client_id }) {:ok, _approved_client} = Repo.insert(changeset) case client.redirect_uri do "urn:ietf:wg:oauth:2.0:oob" -> render(conn, "successfully_authorized.html", %{ auth_code: auth_code }) redirect_uri -> parsed = URI.parse(redirect_uri) query = URI.encode_query( if state, do: %{code: auth_code, state: state}, else: %{code: auth_code} ) uri = %URI{parsed | query: query} redirect(conn, external: URI.to_string(uri)) end end def try_authorize(conn, _params) do conn |> put_status(400) |> json(%{error: "invalid parameters"}) end def token( conn, %{ "redirect_uri" => redirect_uri, "client_id" => client_id, "client_secret" => client_secret } = params ) do case Repo.get_by(FervorClient, client_id: client_id) do nil -> conn |> put_status(401) |> json(%{error: "invalid_client", error_description: "incorrect client information"}) client -> if client_secret == client.client_secret and redirect_uri == client.redirect_uri do conn |> assign(:client, client) |> try_generate_token(params) else conn |> put_status(400) |> json(%{error: "invalid_grant", error_description: "incorrect client information"}) end end end def token(conn, _params) do json(conn, %{error: "invalid_request", error_description: "missing parameters"}) end def try_generate_token(conn, %{ "grant_type" => "authorization_code", "authorization_code" => auth_code }) do case Repo.get_by(ApprovedClient, auth_code: auth_code) do nil -> conn |> put_status(400) |> json(%{error: "invalid_grant", error_description: "invalid authorization code"}) approved_client -> access_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) changeset = ApprovedClient.changeset(approved_client, %{ auth_code: nil, access_token: access_token }) {:ok, _approved_client} = Repo.update(changeset) json(conn, %{ access_token: access_token, token_type: "bearer", owner: to_string(approved_client.user_id) }) end end def try_generate_token(conn, _params) do conn |> put_status(400) |> json(%{ error: "unsupported_grant_type", error_description: "only grant_type=authorization_code is supported" }) end end