Compare commits
No commits in common. "621cbe867b364f4ef928362c73ae8da250f05e82" and "42cfe127eb5be77b28689aad860e567095f53284" have entirely different histories.
621cbe867b
...
42cfe127eb
|
@ -2,8 +2,3 @@ type IntendedUser = {
|
||||||
name: string;
|
name: string;
|
||||||
emails: string[];
|
emails: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type IntendedLink = {
|
|
||||||
filename: string | null,
|
|
||||||
filetype: string | null
|
|
||||||
};
|
|
||||||
|
|
|
@ -27,18 +27,15 @@ interface Keys {
|
||||||
iv: string;
|
iv: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LinkFiles {
|
interface Link {
|
||||||
text: Blob | null;
|
text: Blob | null;
|
||||||
file: Blob | null;
|
file: Blob | null;
|
||||||
filename: string | null;
|
|
||||||
filetype: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthPage = (props: AuthPageProps) => {
|
const AuthPage = (props: AuthPageProps) => {
|
||||||
const { service, recipient, user } = props;
|
const { service, recipient, user } = props;
|
||||||
|
|
||||||
const [secretFileUrl, setSecretFileUrl] = useState<string>("#");
|
const [secretFileUrl, _setsecretFileUrl] = useState<string>("#");
|
||||||
const [secretFileName, setSecretFileName] = useState<string>("");
|
|
||||||
const [secretMessage, setSecretMessage] = useState<string>("Decrypting...");
|
const [secretMessage, setSecretMessage] = useState<string>("Decrypting...");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -48,14 +45,14 @@ const AuthPage = (props: AuthPageProps) => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const init = async (): Promise<void> => {
|
const init = async (): Promise<void> => {
|
||||||
const link: LinkFiles | null = await retrieveLink();
|
|
||||||
const keys: Keys | null = await retrieveKeys();
|
const keys: Keys | null = await retrieveKeys();
|
||||||
|
const link: Link | null = await retrieveLink();
|
||||||
if (link && keys) {
|
if (link && keys) {
|
||||||
await decrypt(link, keys);
|
await decrypt(link, keys);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const retrieveLink = async (): Promise<LinkFiles | null> => {
|
const retrieveLink = async (): Promise<Link | null> => {
|
||||||
const urlSegments = new URL(document.URL).pathname.split("/");
|
const urlSegments = new URL(document.URL).pathname.split("/");
|
||||||
const linkId = urlSegments.pop() || urlSegments.pop();
|
const linkId = urlSegments.pop() || urlSegments.pop();
|
||||||
if (!linkId) {
|
if (!linkId) {
|
||||||
|
@ -63,26 +60,14 @@ const AuthPage = (props: AuthPageProps) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkResponse = await fetch(`/links/${linkId}`);
|
const textResponse = await fetch(`/uploads/links/${linkId}/text`);
|
||||||
const linkData: IntendedLink = await linkResponse.json();
|
|
||||||
const textResponse = await fetch(
|
|
||||||
`/uploads/links/${linkId}/secret_message.txt`
|
|
||||||
);
|
|
||||||
const textData = await textResponse.blob();
|
const textData = await textResponse.blob();
|
||||||
const fileResponse = await fetch(
|
const fileResponse = await fetch(`/uploads/links/${linkId}/file`);
|
||||||
`/uploads/links/${linkId}/${linkData.filename}`
|
|
||||||
);
|
|
||||||
const fileData = await fileResponse.blob();
|
const fileData = await fileResponse.blob();
|
||||||
|
|
||||||
if (linkData.filename) {
|
|
||||||
await setSecretFileName(linkData.filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text: textData.size > 0 ? textData : null,
|
text: textData.size > 0 ? textData : null,
|
||||||
file: fileData.size > 0 ? fileData : null,
|
file: fileData.size > 0 ? fileData : null,
|
||||||
filename: linkData.filename,
|
|
||||||
filetype: linkData.filetype,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -111,7 +96,7 @@ const AuthPage = (props: AuthPageProps) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const decrypt = async (link: LinkFiles, keys: Keys) => {
|
const decrypt = async (link: Link, keys: Keys) => {
|
||||||
const convertedKey = HexMix.hexToUint8(keys.key);
|
const convertedKey = HexMix.hexToUint8(keys.key);
|
||||||
const convertedIv = HexMix.hexToUint8(keys.iv);
|
const convertedIv = HexMix.hexToUint8(keys.iv);
|
||||||
const importedKey = await window.crypto.subtle.importKey(
|
const importedKey = await window.crypto.subtle.importKey(
|
||||||
|
@ -138,21 +123,6 @@ const AuthPage = (props: AuthPageProps) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (link?.file) {
|
if (link?.file) {
|
||||||
const uploadedFile = await link.file.arrayBuffer();
|
|
||||||
const encodedFile = await window.crypto.subtle.decrypt(
|
|
||||||
{
|
|
||||||
name: "AES-GCM",
|
|
||||||
length: 256,
|
|
||||||
iv: convertedIv,
|
|
||||||
},
|
|
||||||
importedKey,
|
|
||||||
uploadedFile
|
|
||||||
);
|
|
||||||
|
|
||||||
const blob = new Blob([encodedFile], {
|
|
||||||
type: link.filetype ? link.filetype : "text/plain",
|
|
||||||
});
|
|
||||||
setSecretFileUrl(window.URL.createObjectURL(blob));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -223,14 +193,12 @@ const AuthPage = (props: AuthPageProps) => {
|
||||||
<TextAlignWrapper align="left">
|
<TextAlignWrapper align="left">
|
||||||
<Label htmlFor="service">Secret File</Label>
|
<Label htmlFor="service">Secret File</Label>
|
||||||
</TextAlignWrapper>
|
</TextAlignWrapper>
|
||||||
<a href={secretFileUrl} download style={{ width: "100%" }}>
|
<InputButtonWithIcon
|
||||||
<InputButtonWithIcon
|
variant="download"
|
||||||
variant="download"
|
id="downloadfile"
|
||||||
id="downloadfile"
|
value={secretFileUrl}
|
||||||
value={secretFileName}
|
onClick={() => {}}
|
||||||
onClick={() => {}}
|
/>
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<Spacer space="3rem" />
|
<Spacer space="3rem" />
|
||||||
<a href={`https://intended.link/auth/${service}`}>
|
<a href={`https://intended.link/auth/${service}`}>
|
||||||
<Button variant="primary" wide onClick={() => {}}>
|
<Button variant="primary" wide onClick={() => {}}>
|
||||||
|
|
|
@ -1,157 +1,80 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import {
|
import { ProgressIndicator, Header2, Button, IconArrow, Label, FileInput, TextArea, CenteredContainer, Spacer, TextAlignWrapper, GlobalStyle } from '@intended/intended-ui';
|
||||||
ProgressIndicator,
|
|
||||||
Header2,
|
|
||||||
Button,
|
|
||||||
IconArrow,
|
|
||||||
Label,
|
|
||||||
FileInput,
|
|
||||||
TextArea,
|
|
||||||
CenteredContainer,
|
|
||||||
Spacer,
|
|
||||||
TextAlignWrapper,
|
|
||||||
GlobalStyle,
|
|
||||||
} from "@intended/intended-ui";
|
|
||||||
import HexMix from "../utils/hexmix";
|
import HexMix from "../utils/hexmix";
|
||||||
|
|
||||||
type JustPageProps = {
|
type JustPageProps = {
|
||||||
csrf: string;
|
csrf: string
|
||||||
};
|
|
||||||
|
|
||||||
interface AESKey {
|
|
||||||
key: CryptoKey;
|
|
||||||
iv: Uint8Array;
|
|
||||||
exported: ArrayBuffer;
|
|
||||||
keyHex: string;
|
|
||||||
ivHex: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const JustPage = (props: JustPageProps) => {
|
const JustPage = (props: JustPageProps) => {
|
||||||
const [secretInput, setSecretInput] = useState("");
|
const [secretInput, setSecretInput] = useState("");
|
||||||
const [fileInput, setFileInput] = useState<string | null>(null);
|
const [fileInput, setFileInput] = useState<File | null>(null);
|
||||||
const [fileName, setFileName] = useState("");
|
const [fileName, setFileName] = useState("");
|
||||||
const [fileType, setFileType] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setSecretInput(e.target.value);
|
setSecretInput(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFile = (file: File) => {
|
const handleFile = (file: File) => {
|
||||||
|
setFileInput(file);
|
||||||
|
|
||||||
setFileName(file.name);
|
setFileName(file.name);
|
||||||
setFileType(file.type);
|
|
||||||
|
|
||||||
if (file instanceof File) {
|
|
||||||
if (file.size > 2097152) {
|
|
||||||
// TODO: may not need this check
|
|
||||||
alert("Error: Max file size is 2mb.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onloadend = loadFile;
|
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadFile = (fileEvent: ProgressEvent<FileReader>) => {
|
|
||||||
// Make sure we actually got a binary file
|
|
||||||
if (
|
|
||||||
fileEvent &&
|
|
||||||
fileEvent.target &&
|
|
||||||
fileEvent.target.result instanceof ArrayBuffer
|
|
||||||
) {
|
|
||||||
const data = fileEvent.target.result as ArrayBuffer;
|
|
||||||
HexMix.arrayBufferToString(data, (result: string) => {
|
|
||||||
setFileInput(result);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
alert("File is either missing or corrupt.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fileFormData = async (form: FormData, aesKey: AESKey) => {
|
|
||||||
const encoded = HexMix.stringToArrayBuffer(fileInput as string);
|
|
||||||
const encrypted = await window.crypto.subtle.encrypt(
|
|
||||||
{
|
|
||||||
name: "AES-GCM",
|
|
||||||
iv: aesKey.iv,
|
|
||||||
},
|
|
||||||
aesKey.key,
|
|
||||||
encoded
|
|
||||||
);
|
|
||||||
const blobData = new Blob([encrypted]);
|
|
||||||
form.append("file_content", blobData, fileName);
|
|
||||||
form.append("filename", fileName);
|
|
||||||
form.append("filetype", fileType);
|
|
||||||
};
|
|
||||||
|
|
||||||
const textFormData = async (form: FormData, aesKey: AESKey) => {
|
|
||||||
const encoded = HexMix.stringToArrayBuffer(secretInput);
|
|
||||||
const encrypted = await window.crypto.subtle.encrypt(
|
|
||||||
{
|
|
||||||
name: "AES-GCM",
|
|
||||||
iv: aesKey.iv,
|
|
||||||
},
|
|
||||||
aesKey.key,
|
|
||||||
encoded
|
|
||||||
);
|
|
||||||
const blobData = new Blob([encrypted]);
|
|
||||||
form.append("text_content", blobData, "secret_message.txt");
|
|
||||||
};
|
|
||||||
|
|
||||||
const createKey = async (): Promise<AESKey> => {
|
|
||||||
const key = await window.crypto.subtle.generateKey(
|
|
||||||
{
|
|
||||||
name: "AES-GCM",
|
|
||||||
length: 256,
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
["encrypt", "decrypt"]
|
|
||||||
);
|
|
||||||
|
|
||||||
const iv = window.crypto.getRandomValues(new Uint8Array(16));
|
|
||||||
const exported = await window.crypto.subtle.exportKey("raw", key);
|
|
||||||
const keyHex = HexMix.uint8ToHex(new Uint8Array(exported));
|
|
||||||
const ivHex = HexMix.uint8ToHex(iv);
|
|
||||||
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
iv,
|
|
||||||
exported,
|
|
||||||
keyHex,
|
|
||||||
ivHex,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const postContents = async () => {
|
const postContents = async () => {
|
||||||
if (!window.crypto.subtle) {
|
if (!window.crypto.subtle) {
|
||||||
alert("Browser does not support SubtleCrypto");
|
alert('Browser does not support SubtleCrypto');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = await createKey();
|
const key = await window.crypto.subtle.generateKey(
|
||||||
|
{
|
||||||
|
name: 'AES-GCM',
|
||||||
|
length: 256
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
['encrypt', 'decrypt']
|
||||||
|
);
|
||||||
|
const encoded = HexMix.stringToArrayBuffer(secretInput);
|
||||||
|
const iv = window.crypto.getRandomValues(new Uint8Array(16));
|
||||||
|
const exported = await window.crypto.subtle.exportKey('raw', key);
|
||||||
|
const encrypted = await window.crypto.subtle.encrypt(
|
||||||
|
{
|
||||||
|
name: 'AES-GCM',
|
||||||
|
iv
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
encoded
|
||||||
|
);
|
||||||
|
|
||||||
|
const keyHex = HexMix.uint8ToHex(new Uint8Array(exported));
|
||||||
|
const ivHex = HexMix.uint8ToHex(iv);
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
await fileFormData(formData, key);
|
const blobData = new Blob([encrypted]);
|
||||||
await textFormData(formData, key);
|
|
||||||
|
formData.append('text_content', blobData);
|
||||||
|
// formData.append('filetype', 'text/plain');
|
||||||
|
// formData.append('filename', 'secret.txt');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const link: Response = await fetch(`${window.location.origin}/just`, {
|
const link: Response = await fetch(`${window.location.origin}/just`, {
|
||||||
headers: {
|
headers: {
|
||||||
"X-CSRF-Token": props.csrf,
|
"X-CSRF-Token": props.csrf
|
||||||
},
|
},
|
||||||
body: formData,
|
body: formData,
|
||||||
method: "POST",
|
method: "POST"
|
||||||
});
|
});
|
||||||
const { id: link_id } = await link.json();
|
const { id: link_id } = await link.json()
|
||||||
|
|
||||||
sessionStorage.setItem("link_id", link_id);
|
sessionStorage.setItem("link_id", link_id);
|
||||||
sessionStorage.setItem("key_hex", key.keyHex);
|
sessionStorage.setItem("key_hex", keyHex);
|
||||||
sessionStorage.setItem("iv_hex", key.ivHex);
|
sessionStorage.setItem("iv_hex", ivHex);
|
||||||
window.location.href = `${window.location.origin}/just/for`;
|
window.location.href = `${window.location.origin}/just/for`;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
alert(err.message);
|
alert(err.message);
|
||||||
|
@ -180,13 +103,10 @@ const JustPage = (props: JustPageProps) => {
|
||||||
</TextAlignWrapper>
|
</TextAlignWrapper>
|
||||||
<Spacer space="1.6rem" />
|
<Spacer space="1.6rem" />
|
||||||
<FileInput id="fileInput" value={fileName} handleFile={handleFile} />
|
<FileInput id="fileInput" value={fileName} handleFile={handleFile} />
|
||||||
|
{ fileInput ? "" : ""}
|
||||||
<Spacer space="4rem" />
|
<Spacer space="4rem" />
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{ display: "flex", justifyContent: "flex-end", width: "100%" }}
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Button variant="secondary" onClick={postContents}>
|
<Button variant="secondary" onClick={postContents}>
|
||||||
<IconArrow arrowDirection="right" />
|
<IconArrow arrowDirection="right" />
|
||||||
|
|
|
@ -47,15 +47,26 @@ defmodule EntenduWeb.LinkController do
|
||||||
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
|
with %Link{service: service, recipient: recipient} = link <- Links.get_link(link_id) do
|
||||||
conn
|
conn
|
||||||
|> put_session(:intended_link, %{service: service, recipient: recipient})
|
|> put_session(:intended_link, link)
|
||||||
|> render("auth.html", %{intended_link: %{service: service, recipient: recipient}})
|
|> render("auth.html", %{intended_link: %{service: service, recipient: recipient}})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorized_link(conn, %{"id" => link_id}) do
|
def text(conn, %{"id" => link_id}) do
|
||||||
with %Link{} = link <- Links.get_link(link_id) do
|
with user = get_session(conn, :current_user),
|
||||||
conn
|
%Link{recipient: recipient} = link <- Links.get_link(link_id),
|
||||||
|> render("show_authorized.json", %{link: link})
|
true <- UserFromAuth.can_access?(recipient, user) 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) do
|
||||||
|
path = EncryptedLink.url({link.file_content, link})
|
||||||
|
send_file(conn, 200, path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,12 +12,8 @@ defmodule EntenduWeb.Plugs.AuthorizeLink do
|
||||||
def init(_params) do
|
def init(_params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_link_id(%{params: %{"id" => link_id}}), do: link_id
|
def call(conn, params) do
|
||||||
|
%{params: %{"path" => [_, link_id, _]}} = conn
|
||||||
defp get_link_id(%{params: %{"path" => [_, link_id, _]}}), do: link_id
|
|
||||||
|
|
||||||
def call(conn, _params) do
|
|
||||||
link_id = get_link_id(conn)
|
|
||||||
user = get_session(conn, :current_user)
|
user = get_session(conn, :current_user)
|
||||||
|
|
||||||
if !user do
|
if !user do
|
||||||
|
@ -27,7 +23,8 @@ defmodule EntenduWeb.Plugs.AuthorizeLink do
|
||||||
|> render("error_code.json", message: "Unauthorized", code: 403)
|
|> render("error_code.json", message: "Unauthorized", code: 403)
|
||||||
|> halt
|
|> halt
|
||||||
else
|
else
|
||||||
with %Link{recipient: recipient} = link <- Links.get_link(link_id),
|
with {:ok, user} <- get_user_from_path(conn),
|
||||||
|
%Link{recipient: recipient} = link <- Links.get_link(link_id),
|
||||||
true <- UserFromAuth.can_access?(recipient, user) do
|
true <- UserFromAuth.can_access?(recipient, user) do
|
||||||
conn
|
conn
|
||||||
|> assign(:link, link)
|
|> assign(:link, link)
|
||||||
|
@ -55,4 +52,19 @@ defmodule EntenduWeb.Plugs.AuthorizeLink do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -16,15 +16,11 @@ defmodule EntenduWeb.Router do
|
||||||
plug :accepts, ["json"]
|
plug :accepts, ["json"]
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :authorized_files do
|
pipeline :authorized_links do
|
||||||
plug AuthorizeLink
|
plug AuthorizeLink
|
||||||
plug Plug.Static, at: "/uploads", from: Path.expand('./uploads'), gzip: false
|
plug Plug.Static, at: "/uploads", from: Path.expand('./uploads'), gzip: false
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :authorized_link do
|
|
||||||
plug AuthorizeLink
|
|
||||||
end
|
|
||||||
|
|
||||||
scope "/", EntenduWeb do
|
scope "/", EntenduWeb do
|
||||||
pipe_through :browser
|
pipe_through :browser
|
||||||
|
|
||||||
|
@ -35,6 +31,8 @@ defmodule EntenduWeb.Router do
|
||||||
post "/just/for", LinkController, :for
|
post "/just/for", LinkController, :for
|
||||||
get "/just/for/you", LinkController, :you_page
|
get "/just/for/you", LinkController, :you_page
|
||||||
get "/just/for/you/:id", LinkController, :auth_page
|
get "/just/for/you/:id", LinkController, :auth_page
|
||||||
|
get "/links/:id/text", LinkController, :text
|
||||||
|
get "/links/:id/file", LinkController, :file
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/auth", EntenduWeb do
|
scope "/auth", EntenduWeb do
|
||||||
|
@ -46,15 +44,10 @@ defmodule EntenduWeb.Router do
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/uploads", EntenduWeb do
|
scope "/uploads", EntenduWeb do
|
||||||
pipe_through [:browser, :authorized_files]
|
pipe_through [:browser, :authorized_links]
|
||||||
get "/*path", FileNotFoundController, :index
|
get "/*path", FileNotFoundController, :index
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/links", EntenduWeb do
|
|
||||||
pipe_through [:browser, :authorized_link]
|
|
||||||
get "/:id", LinkController, :authorized_link
|
|
||||||
end
|
|
||||||
|
|
||||||
# Other scopes may use custom stacks.
|
# Other scopes may use custom stacks.
|
||||||
# scope "/api", EntenduWeb do
|
# scope "/api", EntenduWeb do
|
||||||
# pipe_through :api
|
# pipe_through :api
|
||||||
|
|
|
@ -31,9 +31,9 @@ defmodule Entendu.EncryptedLink do
|
||||||
# end
|
# end
|
||||||
|
|
||||||
# Override the persisted filenames:
|
# Override the persisted filenames:
|
||||||
# def filename(_version, {_file, %{filename: filename}}) do
|
def filename(_version, {_file, %{filename: filename}}) do
|
||||||
# if filename, do: filename, else: "text"
|
if filename, do: filename, else: "text"
|
||||||
# end
|
end
|
||||||
|
|
||||||
# Override the storage directory:
|
# Override the storage directory:
|
||||||
def storage_dir(version, {_file, scope}) do
|
def storage_dir(version, {_file, scope}) do
|
||||||
|
|
Loading…
Reference in New Issue