UNPKG

sveltekit-password-protect

Version:

Simple utility to add a layer of protection to your websites, very useful for agencies and freelancers

163 lines (162 loc) 5.73 kB
import crypto from 'crypto'; import { createSigner, createVerifier } from 'fast-jwt'; export class SessionStore { cacheAdapter; defaultOptions; namespace; constructor(cacheAdapter, defaultOptions) { this.cacheAdapter = cacheAdapter; this.defaultOptions = defaultOptions; this.cacheAdapter = cacheAdapter; this.defaultOptions = defaultOptions; this.namespace = `SessionStore:${defaultOptions.cookieName}`; } async generateSessionToken() { const sessionToken = crypto.randomBytes(32).toString('base64'); await this.setSession(sessionToken, this.defaultOptions.sessionTTL); return sessionToken; } async setSession(sessionToken, ttl) { await this.cacheAdapter.set(`${this.namespace}:${sessionToken}`, 'true', ttl); } async getSession(sessionToken) { return Boolean(await this.cacheAdapter.get(`${this.namespace}:${sessionToken}`)); } async deleteSession(sessionToken) { await this.cacheAdapter.delete(`${this.namespace}:${sessionToken}`); } getCookieHeaderValue(token) { return `${this.defaultOptions.cookieName}=${token}; Path=/; Secure; HttpOnly; SameSite=Strict; Max-Age=${this.defaultOptions.sessionTTL}`; } } export class SessionJWTStore { defaultOptions; signer; verifier; constructor(defaultOptions) { this.defaultOptions = defaultOptions; this.signer = createSigner({ key: this.defaultOptions.jwtSecret, algorithm: 'HS256' }); this.verifier = createVerifier({ key: this.defaultOptions.jwtSecret }); } async generateSessionToken() { const sessionToken = crypto.randomBytes(32).toString('base64'); const payload = { sessionToken, exp: Math.floor(Date.now() / 1000) + this.defaultOptions.sessionTTL }; return this.signer(payload); } // No-op for JWT-based sessions // eslint-disable-next-line @typescript-eslint/no-unused-vars async setSession(_sessionToken, _ttl) { return true; } async getSession(jwtToken) { try { const maybePromise = this.verifier(jwtToken); const payload = maybePromise instanceof Promise ? await maybePromise : maybePromise; return Boolean(payload && payload.sessionToken); } catch (e) { console.error(e); return false; } } // No-op for JWT-based sessions // eslint-disable-next-line @typescript-eslint/no-unused-vars async deleteSession(_sessionToken) { return true; } getCookieHeaderValue(token) { return `${this.defaultOptions.cookieName}=${token}; Path=/; Secure; HttpOnly; SameSite=Strict; Max-Age=${this.defaultOptions.sessionTTL}`; } } export class CSRFTokenStore { cacheAdapter; defaultOptions; namespace; constructor(cacheAdapter, defaultOptions) { this.cacheAdapter = cacheAdapter; this.defaultOptions = defaultOptions; this.namespace = `CSRFTokenStore:${defaultOptions.cookieName}`; } async generateCSRFToken() { const csrfToken = crypto.randomUUID(); await this.cacheAdapter.set(`${this.namespace}:${csrfToken}`, 'true', 1000 * 60 * 10); return csrfToken; } async getCSRFToken(csrfToken) { return Boolean(await this.cacheAdapter.get(`${this.namespace}:${csrfToken}`)); } async deleteCSRFToken(csrfToken) { await this.cacheAdapter.delete(`${this.namespace}:${csrfToken}`); } } export class FakeCSRFTokenStore { async generateCSRFToken() { return crypto.randomUUID(); } // eslint-disable-next-line @typescript-eslint/no-unused-vars async getCSRFToken(_csrfToken) { return true; } // eslint-disable-next-line @typescript-eslint/no-unused-vars async deleteCSRFToken(_csrfToken) { return true; } } export class RateLimiter { defaultOptions; failedCalls; constructor(defaultOptions) { this.defaultOptions = defaultOptions; this.failedCalls = new Map(); } incrementFailedCall(ipOrCookie) { this.failedCalls.set(ipOrCookie, (this.failedCalls.get(ipOrCookie) || 0) + 1); setTimeout(() => this.decrementFailedCall(ipOrCookie), this.defaultOptions.rateLimitTTL); return this.failedCalls.get(ipOrCookie) || 0; } decrementFailedCall(ipOrCookie) { this.failedCalls.set(ipOrCookie, (this.failedCalls.get(ipOrCookie) || 0) - 1); } checkRateLimit(ipOrCookie) { const current = this.incrementFailedCall(ipOrCookie) || 0; return current > this.defaultOptions.maxFailedCalls; } } export class JWTManagement { defaultOptions; signer; verifier; constructor(defaultOptions) { this.defaultOptions = defaultOptions; this.defaultOptions = defaultOptions; this.signer = createSigner({ key: this.defaultOptions.jwtSecret, algorithm: 'HS256' }); this.verifier = createVerifier({ key: this.defaultOptions.jwtSecret }); } generateJWT() { const sessionToken = crypto.randomBytes(32).toString('base64'); const payload = { sessionToken, exp: Math.floor(Date.now() / 1000) + this.defaultOptions.sessionTTL }; return this.signer(payload); } verifyJWT(jwtToken) { return this.verifier(jwtToken); } getJWTCookieHeaderValue(jwtToken) { return `${this.defaultOptions.cookieName}=${jwtToken}; Path=/; Secure; HttpOnly; SameSite=Strict; Max-Age=${this.defaultOptions.sessionTTL}`; } }