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