init
All checks were successful
Validate / Validate (push) Successful in 9s

This commit is contained in:
SimonFJ20 2024-09-20 04:42:15 +02:00
commit a03ad035fb
10 changed files with 369 additions and 0 deletions

View File

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

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Bunker
![](https://git.sfja.dk/sfj/bunker/actions/workflows/validate.yaml/badge.svg)

9
deno.jsonc Normal file
View File

@ -0,0 +1,9 @@
{
"exclude": ["public/**/*.js"],
"fmt": {
"indentWidth": 4
},
"tasks": {
"check": "deno check main.ts"
}
}

157
deno.lock Normal file
View File

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

122
main.ts Normal file
View File

@ -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 });

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

11
public/index.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bunker</title>
</head>
<body>
<h1>Bunker !!!</h1>
</body>
</html>

23
public/login.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bunker</title>
<script src="login.js" defer></script>
</head>
<body>
<h1>Login</h1>
<form id="login">
<label for="username">Username: </label><br>
<input type="text" id="username" name="username"><br>
<label for="password">Password:</label><br>
<input type="password" id="password" name="password"><br>
<input type="submit" value="Login">
</form>
</body>
</html>

15
public/login.js Normal file
View File

@ -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);
};

0
test.db Normal file
View File