This commit is contained in:
JOLIMAITRE Matthieu 2024-05-29 06:09:50 +02:00
parent 8b6a86f17a
commit 3aea126342
14 changed files with 205 additions and 67 deletions

7
README.md Normal file
View file

@ -0,0 +1,7 @@
# Twifeur
Feur
## Feur
Feur feur.

View file

@ -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<Env, string, BlankInput>) {
return context.text("E");
export async function login_route(context: Context<FeurEnv, string, BlankInput>) {
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");
}

9
api/logout.ts Normal file
View file

@ -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<FeurEnv, string, BlankInput>) {
set_user(context, null);
return context.redirect("/");
}

51
deno.lock generated
View file

@ -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"
}
}

24
lib/auth.ts Normal file
View file

@ -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<FeurEnv, string, BlankInput>) {
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<FeurEnv, string, BlankInput>, user: User | null) {
if (user === null) context.get("session").set("user", "");
else context.get("session").set("user", user.id);
}

View file

@ -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<D, Schema> {
kv;
private constructor(kv: Deno.Kv) {
this.kv = kv;
}
}
class Schema<T extends TablesDescriptor<T>> {
descriptor;
constructor(descriptor: T) {
this.descriptor = descriptor;
}
}
type SchemaDescriptor<T extends TablesDescriptor<T>> = {
tables: T;
};
type TablesDescriptor<Self extends TablesDescriptor<Self>> = {
[name: string]: TableDescr<Self, typeof name, { [key: string]: z.ZodType }>;
};
type TableDescr<Tables extends TablesDescriptor<Tables>, Name extends keyof Tables, S extends z.ZodRawShape> = {
structure: z.ZodObject<S>;
relations: Relations<Tables, Name, S>;
};
type Relations<Tables extends TablesDescriptor<Tables>, Name extends keyof Tables, S extends z.ZodRawShape> = {
[name in Exclude<keyof Tables, Name>]?: [keyof S, keyof z.infer<Tables[name]["structure"]>];
};
const store = new Schema({
"user": {
structure: z.object({ feur_id: z.string() }),
relations: { "feur": ["_feur_id", "id"] },
export const db = await Store.open(
new Schema({
user: {
username: "string",
password: "string",
upvoted_posts: ["many", "post"],
upvoted_comments: ["many", "comment"],
posts: ["many", "post"],
},
"feur": {
structure: z.object({ id: z.string(), content: z.string() }),
relations: {},
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`,
);
export type User = EntryFor<typeof db, "user">;
export type Post = EntryFor<typeof db, "post">;
export type Comment = EntryFor<typeof db, "comment">;

View file

@ -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<T, V> = 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);
}

2
local/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!/.gitignore

21
main.ts
View file

@ -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<FeurEnv>();
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);

View file

@ -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 (
<header style={_css({
width: "100vw",
@ -32,6 +34,7 @@ export default function Heading() {
<MenuItem name="Content" location="/home"/>
<MenuItem name="About" location="/about"/>
<MenuItem name="User" location="/user"/>
{(username !== null) && <MenuItem name={`Logout (${username})`} location="/api/logout" />}
</div>
</header>
)

View file

@ -7,6 +7,7 @@ export default function Login() {
<form action="/api/login" method="post">
<input type="text" name="login"/>
<input type="password" name="password"/>
<input type="submit" value="login" />
</form>
)
}

View file

@ -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<FeurEnv, string, BlankInput>) {
const user = await get_user(context);
return (
<BasePage name="Home">
<Heading></Heading>
<Heading username={await user?.get("username") ?? null}></Heading>
<Main>Main</Main>
</BasePage>
)

24
pages/user.tsx Normal file
View file

@ -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<FeurEnv, string, BlankInput>) {
const user = await get_user(context);
if (user === null) return context.text("Must be logged.", 401);
return context.html(
<BasePage name="Login">
<Main>
<h1>Logged as {await user.get("username")}</h1>
</Main>
</BasePage>
)
}