fix file uploads, serve them properly, put behind auth wall, decrypt secret message in frontend
This commit is contained in:
@@ -51,9 +51,10 @@ defmodule Entendu.Links do
|
||||
|
||||
"""
|
||||
def create_link(attrs \\ %{}) do
|
||||
%Link{}
|
||||
|> Link.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.insert(:link, Link.changeset(%Link{}, attrs))
|
||||
|> Ecto.Multi.update(:link_with_file, &Link.file_changeset(&1.link, attrs))
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@@ -5,7 +5,17 @@ defmodule Entendu.Links.Link do
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, Ecto.UUID, autogenerate: true}
|
||||
|
||||
@derive {Jason.Encoder,
|
||||
only: [
|
||||
:burn_after_reading,
|
||||
:expires,
|
||||
:filename,
|
||||
:filetype,
|
||||
:text_content,
|
||||
:file_content,
|
||||
:recipient,
|
||||
:service
|
||||
]}
|
||||
schema "links" do
|
||||
field :burn_after_reading, :boolean, default: false
|
||||
field :expires, :utc_datetime
|
||||
@@ -30,7 +40,10 @@ defmodule Entendu.Links.Link do
|
||||
:recipient,
|
||||
:service
|
||||
])
|
||||
|> cast_attachments(attrs, [:text_content, :file_content])
|
||||
end
|
||||
|
||||
def file_changeset(link, attrs) do
|
||||
link
|
||||
|> cast_attachments(attrs, [:text_content, :file_content])
|
||||
end
|
||||
end
|
||||
|
@@ -25,15 +25,21 @@ defmodule Entendu.UserFromAuth do
|
||||
nil
|
||||
end
|
||||
|
||||
defp emails_from_auth(%Auth{ extra: %Auth.Extra{ raw_info: %{ user: %{ "emails" => emails}}}}), do: emails
|
||||
# github
|
||||
defp emails_from_auth(%Auth{extra: %Auth.Extra{raw_info: %{user: %{"emails" => emails}}}}),
|
||||
do: emails
|
||||
|
||||
defp emails_from_auth(%Auth{ info: %{ email: email }}), do: [email]
|
||||
defp emails_from_auth(%Auth{info: %{email: email}}), do: [email]
|
||||
|
||||
defp emails_from_auth(_auth), do: []
|
||||
|
||||
defp basic_info(auth) do
|
||||
IO.inspect(auth)
|
||||
%{id: auth.uid, name: name_from_auth(auth), avatar: avatar_from_auth(auth), emails: emails_from_auth(auth)}
|
||||
%{
|
||||
id: auth.uid,
|
||||
name: name_from_auth(auth),
|
||||
avatar: avatar_from_auth(auth),
|
||||
emails: emails_from_auth(auth)
|
||||
}
|
||||
end
|
||||
|
||||
defp name_from_auth(auth) do
|
||||
@@ -54,6 +60,6 @@ defmodule Entendu.UserFromAuth do
|
||||
|
||||
def can_access?(recipient, emails) do
|
||||
emails
|
||||
|> Enum.any?(&( &1["verified"] == true and &1["email"] == recipient))
|
||||
|> Enum.any?(&(&1["verified"] == true and &1["email"] == recipient))
|
||||
end
|
||||
end
|
||||
|
@@ -39,6 +39,14 @@ defmodule EntenduWeb do
|
||||
|
||||
import ReactPhoenix.ClientSide
|
||||
|
||||
def current_user(conn) do
|
||||
Plug.Conn.get_session(conn, :current_user)
|
||||
end
|
||||
|
||||
def current_link(conn) do
|
||||
Plug.Conn.get_session(conn, :current_link)
|
||||
end
|
||||
|
||||
# Include shared imports and aliases for views
|
||||
unquote(view_helpers())
|
||||
end
|
||||
|
@@ -8,6 +8,8 @@ defmodule EntenduWeb.AuthController do
|
||||
plug Ueberauth
|
||||
|
||||
alias Entendu.UserFromAuth
|
||||
alias EntenduWeb.LinkView
|
||||
alias Entendu.EncryptedLink
|
||||
|
||||
def delete(conn, _params) do
|
||||
conn
|
||||
@@ -23,27 +25,32 @@ defmodule EntenduWeb.AuthController do
|
||||
end
|
||||
|
||||
def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
|
||||
# TODO: turn this into plug that only proceeds if current_link session var exists
|
||||
%{ id: link_id, recipient: recipient } = get_session(conn, :current_link)
|
||||
link = get_session(conn, :intended_link)
|
||||
|
||||
with {:ok, user} <- UserFromAuth.find_or_create(auth),
|
||||
true <- UserFromAuth.can_access?(recipient, user.emails) do
|
||||
# TODO: send over encrypted data that the frontend can decrypt
|
||||
conn
|
||||
|> put_session(:current_user, user)
|
||||
|> configure_session(renew: true)
|
||||
|> redirect(to: "/just/for/you/#{link_id}")
|
||||
with %{id: link_id, recipient: recipient} <- link,
|
||||
{:ok, user} <- UserFromAuth.find_or_create(auth),
|
||||
true <- UserFromAuth.can_access?(recipient, user.emails) do
|
||||
# TODO: send over encrypted data that the frontend can decrypt
|
||||
|
||||
conn
|
||||
|> put_session(:current_user, user)
|
||||
|> configure_session(renew: true)
|
||||
|> redirect(to: "/just/for/you/#{link_id}")
|
||||
else
|
||||
nil ->
|
||||
conn
|
||||
|> put_flash(:error, "Could not find link to authenticate against")
|
||||
|> redirect(to: "/just/for/you/")
|
||||
|
||||
false ->
|
||||
conn
|
||||
|> put_flash(:error, "#{recipient} was not found in your list of verified emails")
|
||||
|> redirect(to: "/just/for/you/#{link_id}")
|
||||
|> put_flash(:error, "#{link.recipient} was not found in your list of verified emails")
|
||||
|> redirect(to: "/just/for/you/#{link.id}")
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_flash(:error, reason)
|
||||
|> redirect(to: "/just/for/you/#{link_id}")
|
||||
|> redirect(to: "/just/for/you/#{link.id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
9
lib/entendu_web/controllers/file_not_found_controller.ex
Normal file
9
lib/entendu_web/controllers/file_not_found_controller.ex
Normal file
@@ -0,0 +1,9 @@
|
||||
defmodule EntenduWeb.FileNotFoundController do
|
||||
use EntenduWeb, :controller
|
||||
|
||||
def index(conn, _params) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> text("File Not Found")
|
||||
end
|
||||
end
|
@@ -9,6 +9,11 @@ defmodule EntenduWeb.LinkController do
|
||||
alias Entendu.Links
|
||||
alias Links.Link
|
||||
alias EntenduWeb.FallbackController
|
||||
alias Entendu.EncryptedLink
|
||||
alias Entendu.UserFromAuth
|
||||
alias EntenduWeb.Plugs.AuthorizeLink
|
||||
|
||||
plug AuthorizeLink when action in [:text, :file]
|
||||
|
||||
action_fallback(FallbackController)
|
||||
|
||||
@@ -17,12 +22,9 @@ defmodule EntenduWeb.LinkController do
|
||||
end
|
||||
|
||||
def just(conn, params) do
|
||||
with {:ok, %Link{} = link} <- Links.create_link(params) do
|
||||
with {:ok, %{link_with_file: %Link{} = link}} <- Links.create_link(params) do
|
||||
conn
|
||||
|> render("show_authorized.json", %{link: link})
|
||||
else
|
||||
test ->
|
||||
IO.inspect(test)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,7 +34,7 @@ defmodule EntenduWeb.LinkController do
|
||||
|
||||
def for(conn, %{"link_id" => link_id, "recipient" => recipient, "service" => service}) do
|
||||
with %Link{} = link <- Links.get_link(link_id),
|
||||
Links.update_link(link, %{ recipient: recipient, service: service}) do
|
||||
Links.update_link(link, %{recipient: recipient, service: service}) do
|
||||
conn
|
||||
|> render("show_authorized.json", %{link: link})
|
||||
end
|
||||
@@ -42,11 +44,29 @@ defmodule EntenduWeb.LinkController do
|
||||
render(conn, "you.html")
|
||||
end
|
||||
|
||||
def auth_page(conn, %{ "id" => link_id}) do
|
||||
def auth_page(conn, %{"id" => link_id}) do
|
||||
with %Link{service: service, recipient: recipient} = link <- Links.get_link(link_id) do
|
||||
conn
|
||||
|> put_session(:current_link, link)
|
||||
|> render("auth.html", %{ service: service, recipient: recipient })
|
||||
|> put_session(:intended_link, link)
|
||||
|> render("auth.html", %{intended_link: %{service: service, recipient: recipient}})
|
||||
end
|
||||
end
|
||||
|
||||
def text(conn, %{"id" => link_id}) do
|
||||
with user = get_session(conn, :current_user),
|
||||
%Link{recipient: recipient} = link <- Links.get_link(link_id),
|
||||
true <- UserFromAuth.can_access?(recipient, user.emails) do
|
||||
path = EncryptedLink.url({link.text_content, link})
|
||||
send_file(conn, 200, path)
|
||||
end
|
||||
end
|
||||
|
||||
def file(conn, %{"id" => link_id}) do
|
||||
with user = get_session(conn, :current_user),
|
||||
%Link{recipient: recipient} = link <- Links.get_link(link_id),
|
||||
true <- UserFromAuth.can_access?(recipient, user.emails) do
|
||||
path = EncryptedLink.url({link.file_content, link})
|
||||
send_file(conn, 200, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
70
lib/entendu_web/plugs/authorize_link.ex
Normal file
70
lib/entendu_web/plugs/authorize_link.ex
Normal file
@@ -0,0 +1,70 @@
|
||||
defmodule EntenduWeb.Plugs.AuthorizeLink do
|
||||
import Plug.Conn
|
||||
use EntenduWeb, :controller
|
||||
|
||||
alias Entendu.Repo
|
||||
alias Entendu.UserFromAuth
|
||||
alias Entendu.Links
|
||||
alias Entendu.Links.Link
|
||||
alias EntenduWeb.FallbackController
|
||||
alias EntenduWeb.ErrorView
|
||||
|
||||
def init(_params) do
|
||||
end
|
||||
|
||||
def call(conn, params) do
|
||||
%{params: %{"path" => [_, link_id, _]}} = conn
|
||||
user = get_session(conn, :current_user)
|
||||
|
||||
if !user do
|
||||
conn
|
||||
|> put_status(403)
|
||||
|> put_view(EntenduWeb.ErrorView)
|
||||
|> render("error_code.json", message: "Unauthorized", code: 403)
|
||||
|> halt
|
||||
else
|
||||
with {:ok, user} <- get_user_from_path(conn),
|
||||
%Link{recipient: recipient} = link <- Links.get_link(link_id),
|
||||
true <- UserFromAuth.can_access?(recipient, user.emails) do
|
||||
conn
|
||||
|> assign(:link, link)
|
||||
else
|
||||
nil ->
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> put_view(EntenduWeb.ErrorView)
|
||||
|> render("error_code.json", message: "Link could not be found", code: 404)
|
||||
|> halt
|
||||
|
||||
false ->
|
||||
conn
|
||||
|> put_status(403)
|
||||
|> put_view(EntenduWeb.ErrorView)
|
||||
|> render("error_code.json", message: "Unauthorized", code: 403)
|
||||
|> halt
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(422)
|
||||
|> put_view(EntenduWeb.ErrorView)
|
||||
|> render("error_code.json", message: reason, code: 422)
|
||||
|> halt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get_user_from_path(%{params: %{"path" => [_, link_id, _]}} = conn) do
|
||||
get_session(conn, :current_user)
|
||||
|> get_user_from_path()
|
||||
end
|
||||
|
||||
defp get_user_from_path(nil) do
|
||||
{:error, "User not authenticated"}
|
||||
end
|
||||
|
||||
defp get_user_from_path(%{id: _, name: _, emails: _} = user) do
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
defp get_user_from_path(_), do: {:error, "Link does not exist"}
|
||||
end
|
@@ -1,6 +1,8 @@
|
||||
defmodule EntenduWeb.Router do
|
||||
use EntenduWeb, :router
|
||||
|
||||
alias EntenduWeb.Plugs.AuthorizeLink
|
||||
|
||||
pipeline :browser do
|
||||
plug :accepts, ["html"]
|
||||
plug :fetch_session
|
||||
@@ -14,6 +16,11 @@ defmodule EntenduWeb.Router do
|
||||
plug :accepts, ["json"]
|
||||
end
|
||||
|
||||
pipeline :authorized_links do
|
||||
plug AuthorizeLink
|
||||
plug Plug.Static, at: "/uploads", from: Path.expand('./uploads'), gzip: false
|
||||
end
|
||||
|
||||
scope "/", EntenduWeb do
|
||||
pipe_through :browser
|
||||
|
||||
@@ -24,6 +31,8 @@ defmodule EntenduWeb.Router do
|
||||
post "/just/for", LinkController, :for
|
||||
get "/just/for/you", LinkController, :you_page
|
||||
get "/just/for/you/:id", LinkController, :auth_page
|
||||
get "/links/:id/text", LinkController, :text
|
||||
get "/links/:id/file", LinkController, :file
|
||||
end
|
||||
|
||||
scope "/auth", EntenduWeb do
|
||||
@@ -34,6 +43,11 @@ defmodule EntenduWeb.Router do
|
||||
delete "/logout", AuthController, :delete
|
||||
end
|
||||
|
||||
scope "/uploads", EntenduWeb do
|
||||
pipe_through [:browser, :authorized_links]
|
||||
get "/*path", FileNotFoundController, :index
|
||||
end
|
||||
|
||||
# Other scopes may use custom stacks.
|
||||
# scope "/api", EntenduWeb do
|
||||
# pipe_through :api
|
||||
|
@@ -1 +1,7 @@
|
||||
<%= react_component("Components.AuthPage", %{ csrf: Plug.CSRFProtection.get_csrf_token(), service: @service, recipient: @recipient, user: @current_user }) %>
|
||||
<%= react_component("Components.AuthPage", %{
|
||||
csrf: Plug.CSRFProtection.get_csrf_token(),
|
||||
service: @intended_link.service,
|
||||
recipient: @intended_link.recipient,
|
||||
user: current_user(@conn),
|
||||
link: current_link(@conn)
|
||||
}) %>
|
||||
|
@@ -31,14 +31,14 @@ defmodule Entendu.EncryptedLink do
|
||||
# end
|
||||
|
||||
# Override the persisted filenames:
|
||||
# def filename(version, _) do
|
||||
# version
|
||||
# end
|
||||
def filename(_version, {_file, %{filename: filename}}) do
|
||||
if filename, do: filename, else: "text"
|
||||
end
|
||||
|
||||
# Override the storage directory:
|
||||
# def storage_dir(version, {file, scope}) do
|
||||
# "uploads/user/avatars/#{scope.id}"
|
||||
# end
|
||||
def storage_dir(version, {_file, scope}) do
|
||||
"uploads/links/#{scope.id}"
|
||||
end
|
||||
|
||||
# Provide a default URL if there hasn't been a file uploaded
|
||||
# def default_url(version, scope) do
|
||||
|
@@ -13,4 +13,13 @@ defmodule EntenduWeb.ErrorView do
|
||||
def template_not_found(template, _assigns) do
|
||||
Phoenix.Controller.status_message_from_template(template)
|
||||
end
|
||||
|
||||
def render("error_code.json", %{message: message} = params) do
|
||||
code = Map.get(params, :code, "")
|
||||
|
||||
%{
|
||||
message: message,
|
||||
code: code
|
||||
}
|
||||
end
|
||||
end
|
||||
|
Reference in New Issue
Block a user