From a03ad035fb640e80b7f94f1927b7892bc308a627 Mon Sep 17 00:00:00 2001 From: SimonFJ20 Date: Fri, 20 Sep 2024 04:42:15 +0200 Subject: [PATCH] init --- .gitea/workflows/validate.yaml | 27 ++++++ README.md | 5 ++ deno.jsonc | 9 ++ deno.lock | 157 +++++++++++++++++++++++++++++++++ main.ts | 122 +++++++++++++++++++++++++ public/favicon.ico | Bin 0 -> 15406 bytes public/index.html | 11 +++ public/login.html | 23 +++++ public/login.js | 15 ++++ test.db | 0 10 files changed, 369 insertions(+) create mode 100644 .gitea/workflows/validate.yaml create mode 100644 README.md create mode 100644 deno.jsonc create mode 100644 deno.lock create mode 100644 main.ts create mode 100644 public/favicon.ico create mode 100644 public/index.html create mode 100644 public/login.html create mode 100644 public/login.js create mode 100644 test.db diff --git a/.gitea/workflows/validate.yaml b/.gitea/workflows/validate.yaml new file mode 100644 index 0000000..9e1202d --- /dev/null +++ b/.gitea/workflows/validate.yaml @@ -0,0 +1,27 @@ +name: Validate +on: + push: + branches: main + pull_request: + branches: main + +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + + permissions: + id-token: write # Needed for auth with Deno Deploy + contents: read # Needed to clone the repository + + steps: + - name: Clone repository + uses: actions/checkout@v3 + + - name: Install Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + + - name: Check + run: "deno task check" diff --git a/README.md b/README.md new file mode 100644 index 0000000..65255d2 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ + +# Bunker + +![](https://git.sfja.dk/sfj/bunker/actions/workflows/validate.yaml/badge.svg) + diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 0000000..5c4db37 --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,9 @@ +{ + "exclude": ["public/**/*.js"], + "fmt": { + "indentWidth": 4 + }, + "tasks": { + "check": "deno check main.ts" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..5fc43b0 --- /dev/null +++ b/deno.lock @@ -0,0 +1,157 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@db/sqlite@0.11": "jsr:@db/sqlite@0.11.1", + "jsr:@denosaurs/plug@1": "jsr:@denosaurs/plug@1.0.6", + "jsr:@oak/commons@0.7": "jsr:@oak/commons@0.7.0", + "jsr:@oak/oak@14": "jsr:@oak/oak@14.2.0", + "jsr:@std/assert@0.218": "jsr:@std/assert@0.218.2", + "jsr:@std/assert@^0.217.0": "jsr:@std/assert@0.217.0", + "jsr:@std/assert@^0.218.2": "jsr:@std/assert@0.218.2", + "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", + "jsr:@std/bytes@0.218": "jsr:@std/bytes@0.218.2", + "jsr:@std/bytes@^0.218.2": "jsr:@std/bytes@0.218.2", + "jsr:@std/crypto@0.218": "jsr:@std/crypto@0.218.2", + "jsr:@std/encoding@0.218": "jsr:@std/encoding@0.218.2", + "jsr:@std/encoding@^0.218.2": "jsr:@std/encoding@0.218.2", + "jsr:@std/encoding@^0.221.0": "jsr:@std/encoding@0.221.0", + "jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0", + "jsr:@std/fs@^0.221.0": "jsr:@std/fs@0.221.0", + "jsr:@std/http@0.218": "jsr:@std/http@0.218.2", + "jsr:@std/io@0.218": "jsr:@std/io@0.218.2", + "jsr:@std/media-types@0.218": "jsr:@std/media-types@0.218.2", + "jsr:@std/path@0.217": "jsr:@std/path@0.217.0", + "jsr:@std/path@0.218": "jsr:@std/path@0.218.2", + "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", + "npm:@types/node": "npm:@types/node@18.16.19", + "npm:path-to-regexp@6.2.1": "npm:path-to-regexp@6.2.1" + }, + "jsr": { + "@db/sqlite@0.11.1": { + "integrity": "546434e7ed762db07e6ade0f963540dd5e06723b802937bf260ff855b21ef9c5", + "dependencies": [ + "jsr:@denosaurs/plug@1", + "jsr:@std/path@0.217" + ] + }, + "@denosaurs/plug@1.0.6": { + "integrity": "6cf5b9daba7799837b9ffbe89f3450510f588fafef8115ddab1ff0be9cb7c1a7", + "dependencies": [ + "jsr:@std/encoding@^0.221.0", + "jsr:@std/fmt@^0.221.0", + "jsr:@std/fs@^0.221.0", + "jsr:@std/path@^0.221.0" + ] + }, + "@oak/commons@0.7.0": { + "integrity": "4bd889b3dc9ddac1b602034d88c137f06de7078775961b51081beb5f175c120b", + "dependencies": [ + "jsr:@std/assert@0.218", + "jsr:@std/media-types@0.218" + ] + }, + "@oak/oak@14.2.0": { + "integrity": "b683b089693004ac3bca80b52159b3e9ad214dc8246ff5dc61ba658da78bc166", + "dependencies": [ + "jsr:@oak/commons@0.7", + "jsr:@std/assert@0.218", + "jsr:@std/bytes@0.218", + "jsr:@std/crypto@0.218", + "jsr:@std/encoding@0.218", + "jsr:@std/http@0.218", + "jsr:@std/io@0.218", + "jsr:@std/media-types@0.218", + "jsr:@std/path@0.218", + "npm:path-to-regexp@6.2.1" + ] + }, + "@std/assert@0.217.0": { + "integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642" + }, + "@std/assert@0.218.2": { + "integrity": "7f0a5a1a8cf86607cd6c2c030584096e1ffad27fc9271429a8cb48cfbdee5eaf" + }, + "@std/assert@0.221.0": { + "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" + }, + "@std/bytes@0.218.2": { + "integrity": "91fe54b232dcca73856b79a817247f4a651dbb60d51baafafb6408c137241670" + }, + "@std/crypto@0.218.2": { + "integrity": "8c5031a3a1c3ac3bed3c0d4bed2fe7e7faedcb673bbfa0edd10570c8452f5cd2", + "dependencies": [ + "jsr:@std/assert@^0.218.2", + "jsr:@std/encoding@^0.218.2" + ] + }, + "@std/encoding@0.218.2": { + "integrity": "da55a763c29bf0dbf06fd286430b358266eb99c28789d89fe9a3e28edecb8d8e" + }, + "@std/encoding@0.221.0": { + "integrity": "d1dd76ef0dc5d14088411e6dc1dede53bf8308c95d1537df1214c97137208e45" + }, + "@std/fmt@0.221.0": { + "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" + }, + "@std/fs@0.221.0": { + "integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286", + "dependencies": [ + "jsr:@std/assert@^0.221.0", + "jsr:@std/path@^0.221.0" + ] + }, + "@std/http@0.218.2": { + "integrity": "54223b62702e665b9dab6373ea2e51235e093ef47228d21cfa0469ee5ac75c9b", + "dependencies": [ + "jsr:@std/assert@^0.218.2", + "jsr:@std/encoding@^0.218.2" + ] + }, + "@std/io@0.218.2": { + "integrity": "c64fbfa087b7c9d4d386c5672f291f607d88cb7d44fc299c20c713e345f2785f", + "dependencies": [ + "jsr:@std/bytes@^0.218.2" + ] + }, + "@std/media-types@0.218.2": { + "integrity": "1ed3bd2a05e44bad3fc2bab1767d0ce7f2fd68baee62a980751ce51633acb788" + }, + "@std/path@0.217.0": { + "integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11", + "dependencies": [ + "jsr:@std/assert@^0.217.0" + ] + }, + "@std/path@0.218.2": { + "integrity": "b568fd923d9e53ad76d17c513e7310bda8e755a3e825e6289a0ce536404e2662", + "dependencies": [ + "jsr:@std/assert@^0.218.2" + ] + }, + "@std/path@0.221.0": { + "integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095", + "dependencies": [ + "jsr:@std/assert@^0.221.0" + ] + } + }, + "npm": { + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", + "dependencies": {} + }, + "path-to-regexp@6.2.1": { + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dependencies": {} + } + } + }, + "remote": { + "https://deno.land/x/bcrypt@v0.4.1/mod.ts": "ff09bdae282583cf5f7d87efe37ddcecef7f14f6d12e8b8066a3058db8c6c2f7", + "https://deno.land/x/bcrypt@v0.4.1/src/bcrypt/base64.ts": "b8266450a4f1eb6960f60f2f7986afc4dde6b45bd2d7ee7ba10789e67e17b9f7", + "https://deno.land/x/bcrypt@v0.4.1/src/bcrypt/bcrypt.ts": "ec221648cc6453ea5e3803bc817c01157dada06aa6f7a0ba6b9f87aae32b21e2", + "https://deno.land/x/bcrypt@v0.4.1/src/main.ts": "08d201b289c8d9c46f8839c69cd6625b213863db29775c7a200afc3b540e64f8", + "https://deno.land/x/bcrypt@v0.4.1/src/worker.ts": "5a73bdfee9c9e622f47c9733d374b627dce52fb3ec1e74c8226698b3fc57ffac" + } +} diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..77bd582 --- /dev/null +++ b/main.ts @@ -0,0 +1,122 @@ +// import * as sqlite from "jsr:@db/sqlite@0.11"; +import * as oak from "jsr:@oak/oak@14"; +import * as bcrypt from "https://deno.land/x/bcrypt@v0.4.1/mod.ts"; + +const app = new oak.Application(); + +type User = { + username: string; + passwordHash: string; +}; + +const users: { [username: string]: User } = { + ["sfj"]: { + username: "sfj", + passwordHash: await bcrypt.hash("sfj"), + }, +}; + +type Session = { + token: string; + username: string; +}; + +const sessions: { [token: string]: Session } = {}; + +const publicRouter = new oak.Router(); + +publicRouter.get("/favicon.ico", async (ctx) => { + await ctx.send({ path: ctx.request.url.pathname, root: "./public" }); +}); +publicRouter.get("/login.html", async (ctx) => { + await ctx.send({ path: ctx.request.url.pathname, root: "./public" }); +}); +publicRouter.get("/login.js", async (ctx) => { + await ctx.send({ path: ctx.request.url.pathname, root: "./public" }); +}); + +type LoginReq = { + username: string; + password: string; +}; + +function respond< + Ctx extends { + respond: boolean; + // deno-lint-ignore no-explicit-any + response: { status: oak.Status; body: any }; + }, +> // deno-lint-ignore no-explicit-any +(ctx: Ctx, status: oak.Status, body: any) { + ctx.respond = true; + ctx.response.status = status; + ctx.response.body = body; +} + +function generateToken(): string { + let token = ""; + const alfabet = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXUZ123456789"; + for (let i = 0; i < 64; ++i) { + token += alfabet[Math.random() * alfabet.length]; + } + return token; +} + +publicRouter.post("/api/login", async (ctx) => { + const body = await ctx.request.body.json() as LoginReq; + if ( + typeof body.username !== "string" || typeof body.password !== "string" + ) { + return respond(ctx, 400, { ok: false, msg: "malformed request" }); + } + if (!(body.username in users)) { + return respond(ctx, 401, { ok: false, msg: "bad creds" }); + } + const user = users[body.username]; + if (!await bcrypt.compare(body.password, user.passwordHash)) { + return respond(ctx, 401, { ok: false, msg: "bad creds" }); + } + for (const token in sessions) { + if (sessions[token].username === body.username) { + delete sessions[token]; + } + } + const token = generateToken(); + sessions[token] = { + token, + username: body.username, + }; + await ctx.cookies.set("Authorization", token, { sameSite: "strict" }); + return respond(ctx, 200, { ok: true }); +}); + +app.use(publicRouter.routes()); +app.use(publicRouter.allowedMethods()); + +function authMiddleware(): oak.Middleware { + return async (ctx, next) => { + const token = await ctx.cookies.get("Authorization"); + if (!token || !(token in sessions)) { + ctx.response.redirect("/login.html"); + return; + } + await next(); + }; +} + +const authRouter = new oak.Router(); +app.use(authMiddleware(), authRouter.routes()); +app.use(authMiddleware(), authRouter.allowedMethods()); + +app.use(authMiddleware(), async (ctx) => { + console.log(ctx.request.url.pathname); + const path = ((p) => p === "/" ? "/index.html" : p)( + ctx.request.url.pathname, + ); + await oak.send(ctx, path, { + root: "./public", + }); +}); + +app.listen({ port: 8000 }); diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..757846f66a346cd3d274f5c3aa3b227c71133fff GIT binary patch literal 15406 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 bSd|ycS+7?jS2n(nEzJMU`*#oAYY+SrpDMl) literal 0 HcmV?d00001 diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..2c2a950 --- /dev/null +++ b/public/index.html @@ -0,0 +1,11 @@ + + + + + + Bunker + + +

Bunker !!!

+ + diff --git a/public/login.html b/public/login.html new file mode 100644 index 0000000..a679758 --- /dev/null +++ b/public/login.html @@ -0,0 +1,23 @@ + + + + + + Bunker + + + +

Login

+ +
+
+
+ +
+
+ + +
+ + + diff --git a/public/login.js b/public/login.js new file mode 100644 index 0000000..4923dd3 --- /dev/null +++ b/public/login.js @@ -0,0 +1,15 @@ +document.querySelector("#login").onsubmit = async (event) => { + event.preventDefault(); + const form = new FormData(event.target); + const body = JSON.stringify({ + username: form.get("username"), + password: form.get("password"), + }); + const res = await fetch("/api/login", { + method: "POST", + headers: new Headers({ "Content-Type": "application/json" }), + body, + }).then((res) => res.json()); + console.log(res); +}; + diff --git a/test.db b/test.db new file mode 100644 index 0000000..e69de29