@ethercorps/sveltekit-redis-session
Version:
A library which uses svelte compiler to convert html & css to jsx. Useful for using satori with svelte & Kit
197 lines (196 loc) • 10 kB
JavaScript
import { defaultCookiesOption, defaultRenewBeforeSeconds, formattedReturn, generateRandomString, getSessionKey, getUserSessionKey, signSessionKey, validateCookie } from '../shared.js';
export class RedisSessionStore {
redisClient;
secret;
cookieName;
uniqueIdGenerator = generateRandomString;
sessionPrefix;
userSessionsPrefix;
signedCookies;
useTTL;
ttlSeconds;
renewSessionBeforeExpire;
renewBeforeSeconds;
serializer;
cookieOptions;
constructor({ redisClient, secret, cookieName = 'session', sessionPrefix = 'sk_ioredis_session', userSessionsPrefix = 'sk_ioredis_user_sessions', signed = true, useTTL = true, renewSessionBeforeExpire = false, renewBeforeSeconds = defaultRenewBeforeSeconds, serializer = JSON, cookiesOptions = {} }) {
if (!redisClient) {
throw new Error('A pre-initiated redis client must be provided to the RedisStore');
}
redisClient.on('connect', () => {
console.log('Connected to Redis');
});
redisClient.on('error', (error) => {
console.error(`Error connecting to Redis: ${error}`);
throw new Error('Unable to connect with RedisClient');
});
if (cookiesOptions && cookiesOptions.maxAge && cookiesOptions.maxAge < 1) {
console.log('Please define a valid time in cookies maxAge parameter');
throw new Error('Invalid maxAge in cookies options');
}
if (renewSessionBeforeExpire && renewBeforeSeconds && renewBeforeSeconds < 1) {
console.log('Please define a valid time in renewBeforeSeconds');
throw new Error('Invalid renewBeforeSeconds in options');
}
this.redisClient = redisClient;
this.secret = secret;
this.cookieName = cookieName;
this.uniqueIdGenerator = generateRandomString;
this.sessionPrefix = sessionPrefix;
this.userSessionsPrefix = userSessionsPrefix;
this.signedCookies = signed;
this.useTTL = useTTL;
this.renewSessionBeforeExpire = renewSessionBeforeExpire || false;
this.renewBeforeSeconds = renewBeforeSeconds || defaultRenewBeforeSeconds;
this.serializer = serializer || JSON;
this.cookieOptions = { ...defaultCookiesOption, ...cookiesOptions };
this.ttlSeconds = this.cookieOptions.maxAge;
}
createSession = async (cookies, sessionData, userId) => {
let sessionKey = this.uniqueIdGenerator();
let serializedSessionData;
try {
serializedSessionData = this.serializer.stringify(sessionData);
}
catch (er) {
console.log('Error in Set Session while serializing', er);
return formattedReturn(null, true, 'Unable to stringify session data.');
}
const prefixedSessionKey = getSessionKey(this.sessionPrefix, sessionKey);
await Promise.all([
this.redisClient.set(prefixedSessionKey, serializedSessionData, serializedSessionData),
this.redisClient.sAdd(getUserSessionKey(this.userSessionsPrefix, userId), sessionKey)
]);
if (this.useTTL && this.ttlSeconds) {
await this.redisClient.expire(prefixedSessionKey, this.ttlSeconds);
}
if (this.signedCookies) {
sessionKey = await signSessionKey(sessionKey, this.secret);
}
cookies.set(this.cookieName, sessionKey, this.cookieOptions);
return formattedReturn(sessionKey, false, 'Successfully created new session.');
};
getSession = async (cookies) => {
const { data: sessionId, error, message } = await validateCookie(cookies, this.cookieName, this.secret, this.signedCookies);
if (error)
return formattedReturn(sessionId, error, message);
const sessionData = await this.redisClient.get(getSessionKey(this.sessionPrefix, sessionId));
if (!sessionData)
return formattedReturn(null, true, `Unable to find data for the provided key - ${sessionId}`);
let parsedSession;
try {
parsedSession = this.serializer.parse(sessionData);
}
catch (err) {
console.log(err);
return formattedReturn(null, true, 'Unable to parse the session data.');
}
if (this.renewSessionBeforeExpire) {
const sessionValidity = await this.redisClient.ttl(getSessionKey(this.sessionPrefix, sessionId));
if (sessionValidity < this.renewBeforeSeconds && this.ttlSeconds) {
const { error, message } = await this.updateSessionExpiry(cookies, true, sessionId);
if (error) {
console.log(message);
}
}
}
return formattedReturn(parsedSession, false, 'Session Data'); // return session data
};
// From lucia auth & made my own changes
getSessionsByUserId = async (userId) => {
const sessionIds = await this.redisClient.sMembers(getUserSessionKey(this.userSessionsPrefix, userId));
if (!sessionIds)
return formattedReturn(null, true, `Unable to find session for user: ${userId}.`);
const sessionData = await Promise.all(sessionIds.map((sessionId) => this.redisClient.get(getSessionKey(this.sessionPrefix, sessionId))));
const sessions = sessionData
.filter((val) => val !== null)
.map((val) => this.serializer.parse(val));
return formattedReturn(sessions, false, `We found ${sessionData.length} active session for user: ${userId}`);
};
// till here
deleteSession = async (cookies, userId = null) => {
const { data: sessionId, error, message } = await validateCookie(cookies, this.cookieName, this.secret, this.signedCookies);
if (error) {
console.log('Error in delSession method', message);
return formattedReturn(sessionId, error, 'Unable to validate key while deleting');
}
const prefixedSessionKey = getSessionKey(this.sessionPrefix, sessionId);
const sessionData = await this.redisClient.get(prefixedSessionKey);
if (!sessionData)
return formattedReturn(sessionId, true, `Not a valid session key`);
if (userId) {
await Promise.all([
this.redisClient.del(prefixedSessionKey),
this.redisClient.sRem(getUserSessionKey(this.userSessionsPrefix, userId), sessionId)
]);
}
else {
await this.redisClient.del(prefixedSessionKey);
}
await this.deleteCookie(cookies);
return formattedReturn(sessionId, false, `Key successfully deleted`); // Returns unique key without prefix which is deleted from redis
};
deleteSessionsByUserId = async (userId) => {
const sessionIds = await this.redisClient.sMembers(getUserSessionKey(this.userSessionsPrefix, userId));
if (!sessionIds)
return formattedReturn(null, true, `Unable to find session for user: ${userId}.`);
await Promise.all([
...sessionIds.map((sessionId) => this.redisClient.del(getSessionKey(this.sessionPrefix, sessionId))),
this.redisClient.del(getUserSessionKey(this.userSessionsPrefix, userId))
]);
return formattedReturn(userId, false, `Successfully deleted`); // Returns userId without prefix which is deleted from redis
};
deleteCookie = async (cookies) => {
const allCookieOptionsCopy = { ...this.cookieOptions };
delete allCookieOptionsCopy.maxAge;
try {
cookies.delete(this.cookieName, allCookieOptionsCopy);
}
catch (err) {
console.log('error while deleting cookies in deleteCookie method', err);
}
};
async updateSession(cookies, sessionData = {}) {
const { data: sessionId, error, message } = await validateCookie(cookies, this.cookieName, this.secret, this.signedCookies);
if (error) {
console.log('Error in updateSessionExpiry method', message);
return formattedReturn(sessionId, error, 'Unable to validate key while updating session');
}
const keyWithPrefix = getSessionKey(this.sessionPrefix, sessionId);
let serializedSessionData;
try {
serializedSessionData = this.serializer.stringify(sessionData);
}
catch (er) {
console.log('Error in Set Session while serializing', er);
return formattedReturn(null, true, 'Unable to stringify session data.');
}
await this.redisClient.set(keyWithPrefix, serializedSessionData);
if (this.useTTL && this.ttlSeconds) {
await this.redisClient.expire(keyWithPrefix, this.ttlSeconds);
}
return formattedReturn(sessionId, false, 'Cookie data has been updated');
}
updateSessionExpiry = async (cookies, skipValidation = false, key = '') => {
let uniqueKey = key;
if (!skipValidation) {
const { data: sessionKey, error, message } = await validateCookie(cookies, this.cookieName, this.secret, this.signedCookies);
if (error) {
console.log('Error in updateSessionExpiry method', message);
return formattedReturn(sessionKey, error, 'Unable to validate key while updating session');
}
uniqueKey = sessionKey;
}
let isExpireTimeUpdated = true;
if (this.useTTL && this.ttlSeconds) {
isExpireTimeUpdated = await this.redisClient.expire(getSessionKey(this.sessionPrefix, uniqueKey), this.ttlSeconds);
}
if (isExpireTimeUpdated) {
if (this.signedCookies)
uniqueKey = await signSessionKey(uniqueKey, this.secret);
cookies.set(this.cookieName, uniqueKey, this.cookieOptions);
return formattedReturn(uniqueKey, false, 'Session validity extended successfully');
}
return formattedReturn(null, true, 'Unable to extended session validity');
};
}