From 46a417f41175c45c93365b533e6edd79b6bceda0 Mon Sep 17 00:00:00 2001 From: fedyna-k Date: Thu, 16 Jan 2025 23:13:58 +0100 Subject: [PATCH] Added auto database creation based on sql scripts and jwt key cache --- .gitignore | 2 ++ databases/ensure.ts | 28 ++++++++++++++++++++++++++++ databases/init/mobility.sql | 0 databases/init/notes.sql | 0 deno.json | 7 ++++--- fresh.config.ts | 2 ++ routes/_middleware.ts | 27 ++++++++++++++++++++++++--- routes/login.tsx | 11 ++++++++++- 8 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 databases/ensure.ts create mode 100644 databases/init/mobility.sql create mode 100644 databases/init/notes.sql diff --git a/.gitignore b/.gitignore index 5b4bdef..9d74bbb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ _fresh/ # npm dependencies node_modules/ + +databases/data/ \ No newline at end of file diff --git a/databases/ensure.ts b/databases/ensure.ts new file mode 100644 index 0000000..993fba3 --- /dev/null +++ b/databases/ensure.ts @@ -0,0 +1,28 @@ +import { Database } from "@db/sqlite"; + +export default async function ensureDatabases() { + for await (const file of Deno.readDir("databases/init")) { + if (!file.isFile) { + console.warn(`[WARN] Path ${file.name} is not a file.`); + continue; + } + + const databaseName = file.name.substring(0, file.name.length - 4); + const databasePath = `databases/data/${databaseName}.db`; + + try { + await Deno.stat(databasePath); + } catch (error) { + if (!(error instanceof Deno.errors.NotFound)) { + throw error; + } + + const sqlInitCode = await Deno.readTextFile( + `databases/init/${file.name}`, + ); + const database = new Database(databasePath); + database.run(sqlInitCode); + database.close(); + } + } +} diff --git a/databases/init/mobility.sql b/databases/init/mobility.sql new file mode 100644 index 0000000..e69de29 diff --git a/databases/init/notes.sql b/databases/init/notes.sql new file mode 100644 index 0000000..e69de29 diff --git a/deno.json b/deno.json index adc5a39..9061960 100644 --- a/deno.json +++ b/deno.json @@ -4,9 +4,9 @@ "check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx", "cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -", "manifest": "deno task cli manifest $(pwd)", - "start": "deno run -A --watch=static/,routes/ dev.ts", - "build": "deno run -A dev.ts build", - "preview": "deno run -A main.ts", + "start": "deno run -A --unstable-ffi --watch=static/,routes/ dev.ts", + "build": "deno run -A --unstable-ffi dev.ts build", + "preview": "deno run -A --unstable-ffi main.ts", "update": "deno run -A -r https://fresh.deno.dev/update ." }, "lint": { @@ -22,6 +22,7 @@ ], "imports": { "$fresh/": "https://deno.land/x/fresh@1.7.3/", + "@db/sqlite": "jsr:@db/sqlite@^0.12.0", "@melvdouc/xml-parser": "jsr:@melvdouc/xml-parser@^0.1.1", "@popov/jwt": "jsr:@popov/jwt@^1.0.1", "preact": "https://esm.sh/preact@10.22.0", diff --git a/fresh.config.ts b/fresh.config.ts index e6c4033..5881650 100644 --- a/fresh.config.ts +++ b/fresh.config.ts @@ -1,5 +1,7 @@ import { defineConfig } from "$fresh/server.ts"; +import ensureDatabases from "$root/databases/ensure.ts"; +await ensureDatabases(); export default defineConfig({ server: { cert: await Deno.readTextFile("certs/cert.pem"), diff --git a/routes/_middleware.ts b/routes/_middleware.ts index 7b52700..281a9c0 100644 --- a/routes/_middleware.ts +++ b/routes/_middleware.ts @@ -1,6 +1,7 @@ import { FreshContext } from "$fresh/server.ts"; import { getCookies } from "$std/http/cookie.ts"; -import { isJwtValid } from "@popov/jwt"; +import { getJwtPayload, isJwtValid } from "@popov/jwt"; +import { LoginJWT } from "$root/routes/login.tsx"; const PUBLIC_ROUTES = [ "/", @@ -11,6 +12,8 @@ const PUBLIC_ROUTES = [ "/contact", ]; +const jwtKeyCache: Record = {}; + export interface State { isAuthenticated: boolean; } @@ -19,15 +22,33 @@ function isRoutePublic(route: string) { return PUBLIC_ROUTES.includes(route) || route.match(/\..+$/); } +export function getKey(user: string): string { + if (!jwtKeyCache[user]) { + const keyBuffer = new Uint8Array(32); + crypto.getRandomValues(keyBuffer); + jwtKeyCache[user] = new TextDecoder().decode(keyBuffer); + } + + return jwtKeyCache[user]; +} + export const handler = [ async function checkAuthentication( request: Request, context: FreshContext, ) { const cookies = getCookies(request.headers); + if (!cookies["sessionToken"]) { + context.state.isAuthenticated = false; + return await context.next(); + } + + const content = getJwtPayload(cookies["sessionToken"]) as LoginJWT; + const key = getKey(content.user.uid as string); + context.state.isAuthenticated = await isJwtValid( - cookies["sessionToken"] ?? "", - "NEED TO CHANGE THIS KEY FURTHER IN DEV", + cookies["sessionToken"], + key, ); return await context.next(); diff --git a/routes/login.tsx b/routes/login.tsx index 77a569e..284fc5e 100644 --- a/routes/login.tsx +++ b/routes/login.tsx @@ -7,6 +7,7 @@ import { } from "@melvdouc/xml-parser"; import { createJwt } from "@popov/jwt"; import { setCookie } from "$std/http/cookie.ts"; +import { getKey } from "$root/routes/_middleware.ts"; const SERVICE = "https://localhost/login"; const CAS = "https://ident.univ-amu.fr/cas"; @@ -23,6 +24,14 @@ interface CasResponse extends RegularTagNode { children: [TextNode, CasGroupNode]; } +export interface LoginJWT { + iss: "PolyMPR"; + iat: number; + exp: number; + aud: "PolyMPR"; + user: Record; +} + function getTag(tag: CasTagNode): [string, string] { return [ tag.tagName.replace("cas:", ""), @@ -55,7 +64,7 @@ function createUserJWT(casResponse: CasResponse): Promise { user: fullUserInfos, }; - const key = "NEED TO CHANGE THIS KEY FURTHER IN DEV"; + const key = getKey(fullUserInfos.uid as string); return createJwt(payload, key); }