work on security features, logger, snowflakes, http servers

This commit is contained in:
2023-07-19 22:02:14 -07:00
parent e26ba0297a
commit 13457ec125
25 changed files with 1875 additions and 97 deletions

41
src/http/cookies.ts Normal file
View File

@@ -0,0 +1,41 @@
import { FastifyReply, FastifyRequest } from 'fastify';
export type SameSite = 'Strict' | 'Lax' | 'None';
export function set_cookie(res: FastifyReply, name: string, value: string, expires: Date, secure: boolean, same_site: SameSite = 'Strict', path?: string) {
const cookie
= `${name}=${value}; `
+ `Expires=${expires.toUTCString()}; `
+ (path ? ` Path=${path}; ` : '')
+ `HttpOnly; `
+ `SameSite=${same_site};`
+ (secure ? ' Secure;' : '')
;
res.header('set-cookie', cookie);
}
export function invalidate_cookie(res: FastifyReply, name: string, secure: boolean, path?: string) {
set_cookie(res, name, 'invalidate', new Date(0), secure, 'Strict', path);
}
export function parse_req_cookies(req: FastifyRequest) : Record<string, string> {
const result: Record<string, string> = { };
const cookies = req.headers.cookie || '';
for (const cookie of cookies.split(';')) {
const index = cookie.indexOf('=');
if (index < 0) {
continue;
}
const name = cookie.slice(0, index).trim();
const value = cookie.slice(index + 1).trim();
result[name] = decodeURIComponent(value);
}
return result;
}

View File

@@ -0,0 +1,85 @@
import { DateTime } from 'luxon';
import { IncomingHttpHeaders } from 'http';
export interface ParsedCacheHeaders {
age?: number;
etag?: string;
vary?: string[];
date?: DateTime;
cache_control?: CacheControl;
expires?: DateTime;
last_modified?: DateTime;
}
export interface CacheControl {
private?: boolean;
no_store?: boolean;
no_cache?: boolean;
max_age?: number;
must_revalidate?: boolean;
proxy_revalidate?: boolean;
}
export function parse_cache_headers(headers: IncomingHttpHeaders) {
const result: ParsedCacheHeaders = { };
if (headers['age']) {
result.age = parseInt(headers['age'], 10);
}
if (headers['etag']) {
result.etag = headers['etag'];
}
if (headers['vary']) {
result.vary = headers['vary'].split(',').map((str) => str.trim().toLowerCase());
}
if (headers['date']) {
result.date = DateTime.fromHTTP(headers['date']);
}
if (headers['cache-control']) {
result.cache_control = { };
for (let directive of headers['cache-control'].split(',')) {
directive = directive.trim();
switch (directive) {
case 'private':
result.cache_control.private = true;
break;
case 'no-store':
result.cache_control.no_store = true;
break;
case 'no-cache':
result.cache_control.no_cache = true;
break;
case 'must-revalidate':
result.cache_control.must_revalidate = true;
break;
case 'proxy-revalidate':
result.cache_control.proxy_revalidate = true;
break;
default:
if (directive.startsWith('max-age=')) {
result.cache_control.max_age = parseInt(directive.slice(8), 10);
break;
}
// todo: log something here about unknown directive
}
}
}
if (headers['expires']) {
result.expires = DateTime.fromHTTP(headers['expires']);
}
if (headers['last-modified']) {
result.last_modified = DateTime.fromHTTP(headers['last-modified']);
}
return result;
}

12
src/http/request.ts Normal file
View File

@@ -0,0 +1,12 @@
import { FastifyRequest } from 'fastify';
import { RouteGenericInterface } from 'fastify/types/route';
import { SessionKey } from '../security/session-key';
import { SessionData } from '../storage';
export type Req<T = RouteGenericInterface> = FastifyRequest<T> & {
session?: {
key: SessionKey;
data: SessionData;
};
};