@accounts/redis
Version:
Redis adaptor for accounts
104 lines • 4.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisSessions = void 0;
const shortid_1 = require("shortid");
const defaultOptions = {
userCollectionName: 'users',
sessionCollectionName: 'sessions',
timestamps: {
createdAt: 'createdAt',
updatedAt: 'updatedAt',
},
idProvider: () => (0, shortid_1.generate)(),
dateProvider: (date) => (date ? date.getTime() : Date.now()),
};
class RedisSessions {
options;
db;
constructor(db, options = {}) {
this.options = {
...defaultOptions,
...options,
timestamps: { ...defaultOptions.timestamps, ...options.timestamps },
};
if (!db) {
throw new Error('A database connection is required');
}
this.db = db;
}
async createSession(userId, token, connection = {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
extraData) {
const sessionId = this.options.idProvider();
const pipeline = this.db.pipeline();
pipeline.hmset(`${this.options.sessionCollectionName}:${sessionId}`, {
userId,
token,
userAgent: connection.userAgent,
ip: connection.ip,
valid: true,
[this.options.timestamps.createdAt]: this.options.dateProvider(),
[this.options.timestamps.updatedAt]: this.options.dateProvider(),
});
// Push the sessionId inside the userId
pipeline.sadd(`${this.options.sessionCollectionName}:${this.options.userCollectionName}:${userId}`, sessionId);
// Link the session token to the sessionId
pipeline.set(`${this.options.sessionCollectionName}:token:${token}`, sessionId);
await pipeline.exec();
return sessionId;
}
async updateSession(sessionId, connection) {
if (await this.db.exists(`${this.options.sessionCollectionName}:${sessionId}`)) {
await this.db.hmset(`${this.options.sessionCollectionName}:${sessionId}`, {
userAgent: connection.userAgent ?? undefined,
ip: connection.ip ?? undefined,
[this.options.timestamps.updatedAt]: this.options.dateProvider(),
});
}
}
async invalidateSession(sessionId) {
if (await this.db.exists(`${this.options.sessionCollectionName}:${sessionId}`)) {
await this.db.hmset(`${this.options.sessionCollectionName}:${sessionId}`, {
valid: 'false',
[this.options.timestamps.updatedAt]: this.options.dateProvider(),
});
}
}
async invalidateAllSessions(userId, excludedSessionIds) {
if (await this.db.exists(`${this.options.sessionCollectionName}:${this.options.userCollectionName}:${userId}`)) {
let sessionIds = await this.db.smembers(`${this.options.sessionCollectionName}:${this.options.userCollectionName}:${userId}`);
if (excludedSessionIds && excludedSessionIds.length > 0) {
sessionIds = sessionIds.filter((sessionId) => {
return !excludedSessionIds.includes(sessionId);
});
}
await sessionIds.map((sessionId) => this.invalidateSession(sessionId));
}
}
async findSessionByToken(token) {
if (await this.db.exists(`${this.options.sessionCollectionName}:token:${token}`)) {
const sessionId = await this.db.get(`${this.options.sessionCollectionName}:token:${token}`);
if (sessionId) {
return this.findSessionById(sessionId);
}
}
return null;
}
async findSessionById(sessionId) {
if (await this.db.exists(`${this.options.sessionCollectionName}:${sessionId}`)) {
const session = await this.db.hgetall(`${this.options.sessionCollectionName}:${sessionId}`);
return this.formatSession(sessionId, session);
}
return null;
}
/**
* We need to format the session to have an object the server can understand.
*/
formatSession(sessionId, session) {
// Redis doesn't store boolean values, so we need turn this string into a boolean
session.valid = session.valid === 'true';
return { id: sessionId, ...session };
}
}
exports.RedisSessions = RedisSessions;
//# sourceMappingURL=redis.js.map