Compare commits
11 Commits
feature/su
...
3f7da22499
Author | SHA1 | Date | |
---|---|---|---|
3f7da22499
|
|||
50012ad7b3
|
|||
1696995fb6
|
|||
04c96fafc4
|
|||
38a7ac597b
|
|||
0847b0ff3d
|
|||
2ccb3d0053
|
|||
32cefda0a0 | |||
3c9dd96d8b
|
|||
8330bb420e
|
|||
621cbe867b |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -35,5 +35,5 @@ npm-debug.log
|
|||||||
/priv/cert/
|
/priv/cert/
|
||||||
|
|
||||||
dev.secret.exs
|
dev.secret.exs
|
||||||
/uploads/*
|
priv/uploads/*
|
||||||
!/uploads/.gitkeep
|
!priv/uploads/.gitkeep
|
@@ -12,15 +12,15 @@
|
|||||||
transition: opacity 1s ease-out;
|
transition: opacity 1s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phx-disconnected{
|
.phx-disconnected {
|
||||||
cursor: wait;
|
cursor: wait;
|
||||||
}
|
}
|
||||||
.phx-disconnected *{
|
.phx-disconnected * {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phx-modal {
|
.phx-modal {
|
||||||
opacity: 1!important;
|
opacity: 1 !important;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background-color: rgb(0,0,0);
|
background-color: rgb(0, 0, 0);
|
||||||
background-color: rgba(0,0,0,0.4);
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.phx-modal-content {
|
.phx-modal-content {
|
||||||
@@ -54,7 +54,6 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Alerts and form errors */
|
/* Alerts and form errors */
|
||||||
.alert {
|
.alert {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
@@ -88,3 +87,46 @@
|
|||||||
display: block;
|
display: block;
|
||||||
margin: -1rem 0 2rem;
|
margin: -1rem 0 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 200px;
|
||||||
|
padding: 20px;
|
||||||
|
border-right: 1px #efefef solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered-container {
|
||||||
|
margin-top: 3rem;
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1025px) {
|
||||||
|
.centered-container {
|
||||||
|
margin-top: 0px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1024px) {
|
||||||
|
.logo {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px #efefef solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splashHeader {
|
||||||
|
font-size: 1.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splashSubheader {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
13
assets/js/definitions/index.d.ts
vendored
13
assets/js/definitions/index.d.ts
vendored
@@ -1,9 +1,18 @@
|
|||||||
type IntendedUser = {
|
type IntendedUser = {
|
||||||
name: string;
|
name: string;
|
||||||
emails: string[];
|
emails: OAuthEmail[];
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OAuthEmail = {
|
||||||
|
email: string;
|
||||||
|
primary: boolean;
|
||||||
|
verified: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type IntendedLink = {
|
type IntendedLink = {
|
||||||
filename: string | null,
|
filename: string | null,
|
||||||
filetype: string | null
|
filetype: string | null,
|
||||||
|
text_content: string | null,
|
||||||
|
file_content: string | null
|
||||||
};
|
};
|
||||||
|
@@ -20,6 +20,7 @@ type AuthPageProps = {
|
|||||||
service: string;
|
service: string;
|
||||||
recipient: string;
|
recipient: string;
|
||||||
user: IntendedUser | null;
|
user: IntendedUser | null;
|
||||||
|
error: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Keys {
|
interface Keys {
|
||||||
@@ -39,22 +40,34 @@ const AuthPage = (props: AuthPageProps) => {
|
|||||||
|
|
||||||
const [secretFileUrl, setSecretFileUrl] = useState<string>("#");
|
const [secretFileUrl, setSecretFileUrl] = useState<string>("#");
|
||||||
const [secretFileName, setSecretFileName] = useState<string>("");
|
const [secretFileName, setSecretFileName] = useState<string>("");
|
||||||
const [secretMessage, setSecretMessage] = useState<string>("Decrypting...");
|
const [secretMessage, setSecretMessage] = useState<string>("");
|
||||||
|
const [messageRevealed, setMessageRevealed] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
init().catch((reason) => {
|
init().catch((reason) => {
|
||||||
alert(reason);
|
console.log(reason);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const capitalize = (s: string) =>
|
||||||
|
(s && s[0].toUpperCase() + s.slice(1)) || "";
|
||||||
|
|
||||||
const init = async (): Promise<void> => {
|
const init = async (): Promise<void> => {
|
||||||
const link: LinkFiles | null = await retrieveLink();
|
const link: LinkFiles | null = await retrieveLink();
|
||||||
const keys: Keys | null = await retrieveKeys();
|
const keys: Keys | null = await retrieveKeys();
|
||||||
if (link && keys) {
|
if (link && keys && user) {
|
||||||
await decrypt(link, keys);
|
await decrypt(link, keys);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const userEmails = (): string[] => {
|
||||||
|
return user
|
||||||
|
? user.emails
|
||||||
|
.filter((email) => email.verified)
|
||||||
|
.map((email) => email.email)
|
||||||
|
: [];
|
||||||
|
};
|
||||||
|
|
||||||
const retrieveLink = async (): Promise<LinkFiles | null> => {
|
const retrieveLink = async (): Promise<LinkFiles | 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();
|
||||||
@@ -64,25 +77,36 @@ const AuthPage = (props: AuthPageProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const linkResponse = await fetch(`/links/${linkId}`);
|
const linkResponse = await fetch(`/links/${linkId}`);
|
||||||
const linkData: IntendedLink = await linkResponse.json();
|
let linkData: IntendedLink | null;
|
||||||
const textResponse = await fetch(
|
let textData = null;
|
||||||
`/uploads/links/${linkId}/secret_message.txt`
|
let fileData = null;
|
||||||
);
|
if (linkResponse.status !== 200) {
|
||||||
const textData = await textResponse.blob();
|
throw new Error(linkResponse.statusText);
|
||||||
const fileResponse = await fetch(
|
return null;
|
||||||
`/uploads/links/${linkId}/${linkData.filename}`
|
}
|
||||||
);
|
linkData = await linkResponse.json();
|
||||||
const fileData = await fileResponse.blob();
|
|
||||||
|
|
||||||
if (linkData.filename) {
|
if (linkData) {
|
||||||
await setSecretFileName(linkData.filename);
|
const textResponse = linkData.text_content
|
||||||
|
? await fetch(`/uploads/links/${linkId}/secret_message.txt`)
|
||||||
|
: null;
|
||||||
|
textData = textResponse ? await textResponse.blob() : null;
|
||||||
|
|
||||||
|
const fileResponse = linkData.file_content
|
||||||
|
? await fetch(`/uploads/links/${linkId}/${linkData.filename}`)
|
||||||
|
: null;
|
||||||
|
fileData = fileResponse ? await fileResponse.blob() : null;
|
||||||
|
|
||||||
|
if (linkData.filename) {
|
||||||
|
await setSecretFileName(linkData.filename);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text: textData.size > 0 ? textData : null,
|
text: textData,
|
||||||
file: fileData.size > 0 ? fileData : null,
|
file: fileData,
|
||||||
filename: linkData.filename,
|
filename: linkData ? linkData.filename : null,
|
||||||
filetype: linkData.filetype,
|
filetype: linkData ? linkData.filetype : null,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -94,13 +118,13 @@ const AuthPage = (props: AuthPageProps) => {
|
|||||||
fragmentData[0] = fragmentData[0].slice(1);
|
fragmentData[0] = fragmentData[0].slice(1);
|
||||||
|
|
||||||
if (fragmentData.length <= 1) {
|
if (fragmentData.length <= 1) {
|
||||||
key = sessionStorage.getItem("link_key");
|
key = sessionStorage.getItem("key_hex");
|
||||||
iv = sessionStorage.getItem("link_iv");
|
iv = sessionStorage.getItem("iv_hex");
|
||||||
} else {
|
} else {
|
||||||
key = fragmentData[0];
|
key = fragmentData[0];
|
||||||
iv = fragmentData[1];
|
iv = fragmentData[1];
|
||||||
sessionStorage.setItem("link_key", key);
|
sessionStorage.setItem("key_hex", key);
|
||||||
sessionStorage.setItem("link_iv", iv);
|
sessionStorage.setItem("iv_hex", iv);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key && iv) {
|
if (key && iv) {
|
||||||
@@ -121,6 +145,7 @@ const AuthPage = (props: AuthPageProps) => {
|
|||||||
true,
|
true,
|
||||||
["encrypt", "decrypt"]
|
["encrypt", "decrypt"]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (link?.text) {
|
if (link?.text) {
|
||||||
const textFile = await link.text.arrayBuffer();
|
const textFile = await link.text.arrayBuffer();
|
||||||
const encodedText = await window.crypto.subtle.decrypt(
|
const encodedText = await window.crypto.subtle.decrypt(
|
||||||
@@ -135,6 +160,7 @@ const AuthPage = (props: AuthPageProps) => {
|
|||||||
// And voila
|
// And voila
|
||||||
HexMix.arrayBufferToString(encodedText, (result: string) => {
|
HexMix.arrayBufferToString(encodedText, (result: string) => {
|
||||||
setSecretMessage(result);
|
setSecretMessage(result);
|
||||||
|
setMessageRevealed(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (link?.file) {
|
if (link?.file) {
|
||||||
@@ -153,21 +179,57 @@ const AuthPage = (props: AuthPageProps) => {
|
|||||||
type: link.filetype ? link.filetype : "text/plain",
|
type: link.filetype ? link.filetype : "text/plain",
|
||||||
});
|
});
|
||||||
setSecretFileUrl(window.URL.createObjectURL(blob));
|
setSecretFileUrl(window.URL.createObjectURL(blob));
|
||||||
|
setMessageRevealed(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderFooter = (): JSX.Element => {
|
||||||
|
if (!user) return <div></div>;
|
||||||
|
return (
|
||||||
|
<Header3
|
||||||
|
small
|
||||||
|
style={{ color: "#CCCCCC", fontSize: "1.4rem", textAlign: "left" }}
|
||||||
|
>
|
||||||
|
Hello {user.name}, you are logged in to{" "}
|
||||||
|
<span style={{ color: "#A849CF" }}>{capitalize(service)}</span> as{" "}
|
||||||
|
<span style={{ color: "#32EFE7" }}>{user.username}</span>. This account
|
||||||
|
has the following emails associated with it:
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<span style={{ color: "#32EFE7" }}>{userEmails().join(", ")}</span>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
The intended recipient for this message is{" "}
|
||||||
|
<span style={{ color: "#32EFE7" }}>{recipient}</span> on{" "}
|
||||||
|
<span style={{ color: "#A849CF" }}>{capitalize(service)}</span>. If you
|
||||||
|
need to authenticate with a different account, you may do so by logging
|
||||||
|
out and accessing this link again. It's also possible that you have yet
|
||||||
|
to verify your email address on{" "}
|
||||||
|
<span style={{ color: "#A849CF" }}>{capitalize(service)}</span>.
|
||||||
|
</Header3>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const renderHeader = (): JSX.Element => {
|
const renderHeader = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Header2 style={{ margin: ".4rem" }}>Someone sent you a secret</Header2>
|
<Header2 style={{ marginBottom: ".4rem" }}>
|
||||||
|
{user ? "You have been identified!" : "Someone sent you a secret"}
|
||||||
|
</Header2>
|
||||||
{user ? (
|
{user ? (
|
||||||
<Header3 small>
|
<Header3 small>
|
||||||
Hello {user.name}, you are logged in {service} which has the
|
{messageRevealed
|
||||||
following verified emails: {user.emails.join(", ")}
|
? "The following message and/or file is for your eyes only."
|
||||||
|
: "Unfortunately, you are not the intended recipient."}
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
</Header3>
|
</Header3>
|
||||||
) : (
|
) : (
|
||||||
<Header3 small>
|
<Header3 small>
|
||||||
Please verify your identity to reveal this message.
|
The intended recipient for this message is {recipient} on{" "}
|
||||||
|
{capitalize(service)}. Please verify your identity to reveal this
|
||||||
|
message.
|
||||||
</Header3>
|
</Header3>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -176,7 +238,7 @@ const AuthPage = (props: AuthPageProps) => {
|
|||||||
|
|
||||||
const renderAuth = (): JSX.Element => {
|
const renderAuth = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<CenteredContainer fullscreen>
|
<CenteredContainer fullscreen className="centered-container">
|
||||||
<CenteredContainer>
|
<CenteredContainer>
|
||||||
{renderHeader()}
|
{renderHeader()}
|
||||||
<Spacer space="3rem" />
|
<Spacer space="3rem" />
|
||||||
@@ -206,7 +268,7 @@ const AuthPage = (props: AuthPageProps) => {
|
|||||||
|
|
||||||
const renderReveal = (): JSX.Element => {
|
const renderReveal = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<CenteredContainer fullscreen>
|
<CenteredContainer fullscreen className="centered-container">
|
||||||
<CenteredContainer>
|
<CenteredContainer>
|
||||||
{renderHeader()}
|
{renderHeader()}
|
||||||
<Spacer space="3rem" />
|
<Spacer space="3rem" />
|
||||||
@@ -232,11 +294,13 @@ const AuthPage = (props: AuthPageProps) => {
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<Spacer space="3rem" />
|
<Spacer space="3rem" />
|
||||||
<a href={`https://intended.link/auth/${service}`}>
|
<a href={`https://intended.link/auth/logout`}>
|
||||||
<Button variant="primary" wide onClick={() => {}}>
|
<Button variant="primary" wide onClick={() => {}}>
|
||||||
Re-Verify
|
Logout
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
<Spacer space="3rem" />
|
||||||
|
{renderFooter()}
|
||||||
</CenteredContainer>
|
</CenteredContainer>
|
||||||
</CenteredContainer>
|
</CenteredContainer>
|
||||||
);
|
);
|
||||||
|
@@ -1,11 +1,23 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { ProgressIndicator, Header2, Button, IconArrow, Label, Input, Select, CenteredContainer, SpaceBetweenContainer, Spacer, TextAlignWrapper, GlobalStyle } from "@intended/intended-ui";
|
import {
|
||||||
|
ProgressIndicator,
|
||||||
|
Header2,
|
||||||
|
Button,
|
||||||
|
IconArrow,
|
||||||
|
Label,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
CenteredContainer,
|
||||||
|
SpaceBetweenContainer,
|
||||||
|
Spacer,
|
||||||
|
TextAlignWrapper,
|
||||||
|
GlobalStyle,
|
||||||
|
} from "@intended/intended-ui";
|
||||||
|
|
||||||
type ForPageProps = {
|
type ForPageProps = {
|
||||||
csrf: string
|
csrf: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const ForPage = (props: ForPageProps) => {
|
const ForPage = (props: ForPageProps) => {
|
||||||
const [recipientInput, setRecipientInput] = useState("");
|
const [recipientInput, setRecipientInput] = useState("");
|
||||||
@@ -35,20 +47,20 @@ const ForPage = (props: ForPageProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('recipient', recipientInput);
|
formData.append("recipient", recipientInput);
|
||||||
formData.append('service', serviceSelect);
|
formData.append("service", serviceSelect);
|
||||||
formData.append("link_id", linkId);
|
formData.append("link_id", linkId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const results = await fetch(`${window.location.origin}/just/for`, {
|
const results = await fetch(`${window.location.origin}/just/for`, {
|
||||||
headers: {
|
headers: {
|
||||||
"X-CSRF-Token": props.csrf
|
"X-CSRF-Token": props.csrf,
|
||||||
},
|
},
|
||||||
body: formData,
|
body: formData,
|
||||||
method: "POST"
|
method: "POST",
|
||||||
});
|
});
|
||||||
if (!results.ok) {
|
if (!results.ok) {
|
||||||
throw new Error('Network response was not OK');
|
throw new Error("Network response was not OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
await results.json();
|
await results.json();
|
||||||
@@ -61,7 +73,7 @@ const ForPage = (props: ForPageProps) => {
|
|||||||
return (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<CenteredContainer fullscreen>
|
<CenteredContainer fullscreen className="centered-container">
|
||||||
<CenteredContainer>
|
<CenteredContainer>
|
||||||
<ProgressIndicator currentProgress={2} />
|
<ProgressIndicator currentProgress={2} />
|
||||||
<Header2>Tell Someone</Header2>
|
<Header2>Tell Someone</Header2>
|
||||||
@@ -79,7 +91,8 @@ const ForPage = (props: ForPageProps) => {
|
|||||||
<Spacer space="2.5rem" />
|
<Spacer space="2.5rem" />
|
||||||
<TextAlignWrapper align="left">
|
<TextAlignWrapper align="left">
|
||||||
<Label htmlFor="serviceSelector">
|
<Label htmlFor="serviceSelector">
|
||||||
What type of account is the above username or email associated with?
|
What type of account is the above username or email associated
|
||||||
|
with?
|
||||||
</Label>
|
</Label>
|
||||||
</TextAlignWrapper>
|
</TextAlignWrapper>
|
||||||
<Select
|
<Select
|
||||||
@@ -87,11 +100,14 @@ const ForPage = (props: ForPageProps) => {
|
|||||||
onChange={handleServiceChange}
|
onChange={handleServiceChange}
|
||||||
value={serviceSelect}
|
value={serviceSelect}
|
||||||
>
|
>
|
||||||
<option value='github'>Github</option>
|
<option value="github">Github</option>
|
||||||
</Select>
|
</Select>
|
||||||
<Spacer space="3rem" />
|
<Spacer space="3rem" />
|
||||||
<SpaceBetweenContainer>
|
<SpaceBetweenContainer>
|
||||||
<Button variant="secondary" onClick={() => window.location.href = "/just"}>
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => (window.location.href = "/just")}
|
||||||
|
>
|
||||||
<IconArrow arrowDirection="left" />
|
<IconArrow arrowDirection="left" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={postContacts}>Generate Secret Code</Button>
|
<Button onClick={postContacts}>Generate Secret Code</Button>
|
||||||
|
@@ -74,7 +74,11 @@ const JustPage = (props: JustPageProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileFormData = async (form: FormData, aesKey: AESKey) => {
|
const fileFormData = async (
|
||||||
|
form: FormData,
|
||||||
|
aesKey: AESKey
|
||||||
|
): Promise<FormData> => {
|
||||||
|
if (!fileInput) return form;
|
||||||
const encoded = HexMix.stringToArrayBuffer(fileInput as string);
|
const encoded = HexMix.stringToArrayBuffer(fileInput as string);
|
||||||
const encrypted = await window.crypto.subtle.encrypt(
|
const encrypted = await window.crypto.subtle.encrypt(
|
||||||
{
|
{
|
||||||
@@ -88,9 +92,17 @@ const JustPage = (props: JustPageProps) => {
|
|||||||
form.append("file_content", blobData, fileName);
|
form.append("file_content", blobData, fileName);
|
||||||
form.append("filename", fileName);
|
form.append("filename", fileName);
|
||||||
form.append("filetype", fileType);
|
form.append("filetype", fileType);
|
||||||
|
HexMix.arrayBufferToString(encrypted, (result: string) => {
|
||||||
|
sessionStorage.setItem("encoded_file", result);
|
||||||
|
});
|
||||||
|
return form;
|
||||||
};
|
};
|
||||||
|
|
||||||
const textFormData = async (form: FormData, aesKey: AESKey) => {
|
const textFormData = async (
|
||||||
|
form: FormData,
|
||||||
|
aesKey: AESKey
|
||||||
|
): Promise<FormData> => {
|
||||||
|
if (!secretInput) return form;
|
||||||
const encoded = HexMix.stringToArrayBuffer(secretInput);
|
const encoded = HexMix.stringToArrayBuffer(secretInput);
|
||||||
const encrypted = await window.crypto.subtle.encrypt(
|
const encrypted = await window.crypto.subtle.encrypt(
|
||||||
{
|
{
|
||||||
@@ -102,6 +114,10 @@ const JustPage = (props: JustPageProps) => {
|
|||||||
);
|
);
|
||||||
const blobData = new Blob([encrypted]);
|
const blobData = new Blob([encrypted]);
|
||||||
form.append("text_content", blobData, "secret_message.txt");
|
form.append("text_content", blobData, "secret_message.txt");
|
||||||
|
HexMix.arrayBufferToString(encrypted, (result: string) => {
|
||||||
|
sessionStorage.setItem("encoded_message", result);
|
||||||
|
});
|
||||||
|
return form;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createKey = async (): Promise<AESKey> => {
|
const createKey = async (): Promise<AESKey> => {
|
||||||
@@ -135,9 +151,9 @@ const JustPage = (props: JustPageProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const key = await createKey();
|
const key = await createKey();
|
||||||
const formData = new FormData();
|
let formData = new FormData();
|
||||||
await fileFormData(formData, key);
|
formData = await fileFormData(formData, key);
|
||||||
await textFormData(formData, key);
|
formData = await textFormData(formData, key);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const link: Response = await fetch(`${window.location.origin}/just`, {
|
const link: Response = await fetch(`${window.location.origin}/just`, {
|
||||||
@@ -161,18 +177,18 @@ const JustPage = (props: JustPageProps) => {
|
|||||||
return (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<CenteredContainer fullscreen>
|
<CenteredContainer fullscreen className="centered-container">
|
||||||
<CenteredContainer>
|
<CenteredContainer>
|
||||||
<ProgressIndicator currentProgress={1} />
|
<ProgressIndicator currentProgress={1} />
|
||||||
<Header2>Create a secret</Header2>
|
<Header2>Create a secret</Header2>
|
||||||
<TextAlignWrapper align="left">
|
<TextAlignWrapper align="left">
|
||||||
<Label htmlFor="secretInput">Enter your secret here</Label>
|
<Label htmlFor="secretInput">Enter a secret message</Label>
|
||||||
</TextAlignWrapper>
|
</TextAlignWrapper>
|
||||||
<TextArea
|
<TextArea
|
||||||
id="secretInput"
|
id="secretInput"
|
||||||
value={secretInput}
|
value={secretInput}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Tell me your secrets"
|
placeholder="Only your intended recipient will see this message."
|
||||||
/>
|
/>
|
||||||
<Spacer space="2rem" />
|
<Spacer space="2rem" />
|
||||||
<TextAlignWrapper align="center">
|
<TextAlignWrapper align="center">
|
||||||
|
@@ -1,21 +1,65 @@
|
|||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
import { CenteredContainer, SplashIconHeader, Header1, Header3, Spacer, Button, GlobalStyle } from '@intended/intended-ui';
|
import {
|
||||||
|
CenteredContainer,
|
||||||
|
SplashIconHeader,
|
||||||
|
Header1,
|
||||||
|
Header3,
|
||||||
|
Spacer,
|
||||||
|
Button,
|
||||||
|
GlobalStyle,
|
||||||
|
} from "@intended/intended-ui";
|
||||||
|
|
||||||
|
type SplashPageProps = {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SplashPage = (props: SplashPageProps) => {
|
||||||
|
useEffect(() => {
|
||||||
|
displayErrors();
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayErrors = () => {
|
||||||
|
const { error } = props;
|
||||||
|
|
||||||
|
if (error) alert(error);
|
||||||
|
};
|
||||||
|
|
||||||
const SplashPage = () => {
|
|
||||||
return (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<CenteredContainer fullscreen>
|
<CenteredContainer
|
||||||
|
fullscreen
|
||||||
|
style={{
|
||||||
|
background: "none",
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
transform: "translate(0, -50%)",
|
||||||
|
height: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<CenteredContainer wide>
|
<CenteredContainer wide>
|
||||||
<SplashIconHeader />
|
<SplashIconHeader style={{ width: "100%", maxWidth: "440px" }} />
|
||||||
<Header1>Securely Share Your Secrets</Header1>
|
<Header1>
|
||||||
|
<span
|
||||||
|
className="splashHeader"
|
||||||
|
style={{ display: "block", marginTop: "20px" }}
|
||||||
|
>
|
||||||
|
Securely Share Your Secrets
|
||||||
|
</span>
|
||||||
|
</Header1>
|
||||||
<Header3>
|
<Header3>
|
||||||
With Intended Link you can easily share messages and files securely
|
<span className="splashSubheader">
|
||||||
and secretly.
|
With Intended Link, you can send messages and files to any social
|
||||||
|
account in a secure and private manner.
|
||||||
|
</span>
|
||||||
</Header3>
|
</Header3>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Button variant="secondary" boldFont onClick={() => window.location.href = "/just"}>
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
boldFont
|
||||||
|
onClick={() => (window.location.href = "/just")}
|
||||||
|
>
|
||||||
START SHARING
|
START SHARING
|
||||||
</Button>
|
</Button>
|
||||||
</CenteredContainer>
|
</CenteredContainer>
|
||||||
|
@@ -1,8 +1,29 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { ProgressIndicator, Header2, Button, IconArrow, InputButtonWithIcon, Label, Input, CenteredContainer, SpaceBetweenContainer, Spacer, TextAlignWrapper, GlobalStyle } from "@intended/intended-ui";
|
import {
|
||||||
|
ProgressIndicator,
|
||||||
|
Header2,
|
||||||
|
Button,
|
||||||
|
IconArrow,
|
||||||
|
InputButtonWithIcon,
|
||||||
|
Label,
|
||||||
|
Input,
|
||||||
|
CenteredContainer,
|
||||||
|
SpaceBetweenContainer,
|
||||||
|
Spacer,
|
||||||
|
TextAlignWrapper,
|
||||||
|
GlobalStyle,
|
||||||
|
} from "@intended/intended-ui";
|
||||||
|
|
||||||
const YouPage = () => {
|
const YouPage = () => {
|
||||||
|
const [url, setUrl] = useState("#");
|
||||||
|
const [encoded, setEncoded] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUrl(calculateUrl());
|
||||||
|
setEncoded(calculateEncoded());
|
||||||
|
}, []);
|
||||||
|
|
||||||
const calculateUrl = () => {
|
const calculateUrl = () => {
|
||||||
const linkId = sessionStorage.getItem("link_id");
|
const linkId = sessionStorage.getItem("link_id");
|
||||||
const keyHex = sessionStorage.getItem("key_hex");
|
const keyHex = sessionStorage.getItem("key_hex");
|
||||||
@@ -11,7 +32,11 @@ const YouPage = () => {
|
|||||||
return `${window.location.origin}/just/for/you/${linkId}#${keyHex}.${ivHex}`;
|
return `${window.location.origin}/just/for/you/${linkId}#${keyHex}.${ivHex}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const [url, _setUrl] = useState(calculateUrl());
|
const calculateEncoded = () => {
|
||||||
|
const encodedFile = sessionStorage.getItem("encoded_file");
|
||||||
|
const encodedMessage = sessionStorage.getItem("encoded_message");
|
||||||
|
return `${encodedMessage}${encodedFile}`;
|
||||||
|
};
|
||||||
|
|
||||||
const copyUrl = async () => {
|
const copyUrl = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -19,12 +44,12 @@ const YouPage = () => {
|
|||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
alert("Could not copy url to clipboard.");
|
alert("Could not copy url to clipboard.");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
<CenteredContainer fullscreen>
|
<CenteredContainer fullscreen className="centered-container">
|
||||||
<CenteredContainer>
|
<CenteredContainer>
|
||||||
<ProgressIndicator currentProgress={3} />
|
<ProgressIndicator currentProgress={3} />
|
||||||
<Header2>Share the secret</Header2>
|
<Header2>Share the secret</Header2>
|
||||||
@@ -47,20 +72,23 @@ const YouPage = () => {
|
|||||||
looking eh?:
|
looking eh?:
|
||||||
</Label>
|
</Label>
|
||||||
</TextAlignWrapper>
|
</TextAlignWrapper>
|
||||||
<Input
|
<Input variant="disabled-light" id="encodedSecret" value={encoded} />
|
||||||
variant="disabled-light"
|
|
||||||
id="encodedSecret"
|
|
||||||
value={url}
|
|
||||||
/>
|
|
||||||
<Spacer space="3rem" />
|
<Spacer space="3rem" />
|
||||||
<SpaceBetweenContainer>
|
<SpaceBetweenContainer>
|
||||||
<Button variant="secondary" onClick={() => window.location.href = "/just/for"}>
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => (window.location.href = "/just/for")}
|
||||||
|
>
|
||||||
<IconArrow arrowDirection="left" />
|
<IconArrow arrowDirection="left" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => {
|
<Button
|
||||||
sessionStorage.clear();
|
onClick={() => {
|
||||||
window.location.href = "/just";
|
sessionStorage.clear();
|
||||||
}}>Create Another Secret</Button>
|
window.location.href = "/just";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create Another Secret
|
||||||
|
</Button>
|
||||||
</SpaceBetweenContainer>
|
</SpaceBetweenContainer>
|
||||||
</CenteredContainer>
|
</CenteredContainer>
|
||||||
</CenteredContainer>
|
</CenteredContainer>
|
||||||
|
18
assets/package-lock.json
generated
18
assets/package-lock.json
generated
@@ -1143,9 +1143,9 @@
|
|||||||
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
|
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
|
||||||
},
|
},
|
||||||
"@intended/intended-ui": {
|
"@intended/intended-ui": {
|
||||||
"version": "0.1.21",
|
"version": "0.1.26",
|
||||||
"resolved": "https://registry.npmjs.org/@intended/intended-ui/-/intended-ui-0.1.21.tgz",
|
"resolved": "https://registry.npmjs.org/@intended/intended-ui/-/intended-ui-0.1.26.tgz",
|
||||||
"integrity": "sha512-rNyJOGLOw8iKP0AaSYSytZXFssPhlE1FbUjxEze8F4iOgXxs6iu7B9+gSIF4QJg/IrQUVu76tkevBGDk2nTQNg==",
|
"integrity": "sha512-+fSZctq4ywDUN6IJ+SbQyGhcx1fZmdyq/zsDqveJShFOGLOU39l5tT34/QHBScXlSLwgAkosCycldWyfW/olUg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"polished": "^4.1.3",
|
"polished": "^4.1.3",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
@@ -1720,14 +1720,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"babel-plugin-styled-components": {
|
"babel-plugin-styled-components": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.5.tgz",
|
||||||
"integrity": "sha512-7eG5NE8rChnNTDxa6LQfynwgHTVOYYaHJbUYSlOhk8QBXIQiMBKq4gyfHBBKPrxUcVBXVJL61ihduCpCQbuNbw==",
|
"integrity": "sha512-A7kfST5odbf8Ev42OQbj5teEiT8DskpRoQ/iPYePLLdcTCAsodpYKqtoy4SJthpsGzQKc2vvnrtlUgdmJq6WKQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-annotate-as-pure": "^7.16.0",
|
"@babel/helper-annotate-as-pure": "^7.16.0",
|
||||||
"@babel/helper-module-imports": "^7.16.0",
|
"@babel/helper-module-imports": "^7.16.0",
|
||||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||||
"lodash": "^4.17.11"
|
"lodash": "^4.17.11",
|
||||||
|
"picomatch": "^2.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"babel-plugin-syntax-jsx": {
|
"babel-plugin-syntax-jsx": {
|
||||||
@@ -5863,8 +5864,7 @@
|
|||||||
"picomatch": {
|
"picomatch": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
|
||||||
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
|
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
"watch": "webpack --mode development --watch"
|
"watch": "webpack --mode development --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@intended/intended-ui": "0.1.21",
|
"@intended/intended-ui": "0.1.26",
|
||||||
"phoenix": "file:../deps/phoenix",
|
"phoenix": "file:../deps/phoenix",
|
||||||
"phoenix_html": "file:../deps/phoenix_html",
|
"phoenix_html": "file:../deps/phoenix_html",
|
||||||
"phoenix_live_view": "file:../deps/phoenix_live_view",
|
"phoenix_live_view": "file:../deps/phoenix_live_view",
|
||||||
|
BIN
assets/static/images/logo.png
Normal file
BIN
assets/static/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
@@ -10,7 +10,7 @@ use Mix.Config
|
|||||||
# which you should run after static files are built and
|
# which you should run after static files are built and
|
||||||
# before starting your production server.
|
# before starting your production server.
|
||||||
config :entendu, EntenduWeb.Endpoint,
|
config :entendu, EntenduWeb.Endpoint,
|
||||||
url: [host: "example.com", port: 80],
|
url: [host: "intended.link", port: 80],
|
||||||
cache_static_manifest: "priv/static/cache_manifest.json"
|
cache_static_manifest: "priv/static/cache_manifest.json"
|
||||||
|
|
||||||
# Do not print debug messages in production
|
# Do not print debug messages in production
|
||||||
@@ -45,8 +45,7 @@ config :logger, level: :info
|
|||||||
# We also recommend setting `force_ssl` in your endpoint, ensuring
|
# We also recommend setting `force_ssl` in your endpoint, ensuring
|
||||||
# no data is ever sent via http, always redirecting to https:
|
# no data is ever sent via http, always redirecting to https:
|
||||||
#
|
#
|
||||||
# config :entendu, EntenduWeb.Endpoint,
|
config :my_app, EntenduWeb.Endpoint, force_ssl: [rewrite_on: [:x_forwarded_proto]]
|
||||||
# force_ssl: [hsts: true]
|
|
||||||
#
|
#
|
||||||
# Check `Plug.SSL` for all available options in `force_ssl`.
|
# Check `Plug.SSL` for all available options in `force_ssl`.
|
||||||
|
|
||||||
|
@@ -28,8 +28,7 @@ defmodule EntenduWeb.AuthController do
|
|||||||
link = get_session(conn, :intended_link)
|
link = get_session(conn, :intended_link)
|
||||||
|
|
||||||
with %{id: link_id, recipient: recipient} <- link,
|
with %{id: link_id, recipient: recipient} <- link,
|
||||||
{:ok, user} <- UserFromAuth.find_or_create(auth),
|
{:ok, user} <- UserFromAuth.find_or_create(auth) do
|
||||||
true <- UserFromAuth.can_access?(recipient, user) do
|
|
||||||
# TODO: send over encrypted data that the frontend can decrypt
|
# TODO: send over encrypted data that the frontend can decrypt
|
||||||
|
|
||||||
conn
|
conn
|
||||||
@@ -42,11 +41,6 @@ defmodule EntenduWeb.AuthController do
|
|||||||
|> put_flash(:error, "Could not find link to authenticate against")
|
|> put_flash(:error, "Could not find link to authenticate against")
|
||||||
|> redirect(to: "/just/for/you/")
|
|> redirect(to: "/just/for/you/")
|
||||||
|
|
||||||
false ->
|
|
||||||
conn
|
|
||||||
|> put_flash(:error, "#{link.recipient} was not found in your list of verified emails")
|
|
||||||
|> redirect(to: "/just/for/you/#{link.id}")
|
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
conn
|
conn
|
||||||
|> put_flash(:error, reason)
|
|> put_flash(:error, reason)
|
||||||
|
@@ -36,6 +36,7 @@ defmodule EntenduWeb.LinkController do
|
|||||||
with %Link{} = link <- Links.get_link(link_id),
|
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
|
conn
|
||||||
|
|> put_session(:intended_link, %{})
|
||||||
|> render("show_authorized.json", %{link: link})
|
|> render("show_authorized.json", %{link: link})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -45,9 +46,9 @@ defmodule EntenduWeb.LinkController do
|
|||||||
end
|
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
|
with %Link{id: id, 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, %{id: id, service: service, recipient: recipient})
|
||||||
|> render("auth.html", %{intended_link: %{service: service, recipient: recipient}})
|
|> render("auth.html", %{intended_link: %{service: service, recipient: recipient}})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -6,6 +6,8 @@ defmodule EntenduWeb.PageController do
|
|||||||
use EntenduWeb, :controller
|
use EntenduWeb, :controller
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, _params) do
|
||||||
render(conn, "index.html", current_user: get_session(conn, :current_user))
|
conn
|
||||||
|
|> clear_session()
|
||||||
|
|> render("index.html")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -18,7 +18,7 @@ defmodule EntenduWeb.Router do
|
|||||||
|
|
||||||
pipeline :authorized_files do
|
pipeline :authorized_files do
|
||||||
plug AuthorizeLink
|
plug AuthorizeLink
|
||||||
plug Plug.Static, at: "/uploads", from: Path.expand('./uploads'), gzip: false
|
plug Plug.Static, at: "/uploads", from: {:entendu, "priv/uploads"}, gzip: false
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :authorized_link do
|
pipeline :authorized_link do
|
||||||
@@ -40,9 +40,9 @@ defmodule EntenduWeb.Router do
|
|||||||
scope "/auth", EntenduWeb do
|
scope "/auth", EntenduWeb do
|
||||||
pipe_through :browser
|
pipe_through :browser
|
||||||
|
|
||||||
|
get "/logout", AuthController, :delete
|
||||||
get "/:provider", AuthController, :request
|
get "/:provider", AuthController, :request
|
||||||
get "/:provider/callback", AuthController, :callback
|
get "/:provider/callback", AuthController, :callback
|
||||||
delete "/logout", AuthController, :delete
|
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/uploads", EntenduWeb do
|
scope "/uploads", EntenduWeb do
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
<main role="main">
|
<main role="main">
|
||||||
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
|
<a href="/">
|
||||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
<img src="<%= Routes.static_path(@conn, "/images/logo.png") %>" class="logo" />
|
||||||
|
</a>
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
</main>
|
</main>
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
<link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
|
<link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
|
||||||
<script defer phx-track-static type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
<script defer phx-track-static type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
||||||
</head>
|
</head>
|
||||||
<body style="background: #060b2e;">
|
<body style="background: linear-gradient(180deg,#060b2e 0%,#051745 100%); min-height: 100%;">
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -3,5 +3,6 @@
|
|||||||
service: @intended_link.service,
|
service: @intended_link.service,
|
||||||
recipient: @intended_link.recipient,
|
recipient: @intended_link.recipient,
|
||||||
user: current_user(@conn),
|
user: current_user(@conn),
|
||||||
link: current_link(@conn)
|
link: current_link(@conn),
|
||||||
|
error: get_flash(@conn, :error)
|
||||||
}) %>
|
}) %>
|
||||||
|
@@ -1,46 +1,5 @@
|
|||||||
<section>
|
<section>
|
||||||
|
|
||||||
<%= react_component("Components.SplashPage") %>
|
<%= react_component("Components.SplashPage", %{ error: get_flash(@conn, :error) }) %>
|
||||||
|
|
||||||
<%= if @current_user do %>
|
|
||||||
<h2>Welcome, <%= @current_user.name %>!</h2>
|
|
||||||
<div>
|
|
||||||
<img src="<%= @current_user.avatar %>" />
|
|
||||||
</div>
|
|
||||||
<%= link "Logout", to: Routes.auth_path(@conn, :delete), method: "delete", class: "button" %>
|
|
||||||
<br>
|
|
||||||
<% else %>
|
|
||||||
<ul style="display: none;">
|
|
||||||
<li>
|
|
||||||
<a class="button" href="<%= Routes.auth_path(@conn, :request, "github") %>">
|
|
||||||
<i class="fa fa-github"></i>
|
|
||||||
Sign in with GitHub
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="button" href="<%= Routes.auth_path(@conn, :request, "facebook") %>">
|
|
||||||
<i class="fa fa-facebook"></i>
|
|
||||||
Sign in with Facebook
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="button" href="<%= Routes.auth_path(@conn, :request, "google") %>">
|
|
||||||
<i class="fa fa-google"></i>
|
|
||||||
Sign in with Google
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="button" href="<%= Routes.auth_path(@conn, :request, "slack") %>">
|
|
||||||
<i class="fa fa-slack"></i>
|
|
||||||
Sign in with Slack
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="button" href="<%= Routes.auth_path(@conn, :request, "twitter") %>">
|
|
||||||
<i class="fa fa-twitter"></i>
|
|
||||||
Sign in with Twitter
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<% end %>
|
|
||||||
</section>
|
</section>
|
||||||
|
@@ -37,7 +37,7 @@ defmodule Entendu.EncryptedLink do
|
|||||||
|
|
||||||
# Override the storage directory:
|
# Override the storage directory:
|
||||||
def storage_dir(version, {_file, scope}) do
|
def storage_dir(version, {_file, scope}) do
|
||||||
"uploads/links/#{scope.id}"
|
"priv/uploads/links/#{scope.id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Provide a default URL if there hasn't been a file uploaded
|
# Provide a default URL if there hasn't been a file uploaded
|
||||||
|
Reference in New Issue
Block a user