bunker/main.ts

123 lines
3.3 KiB
TypeScript
Raw Normal View History

2024-09-20 04:42:15 +02:00
// 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 });