Skip to content

Commit

Permalink
Organized modules
Browse files Browse the repository at this point in the history
  • Loading branch information
ciscoheat committed Sep 19, 2024
1 parent 9f36f82 commit c4e3c1a
Show file tree
Hide file tree
Showing 10 changed files with 527 additions and 505 deletions.
5 changes: 3 additions & 2 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { RateLimiter, RetryAfterRateLimiter } from '$lib/server';
import type { Rate, RateLimiterPlugin } from '$lib/server/index.js';
import { RateLimiter } from '$lib/server/rateLimiter.js';
import { RetryAfterRateLimiter } from '$lib/server/retryAfterRateLimiter.js';
import type { RequestEvent } from '@sveltejs/kit';
import { describe, it, expect, beforeEach } from 'vitest';
import { mock } from 'vitest-mock-extended';
import type { Rate, RateLimiterPlugin } from '$lib/server';

const hashFunction = (input: string) => {
const msgUint8 = new TextEncoder().encode(input);
Expand Down
82 changes: 82 additions & 0 deletions src/lib/limiters/cookieRateLimiter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type { HashFunction, Rate, RateLimiterPlugin } from '$lib/server';
import { defaultHashFunction } from '$lib/server/hashFunction';
import type { Cookies, RequestEvent } from '@sveltejs/kit';
import { nanoid } from 'nanoid';

export type CookieSerializeOptions = NonNullable<Parameters<Cookies['set']>[2]>;

export type CookieRateLimiterOptions = {
name: string;
secret: string;
rate: Rate | Rate[];
preflight: boolean;
serializeOptions?: CookieSerializeOptions;
hashFunction?: HashFunction;
};

export class CookieRateLimiter implements RateLimiterPlugin {
readonly rate: Rate | Rate[];
private readonly cookieOptions: CookieSerializeOptions;
private readonly secret: string;
private readonly requirePreflight: boolean;
private readonly cookieId: string;
private readonly hashFunction: HashFunction;

constructor(options: CookieRateLimiterOptions) {
this.cookieId = options.name;
this.secret = options.secret;
this.rate = options.rate;
this.requirePreflight = options.preflight;
this.hashFunction = options.hashFunction ?? defaultHashFunction;

this.cookieOptions = {
path: '/',
httpOnly: true,
maxAge: 60 * 60 * 24 * 7,
sameSite: 'strict',
...options.serializeOptions
};
}

async hash(event: RequestEvent) {
const currentId = await this.userIdFromCookie(
event.cookies.get(this.cookieId),
event
);
return currentId ? currentId : false;
}

async preflight(event: RequestEvent): Promise<string> {
const data = event.cookies.get(this.cookieId);
if (data) {
const userId = await this.userIdFromCookie(data, event);
if (userId) return userId;
}

const userId = nanoid();

event.cookies.set(
this.cookieId,
userId + ';' + (await this.hashFunction(this.secret + userId)),
this.cookieOptions
);
return userId;
}

private async userIdFromCookie(
cookie: string | undefined,
event: RequestEvent
): Promise<string | null> {
const empty = () => {
return this.requirePreflight ? null : this.preflight(event);
};

if (!cookie) return empty();
const [userId, secretHash] = cookie.split(';');
if (!userId || !secretHash) return empty();
if ((await this.hashFunction(this.secret + userId)) != secretHash) {
return empty();
}
return userId;
}
}
14 changes: 14 additions & 0 deletions src/lib/limiters/ipRateLimiter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Rate, RateLimiterPlugin } from '$lib/server';
import type { RequestEvent } from '@sveltejs/kit';

export class IPRateLimiter implements RateLimiterPlugin {
readonly rate: Rate | Rate[];

constructor(rate: Rate | Rate[]) {
this.rate = rate;
}

async hash(event: RequestEvent) {
return event.getClientAddress();
}
}
16 changes: 16 additions & 0 deletions src/lib/limiters/ipUaRateLimiter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Rate, RateLimiterPlugin } from '$lib/server';
import type { RequestEvent } from '@sveltejs/kit';

export class IPUserAgentRateLimiter implements RateLimiterPlugin {
readonly rate: Rate | Rate[];

constructor(rate: Rate | Rate[]) {
this.rate = rate;
}

async hash(event: RequestEvent) {
const ua = event.request.headers.get('user-agent');
if (!ua) return false;
return event.getClientAddress() + ua;
}
}
17 changes: 17 additions & 0 deletions src/lib/server/hashFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { HashFunction } from '$lib/server/index.js';

export let defaultHashFunction: HashFunction;

if (globalThis?.crypto?.subtle) {
defaultHashFunction = _subtleSha256;
}

async function _subtleSha256(str: string) {
const digest = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(str)
);
return [...new Uint8Array(digest)]
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}
Loading

0 comments on commit c4e3c1a

Please # to comment.