Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions fresh.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import * as $_app from "./routes/_app.tsx";
import * as $api_create from "./routes/api/create.ts";
import * as $api_res_tailwind_css from "./routes/api/res/tailwind.css.ts";
import * as $api_secret_uuid_ from "./routes/api/secret/[uuid].ts";
import * as $create from "./routes/create.tsx";
import * as $index from "./routes/index.tsx";
import * as $secret_uuid_ from "./routes/secret/[uuid].tsx";
import * as $CreateSecret from "./islands/CreateSecret.tsx";
import * as $FetchSecret from "./islands/FetchSecret.tsx";
import * as $Log from "./islands/Log.tsx";
import * as $messages_CardMsg from "./islands/messages/CardMsg.tsx";
import * as $messages_ErrorCardMsg from "./islands/messages/ErrorCardMsg.tsx";
import type { Manifest } from "$fresh/server.ts";

const manifest = {
Expand All @@ -21,13 +24,16 @@ const manifest = {
"./routes/api/create.ts": $api_create,
"./routes/api/res/tailwind.css.ts": $api_res_tailwind_css,
"./routes/api/secret/[uuid].ts": $api_secret_uuid_,
"./routes/create.tsx": $create,
"./routes/index.tsx": $index,
"./routes/secret/[uuid].tsx": $secret_uuid_,
},
islands: {
"./islands/CreateSecret.tsx": $CreateSecret,
"./islands/FetchSecret.tsx": $FetchSecret,
"./islands/Log.tsx": $Log,
"./islands/messages/CardMsg.tsx": $messages_CardMsg,
"./islands/messages/ErrorCardMsg.tsx": $messages_ErrorCardMsg,
},
baseUrl: import.meta.url,
} satisfies Manifest;
Expand Down
11 changes: 5 additions & 6 deletions islands/CreateSecret.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,19 @@ export default function CreateSecret() {
name="hidden"
type="text"
style="display:none;"
>
</input>
/>
<textarea
rows={10}
cols={60}
name="secret"
class="w-full p-2 border-2 border-gray-500 rounded"
placeholder="Passwort, Token oder andere Information..."
/>

<ExpireForm/>
<PassphraseInput/>

<button type="submit">Anlegen</button>
<ExpireForm />
<PassphraseInput />

<button class="p-2" type="submit">Anlegen</button>
</form>
</main>
);
Expand Down
1 change: 1 addition & 0 deletions islands/FetchSecret.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default function FetchSecret(
name="fetchSecretButton"
type="submit"
disabled={fetched.value}
class="p-2"
>
Abrufen
</button>
Expand Down
77 changes: 45 additions & 32 deletions islands/Log.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,48 @@
import { lastMessage } from "../src/utils/log.ts";
import { lastMessage, RichMessage } from "../src/utils/log.ts";
import CardMsg from "./messages/CardMsg.tsx";
import ErrorCardMsg from "./messages/ErrorCardMsg.tsx";

function renderMessage(message: RichMessage | undefined) {
if (!message) {
return undefined;
}

switch (message.type) {
case "string":
return (
<CardMsg header="Error">
<span>{message.text}</span>
</CardMsg>
);
case "error":
return (
<ErrorCardMsg header="Error">
<span>{message.text}</span>
</ErrorCardMsg>
);
case "secretFetch":
return (
<CardMsg header="Secret Data">
<pre class="monospace whitespace-pre-wrap wrap-anywhere bg-gray-100 p-2 rounded-md overflow-auto max-w-[90dvw] min-w-md">
{message.secret}
</pre>
</CardMsg>
);
case "secretCreate":
return (
<CardMsg header={message.header} extraInfo={message.extraInfo}>
<a
class="text-primary-dark text-lg"
href={message.secretLink}
target="_blank"
>
{message.secretLink}
</a>
</CardMsg>
);
}
}

export default function Log() {
return (
// TODO: enable when tailwind integration is working
// <section class="max-w-full w-full overflow-x-auto md:max-w-[80%] md:w-[80%] border-solid border-2 border-gray-300 bg-gray-100 p-2">
// <pre class="wrap-break-word whitespace-pre-wrap">{lastMessage}</pre>
// </section>
<section>
<pre
style={{
// correct line breaks for really long messages (like base64 encoded certificate secrets)
whiteSpace: "pre-wrap",
overflowWrap: "anywhere",
// styles to mark this as some kind of log output
border: "solid 2px gray",
padding: "1rem",
backgroundColor: "lightgray",
// correct sizing
maxWidth: "90%",
width: "90%",
overflow: "auto",
maxHeight: "50vh",
margin: "0 auto",
// hide the log box if there is no message
display: lastMessage.value && lastMessage.value.length > 0
? "block"
: "none",
}}
>
{lastMessage}
</pre>
</section>
);
return <div>{renderMessage(lastMessage.value)}</div>;
}
17 changes: 17 additions & 0 deletions islands/messages/CardMsg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { JSX } from "preact/jsx-runtime";

export default function CardMsg(
props: {
header?: string;
extraInfo?: string;
children: JSX.Element | undefined;
},
) {
return (
<div class="flex flex-col items-start justify-center gap-2 shadow-lg rounded-lg p-4 max-w-2xl border-[1px] border-gray-200">
{props.header && <h3 class="text-lg font-bold">{props.header}</h3>}
{props.children}
{props.extraInfo && <p>{props.extraInfo}</p>}
</div>
);
}
19 changes: 19 additions & 0 deletions islands/messages/ErrorCardMsg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { JSX } from "preact/jsx-runtime";

export default function CardMsg(
props: {
header?: string;
extraInfo?: string;
children: JSX.Element | undefined;
},
) {
return (
<div class="flex flex-col items-start justify-center gap-2 shadow-lg rounded-lg p-4 max-w-2xl border-[1px] border-red-200 shadow-red-500/50">
{props.header && (
<h3 class="text-lg font-bold text-red-600">{props.header}</h3>
)}
{props.children}
{props.extraInfo && <p>{props.extraInfo}</p>}
</div>
);
}
11 changes: 11 additions & 0 deletions routes/create.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import CreateSecret from "../islands/CreateSecret.tsx";
import Log from "../islands/Log.tsx";

export default function CreateSecretPage() {
return (
<main class="flex flex-col items-center justify-center">
<CreateSecret />
<Log />
</main>
);
}
16 changes: 7 additions & 9 deletions routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import CreateSecret from "../islands/CreateSecret.tsx";
import Log from "../islands/Log.tsx";
import { redirectTo } from "../utils/routing.ts";

export default function Home() {
return (
<main class="flex flex-col items-center justify-center">
<CreateSecret />
<Log />
</main>
);
/**
* @bjesuiter: "async" is required here, otherwise the redirectTo will not work for some reason
* (async pages are handled slightly differently in fresh than sync pages)
*/
export default async function Home() {
return redirectTo("/create");
}
28 changes: 22 additions & 6 deletions src/formHandler/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ export default async function onSubmit(
const secret = formData.get("secret") as string;

if (!secret || secret.length < 1) {
lastMessage.value = "Bitte geben Sie ein Secret ein!";
lastMessage.value = {
type: "string",
text: "Bitte geben Sie ein Secret ein!",
};
return;
}

Expand All @@ -32,7 +35,10 @@ export default async function onSubmit(

if (formData.get("usePassphrase") == "on") {
if (!encryptionKey) {
lastMessage.value = "Bitte geben Sie ein Passphrase ein!";
lastMessage.value = {
type: "string",
text: "Bitte geben Sie ein Passphrase ein!",
};
return;
}
} else {
Expand All @@ -50,7 +56,10 @@ export default async function onSubmit(
secret,
);
if (encryptedData == undefined) {
lastMessage.value = "Fehler beim Verschlüsseln des Secrets!";
lastMessage.value = {
type: "string",
text: "Fehler beim Verschlüsseln des Secrets!",
};
return;
}
encryptedSecret = encryptedData.encryptedSecret;
Expand All @@ -72,15 +81,22 @@ export default async function onSubmit(
body: JSON.stringify({ encryptedSecret, expireIn, hash, allowedViews, iv }),
});
const data = await response.json().catch(() =>
lastMessage.value = "Fehler beim Erstellen des Secrets!"
lastMessage.value = {
type: "string",
text: "Fehler beim Erstellen des Secrets!",
}
);
if (data && data.link) {
const secretUrl = `${location.origin}/secret/${data.link}${
generatedKey ? `?encryptionKey=${btoa(encryptionKey)}` : ""
}`;
// copy url to clipboard
navigator.clipboard.writeText(secretUrl);
lastMessage.value =
`Link erstellt: \n${secretUrl} \nLink wurde in die Zwischenablage kopiert!`;
lastMessage.value = {
type: "secretCreate",
secretLink: secretUrl,
header: `Link erstellt`,
extraInfo: `Link wurde in die Zwischenablage kopiert!`,
};
}
}
34 changes: 25 additions & 9 deletions src/formHandler/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,29 @@ export default async function onSubmit(
});

if (response.status >= 400) {
lastMessage.value =
"Fehler beim Abrufen des Secrets! Entweder ist der Link abgelaufen oder ungültig.";
lastMessage.value = {
type: "error",
text:
"Fehler beim Abrufen des Secrets! Entweder ist der Link abgelaufen oder ungültig.",
};
return;
}

const data = await response.json().catch(() =>
lastMessage.value =
"Fehler beim Abrufen des Secrets! Entweder ist der Link abgelaufen oder ungültig."
lastMessage.value = {
type: "error",
text:
"Fehler beim Abrufen des Secrets! Entweder ist der Link abgelaufen oder ungültig.",
}
);
// turn json object iv into Uint8Array
data.iv = new Uint8Array(Object.values(data.iv));
if (data == undefined || data.secret == undefined) {
lastMessage.value =
"Fehler beim Abrufen des Secrets! Entweder ist der Link abgelaufen oder ungültig.";
lastMessage.value = {
type: "error",
text:
"Fehler beim Abrufen des Secrets! Entweder ist der Link abgelaufen oder ungültig.",
};
return;
}

Expand All @@ -56,9 +65,16 @@ export default async function onSubmit(

if (hash !== data.hash) {
console.log(`${hash} !== ${data.hash}`);
lastMessage.value =
`Verifikation des Secrets fehlgeschlagen. Falsche Passphrase. Verbleibende versuche: ${data.viewsLeft}`;
lastMessage.value = {
type: "error",
text:
`Verifikation des Secrets fehlgeschlagen. Falsche Passphrase. Verbleibende versuche: ${data.viewsLeft}`,
};
return;
}
lastMessage.value = `Secret Data: \n${data.secret}`;

lastMessage.value = {
type: "secretFetch",
secret: data.secret,
};
}
20 changes: 16 additions & 4 deletions src/utils/crypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export async function encrypt(key: string, secret: string) {
),
);
if (!key || key.length < 1) {
lastMessage.value = "Bitte geben Sie ein Passphrase ein!";
lastMessage.value = {
type: "string",
text: "Bitte geben Sie eine Passphrase ein!",
};
return;
}
const iv = crypto.getRandomValues(new Uint8Array(12));
Expand Down Expand Up @@ -38,7 +41,10 @@ export async function decrypt(
key: string,
) {
if (!key || key.length < 1) {
lastMessage.value = "Bitte geben Sie ein Passphrase ein!";
lastMessage.value = {
type: "string",
text: "Bitte geben Sie eine Passphrase ein!",
};
return;
}

Expand All @@ -58,7 +64,10 @@ export async function decrypt(
["decrypt"],
).catch((e) => {
console.error(e);
lastMessage.value = "Fehler beim Entschlüsseln des Secrets!";
lastMessage.value = {
type: "string",
text: "Fehler beim Entschlüsseln des Secrets!",
};
return;
});

Expand All @@ -82,7 +91,10 @@ export async function decrypt(
secret,
).catch((e: Error) => {
console.error(e);
lastMessage.value = "Fehler beim Entschlüsseln des Secrets!";
lastMessage.value = {
type: "string",
text: "Fehler beim Entschlüsseln des Secrets!",
};
return;
});

Expand Down
4 changes: 3 additions & 1 deletion src/utils/cssCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export async function computeTailwindStyles() {
@import "tailwindcss";

@theme {
--color-primary: #60c0ff;
--color-primary: oklch(0.7751 0.127965 240.0314);
--color-primary-dark: oklch(0.5327 0.1152 242.02);

}

`;
Expand Down
Loading