add waffle library, handle file upload, authenticate user with oauth and see if they have the link's email associated to their account
This commit is contained in:
parent
5de53e23ea
commit
cac3757723
|
@ -14,15 +14,17 @@ import "../css/app.css"
|
||||||
// import {Socket} from "phoenix"
|
// import {Socket} from "phoenix"
|
||||||
// import socket from "./socket"
|
// import socket from "./socket"
|
||||||
//
|
//
|
||||||
import "phoenix_html"
|
import "phoenix_html";
|
||||||
import {Socket} from "phoenix"
|
import {Socket} from "phoenix";
|
||||||
import topbar from "topbar"
|
import topbar from "topbar";
|
||||||
import {LiveSocket} from "phoenix_live_view"
|
import {LiveSocket} from "phoenix_live_view";
|
||||||
|
|
||||||
import SplashPage from './pages/SplashPage';
|
import SplashPage from './pages/SplashPage';
|
||||||
import JustPage from './pages/JustPage'
|
import JustPage from './pages/JustPage';
|
||||||
import ForPage from './pages/ForPage'
|
import ForPage from './pages/ForPage';
|
||||||
import YouPage from './pages/YouPage'
|
import YouPage from './pages/YouPage';
|
||||||
|
import AuthPage from './pages/AuthPage';
|
||||||
|
import RevealPage from './pages/RevealPage';
|
||||||
|
|
||||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
||||||
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
|
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
|
||||||
|
@ -42,5 +44,5 @@ liveSocket.connect()
|
||||||
window.liveSocket = liveSocket
|
window.liveSocket = liveSocket
|
||||||
|
|
||||||
window.Components = {
|
window.Components = {
|
||||||
SplashPage, JustPage, ForPage, YouPage
|
SplashPage, JustPage, ForPage, YouPage, AuthPage, RevealPage
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,54 @@
|
||||||
// import HexMix from "../utils/hexmix";
|
import { Button, CenteredContainer, GlobalStyle, Header2, Header3, Input, Label, Spacer, TextAlignWrapper } from "@intended/intended-ui";
|
||||||
// const fragmentData = window.location.hash.split('.');
|
import React, { useEffect } from "react";
|
||||||
// if (fragmentData.length <= 0) {
|
|
||||||
// alert("No key found in fragment URI");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// const key = HexMix.hexToUint8(fragmentData[0]);
|
|
||||||
// const iv = HexMix.hexToUint8(fragmentData[1]);
|
|
||||||
|
|
||||||
// const importedKey = await window.crypto.subtle.importKey(
|
type AuthPageProps = {
|
||||||
// 'raw',
|
csrf: string,
|
||||||
// key,
|
service: string,
|
||||||
// 'AES-GCM',
|
recipient: string
|
||||||
// true,
|
}
|
||||||
// ['encrypt', 'decrypt']
|
|
||||||
// );
|
const AuthPage = (props: AuthPageProps) => {
|
||||||
|
const { service, recipient } = props;
|
||||||
|
// const [recipientInput, setRecipientInput] = useState("");
|
||||||
|
// const [serviceSelect, setServiceSelect] = useState("github");
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
|
||||||
|
// }, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.StrictMode>
|
||||||
|
<GlobalStyle />
|
||||||
|
<CenteredContainer fullscreen>
|
||||||
|
<CenteredContainer>
|
||||||
|
<Header2 style={{ margin: ".4rem" }}>Someone sent you a secret</Header2>
|
||||||
|
<Header3 small>
|
||||||
|
Please verify your identity to reveal this message.
|
||||||
|
</Header3>
|
||||||
|
<Spacer space="3rem" />
|
||||||
|
<TextAlignWrapper align="left">
|
||||||
|
<Label htmlFor="usernameEmail">Username / Email</Label>
|
||||||
|
</TextAlignWrapper>
|
||||||
|
<Input
|
||||||
|
variant="disabled-medium"
|
||||||
|
id="usernameEmail"
|
||||||
|
value={recipient}
|
||||||
|
/>
|
||||||
|
<Spacer space="3rem" />
|
||||||
|
<TextAlignWrapper align="left">
|
||||||
|
<Label htmlFor="service">Service</Label>
|
||||||
|
</TextAlignWrapper>
|
||||||
|
<Input variant="disabled-medium" id="service" value={service} />
|
||||||
|
<Spacer space="3rem" />
|
||||||
|
<a href={`https://intended.link/auth/${service}`}>
|
||||||
|
<Button variant="primary" wide onClick={() => {}}>
|
||||||
|
Verify
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</CenteredContainer>
|
||||||
|
</CenteredContainer>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthPage;
|
||||||
|
|
|
@ -3,9 +3,13 @@ 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";
|
||||||
|
|
||||||
|
|
||||||
const ForPage = () => {
|
type ForPageProps = {
|
||||||
|
csrf: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ForPage = (props: ForPageProps) => {
|
||||||
const [recipientInput, setRecipientInput] = useState("");
|
const [recipientInput, setRecipientInput] = useState("");
|
||||||
const [serviceSelect, setServiceSelect] = useState("");
|
const [serviceSelect, setServiceSelect] = useState("github");
|
||||||
|
|
||||||
const handleRecipientInputChange = (
|
const handleRecipientInputChange = (
|
||||||
e: React.ChangeEvent<HTMLInputElement>
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
@ -18,14 +22,14 @@ const ForPage = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const postContacts = async () => {
|
const postContacts = async () => {
|
||||||
const fragmentData = window.location.hash.split('.');
|
// const fragmentData = window.location.hash.split('.');
|
||||||
if (fragmentData.length <= 0) {
|
// if (fragmentData.length <= 0) {
|
||||||
alert("No key found in fragment URI");
|
// alert("No key found in fragment URI");
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const linkId = sessionStorage.getItem("link_id");
|
const linkId = sessionStorage.getItem("link_id");
|
||||||
if (linkId == null || linkId == "") {
|
if (!linkId) {
|
||||||
alert("No created link found in storage");
|
alert("No created link found in storage");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -36,10 +40,18 @@ const ForPage = () => {
|
||||||
formData.append("link_id", linkId);
|
formData.append("link_id", linkId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetch(`${window.location.origin}/just/for`, {
|
const results = await fetch(`${window.location.origin}/just/for`, {
|
||||||
|
headers: {
|
||||||
|
"X-CSRF-Token": props.csrf
|
||||||
|
},
|
||||||
body: formData,
|
body: formData,
|
||||||
method: "POST"
|
method: "POST"
|
||||||
});
|
});
|
||||||
|
if (!results.ok) {
|
||||||
|
throw new Error('Network response was not OK');
|
||||||
|
}
|
||||||
|
|
||||||
|
await results.json();
|
||||||
window.location.href = `${window.location.origin}/just/for/you`;
|
window.location.href = `${window.location.origin}/just/for/you`;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
alert(err.message);
|
alert(err.message);
|
||||||
|
@ -74,7 +86,9 @@ const ForPage = () => {
|
||||||
id="serviceSelector"
|
id="serviceSelector"
|
||||||
onChange={handleServiceChange}
|
onChange={handleServiceChange}
|
||||||
value={serviceSelect}
|
value={serviceSelect}
|
||||||
/>
|
>
|
||||||
|
<option value='github'>Github</option>
|
||||||
|
</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"}>
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { ProgressIndicator, Header2, Button, IconArrow, Label, FileInput, TextArea, CenteredContainer, Spacer, TextAlignWrapper, GlobalStyle } from '@intended/intended-ui';
|
import { 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";
|
||||||
|
|
||||||
const JustPage = (props) => {
|
type JustPageProps = {
|
||||||
|
csrf: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const JustPage = (props: JustPageProps) => {
|
||||||
const [secretInput, setSecretInput] = useState("");
|
const [secretInput, setSecretInput] = useState("");
|
||||||
const [fileInput, setFileInput] = useState<File | null>(null);
|
const [fileInput, setFileInput] = useState<File | null>(null);
|
||||||
const [fileName, setFileName] = useState("");
|
const [fileName, setFileName] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sessionStorage.clear();
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setSecretInput(e.target.value);
|
setSecretInput(e.target.value);
|
||||||
};
|
};
|
||||||
|
@ -51,18 +59,20 @@ const JustPage = (props) => {
|
||||||
const blobData = new Blob([encrypted]);
|
const blobData = new Blob([encrypted]);
|
||||||
|
|
||||||
formData.append('text_content', blobData);
|
formData.append('text_content', blobData);
|
||||||
formData.append('filetype', 'text/plain');
|
// formData.append('filetype', 'text/plain');
|
||||||
formData.append('filename', 'secret.txt');
|
// formData.append('filename', 'secret.txt');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const link: unknown = 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"
|
||||||
});
|
});
|
||||||
sessionStorage.setItem("link_id", (link as Link).id);
|
const { id: link_id } = await link.json()
|
||||||
|
|
||||||
|
sessionStorage.setItem("link_id", link_id);
|
||||||
sessionStorage.setItem("key_hex", keyHex);
|
sessionStorage.setItem("key_hex", keyHex);
|
||||||
sessionStorage.setItem("iv_hex", ivHex);
|
sessionStorage.setItem("iv_hex", ivHex);
|
||||||
window.location.href = `${window.location.origin}/just/for`;
|
window.location.href = `${window.location.origin}/just/for`;
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { Button, CenteredContainer, GlobalStyle, Header2, Header3, InputButtonWithIcon, Label, SpaceBetweenContainer, Spacer, TextAlignWrapper, TextAreaParagraph } from "@intended/intended-ui";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
// import HexMix from "../utils/hexmix";
|
||||||
|
// const fragmentData = window.location.hash.split('.');
|
||||||
|
// if (fragmentData.length <= 0) {
|
||||||
|
// alert("No key found in fragment URI");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// const key = HexMix.hexToUint8(fragmentData[0]);
|
||||||
|
// const iv = HexMix.hexToUint8(fragmentData[1]);
|
||||||
|
|
||||||
|
// const importedKey = await window.crypto.subtle.importKey(
|
||||||
|
// 'raw',
|
||||||
|
// key,
|
||||||
|
// 'AES-GCM',
|
||||||
|
// true,
|
||||||
|
// ['encrypt', 'decrypt']
|
||||||
|
// );
|
||||||
|
|
||||||
|
|
||||||
|
const RevealPage = () => {
|
||||||
|
return (
|
||||||
|
<React.StrictMode>
|
||||||
|
<GlobalStyle />
|
||||||
|
<CenteredContainer fullscreen>
|
||||||
|
<CenteredContainer>
|
||||||
|
<Header2 style={{ margin: ".4rem" }}>Someone sent you a secret</Header2>
|
||||||
|
<Header3 small>
|
||||||
|
Please verify your identity to reveal this message.
|
||||||
|
</Header3>
|
||||||
|
<Spacer space="3rem" />
|
||||||
|
|
||||||
|
<SpaceBetweenContainer>
|
||||||
|
<Label htmlFor="secretMessage">Secret message</Label>
|
||||||
|
<Label htmlFor="secretMessage">Sent 8/24/21 @ 1:27pm</Label>
|
||||||
|
</SpaceBetweenContainer>
|
||||||
|
<TextAreaParagraph id="secretMessage">
|
||||||
|
"Sup. What are you doing for lunch?"
|
||||||
|
</TextAreaParagraph>
|
||||||
|
|
||||||
|
<Spacer space="3rem" />
|
||||||
|
<TextAlignWrapper align="left">
|
||||||
|
<Label htmlFor="service">Secret File</Label>
|
||||||
|
</TextAlignWrapper>
|
||||||
|
<InputButtonWithIcon
|
||||||
|
variant="download"
|
||||||
|
id="downloadfile"
|
||||||
|
value="1780983.jpg"
|
||||||
|
onClick={() => {}}
|
||||||
|
/>
|
||||||
|
<Spacer space="3rem" />
|
||||||
|
<Button variant="secondary" wide onClick={() => {}}>
|
||||||
|
Send a secret
|
||||||
|
</Button>
|
||||||
|
</CenteredContainer>
|
||||||
|
</CenteredContainer>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RevealPage;
|
|
@ -8,11 +8,10 @@ const YouPage = () => {
|
||||||
const keyHex = sessionStorage.getItem("key_hex");
|
const keyHex = sessionStorage.getItem("key_hex");
|
||||||
const ivHex = sessionStorage.getItem("iv_hex");
|
const ivHex = sessionStorage.getItem("iv_hex");
|
||||||
|
|
||||||
`${window.location.origin}/just/for/you/${linkId}#${keyHex}.${ivHex}`
|
return `${window.location.origin}/just/for/you/${linkId}#${keyHex}.${ivHex}`;
|
||||||
return "";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [url, setUrl] = useState(calculateUrl());
|
const [url, _setUrl] = useState(calculateUrl());
|
||||||
|
|
||||||
const copyUrl = async () => {
|
const copyUrl = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -51,7 +50,7 @@ const YouPage = () => {
|
||||||
<Input
|
<Input
|
||||||
variant="disabled-light"
|
variant="disabled-light"
|
||||||
id="encodedSecret"
|
id="encodedSecret"
|
||||||
value="\4RÏÇmÄyÆFÕ¬Ð$CÑÓÃyÛ"
|
value={url}
|
||||||
/>
|
/>
|
||||||
<Spacer space="3rem" />
|
<Spacer space="3rem" />
|
||||||
<SpaceBetweenContainer>
|
<SpaceBetweenContainer>
|
||||||
|
|
|
@ -1079,6 +1079,7 @@
|
||||||
"version": "7.16.3",
|
"version": "7.16.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
||||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
|
@ -1142,9 +1143,9 @@
|
||||||
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
|
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
|
||||||
},
|
},
|
||||||
"@intended/intended-ui": {
|
"@intended/intended-ui": {
|
||||||
"version": "0.1.19",
|
"version": "0.1.21",
|
||||||
"resolved": "https://registry.npmjs.org/@intended/intended-ui/-/intended-ui-0.1.19.tgz",
|
"resolved": "https://registry.npmjs.org/@intended/intended-ui/-/intended-ui-0.1.21.tgz",
|
||||||
"integrity": "sha512-dOQZBvBm5UyKhyilTE6y/3KL1suyjbAuu+kn4b3tKhpy/Y23AglVlHLj4oi/AUgGRMtcRT/o9eySJWkNWQ9weA==",
|
"integrity": "sha512-rNyJOGLOw8iKP0AaSYSytZXFssPhlE1FbUjxEze8F4iOgXxs6iu7B9+gSIF4QJg/IrQUVu76tkevBGDk2nTQNg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"polished": "^4.1.3",
|
"polished": "^4.1.3",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
@ -1719,9 +1720,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"babel-plugin-styled-components": {
|
"babel-plugin-styled-components": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.2.tgz",
|
||||||
"integrity": "sha512-U3wmORxerYBiqcRCo6thItIosEIga3F+ph0jJPkiOZJjyhpZyUZFQV9XvrZ2CbBIihJ3rDBC/itQ+Wx3VHMauw==",
|
"integrity": "sha512-7eG5NE8rChnNTDxa6LQfynwgHTVOYYaHJbUYSlOhk8QBXIQiMBKq4gyfHBBKPrxUcVBXVJL61ihduCpCQbuNbw==",
|
||||||
"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",
|
||||||
|
@ -5896,11 +5897,21 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"polished": {
|
"polished": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/polished/-/polished-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/polished/-/polished-4.1.4.tgz",
|
||||||
"integrity": "sha512-ocPAcVBUOryJEKe0z2KLd1l9EBa1r5mSwlKpExmrLzsnIzJo4axsoU9O2BjOTkDGDT4mZ0WFE5XKTlR3nLnZOA==",
|
"integrity": "sha512-Nq5Mbza+Auo7N3sQb1QMFaQiDO+4UexWuSGR7Cjb4Sw11SZIJcrrFtiZ+L0jT9MBsUsxDboHVASbCLbE1rnECg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.14.0"
|
"@babel/runtime": "^7.16.7"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.17.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz",
|
||||||
|
"integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==",
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.13.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"posix-character-classes": {
|
"posix-character-classes": {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"watch": "webpack --mode development --watch"
|
"watch": "webpack --mode development --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@intended/intended-ui": "0.1.19",
|
"@intended/intended-ui": "0.1.21",
|
||||||
"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",
|
||||||
|
|
|
@ -33,6 +33,9 @@ config :ueberauth, Ueberauth,
|
||||||
github: {Ueberauth.Strategy.Github, [default_scope: "user:email", allow_private_emails: true]}
|
github: {Ueberauth.Strategy.Github, [default_scope: "user:email", allow_private_emails: true]}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
config :waffle,
|
||||||
|
storage: Waffle.Storage.Local
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
@ -18,7 +18,7 @@ config :entendu, Entendu.Repo,
|
||||||
config :entendu, EntenduWeb.Endpoint,
|
config :entendu, EntenduWeb.Endpoint,
|
||||||
http: [port: System.get_env("PORT", "4000")],
|
http: [port: System.get_env("PORT", "4000")],
|
||||||
https: [
|
https: [
|
||||||
port: 4001,
|
port: 443,
|
||||||
cipher_suite: :strong,
|
cipher_suite: :strong,
|
||||||
certfile: "priv/cert/selfsigned.pem",
|
certfile: "priv/cert/selfsigned.pem",
|
||||||
keyfile: "priv/cert/selfsigned_key.pem"
|
keyfile: "priv/cert/selfsigned_key.pem"
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
defmodule Entendu.Links.Link do
|
defmodule Entendu.Links.Link do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
use Waffle.Ecto.Schema
|
||||||
|
alias Entendu.EncryptedLink
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
@primary_key {:id, Ecto.UUID, autogenerate: true}
|
@primary_key {:id, Ecto.UUID, autogenerate: true}
|
||||||
|
@ -9,8 +11,8 @@ defmodule Entendu.Links.Link do
|
||||||
field :expires, :utc_datetime
|
field :expires, :utc_datetime
|
||||||
field :filename, :string
|
field :filename, :string
|
||||||
field :filetype, :string
|
field :filetype, :string
|
||||||
field :text_content, :string
|
field :text_content, EncryptedLink.Type
|
||||||
field :file_content, :string
|
field :file_content, EncryptedLink.Type
|
||||||
field :recipient, :string
|
field :recipient, :string
|
||||||
field :service, :string
|
field :service, :string
|
||||||
|
|
||||||
|
@ -25,10 +27,10 @@ defmodule Entendu.Links.Link do
|
||||||
:burn_after_reading,
|
:burn_after_reading,
|
||||||
:filename,
|
:filename,
|
||||||
:filetype,
|
:filetype,
|
||||||
:text_content,
|
|
||||||
:file_content,
|
|
||||||
:recipient,
|
:recipient,
|
||||||
:service
|
:service
|
||||||
])
|
])
|
||||||
|
|> cast_attachments(attrs, [:text_content, :file_content])
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Entendu.UserFromAuth do
|
||||||
require Jason
|
require Jason
|
||||||
|
|
||||||
alias Ueberauth.Auth
|
alias Ueberauth.Auth
|
||||||
|
alias Entendu.Links.Link
|
||||||
|
|
||||||
def find_or_create(%Auth{} = auth) do
|
def find_or_create(%Auth{} = auth) do
|
||||||
{:ok, basic_info(auth)}
|
{:ok, basic_info(auth)}
|
||||||
|
@ -24,9 +25,15 @@ defmodule Entendu.UserFromAuth do
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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), do: []
|
||||||
|
|
||||||
defp basic_info(auth) do
|
defp basic_info(auth) do
|
||||||
IO.inspect(auth)
|
IO.inspect(auth)
|
||||||
%{id: auth.uid, name: name_from_auth(auth), avatar: avatar_from_auth(auth)}
|
%{id: auth.uid, name: name_from_auth(auth), avatar: avatar_from_auth(auth), emails: emails_from_auth(auth)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp name_from_auth(auth) do
|
defp name_from_auth(auth) do
|
||||||
|
@ -44,4 +51,9 @@ defmodule Entendu.UserFromAuth do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can_access?(recipient, emails) do
|
||||||
|
emails
|
||||||
|
|> Enum.any?(&( &1["verified"] == true and &1["email"] == recipient))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,18 +23,27 @@ defmodule EntenduWeb.AuthController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
|
def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
|
||||||
case UserFromAuth.find_or_create(auth) do
|
# TODO: turn this into plug that only proceeds if current_link session var exists
|
||||||
{:ok, user} ->
|
%{ id: link_id, recipient: recipient } = get_session(conn, :current_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
|
conn
|
||||||
|> put_flash(:info, "Successfully authenticated.")
|
|
||||||
|> put_session(:current_user, user)
|
|> put_session(:current_user, user)
|
||||||
|> configure_session(renew: true)
|
|> configure_session(renew: true)
|
||||||
|> redirect(to: "/")
|
|> redirect(to: "/just/for/you/#{link_id}")
|
||||||
|
|
||||||
|
else
|
||||||
|
false ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "#{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)
|
||||||
|> redirect(to: "/")
|
|> redirect(to: "/just/for/you/#{link_id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule EntenduWeb.LinkController do
|
||||||
|
|
||||||
alias Entendu.Links
|
alias Entendu.Links
|
||||||
alias Links.Link
|
alias Links.Link
|
||||||
alias Ecto.Changeset
|
|
||||||
alias EntenduWeb.FallbackController
|
alias EntenduWeb.FallbackController
|
||||||
|
|
||||||
action_fallback(FallbackController)
|
action_fallback(FallbackController)
|
||||||
|
@ -17,23 +16,13 @@ defmodule EntenduWeb.LinkController do
|
||||||
render(conn, "just.html")
|
render(conn, "just.html")
|
||||||
end
|
end
|
||||||
|
|
||||||
defparams(
|
|
||||||
first_step(%{
|
|
||||||
burn_after_reading: [field: :boolean, default: false],
|
|
||||||
expires: :utc_datetime,
|
|
||||||
filename: :string,
|
|
||||||
filetype: :string,
|
|
||||||
text_content: :string,
|
|
||||||
file_content: :string
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
def just(conn, params) do
|
def just(conn, params) do
|
||||||
with %Changeset{valid?: true} = changeset <- first_step(params),
|
with {:ok, %Link{} = link} <- Links.create_link(params) do
|
||||||
link_params <- Params.to_map(changeset),
|
|
||||||
{:ok, %Link{} = link} <- Links.create_link(link_params) do
|
|
||||||
conn
|
conn
|
||||||
|> render("show_authorized.json", %{link: link})
|
|> render("show_authorized.json", %{link: link})
|
||||||
|
else
|
||||||
|
test ->
|
||||||
|
IO.inspect(test)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -41,18 +30,9 @@ defmodule EntenduWeb.LinkController do
|
||||||
render(conn, "for.html")
|
render(conn, "for.html")
|
||||||
end
|
end
|
||||||
|
|
||||||
defparams(
|
def for(conn, %{"link_id" => link_id, "recipient" => recipient, "service" => service}) do
|
||||||
second_step(%{
|
with %Link{} = link <- Links.get_link(link_id),
|
||||||
service: :string,
|
Links.update_link(link, %{ recipient: recipient, service: service}) do
|
||||||
recipient: :string
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
def for(conn, %{link_id: link_id} = params) do
|
|
||||||
with %Changeset{valid?: true} = changeset <- first_step(params),
|
|
||||||
link_params <- Params.to_map(changeset),
|
|
||||||
%Link{} = link <- Links.get_link(link_id),
|
|
||||||
Links.update_link(link, link_params) do
|
|
||||||
conn
|
conn
|
||||||
|> render("show_authorized.json", %{link: link})
|
|> render("show_authorized.json", %{link: link})
|
||||||
end
|
end
|
||||||
|
@ -61,4 +41,12 @@ defmodule EntenduWeb.LinkController do
|
||||||
def you_page(conn, _params) do
|
def you_page(conn, _params) do
|
||||||
render(conn, "you.html")
|
render(conn, "you.html")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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 })
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,6 +23,7 @@ defmodule EntenduWeb.Router do
|
||||||
get "/just/for", LinkController, :for_page
|
get "/just/for", LinkController, :for_page
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/auth", EntenduWeb do
|
scope "/auth", EntenduWeb do
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<%= react_component("Components.AuthPage", %{ csrf: Plug.CSRFProtection.get_csrf_token(), service: @service, recipient: @recipient, user: @current_user }) %>
|
|
@ -1 +1 @@
|
||||||
<%= react_component("Components.ForPage") %>
|
<%= react_component("Components.ForPage", %{ csrf: Plug.CSRFProtection.get_csrf_token() }) %>
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
defmodule Entendu.EncryptedLink do
|
||||||
|
use Waffle.Definition
|
||||||
|
use Waffle.Ecto.Definition
|
||||||
|
|
||||||
|
# Include ecto support (requires package waffle_ecto installed):
|
||||||
|
# use Waffle.Ecto.Definition
|
||||||
|
|
||||||
|
@versions [:original]
|
||||||
|
|
||||||
|
# To add a thumbnail version:
|
||||||
|
# @versions [:original, :thumb]
|
||||||
|
|
||||||
|
# Override the bucket on a per definition basis:
|
||||||
|
# def bucket do
|
||||||
|
# :custom_bucket_name
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Whitelist file extensions:
|
||||||
|
# def validate({file, _}) do
|
||||||
|
# file_extension = file.file_name |> Path.extname() |> String.downcase()
|
||||||
|
#
|
||||||
|
# case Enum.member?(~w(.jpg .jpeg .gif .png), file_extension) do
|
||||||
|
# true -> :ok
|
||||||
|
# false -> {:error, "invalid file type"}
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Define a thumbnail transformation:
|
||||||
|
# def transform(:thumb, _) do
|
||||||
|
# {:convert, "-strip -thumbnail 250x250^ -gravity center -extent 250x250 -format png", :png}
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override the persisted filenames:
|
||||||
|
# def filename(version, _) do
|
||||||
|
# version
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override the storage directory:
|
||||||
|
# def storage_dir(version, {file, scope}) do
|
||||||
|
# "uploads/user/avatars/#{scope.id}"
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Provide a default URL if there hasn't been a file uploaded
|
||||||
|
# def default_url(version, scope) do
|
||||||
|
# "/images/avatars/default_#{version}.png"
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Specify custom headers for s3 objects
|
||||||
|
# Available options are [:cache_control, :content_disposition,
|
||||||
|
# :content_encoding, :content_length, :content_type,
|
||||||
|
# :expect, :expires, :storage_class, :website_redirect_location]
|
||||||
|
#
|
||||||
|
# def s3_object_headers(version, {file, scope}) do
|
||||||
|
# [content_type: MIME.from_path(file.file_name)]
|
||||||
|
# end
|
||||||
|
end
|
4
mix.exs
4
mix.exs
|
@ -51,7 +51,9 @@ defmodule Entendu.MixProject do
|
||||||
{:ueberauth, "~> 0.7.0"},
|
{:ueberauth, "~> 0.7.0"},
|
||||||
{:ueberauth_github, "~> 0.8.1"},
|
{:ueberauth_github, "~> 0.8.1"},
|
||||||
{:react_phoenix, "~> 1.3"},
|
{:react_phoenix, "~> 1.3"},
|
||||||
{:params, "~> 2.2"}
|
{:params, "~> 2.2"},
|
||||||
|
{:waffle, "~> 1.1"},
|
||||||
|
{:waffle_ecto, "~> 0.0.11"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -42,4 +42,6 @@
|
||||||
"ueberauth": {:hex, :ueberauth, "0.7.0", "9c44f41798b5fa27f872561b6f7d2bb0f10f03fdd22b90f454232d7b087f4b75", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2efad9022e949834f16cc52cd935165049d81fa9e925690f91035c2e4b58d905"},
|
"ueberauth": {:hex, :ueberauth, "0.7.0", "9c44f41798b5fa27f872561b6f7d2bb0f10f03fdd22b90f454232d7b087f4b75", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2efad9022e949834f16cc52cd935165049d81fa9e925690f91035c2e4b58d905"},
|
||||||
"ueberauth_github": {:hex, :ueberauth_github, "0.8.1", "0be487b5afc29bc805fa5e31636f37c8f09d5159ef73fc08c4c7a98c9cfe2c18", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "143d6130b945ea9bdbd0ef94987f40788f1d7e8090decbfc0722773155e7a74a"},
|
"ueberauth_github": {:hex, :ueberauth_github, "0.8.1", "0be487b5afc29bc805fa5e31636f37c8f09d5159ef73fc08c4c7a98c9cfe2c18", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7.0", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "143d6130b945ea9bdbd0ef94987f40788f1d7e8090decbfc0722773155e7a74a"},
|
||||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||||
|
"waffle": {:hex, :waffle, "1.1.5", "11b8b41c9dc46a21c8e1e619e1e9048d18d166b57b33d1fada8e11fcd4e678b3", [:mix], [{:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.1", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "68e6f92b457b13c71e33cc23f7abb60446a01515dc6618b7d493d8cd466b1f39"},
|
||||||
|
"waffle_ecto": {:hex, :waffle_ecto, "0.0.11", "3d9581b3dfc83964ad968ef6bbf31132b5e6959c542a74c49e2a2245a9521048", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:waffle, "~> 1.0", [hex: :waffle, repo: "hexpm", optional: false]}], "hexpm", "626c2832ba94e20840532e609d3af70526d18ff9dfe1b352afb3fbabedb31a7e"},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue