UNPKG

4.18 kBPlain TextView Raw
1import * as IORedis from 'ioredis';
2import * as shortid from 'shortid';
3import { merge } from 'lodash';
4import { Session, DatabaseInterfaceSessions, ConnectionInformations } from '@accounts/types';
5import { AccountsRedisOptions } from './types';
6
7const defaultOptions = {
8 userCollectionName: 'users',
9 sessionCollectionName: 'sessions',
10 timestamps: {
11 createdAt: 'createdAt',
12 updatedAt: 'updatedAt',
13 },
14 idProvider: () => shortid.generate(),
15 dateProvider: (date?: Date) => (date ? date.getTime() : Date.now()),
16};
17
18export class RedisSessions implements DatabaseInterfaceSessions {
19 private options: AccountsRedisOptions & typeof defaultOptions;
20 private db: IORedis.Redis;
21
22 constructor(db: IORedis.Redis, options?: AccountsRedisOptions) {
23 this.options = merge({ ...defaultOptions }, options);
24 if (!db) {
25 throw new Error('A database connection is required');
26 }
27 this.db = db;
28 }
29
30 public async createSession(
31 userId: string,
32 token: string,
33 connection: ConnectionInformations = {},
34 extraData?: object
35 ): Promise<string> {
36 const sessionId = this.options.idProvider();
37 const pipeline = this.db.pipeline();
38 pipeline.hmset(`${this.options.sessionCollectionName}:${sessionId}`, {
39 userId,
40 token,
41 userAgent: connection.userAgent,
42 ip: connection.ip,
43 valid: true,
44 [this.options.timestamps.createdAt]: this.options.dateProvider(),
45 [this.options.timestamps.updatedAt]: this.options.dateProvider(),
46 });
47 // Push the sessionId inside the userId
48 pipeline.sadd(
49 `${this.options.sessionCollectionName}:${this.options.userCollectionName}:${userId}`,
50 sessionId
51 );
52 // Link the session token to the sessionId
53 pipeline.set(`${this.options.sessionCollectionName}:token:${token}`, sessionId);
54 await pipeline.exec();
55 return sessionId;
56 }
57
58 public async updateSession(sessionId: string, connection: ConnectionInformations): Promise<void> {
59 if (await this.db.exists(`${this.options.sessionCollectionName}:${sessionId}`)) {
60 await this.db.hmset(`${this.options.sessionCollectionName}:${sessionId}`, {
61 userAgent: connection.userAgent,
62 ip: connection.ip,
63 [this.options.timestamps.updatedAt]: this.options.dateProvider(),
64 });
65 }
66 }
67
68 public async invalidateSession(sessionId: string): Promise<void> {
69 if (await this.db.exists(`${this.options.sessionCollectionName}:${sessionId}`)) {
70 await this.db.hmset(`${this.options.sessionCollectionName}:${sessionId}`, {
71 valid: false,
72 [this.options.timestamps.updatedAt]: this.options.dateProvider(),
73 });
74 }
75 }
76
77 public async invalidateAllSessions(userId: string): Promise<void> {
78 if (
79 await this.db.exists(
80 `${this.options.sessionCollectionName}:${this.options.userCollectionName}:${userId}`
81 )
82 ) {
83 const sessionIds: string[] = await this.db.smembers(
84 `${this.options.sessionCollectionName}:${this.options.userCollectionName}:${userId}`
85 );
86 await sessionIds.map(sessionId => this.invalidateSession(sessionId));
87 }
88 }
89
90 public async findSessionByToken(token: string): Promise<Session | null> {
91 if (await this.db.exists(`${this.options.sessionCollectionName}:token:${token}`)) {
92 const sessionId = await this.db.get(`${this.options.sessionCollectionName}:token:${token}`);
93 if (sessionId) {
94 return this.findSessionById(sessionId);
95 }
96 }
97 return null;
98 }
99
100 public async findSessionById(sessionId: string): Promise<Session | null> {
101 if (await this.db.exists(`${this.options.sessionCollectionName}:${sessionId}`)) {
102 const session = await this.db.hgetall(`${this.options.sessionCollectionName}:${sessionId}`);
103 return this.formatSession(sessionId, session);
104 }
105 return null;
106 }
107
108 /**
109 * We need to format the session to have an object the server can understand.
110 */
111 private formatSession(sessionId: string, session: any): Session {
112 // Redis doesn't store boolean values, so we need turn this string into a boolean
113 session.valid = session.valid === 'true';
114 return { id: sessionId, ...session };
115 }
116}