123 lines
3.3 KiB
TypeScript
123 lines
3.3 KiB
TypeScript
|
// 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 });
|