This commit is contained in:
commit
a03ad035fb
27
.gitea/workflows/validate.yaml
Normal file
27
.gitea/workflows/validate.yaml
Normal 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
5
README.md
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
# Bunker
|
||||
|
||||
![](https://git.sfja.dk/sfj/bunker/actions/workflows/validate.yaml/badge.svg)
|
||||
|
9
deno.jsonc
Normal file
9
deno.jsonc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"exclude": ["public/**/*.js"],
|
||||
"fmt": {
|
||||
"indentWidth": 4
|
||||
},
|
||||
"tasks": {
|
||||
"check": "deno check main.ts"
|
||||
}
|
||||
}
|
157
deno.lock
Normal file
157
deno.lock
Normal 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
122
main.ts
Normal 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
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
11
public/index.html
Normal file
11
public/index.html
Normal 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
23
public/login.html
Normal 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
15
public/login.js
Normal 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);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user