diff --git a/migrations/0002_naive_revanche.sql b/migrations/0002_naive_revanche.sql new file mode 100644 index 0000000..fb06ba7 --- /dev/null +++ b/migrations/0002_naive_revanche.sql @@ -0,0 +1,2 @@ +ALTER TABLE `cv` ADD `sha256` text;--> statement-breakpoint +ALTER TABLE `cv` ADD `pgp_signature` text; \ No newline at end of file diff --git a/migrations/meta/0002_snapshot.json b/migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..915a00b --- /dev/null +++ b/migrations/meta/0002_snapshot.json @@ -0,0 +1,92 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "5f7163dc-e8ca-4bd2-ab41-6b244d02aaf7", + "prevId": "a68f3965-0dd6-46c6-af01-4f96061e8b11", + "tables": { + "cv": { + "name": "cv", + "columns": { + "uuid": { + "name": "uuid", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created": { + "name": "created", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "purpose": { + "name": "purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tooling": { + "name": "tooling", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'active'" + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pgp_signature": { + "name": "pgp_signature", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 9cf610b..ec30978 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1735735966188, "tag": "0001_confused_wendell_rand", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1735887287143, + "tag": "0002_naive_revanche", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/components/verification/AddCVVerification.astro b/src/components/verification/AddCVVerification.astro index eb23c58..de7b818 100644 --- a/src/components/verification/AddCVVerification.astro +++ b/src/components/verification/AddCVVerification.astro @@ -1,6 +1,5 @@
@@ -69,7 +68,7 @@ focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:outline-none placeholder:text-gray-400 dark:placeholder:text-gray-500" - >Application + >Job Application diff --git a/src/components/verification/AddHashAndSig.astro b/src/components/verification/AddHashAndSig.astro new file mode 100644 index 0000000..2e7307c --- /dev/null +++ b/src/components/verification/AddHashAndSig.astro @@ -0,0 +1,101 @@ +--- +import { Icon } from "astro-icon/components"; + +const { uid } = Astro.props; +--- + +
+

+ Generated UID +

+
+
+ + {uid} + + +
+
+

+ Embed this uid in the CV typst document. After that, calculate the sha256sum + and enter it below along with your GPG signature +

+ + +
+ + +
+
+ + +
+ + +
+ + diff --git a/src/components/verification/DataTable.astro b/src/components/verification/DataTable.astro index d4714ae..58972cb 100644 --- a/src/components/verification/DataTable.astro +++ b/src/components/verification/DataTable.astro @@ -1,6 +1,7 @@ --- import type { InferSelectModel } from "drizzle-orm"; import type { cvTable } from "../../db/schema"; +import { Icon } from "astro-icon/components"; interface Props { cv: InferSelectModel; @@ -9,7 +10,7 @@ interface Props { const { cv } = Astro.props; --- -
+
  • +
  • + + SHA256: + + + {cv.sha256} + +
+ +
+
{cv.pgp_signature}
+
+ +
+
+ +

+ + + Verification Instructions +

+ + + + + +
+ +
+
+

+ 1. Verify SHA256 Hash +

+

+ Compare the output of following command with the SHA256 hash shown + above. +

+
shasum -a 256 cv.pdf
+
+ +
+

+ 2. Verify PGP Signature +

+ +
    +
  1. + Download Alexander Daichendt's public pgp key from here +
  2. +
  3. + Import the key into your keyring with +
    gpg --import ~/Downloads/pub.key
    + +
  4. + Download the above PGP signature by clicking +
  5. +
  6. Run the following command:
  7. + +
    gpg --verify signature.asc cv.pdf
    +

    + If the verification is successful, GPG will indicate so. +

    gpg: Good signature from "Alexander Daichendt <alexander@daichendt.one>" [ultimate]
    +

    +
+
+
+
+
+ + + + diff --git a/src/components/verification/Revoked.astro b/src/components/verification/Revoked.astro index 6f1fa73..339e00c 100644 --- a/src/components/verification/Revoked.astro +++ b/src/components/verification/Revoked.astro @@ -13,7 +13,9 @@ d="M6 18L18 6M6 6l12 12">
-

Revoked CV

+

+ Revoked CV +

This CV has been revoked and is no longer valid. diff --git a/src/components/verification/Verified.astro b/src/components/verification/Verified.astro index 3b17d08..85da61c 100644 --- a/src/components/verification/Verified.astro +++ b/src/components/verification/Verified.astro @@ -24,7 +24,7 @@ const { cv } = Astro.props; d="M5 13l4 4L19 7"> -

+

Verified CV

diff --git a/src/db/schema.ts b/src/db/schema.ts index 61c4e4c..25b9a66 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -12,4 +12,6 @@ export const cvTable = sqliteTable("cv", { status: text("status", { enum: ["active", "revoked"], }).default("active"), + sha256: text("sha256"), + pgp_signature: text("pgp_signature"), }); diff --git a/src/pages/admin/api/verifications/index.ts b/src/pages/admin/api/verifications/index.ts deleted file mode 100644 index fffa951..0000000 --- a/src/pages/admin/api/verifications/index.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { APIContext } from "astro"; -import { drizzle } from "drizzle-orm/d1"; -import { cvTable } from "../../../../db/schema"; -import { nanoid } from "nanoid"; -import { eq } from "drizzle-orm"; - -export const prerender = false; - -export async function POST(context: APIContext) { - const runtime = context.locals.runtime; - const d1 = runtime.env.DB; - const db = drizzle(d1, { schema: { cvTable } }); - - // parse form data - const formData = await context.request.formData(); - const company_name = formData.get("company_name") as string; - const author = formData.get("author") as string; - const purpose = formData.get("purpose") as string; - const tooling = formData.get("tooling") as string; - const created = new Date(); - - let uuid; - let existing; - let maxAttempts = 10; - - do { - uuid = nanoid(8); - - // check if uuid already exists - existing = await db - .select() - .from(cvTable) - .where(eq(cvTable.uuid, uuid)) - .execute(); - - maxAttempts--; - - // Safety check to prevent infinite loops - if (maxAttempts <= 0) { - return new Response( - JSON.stringify({ - success: false, - error: "Failed to generate unique UUID after multiple attempts", - }), - ); - } - } while (existing.length > 0); - - try { - await db - .insert(cvTable) - .values({ company_name, author, purpose, tooling, created, uuid }) - .execute(); - } catch (error) { - console.error(error); - return new Response(JSON.stringify({ success: false, error })); - } - - return new Response(JSON.stringify({ success: true, uuid })); -} diff --git a/src/pages/admin/index.astro b/src/pages/admin/index.astro index 6b59b1d..e2aa335 100644 --- a/src/pages/admin/index.astro +++ b/src/pages/admin/index.astro @@ -1,10 +1,104 @@ --- import BaseLayout from "../../layouts/BaseLayout.astro"; import AddCVVerification from "../../components/verification/AddCVVerification.astro"; +import { cvTable } from "../../db/schema"; +import { eq } from "drizzle-orm"; +import { drizzle } from "drizzle-orm/d1"; +import { nanoid } from "nanoid"; +import AddHashAndSig from "../../components/verification/AddHashAndSig.astro"; + +export const prerender = false; + +let uid = undefined; + +if (Astro.request.method === "POST") { + try { + const data = await Astro.request.formData(); + const company_name = data.get("company_name")?.toString(); + const author = data.get("author")?.toString(); + const purpose = data.get("purpose")?.toString(); + const tooling = data.get("tooling")?.toString(); + + const _uid = data.get("uid")?.toString(); + const sha256 = data.get("sha256")?.toString(); + const pgp_signature = data.get("pgp_signature")?.toString(); + + const d1 = Astro.locals.runtime.env.DB; + const db = drizzle(d1, { schema: { cvTable } }); + + if (company_name && author && purpose && tooling) { + const created = new Date(); + + let uuid: string; + let existing; + let maxAttempts = 10; + + do { + uuid = nanoid(8); + + // check if uuid already exists + existing = await db + .select() + .from(cvTable) + .where(eq(cvTable.uuid, uuid)) + .execute(); + + maxAttempts--; + + // Safety check to prevent infinite loops + if (maxAttempts <= 0) { + return new Response( + JSON.stringify({ + success: false, + error: "Failed to generate unique UUID after multiple attempts", + }), + ); + } + } while (existing.length > 0); + + try { + await db + .insert(cvTable) + .values({ + company_name, + author, + purpose, + tooling, + created, + uuid, + }) + .execute(); + } catch (error) { + console.error(error); + return new Response(JSON.stringify({ success: false, error })); + } + + uid = uuid; + } else if (_uid && sha256) { + // Update the record with the sha256sum + try { + console.log(pgp_signature); + await db + .update(cvTable) + .set({ sha256, pgp_signature }) + .where(eq(cvTable.uuid, _uid)) + .execute(); + } catch (error) { + console.error(error); + return new Response(JSON.stringify({ success: false, error })); + } + } else { + console.error("Missing data"); + } + } catch (error) { + console.error(error); + } +} ---

Admin

+ {uid && }
diff --git a/src/pages/cv/index.astro b/src/pages/cv/index.astro index 7d74945..53ce9ba 100644 --- a/src/pages/cv/index.astro +++ b/src/pages/cv/index.astro @@ -25,12 +25,12 @@ const cv = id cv.length === 0 ? ( ) : cv[0].status !== "active" ? ( -
+
) : ( -
+