add sessions, add session example
This commit is contained in:
parent
b2e357227c
commit
d519c7bf9c
|
@ -79,6 +79,85 @@ httpServer.get("/site", (_req, rep) => {
|
|||
rep.html(htmlTest);
|
||||
});
|
||||
|
||||
httpServer.delete("/session", (req, _rep) => {
|
||||
const username = req.session.user as string ?? "";
|
||||
if (username.length > 0) {
|
||||
delete req.session.user;
|
||||
return {
|
||||
code: 200,
|
||||
message: "Logged out!",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
code: 403,
|
||||
message: "Not logged in!",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
httpServer.post("/session", (req, _rep) => {
|
||||
const username = req.queryParam("username") ?? "";
|
||||
if (username.length > 0) {
|
||||
req.session.user = username;
|
||||
return {
|
||||
code: 200,
|
||||
message: "Logged in!",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
code: 403,
|
||||
message: "Please enter a Username",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
httpServer.get("/session", (req, rep) => {
|
||||
const headerText = req.session.user
|
||||
? `Hello, ${req.session.user}!`
|
||||
: `Please login!`;
|
||||
const htmlTest = `
|
||||
<html>
|
||||
<head>
|
||||
<title>Session Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${headerText}</h1>
|
||||
<input type="text" placeholder="Username" id="username" style="margin-bottom: 15px;" ${req.session.user ? "value='" + req.session.user + "' disabled" : ""}/>
|
||||
<br>
|
||||
<button onclick="${req.session.user ? "doLogout" : "doLogin"}()">${
|
||||
req.session.user ? "Logout" : "Login"
|
||||
}</button>
|
||||
</body>
|
||||
<script type="">
|
||||
async function doLogout() {
|
||||
const fetchResult = await fetch("/session", { method: 'DELETE'});
|
||||
const jsonResult = await fetchResult.json();
|
||||
if("code" in jsonResult){
|
||||
if(jsonResult.code == 200){
|
||||
document.location.reload(true)
|
||||
}else{
|
||||
alert(jsonResult.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
async function doLogin() {
|
||||
const username = document.getElementById('username').value;
|
||||
const fetchResult = await fetch("/session?username=" + username, { method: 'POST'});
|
||||
const jsonResult = await fetchResult.json();
|
||||
if("code" in jsonResult){
|
||||
if(jsonResult.code == 200){
|
||||
document.location.reload(true)
|
||||
}else{
|
||||
alert(jsonResult.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
`;
|
||||
rep.html(htmlTest);
|
||||
});
|
||||
|
||||
httpServer.get("/", (req, rep) => {
|
||||
rep.status(Status.Teapot)
|
||||
.header("working", "true")
|
||||
|
@ -114,4 +193,5 @@ httpServer.listen({
|
|||
port: 8080,
|
||||
staticLocalDir: "/static",
|
||||
staticServePath: "/assets",
|
||||
sessionSecret: "SuperDuperSecret",
|
||||
});
|
||||
|
|
88
mod.ts
88
mod.ts
|
@ -4,12 +4,15 @@ import {
|
|||
} from "https://deno.land/std@0.186.0/http/http_status.ts";
|
||||
import * as path from "https://deno.land/std@0.185.0/path/mod.ts";
|
||||
import * as cookie from "https://deno.land/std@0.185.0/http/cookie.ts";
|
||||
import { Aes } from "https://deno.land/x/crypto/aes.ts";
|
||||
import { Cbc, Padding } from "https://deno.land/x/crypto/block-modes.ts";
|
||||
|
||||
type ListenOptions = {
|
||||
type HTTPServerOptions = {
|
||||
port: number;
|
||||
host?: string;
|
||||
staticLocalDir?: string;
|
||||
staticServePath?: string;
|
||||
sessionSecret?: string;
|
||||
};
|
||||
|
||||
export enum HTTPMethod {
|
||||
|
@ -50,8 +53,10 @@ export class HTTPServer {
|
|||
private notFoundHandler?: RouteHandler;
|
||||
private preprocessors: RoutePreprocessor[] = [];
|
||||
private middlewareHandler?: RouteMiddlewareHandler;
|
||||
settings?: HTTPServerOptions;
|
||||
|
||||
async listen(options: ListenOptions) {
|
||||
async listen(options: HTTPServerOptions) {
|
||||
this.settings = options;
|
||||
this.server = Deno.listen({
|
||||
port: options.port,
|
||||
hostname: options.host,
|
||||
|
@ -118,11 +123,11 @@ export class HTTPServer {
|
|||
const request = requestEvent.request;
|
||||
const url = request.url;
|
||||
const routeRequest = new RouteRequest(
|
||||
this,
|
||||
request,
|
||||
conn,
|
||||
filepath,
|
||||
url,
|
||||
this.staticServePath ?? "",
|
||||
);
|
||||
const routeReply: RouteReply = new RouteReply();
|
||||
|
||||
|
@ -163,6 +168,7 @@ export class HTTPServer {
|
|||
const hrArray: number[] = [0, Math.trunc(pt * 1000000)];
|
||||
resolveAction(hrArray);
|
||||
}
|
||||
this.processSession(routeRequest, routeReply);
|
||||
this.handleNotFound(routeRequest, routeReply, requestEvent);
|
||||
continue;
|
||||
}
|
||||
|
@ -174,11 +180,14 @@ export class HTTPServer {
|
|||
const hrArray: number[] = [0, Math.trunc(pt * 1000000)];
|
||||
resolveAction(hrArray);
|
||||
}
|
||||
this.processSession(routeRequest, routeReply);
|
||||
await requestEvent.respondWith(response);
|
||||
continue;
|
||||
}
|
||||
|
||||
const routeName = `${requestEvent.request.method}@${filepath}`;
|
||||
const routeName = `${requestEvent.request.method}@${
|
||||
filepath.replace(/(?!\/)\W\D.*/gm, "")
|
||||
}`;
|
||||
let route = this.routes.get(routeName);
|
||||
|
||||
if (route) {
|
||||
|
@ -196,6 +205,7 @@ export class HTTPServer {
|
|||
const hrArray: number[] = [0, Math.trunc(pt * 1000000)];
|
||||
resolveAction(hrArray);
|
||||
}
|
||||
this.processSession(routeRequest, routeReply);
|
||||
await requestEvent.respondWith(
|
||||
new Response(handler as string, {
|
||||
status: routeReply.statusCode,
|
||||
|
@ -235,6 +245,8 @@ export class HTTPServer {
|
|||
const hrArray: number[] = [0, Math.trunc(pt * 1000000)];
|
||||
resolveAction(hrArray);
|
||||
}
|
||||
|
||||
this.processSession(routeRequest, routeReply);
|
||||
await requestEvent.respondWith(
|
||||
new Response(handler as string, {
|
||||
status: routeReply.statusCode,
|
||||
|
@ -249,6 +261,7 @@ export class HTTPServer {
|
|||
const hrArray: number[] = [0, Math.trunc(pt * 1000000)];
|
||||
resolveAction(hrArray);
|
||||
}
|
||||
this.processSession(routeRequest, routeReply);
|
||||
this.handleNotFound(routeRequest, routeReply, requestEvent);
|
||||
}
|
||||
} catch (_err) {
|
||||
|
@ -257,6 +270,17 @@ export class HTTPServer {
|
|||
}
|
||||
}
|
||||
|
||||
private processSession(routeRequest: RouteRequest, routeReply: RouteReply) {
|
||||
if (this.settings?.sessionSecret) {
|
||||
const sessionObject = JSON.stringify(routeRequest.session);
|
||||
const encodedSession = encryptData(
|
||||
sessionObject,
|
||||
this.settings?.sessionSecret,
|
||||
);
|
||||
routeReply.cookie("session", encodedSession);
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
|
@ -327,6 +351,34 @@ export const extractRouteParams: (route: string) => RouteParam[] = (route) =>
|
|||
return accum;
|
||||
}, []);
|
||||
|
||||
function encryptData(data: string, key: string) {
|
||||
const te = new TextEncoder();
|
||||
const aeskey = te.encode(key);
|
||||
const encodeddata = te.encode(data);
|
||||
const iv = new Uint8Array(16);
|
||||
const cipher = new Cbc(Aes, aeskey, iv, Padding.PKCS7);
|
||||
const encrypted = cipher.encrypt(encodeddata);
|
||||
const hexed = Array.from(encrypted).map((b) =>
|
||||
b.toString(16).padStart(2, "0")
|
||||
).join("");
|
||||
return hexed;
|
||||
}
|
||||
|
||||
function decryptHex(data: string, key: string) {
|
||||
const te = new TextEncoder();
|
||||
const td = new TextDecoder();
|
||||
const byteArray = new Uint8Array(data.length / 2);
|
||||
for (let i = 0; i < data.length; i += 2) {
|
||||
const byte = parseInt(data.substring(i, i + 2), 16);
|
||||
byteArray[Math.floor(i / 2)] = byte;
|
||||
}
|
||||
const aeskey = te.encode(key);
|
||||
const iv = new Uint8Array(16);
|
||||
const decipher = new Cbc(Aes, aeskey, iv, Padding.PKCS7);
|
||||
const decrypted = decipher.decrypt(byteArray);
|
||||
return td.decode(decrypted);
|
||||
}
|
||||
|
||||
export class Route {
|
||||
routeName: string;
|
||||
path: string;
|
||||
|
@ -345,31 +397,48 @@ export class RouteRequest {
|
|||
url: string;
|
||||
path: string;
|
||||
headers: Headers;
|
||||
cookies: Record<string, string>;
|
||||
method: HTTPMethod;
|
||||
queryParams: { [key: string]: string };
|
||||
pathParams: { [key: string]: string };
|
||||
remoteIpAddr: string;
|
||||
resourceRequest: boolean;
|
||||
session: { [key: string]: unknown } = {};
|
||||
|
||||
constructor(
|
||||
httpServer: HTTPServer,
|
||||
request: Request,
|
||||
conn: Deno.Conn,
|
||||
path: string,
|
||||
url: string,
|
||||
staticServePath: string,
|
||||
) {
|
||||
this.url = url;
|
||||
this.path = decodeURIComponent(path);
|
||||
this.headers = request.headers;
|
||||
this.method = request.method as HTTPMethod;
|
||||
this.pathParams = {};
|
||||
this.resourceRequest = staticServePath.length > 0
|
||||
? path.startsWith(staticServePath)
|
||||
this.resourceRequest = httpServer.settings?.staticServePath &&
|
||||
httpServer.settings?.staticServePath.length > 0
|
||||
? path.startsWith(httpServer.settings?.staticServePath)
|
||||
: false;
|
||||
this.queryParams = Object.fromEntries(new URL(url).searchParams.entries());
|
||||
this.cookies = cookie.getCookies(this.headers);
|
||||
this.remoteIpAddr = "hostname" in conn.remoteAddr
|
||||
? conn.remoteAddr["hostname"]
|
||||
: "127.0.0.1";
|
||||
|
||||
const sessionCookie = this.cookie("session") as string;
|
||||
if (sessionCookie && httpServer.settings?.sessionSecret) {
|
||||
const decodedSessionCookie = decryptHex(
|
||||
sessionCookie,
|
||||
httpServer.settings.sessionSecret,
|
||||
);
|
||||
try{
|
||||
this.session = JSON.parse(decodedSessionCookie);
|
||||
}catch(_err){
|
||||
// Ignore if sessionCookie is not in JSON format
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header(name: string): unknown {
|
||||
|
@ -380,9 +449,8 @@ export class RouteRequest {
|
|||
}
|
||||
|
||||
cookie(name: string): unknown {
|
||||
const allCookies = cookie.getCookies(this.headers);
|
||||
const allCookieNames = Object.keys(allCookies);
|
||||
return allCookieNames.includes(name) ? allCookies[name] : undefined;
|
||||
const allCookieNames = Object.keys(this.cookies);
|
||||
return allCookieNames.includes(name) ? this.cookies[name] : undefined;
|
||||
}
|
||||
|
||||
pathParam(name: string): string {
|
||||
|
|
Reference in New Issue
Block a user