Skip to content

Commit

Permalink
Merge pull request #248 from huilensolis/238-featclient-remove-keys-f…
Browse files Browse the repository at this point in the history
…rom-database-when-user-signs-out

238 feat(client) remove keys from database when user signs out
  • Loading branch information
huilensolis authored Dec 18, 2024
2 parents 555cd0e + 9a350cd commit 9b46c73
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 76 deletions.
10 changes: 9 additions & 1 deletion apps/api/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@
bun run setup
```

## start development server
## start development server (this will also run `setup`)

```bash
bun run dev
```

# troubleshooting
```bash
error: Connection terminated unexpectedly
```
this error is usual, but dont worry, its not an issue. it means that the postgres databse is not ready to accept requests yet. just wait until it is ready

---
99 changes: 50 additions & 49 deletions apps/api/scripts/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,78 @@ import { $ } from "bun";
import { Pool } from "pg";

async function startDocker() {
const result = await $`docker compose up -d`;
const result = await $`docker compose up -d`;

if (result.exitCode !== 0)
throw new Error("error starting docker containers, volumes and networks");
if (result.exitCode !== 0)
throw new Error("error starting docker containers, volumes and networks");
}

async function awaitForDocker() {
console.log("testing database connection...");

const pool = new Pool({
host: "127.0.0.1",
port: 5432,
user: "memoir_user",
password: "memoir_password",
database: "memoir_db",
});

for (let i = 0; i <= 10; i++) {
try {
await pool.connect();
console.log("connected succesfully to database!");

break;
} catch (error) {
console.log("database connection is not ready yet, retrying in 5s");
console.log(`attempt ${i} of 10`);

await Bun.sleep(5000);
if (i >= 10) throw new Error(JSON.stringify(error));
}
}
console.log("testing database connection...");

const pool = new Pool({
host: "127.0.0.1",
port: 5432,
user: "memoir_user",
password: "memoir_password",
database: "memoir_db",
});

for (let i = 0; i <= 10; i++) {
try {
await pool.connect();
console.log("connected succesfully to database!");

break;
} catch (error) {
console.log("failed to connect to databse with error: ", error);
console.log("database connection is not ready yet, retrying in 5s");
console.log(`attempt ${i} of 10`);

await Bun.sleep(5000);
if (i >= 10) throw new Error(JSON.stringify(error));
}
}
}

async function generateMigration() {
console.log("generating migrations schemas...");
console.log("generating migrations schemas...");

try {
const result = await $`drizzle-kit generate`;
try {
const result = await $`drizzle-kit generate`;

if (result.exitCode !== 0)
throw new Error("error generating migration schemas");
if (result.exitCode !== 0)
throw new Error("error generating migration schemas");

return Promise.resolve("");
} catch (error) {
return Promise.reject({
message: "error generating migration schemas",
error,
});
}
return Promise.resolve("");
} catch (error) {
return Promise.reject({
message: "error generating migration schemas",
error,
});
}
}

async function runMigration() {
console.log("running migration");
console.log("running migration");

const result = await $`bun ./scripts/migration.ts`;
const result = await $`bun ./scripts/migration.ts`;

if (result.exitCode !== 0) {
throw new Error("error running migrations");
}
if (result.exitCode !== 0) {
throw new Error("error running migrations");
}
}

async function setup() {
await startDocker();
await startDocker();

await awaitForDocker();
await awaitForDocker();

await generateMigration();
await generateMigration();

await runMigration();
await runMigration();

console.log(` /| __
console.log(` /| __
* + / | ,-~ / +
. Y :| // / . *
. | jj /( .^ *
Expand Down
9 changes: 6 additions & 3 deletions apps/client/src/app/app/(components)/check-for-keys/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@

import { CryptographyCustomApi } from "@/models/cryptography";
import { ClientRoutingService } from "@/models/routing/client";
import { useRouter } from "next/navigation";
import { usePathname, useRouter } from "next/navigation";
import { useEffect } from "react";

export function CheckForClientKeys() {
const router = useRouter();

const currentPath = usePathname();

useEffect(() => {
async function checkKeys() {
const cryptoApi = new CryptographyCustomApi();
const doesClientHaveCRyptoKey =
await cryptoApi.doesClientHaveAStroredKey();

if (!doesClientHaveCRyptoKey) {
router.push(ClientRoutingService.app.keys.generate);
if (!currentPath.startsWith(ClientRoutingService.app.keys.home))
router.push(ClientRoutingService.app.keys.home);
}
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
checkKeys();
}, []);
}, [currentPath]);

return <></>;
}
54 changes: 33 additions & 21 deletions apps/client/src/app/app/key/gen/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { Button } from "@/components/ui/button";
import { CryptographyCustomApi } from "@/models/cryptography";
import { FileDown, Key } from "lucide-react";
import { FileDown, Key, ShieldCheck } from "lucide-react";
import { useEffect, useState } from "react";

export default function GenKeyPage() {
Expand Down Expand Up @@ -65,35 +65,47 @@ export default function GenKeyPage() {
return (
<div className="h-full min-h-screen w-full flex items-center justify-center flex-col">
<article className="prose prose-neutral mb-10 prose-strong:font-bold">
<h1>Encrypt your data!</h1>
<h1>Generate an encryption key</h1>
<p>
Secure your data with encryption. Genereate a key, wich will server
for encrypting & decrypting your documents.{" "}
Secure your data with encryption. Generete a key, wich will server for
encrypting & decrypting your documents.{" "}
</p>
<p>
<strong>
Do not lose your key, as it will be necessary to authenticate you in
the future{" "}
</strong>
. Save it in a secure place, download it as a file, write it dawn in
paper, save it to your password-manager or save wherever you wish.
</p>{" "}
<p>
<strong>
{" "}
When logging in again, you will be asked for your key to decrypt
your documents; if you lose your key, you wont be able to read old
documents anymore. Not even administrators can access encrypted
documents{" "}
</strong>
.
</p>
<strong>
Do not lose your key, as it will be necessary to authenticate you in
the future. Save it in a secure place, download it as a file, write it
dawn in paper, save it to your password-manager or whatever you find
useful.
</strong>
<strong>
If you loose your key, in the future, when youre asked for the key,
you will lose acccess to your documents.
</strong>
</article>
{key && (
<div className="bg-neutral-200 rounded-md p-3 mb-2">
<p>{key}</p>
</div>
)}
<footer className="flex gap-2">
<Button
onClick={generateKey}
loading={isLoading}
disabled={isLoading || Boolean(key)}
className="flex gap-2"
className={`flex gap-2 disabled:opacity-100 ${key && "bg-purple-700"}`}
>
generate key <Key className="w-5 h-5" />
{key ? (
<>
key generated successfuly <ShieldCheck className="w-5 h-5" />{" "}
</>
) : (
<>
{" "}
generate key <Key className="w-5 h-5" />{" "}
</>
)}
</Button>
{key && (
<Button onClick={downloadKeyInFile} className="flex gap-2">
Expand Down
102 changes: 102 additions & 0 deletions apps/client/src/app/app/key/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"use client";

import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { CryptographyCustomApi } from "@/models/cryptography";
import { ClientRoutingService } from "@/models/routing/client";
import { ArrowUpRight, FileDown, ShieldCheck } from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";

export default function KeyPage() {
const [key, setKey] = useState<string | null>(null);

const [loading, setLoading] = useState<boolean>(true);

useEffect(() => {
async function getCurrentKey() {
const keyService = new CryptographyCustomApi();

try {
const { base64key } = await keyService.getBase64Key();

setKey(base64key);
} catch (error) {
setKey(null);
} finally {
setLoading(false);
}
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
getCurrentKey();
}, []);

function downloadKeyInFile() {
if (!key) return;

const fileContent = key;
const blob = new Blob([fileContent], { type: "text/plain" });

const link = document.createElement("a");
link.href = URL.createObjectURL(blob);

link.download = "memoir-encryption-key.txt";

link.click();
URL.revokeObjectURL(link.href);
}

return (
<div className="h-full min-h-screen w-full flex items-center justify-center flex-col">
<article className="prose prose-neutral mb-10 prose-strong:font-bold">
<h1>Encrypt your data!</h1>
<p>
Secure your data with encryption. Generete or input a key for
encrypting & decrypting your documents.{" "}
</p>
{!loading && !key && (
<>
<p>First time using the site? generate a key</p>
<p>Already have a key? input a key</p>
</>
)}
</article>
<footer className="flex flex-col gap-8 items-center">
<div className="flex gap-8 justify-center">
{loading ? (
<Spinner />
) : key ? (
<>
<Button
disabled={true}
className={`flex gap-2 disabled:opacity-100 bg-purple-700`}
>
key generated successfuly <ShieldCheck className="w-5 h-5" />
</Button>
<Button onClick={downloadKeyInFile} className="flex gap-2 w-max">
Download key <FileDown className="w-5 h-5" />
</Button>
</>
) : (
<>
<Link
href={ClientRoutingService.app.keys.generate}
className="flex justify-center items-center underline"
>
Generate key{" "}
<ArrowUpRight strokeWidth={1} className="w-5 h-5" />
</Link>
<Link
href={ClientRoutingService.app.keys.input}
className="flex justify-center items-center underline"
>
Input key <ArrowUpRight strokeWidth={1} className="w-5 h-5" />
</Link>
</>
)}
</div>
</footer>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ export function ProfileCard() {
<li>
<NavLink
icon={Key}
href={ClientRoutingService.app.keys.generate}
title="Encryption key"
href={ClientRoutingService.app.keys.home}
title="Manage encryption key"
/>
</li>
<li>
Expand Down
Loading

0 comments on commit 9b46c73

Please sign in to comment.