commit a03ad035fb640e80b7f94f1927b7892bc308a627 Author: SimonFJ20 Date: Fri Sep 20 04:42:15 2024 +0200 init 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 0000000..757846f Binary files /dev/null and b/public/favicon.ico differ 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