Compare commits
2 Commits
621cbe867b
...
feature/pr
Author | SHA1 | Date | |
---|---|---|---|
3c9dd96d8b
|
|||
8330bb420e
|
9
assets/js/definitions/index.d.ts
vendored
9
assets/js/definitions/index.d.ts
vendored
@@ -1,6 +1,13 @@
|
|||||||
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 = {
|
||||||
|
@@ -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 {
|
||||||
@@ -40,6 +41,7 @@ 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>("Decrypting...");
|
||||||
|
const [messageRevealed, setMessageRevealed] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
init().catch((reason) => {
|
init().catch((reason) => {
|
||||||
@@ -47,6 +49,9 @@ const AuthPage = (props: AuthPageProps) => {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
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();
|
||||||
@@ -55,6 +60,14 @@ const AuthPage = (props: AuthPageProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
@@ -135,6 +148,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 +167,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={{ margin: ".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>
|
||||||
@@ -232,11 +282,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>
|
||||||
);
|
);
|
||||||
|
@@ -88,6 +88,9 @@ 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);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const textFormData = async (form: FormData, aesKey: AESKey) => {
|
const textFormData = async (form: FormData, aesKey: AESKey) => {
|
||||||
@@ -102,6 +105,9 @@ 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);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createKey = async (): Promise<AESKey> => {
|
const createKey = async (): Promise<AESKey> => {
|
||||||
|
@@ -1,8 +1,30 @@
|
|||||||
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 />
|
||||||
@@ -15,7 +37,11 @@ const SplashPage = () => {
|
|||||||
and secretly.
|
and secretly.
|
||||||
</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,7 +44,7 @@ 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>
|
||||||
@@ -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
|
||||||
|
onClick={() => {
|
||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
window.location.href = "/just";
|
window.location.href = "/just";
|
||||||
}}>Create Another Secret</Button>
|
}}
|
||||||
|
>
|
||||||
|
Create Another Secret
|
||||||
|
</Button>
|
||||||
</SpaceBetweenContainer>
|
</SpaceBetweenContainer>
|
||||||
</CenteredContainer>
|
</CenteredContainer>
|
||||||
</CenteredContainer>
|
</CenteredContainer>
|
||||||
|
@@ -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,3 @@
|
|||||||
<main role="main">
|
<main role="main">
|
||||||
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
|
|
||||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
</main>
|
</main>
|
||||||
|
@@ -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,6 +1,6 @@
|
|||||||
<section>
|
<section>
|
||||||
|
|
||||||
<%= react_component("Components.SplashPage") %>
|
<%= react_component("Components.SplashPage", %{ error: get_flash(@conn, :error) }) %>
|
||||||
|
|
||||||
<%= if @current_user do %>
|
<%= if @current_user do %>
|
||||||
<h2>Welcome, <%= @current_user.name %>!</h2>
|
<h2>Welcome, <%= @current_user.name %>!</h2>
|
||||||
|
Reference in New Issue
Block a user