diff --git a/.vscode/settings.json b/.vscode/settings.json index 002a080..c89c3aa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,8 @@ { - "deno.enablePaths": [ - "./" - ], - "deno.enable": true, - "deno.unstable": true, - "editor.inlayHints.enabled": "off" -} \ No newline at end of file + "deno.enablePaths": [ + "./" + ], + "deno.enable": true, + "deno.unstable": true, + "editor.inlayHints.enabled": "off" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3988d7 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Twifeur + +Feur + +## Feur + +Feur feur. diff --git a/api/login.ts b/api/login.ts index 5d68c0a..8cfbae6 100644 --- a/api/login.ts +++ b/api/login.ts @@ -1,7 +1,16 @@ import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts"; -import { Env } from "https://deno.land/x/hono@v4.3.10/mod.ts"; import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts"; +import { FeurEnv } from "../main.ts"; +import { login, set_user } from "../lib/auth.ts"; -export function login_route(context: Context) { - return context.text("E"); +export async function login_route(context: Context) { + const data = await context.req.formData(); + let username = data.get("login"), pass = data.get("password"); + if (username === null || pass === null) return context.redirect("/login"); + username = username.toString(), pass = pass.toString(); + const logged = await login(username, pass); + if (logged === null) return context.redirect("/login"); + console.log("Logged in", { username }); + set_user(context, logged); + return context.redirect("/user"); } diff --git a/api/logout.ts b/api/logout.ts new file mode 100644 index 0000000..eeabd70 --- /dev/null +++ b/api/logout.ts @@ -0,0 +1,9 @@ +import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts"; +import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts"; +import { FeurEnv } from "../main.ts"; +import { set_user } from "../lib/auth.ts"; + +export function logout_route(context: Context) { + set_user(context, null); + return context.redirect("/"); +} diff --git a/deno.lock b/deno.lock index 0162744..3f7e4a4 100644 --- a/deno.lock +++ b/deno.lock @@ -2,21 +2,42 @@ "version": "3", "packages": { "specifiers": { - "npm:@types/node": "npm:@types/node@18.16.19" + "npm:@types/node": "npm:@types/node@18.16.19", + "npm:hono@^4.0.0": "npm:hono@4.4.0", + "npm:iron-webcrypto@0.10.1": "npm:iron-webcrypto@0.10.1" }, "npm": { "@types/node@18.16.19": { "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", "dependencies": {} + }, + "hono@4.4.0": { + "integrity": "sha512-Bb2GHk8jmlLIuxc3U+7UBGOoA5lByJTAFnRdH2N2fqEVy9TEQzJ9saIJUQ/ZqBvEvgEFe7UjPFNSFi8cyeU+3Q==", + "dependencies": {} + }, + "iron-webcrypto@0.10.1": { + "integrity": "sha512-QGOS8MRMnj/UiOa+aMIgfyHcvkhqNUsUxb1XzskENvbo+rEfp6TOwqd1KPuDzXC4OnGHcMSVxDGRoilqB8ViqA==", + "dependencies": {} } } }, "redirects": { "https://deno.land/x/deno_dom/deno-dom-wasm.ts": "https://deno.land/x/deno_dom@v0.1.45/deno-dom-wasm.ts", "https://deno.land/x/hono/middleware.ts": "https://deno.land/x/hono@v4.3.10/middleware.ts", + "https://deno.land/x/hono_sessions/mod.ts": "https://deno.land/x/hono_sessions@v0.5.8/mod.ts", "https://esm.sh/v135/@types/css@latest/index.d.ts": "https://esm.sh/v135/@types/css@0.0.37/index.d.ts" }, "remote": { + "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", + "https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", + "https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a", + "https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15", + "https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c", + "https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d", + "https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00", + "https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808", + "https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", "https://deno.land/x/deno_dom@v0.1.45/build/deno-wasm/deno-wasm.js": "d6841a06342eb6a2798ef28de79ad69c0f2fa349fa04d3ca45e5fcfbf50a9340", "https://deno.land/x/deno_dom@v0.1.45/deno-dom-wasm.ts": "a33d160421bbb6e3104285ea5ebf33352b7ad50d82ea8765e3cf65f972b25119", "https://deno.land/x/deno_dom@v0.1.45/src/api.ts": "0ff5790f0a3eeecb4e00b7d8fbfa319b165962cf6d0182a65ba90f158d74f7d7", @@ -125,6 +146,27 @@ "https://deno.land/x/hono@v4.3.10/utils/url.ts": "855169632c61d03703bd08cafb27664ba3fdb352892f01687d5cce8fd49e3cb1", "https://deno.land/x/hono@v4.3.10/validator/index.ts": "6c986e8b91dcf857ecc8164a506ae8eea8665792a4ff7215471df669c632ae7c", "https://deno.land/x/hono@v4.3.10/validator/validator.ts": "53f3d2ad442e22f0bc2d85b7d8d90320d4e5ecf5fdd58882f906055d33a18e13", + "https://deno.land/x/hono_sessions@v0.5.8/deps.ts": "a86ee9f6d96c0dcc80380eaa3d1cef13ff972a56003d8e1d7f461cc32554fa34", + "https://deno.land/x/hono_sessions@v0.5.8/mod.ts": "67e89aebb3c0e87e7e6065aefb56606d2f1017dd8ed030ef742f5bb73398abda", + "https://deno.land/x/hono_sessions@v0.5.8/src/Crypto.ts": "dcbf1dc60d5c67426f3a25bcd56a38012336c2418ab1291550f2fa1f76b66e25", + "https://deno.land/x/hono_sessions@v0.5.8/src/Middleware.ts": "05879181e4f649098c05800a47c12c21649dde41f8d21aceefc7b2dbb91b261b", + "https://deno.land/x/hono_sessions@v0.5.8/src/Session.ts": "9de1c3661a9b713a4eded0457a60432a96118be59741cb32e43c39a437da33cd", + "https://deno.land/x/hono_sessions@v0.5.8/src/store/CookieStore.ts": "9175f9da3e555c0e07928db03bb26ad40b6b0c1f661bfccd9aeddad0a615ccc2", + "https://deno.land/x/hono_sessions@v0.5.8/src/store/MemoryStore.ts": "f702d81a948a775207f047b60e2dd6c903a4b25b041c827309486055be3103fd", + "https://deno.land/x/hono_sessions@v0.5.8/src/store/Store.ts": "8f26b96fa22c330846ccb318cb90d3d1135aec9078c0faa7b5dc39ceb49a0da4", + "https://deno.land/x/zod@v3.22.4/ZodError.ts": "4de18ff525e75a0315f2c12066b77b5c2ae18c7c15ef7df7e165d63536fdf2ea", + "https://deno.land/x/zod@v3.22.4/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", + "https://deno.land/x/zod@v3.22.4/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", + "https://deno.land/x/zod@v3.22.4/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", + "https://deno.land/x/zod@v3.22.4/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", + "https://deno.land/x/zod@v3.22.4/helpers/parseUtil.ts": "f791e6e65a0340d85ad37d26cd7a3ba67126cd9957eac2b7163162155283abb1", + "https://deno.land/x/zod@v3.22.4/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", + "https://deno.land/x/zod@v3.22.4/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", + "https://deno.land/x/zod@v3.22.4/helpers/util.ts": "8baf19b19b2fca8424380367b90364b32503b6b71780269a6e3e67700bb02774", + "https://deno.land/x/zod@v3.22.4/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", + "https://deno.land/x/zod@v3.22.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", + "https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4", + "https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e", "https://esm.sh/jsdom@22.1.0": "2efb54a90536dc0f4bb00f442c32fcedb2224af5aad5c87db87660f15c013170", "https://esm.sh/v128/@tootallnate/once@2.0.0/denonext/once.mjs": "2c69d53dd1de1d7e5f9a78d46041f5d869f7d058f22d72e7f25821e61dddd147", "https://esm.sh/v128/abab@2.0.6/denonext/abab.mjs": "7101c07a728882d686222e5f5c5a6fa956a558e4f406c521f1c0b61bc60cd07d", @@ -171,6 +213,11 @@ "https://esm.sh/v128/xml-name-validator@4.0.0/denonext/xml-name-validator.mjs": "69af66c891312f304a8be720961ad5cce5c49c85a3bba03275b56a70dec7a21e", "https://esm.sh/v128/xmlchars@2.2.0/denonext/xml/1.0/ed5.js": "60f8f018eb1d79d69a41324155b7d9f52f1058b37060b28acc1dfc49446e549d", "https://esm.sh/v128/xmlchars@2.2.0/denonext/xml/1.1/ed2.js": "ba7d1fe5694f62469c4b293a1fadad332c637cbcfbc74147a296475c2ff8ad3d", - "https://esm.sh/v128/xmlchars@2.2.0/denonext/xmlns/1.0/ed3.js": "929d15ffc72d56c8909f87e7df8288f060bda0256622e8e95c24f0decb06adc7" + "https://esm.sh/v128/xmlchars@2.2.0/denonext/xmlns/1.0/ed3.js": "929d15ffc72d56c8909f87e7df8288f060bda0256622e8e95c24f0decb06adc7", + "https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/mod.ts": "bc9436bcf45f3204ebcd0b2559f53e5d38162195fc48ac6d435cf8ca38c77ae3", + "https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/src/entry.ts": "243a53b27e494c01e944331e2b71dfbe96ff906921aca8870603eb19bbce8235", + "https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/src/lib.ts": "76973ce240e2bcadaf13e43115d050dee70b92dc5cd7cd726cdd5d9adf4c1ad6", + "https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/src/store.ts": "ff6cdb0c045818d8e4ab6eb2bb304db8ac387ef05514af09e6c731bb65815a10", + "https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/src/typing.ts": "cb0d315461aab883ddd2396f5a5ce2a88615257b402d5a5cd06c10dc44ffa453" } } diff --git a/lib/auth.ts b/lib/auth.ts new file mode 100644 index 0000000..c834ca8 --- /dev/null +++ b/lib/auth.ts @@ -0,0 +1,24 @@ +import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts"; +import { db, User } from "./storage.ts"; +import { FeurEnv } from "../main.ts"; +import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts"; + +export async function login(login: string, password: string) { + for await (const user of db.all("user")) { + if (await user.get("username") !== login) continue; + if (await user.get("password") === password) return user; + return null; + } + return null; +} + +export async function get_user(context: Context) { + const id = context.get("session").get("user"); + if (typeof id !== "string") return null; + return await db.get("user", id); +} + +export function set_user(context: Context, user: User | null) { + if (user === null) context.get("session").set("user", ""); + else context.get("session").set("user", user.id); +} diff --git a/lib/storage.ts b/lib/storage.ts index ede87b8..804be37 100644 --- a/lib/storage.ts +++ b/lib/storage.ts @@ -1,45 +1,36 @@ -import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts"; +import { + EntryFor, + Schema, + Store, +} from "https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/mod.ts"; +import { project_root } from "./utils.ts"; -class Store { - kv; +export const db = await Store.open( + new Schema({ + user: { + username: "string", + password: "string", + upvoted_posts: ["many", "post"], + upvoted_comments: ["many", "comment"], + posts: ["many", "post"], + }, + post: { + title: "string", + content: "string", + upvotes: "number", + author: ["one", "user"], + comments: ["many", "comment"], + }, + comment: { + content: "string", + upvotes: "number", + author: ["one", "user"], + post: ["one", "post"], + }, + }), + `${project_root()}/local/db.kv`, +); - private constructor(kv: Deno.Kv) { - this.kv = kv; - } -} - -class Schema> { - descriptor; - - constructor(descriptor: T) { - this.descriptor = descriptor; - } -} - -type SchemaDescriptor> = { - tables: T; -}; - -type TablesDescriptor> = { - [name: string]: TableDescr; -}; - -type TableDescr, Name extends keyof Tables, S extends z.ZodRawShape> = { - structure: z.ZodObject; - relations: Relations; -}; - -type Relations, Name extends keyof Tables, S extends z.ZodRawShape> = { - [name in Exclude]?: [keyof S, keyof z.infer]; -}; - -const store = new Schema({ - "user": { - structure: z.object({ feur_id: z.string() }), - relations: { "feur": ["_feur_id", "id"] }, - }, - "feur": { - structure: z.object({ id: z.string(), content: z.string() }), - relations: {}, - }, -}); +export type User = EntryFor; +export type Post = EntryFor; +export type Comment = EntryFor; diff --git a/lib/utils.ts b/lib/utils.ts index e29749c..565b5bf 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,9 +1,14 @@ +import { dirname } from "https://deno.land/std@0.224.0/path/dirname.ts"; + import { CSSProperties } from "https://deno.land/std@0.40.0/types/react.d.ts"; export function _css(prop: CSSProperties) { return prop; } -// type wizardry - -export type KeysOfType = keyof { [P in keyof T as T[P] extends V ? P : never]: P }; +export function project_root() { + const this_url = new URL(import.meta.url); + const this_abs_path = Deno.realPathSync(this_url.pathname); + const lib_path = dirname(this_abs_path); + return dirname(lib_path); +} diff --git a/local/.gitignore b/local/.gitignore new file mode 100644 index 0000000..aa0e8eb --- /dev/null +++ b/local/.gitignore @@ -0,0 +1,2 @@ +* +!/.gitignore \ No newline at end of file diff --git a/main.ts b/main.ts index 1914446..5ad6af6 100755 --- a/main.ts +++ b/main.ts @@ -1,17 +1,22 @@ -#!/bin/env -S deno run --allow-net --unstable-kv --watch --allow-read=./pages +#!/bin/env -S deno run --allow-net --unstable-kv --watch --allow-read=./pages,./local,./lib,. --allow-write=./local import { Hono } from "https://deno.land/x/hono@v4.3.10/mod.ts"; -import { basicAuth, serveStatic } from "https://deno.land/x/hono@v4.3.10/middleware.ts"; +import { serveStatic } from "https://deno.land/x/hono@v4.3.10/middleware.ts"; +import { CookieStore, Session, sessionMiddleware } from "https://deno.land/x/hono_sessions@v0.5.8/mod.ts"; -const app = new Hono(); +export type FeurEnv = { Variables: { session: Session } }; +const app = new Hono(); app.use("/static/*", serveStatic({ root: "./pages/" })); -app.use("/user", basicAuth({ username: "feur", password: "feur" })); // todo : transition to verify_user + +const store = new CookieStore(); +const encryptionKey = "FeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeur"; +app.use("*", sessionMiddleware({ store, encryptionKey })); app.get("/", (c) => c.redirect("/home")); import HomePage from "./pages/home.tsx"; -app.get("/home", (c) => c.html(HomePage())); +app.get("/home", (c) => c.html(HomePage(c))); import LoginPage from "./pages/login.tsx"; app.get("/login", (c) => c.html(LoginPage())); @@ -19,4 +24,10 @@ app.get("/login", (c) => c.html(LoginPage())); import { login_route } from "./api/login.ts"; app.post("/api/login", (c) => login_route(c)); +import { logout_route } from "./api/logout.ts"; +app.get("/api/logout", (c) => logout_route(c)); + +import UserPage from "./pages/user.tsx"; +app.get("/user", async (c) => await UserPage(c)); + Deno.serve(app.fetch); diff --git a/pages/components/heading.tsx b/pages/components/heading.tsx index 5e31a46..8fb551e 100644 --- a/pages/components/heading.tsx +++ b/pages/components/heading.tsx @@ -1,9 +1,11 @@ /** @jsx jsx */ -import { jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts" +import { PropsWithChildren, jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts" import { _css } from "../../lib/utils.ts"; +import { User } from "../../lib/storage.ts"; -export default function Heading() { +// +export default function Heading({ username }: PropsWithChildren<{username: string | null}>) { return (
+ {(username !== null) && }
) diff --git a/pages/components/login.tsx b/pages/components/login.tsx index 358f0f5..b926bc3 100644 --- a/pages/components/login.tsx +++ b/pages/components/login.tsx @@ -7,6 +7,7 @@ export default function Login() {
+
) } diff --git a/pages/home.tsx b/pages/home.tsx index fc22209..795de82 100644 --- a/pages/home.tsx +++ b/pages/home.tsx @@ -4,11 +4,16 @@ import { jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts" import { BasePage } from "./components/base.tsx" import Main from "./components/main.tsx"; import Heading from "./components/heading.tsx"; +import { get_user } from "../lib/auth.ts"; +import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts"; +import { FeurEnv } from "../main.ts"; +import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts"; -export default function HomePage() { +export default async function HomePage(context: Context) { + const user = await get_user(context); return ( - +
Main
) diff --git a/pages/user.tsx b/pages/user.tsx new file mode 100644 index 0000000..779c2d9 --- /dev/null +++ b/pages/user.tsx @@ -0,0 +1,24 @@ +/** @jsx jsx */ + +import { jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts" +import { BasePage } from "./components/base.tsx"; +import Heading from "./components/heading.tsx"; +import Main from "./components/main.tsx"; +import Login from "./components/login.tsx"; +import { User } from "../lib/storage.ts"; +import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts"; +import { FeurEnv } from "../main.ts"; +import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts"; +import { get_user } from "../lib/auth.ts"; + +export default async function UserPage(context: Context) { + const user = await get_user(context); + if (user === null) return context.text("Must be logged.", 401); + return context.html( + +
+

Logged as {await user.get("username")}

+
+
+ ) +}