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
JavaScript
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}`;
}
}