@spinajs/session-provider-redis 2.0.481 → 2.0.484

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ import { Redis, RedisOptions } from 'ioredis';
2
+ import { DateTime } from 'luxon';
3
+ import { SessionProvider, ISession } from '@spinajs/rbac';
4
+ import { Log } from '@spinajs/log';
5
+ /**
6
+ * Redis-backed session store (ioredis). Conforms to the `@spinajs/rbac`
7
+ * `SessionProvider` contract: ownership is the numeric `UserId`, expiration is
8
+ * owned by the injected strategy (`this.Expiration`) and persisted verbatim, and
9
+ * `Data` is (de)serialized with the shared session codec.
10
+ *
11
+ * Key layout (all under the optional connection `keyPrefix`):
12
+ * - `session:${SessionId}` → the encoded session JSON
13
+ * - `session:user:${UserId}` → a SET of that user's session ids (the index
14
+ * that keeps `deleteByUser` / `listByUser` O(members) instead of a full scan)
15
+ *
16
+ * Redis TTL mirrors `session.Expiration` (`PEXPIREAT` epoch-millis, `PERSIST` for
17
+ * a never-expiring session), but `restore` / `listByUser` also gate on
18
+ * `isExpired` explicitly — Redis TTL eviction is not relied upon for correctness.
19
+ */
20
+ export declare class RedisSessionStore extends SessionProvider {
21
+ protected Log: Log;
22
+ protected RedisConfig: RedisOptions;
23
+ protected Client: Redis;
24
+ resolve(): Promise<void>;
25
+ restore(sessionId: string): Promise<ISession | null>;
26
+ save(session: ISession): Promise<void>;
27
+ touch(session: ISession): Promise<boolean>;
28
+ delete(sessionId: string): Promise<void>;
29
+ deleteByUser(userId: number): Promise<void>;
30
+ listByUser(userId: number): Promise<ISession[]>;
31
+ truncate(): Promise<void>;
32
+ protected sessionKey(sessionId: string): string;
33
+ protected userKey(userId: number): string;
34
+ protected toSession(sessionId: string, raw: string): ISession;
35
+ protected expirationEquals(a: DateTime | undefined, b: DateTime | undefined): boolean;
36
+ }
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAqD,MAAM,eAAe,CAAC;AAG7G,OAAO,EAAU,GAAG,EAAE,MAAM,cAAc,CAAC;AAc3C;;;;;;;;;;;;;;GAcG;AACH,qBACa,iBAAkB,SAAQ,eAAe;IAEpD,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAGnB,SAAS,CAAC,WAAW,EAAE,YAAY,CAAC;IAEpC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC;IAEX,OAAO;IAMP,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAkBpD,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BtC,KAAK,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB1C,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS3C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAyB/C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBtC,SAAS,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAI/C,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAIzC,SAAS,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ;IAY7D,SAAS,CAAC,gBAAgB,CAAC,CAAC,EAAE,QAAQ,GAAG,SAAS,EAAE,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO;CAMtF"}
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.RedisSessionStore = void 0;
13
+ const ioredis_1 = require("ioredis");
14
+ const luxon_1 = require("luxon");
15
+ const rbac_1 = require("@spinajs/rbac");
16
+ const di_1 = require("@spinajs/di");
17
+ const configuration_1 = require("@spinajs/configuration");
18
+ const log_1 = require("@spinajs/log");
19
+ /**
20
+ * Redis-backed session store (ioredis). Conforms to the `@spinajs/rbac`
21
+ * `SessionProvider` contract: ownership is the numeric `UserId`, expiration is
22
+ * owned by the injected strategy (`this.Expiration`) and persisted verbatim, and
23
+ * `Data` is (de)serialized with the shared session codec.
24
+ *
25
+ * Key layout (all under the optional connection `keyPrefix`):
26
+ * - `session:${SessionId}` → the encoded session JSON
27
+ * - `session:user:${UserId}` → a SET of that user's session ids (the index
28
+ * that keeps `deleteByUser` / `listByUser` O(members) instead of a full scan)
29
+ *
30
+ * Redis TTL mirrors `session.Expiration` (`PEXPIREAT` epoch-millis, `PERSIST` for
31
+ * a never-expiring session), but `restore` / `listByUser` also gate on
32
+ * `isExpired` explicitly — Redis TTL eviction is not relied upon for correctness.
33
+ */
34
+ let RedisSessionStore = class RedisSessionStore extends rbac_1.SessionProvider {
35
+ async resolve() {
36
+ // The whole config object is passed straight to ioredis (host/port/password/
37
+ // db/keyPrefix/tls/… are all honored).
38
+ this.Client = new ioredis_1.Redis(this.RedisConfig ?? {});
39
+ }
40
+ async restore(sessionId) {
41
+ const raw = await this.Client.get(this.sessionKey(sessionId));
42
+ if (!raw) {
43
+ return null;
44
+ }
45
+ const session = this.toSession(sessionId, raw);
46
+ // Redis TTL eviction is eventual / clock-dependent — treat an expired
47
+ // session as absent, matching the contract, and self-heal the key.
48
+ if (this.isExpired(session)) {
49
+ await this.delete(sessionId);
50
+ return null;
51
+ }
52
+ return session;
53
+ }
54
+ async save(session) {
55
+ // Persist `Expiration` verbatim. Only a brand-new session with no scheduled
56
+ // expiration is given its initial expiry via the strategy (fixes B3).
57
+ if (session.Expiration === undefined) {
58
+ this.applyInitialExpiration(session);
59
+ }
60
+ const stored = {
61
+ UserId: session.UserId,
62
+ Creation: session.Creation.toISO(),
63
+ Expiration: session.Expiration ? session.Expiration.toISO() : undefined,
64
+ Data: (0, rbac_1.encodeSessionData)(session.Data),
65
+ };
66
+ const key = this.sessionKey(session.SessionId);
67
+ await this.Client.set(key, JSON.stringify(stored));
68
+ // Drive the Redis TTL from the session's real expiration; a never-expiring
69
+ // session simply has no TTL.
70
+ if (session.Expiration !== undefined) {
71
+ await this.Client.pexpireat(key, session.Expiration.toMillis());
72
+ }
73
+ else {
74
+ await this.Client.persist(key);
75
+ }
76
+ // Maintain the per-user index set so deleteByUser / listByUser stay O(members).
77
+ await this.Client.sadd(this.userKey(session.UserId), session.SessionId);
78
+ }
79
+ async touch(session) {
80
+ const current = session.Expiration;
81
+ const renewed = this.Expiration.renew(session);
82
+ // unchanged (e.g. AbsoluteExpiration) — skip the write, report false
83
+ if (this.expirationEquals(current, renewed)) {
84
+ return false;
85
+ }
86
+ session.Expiration = renewed;
87
+ // re-persist verbatim + reset the Redis TTL (save handles PEXPIREAT/PERSIST)
88
+ await this.save(session);
89
+ return true;
90
+ }
91
+ async delete(sessionId) {
92
+ // read first so we know which user index set to prune
93
+ const raw = await this.Client.get(this.sessionKey(sessionId));
94
+ await this.Client.del(this.sessionKey(sessionId));
95
+ if (raw) {
96
+ const stored = JSON.parse(raw);
97
+ await this.Client.srem(this.userKey(stored.UserId), sessionId);
98
+ }
99
+ }
100
+ async deleteByUser(userId) {
101
+ const ids = await this.Client.smembers(this.userKey(userId));
102
+ if (ids.length > 0) {
103
+ await this.Client.del(...ids.map((id) => this.sessionKey(id)));
104
+ }
105
+ await this.Client.del(this.userKey(userId));
106
+ }
107
+ async listByUser(userId) {
108
+ const ids = await this.Client.smembers(this.userKey(userId));
109
+ const sessions = [];
110
+ for (const id of ids) {
111
+ const raw = await this.Client.get(this.sessionKey(id));
112
+ // key gone (TTL-evicted) or expired → prune the dead id from the set
113
+ if (!raw) {
114
+ await this.Client.srem(this.userKey(userId), id);
115
+ continue;
116
+ }
117
+ const session = this.toSession(id, raw);
118
+ if (this.isExpired(session)) {
119
+ await this.delete(id);
120
+ continue;
121
+ }
122
+ sessions.push(session);
123
+ }
124
+ return sessions;
125
+ }
126
+ async truncate() {
127
+ // Prefix-scoped delete — only keys this store owns (session:* covers both the
128
+ // session keys and the per-user index sets). Never a blind FLUSHDB.
129
+ const prefix = this.Client.options.keyPrefix ?? '';
130
+ const pattern = `${prefix}session:*`;
131
+ let cursor = '0';
132
+ do {
133
+ const [next, keys] = await this.Client.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
134
+ cursor = next;
135
+ if (keys.length > 0) {
136
+ // SCAN returns full keys (incl. the connection keyPrefix); strip it so the
137
+ // client does not double-prefix on DEL.
138
+ const stripped = keys.map((k) => (prefix && k.startsWith(prefix) ? k.slice(prefix.length) : k));
139
+ await this.Client.del(...stripped);
140
+ }
141
+ } while (cursor !== '0');
142
+ }
143
+ sessionKey(sessionId) {
144
+ return `session:${sessionId}`;
145
+ }
146
+ userKey(userId) {
147
+ return `session:user:${userId}`;
148
+ }
149
+ toSession(sessionId, raw) {
150
+ const stored = JSON.parse(raw);
151
+ return new rbac_1.UserSession({
152
+ SessionId: sessionId,
153
+ UserId: stored.UserId,
154
+ Creation: luxon_1.DateTime.fromISO(stored.Creation),
155
+ Expiration: stored.Expiration ? luxon_1.DateTime.fromISO(stored.Expiration) : undefined,
156
+ Data: (0, rbac_1.decodeSessionData)(stored.Data),
157
+ });
158
+ }
159
+ expirationEquals(a, b) {
160
+ if (a === undefined || b === undefined) {
161
+ return a === b;
162
+ }
163
+ return a.toMillis() === b.toMillis();
164
+ }
165
+ };
166
+ exports.RedisSessionStore = RedisSessionStore;
167
+ __decorate([
168
+ (0, log_1.Logger)('redis-session-store'),
169
+ __metadata("design:type", log_1.Log)
170
+ ], RedisSessionStore.prototype, "Log", void 0);
171
+ __decorate([
172
+ (0, configuration_1.Config)('rbac.session.redis'),
173
+ __metadata("design:type", Object)
174
+ ], RedisSessionStore.prototype, "RedisConfig", void 0);
175
+ exports.RedisSessionStore = RedisSessionStore = __decorate([
176
+ (0, di_1.Injectable)(rbac_1.SessionProvider)
177
+ ], RedisSessionStore);
178
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,37 @@
1
+ import { Redis, RedisOptions } from 'ioredis';
2
+ import { DateTime } from 'luxon';
3
+ import { SessionProvider, ISession } from '@spinajs/rbac';
4
+ import { Log } from '@spinajs/log';
5
+ /**
6
+ * Redis-backed session store (ioredis). Conforms to the `@spinajs/rbac`
7
+ * `SessionProvider` contract: ownership is the numeric `UserId`, expiration is
8
+ * owned by the injected strategy (`this.Expiration`) and persisted verbatim, and
9
+ * `Data` is (de)serialized with the shared session codec.
10
+ *
11
+ * Key layout (all under the optional connection `keyPrefix`):
12
+ * - `session:${SessionId}` → the encoded session JSON
13
+ * - `session:user:${UserId}` → a SET of that user's session ids (the index
14
+ * that keeps `deleteByUser` / `listByUser` O(members) instead of a full scan)
15
+ *
16
+ * Redis TTL mirrors `session.Expiration` (`PEXPIREAT` epoch-millis, `PERSIST` for
17
+ * a never-expiring session), but `restore` / `listByUser` also gate on
18
+ * `isExpired` explicitly — Redis TTL eviction is not relied upon for correctness.
19
+ */
20
+ export declare class RedisSessionStore extends SessionProvider {
21
+ protected Log: Log;
22
+ protected RedisConfig: RedisOptions;
23
+ protected Client: Redis;
24
+ resolve(): Promise<void>;
25
+ restore(sessionId: string): Promise<ISession | null>;
26
+ save(session: ISession): Promise<void>;
27
+ touch(session: ISession): Promise<boolean>;
28
+ delete(sessionId: string): Promise<void>;
29
+ deleteByUser(userId: number): Promise<void>;
30
+ listByUser(userId: number): Promise<ISession[]>;
31
+ truncate(): Promise<void>;
32
+ protected sessionKey(sessionId: string): string;
33
+ protected userKey(userId: number): string;
34
+ protected toSession(sessionId: string, raw: string): ISession;
35
+ protected expirationEquals(a: DateTime | undefined, b: DateTime | undefined): boolean;
36
+ }
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAqD,MAAM,eAAe,CAAC;AAG7G,OAAO,EAAU,GAAG,EAAE,MAAM,cAAc,CAAC;AAc3C;;;;;;;;;;;;;;GAcG;AACH,qBACa,iBAAkB,SAAQ,eAAe;IAEpD,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAGnB,SAAS,CAAC,WAAW,EAAE,YAAY,CAAC;IAEpC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC;IAEX,OAAO;IAMP,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAkBpD,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BtC,KAAK,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB1C,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS3C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAyB/C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBtC,SAAS,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAI/C,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAIzC,SAAS,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ;IAY7D,SAAS,CAAC,gBAAgB,CAAC,CAAC,EAAE,QAAQ,GAAG,SAAS,EAAE,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO;CAMtF"}
@@ -0,0 +1,175 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Redis } from 'ioredis';
11
+ import { DateTime } from 'luxon';
12
+ import { SessionProvider, UserSession, encodeSessionData, decodeSessionData } from '@spinajs/rbac';
13
+ import { Injectable } from '@spinajs/di';
14
+ import { Config } from '@spinajs/configuration';
15
+ import { Logger, Log } from '@spinajs/log';
16
+ /**
17
+ * Redis-backed session store (ioredis). Conforms to the `@spinajs/rbac`
18
+ * `SessionProvider` contract: ownership is the numeric `UserId`, expiration is
19
+ * owned by the injected strategy (`this.Expiration`) and persisted verbatim, and
20
+ * `Data` is (de)serialized with the shared session codec.
21
+ *
22
+ * Key layout (all under the optional connection `keyPrefix`):
23
+ * - `session:${SessionId}` → the encoded session JSON
24
+ * - `session:user:${UserId}` → a SET of that user's session ids (the index
25
+ * that keeps `deleteByUser` / `listByUser` O(members) instead of a full scan)
26
+ *
27
+ * Redis TTL mirrors `session.Expiration` (`PEXPIREAT` epoch-millis, `PERSIST` for
28
+ * a never-expiring session), but `restore` / `listByUser` also gate on
29
+ * `isExpired` explicitly — Redis TTL eviction is not relied upon for correctness.
30
+ */
31
+ let RedisSessionStore = class RedisSessionStore extends SessionProvider {
32
+ async resolve() {
33
+ // The whole config object is passed straight to ioredis (host/port/password/
34
+ // db/keyPrefix/tls/… are all honored).
35
+ this.Client = new Redis(this.RedisConfig ?? {});
36
+ }
37
+ async restore(sessionId) {
38
+ const raw = await this.Client.get(this.sessionKey(sessionId));
39
+ if (!raw) {
40
+ return null;
41
+ }
42
+ const session = this.toSession(sessionId, raw);
43
+ // Redis TTL eviction is eventual / clock-dependent — treat an expired
44
+ // session as absent, matching the contract, and self-heal the key.
45
+ if (this.isExpired(session)) {
46
+ await this.delete(sessionId);
47
+ return null;
48
+ }
49
+ return session;
50
+ }
51
+ async save(session) {
52
+ // Persist `Expiration` verbatim. Only a brand-new session with no scheduled
53
+ // expiration is given its initial expiry via the strategy (fixes B3).
54
+ if (session.Expiration === undefined) {
55
+ this.applyInitialExpiration(session);
56
+ }
57
+ const stored = {
58
+ UserId: session.UserId,
59
+ Creation: session.Creation.toISO(),
60
+ Expiration: session.Expiration ? session.Expiration.toISO() : undefined,
61
+ Data: encodeSessionData(session.Data),
62
+ };
63
+ const key = this.sessionKey(session.SessionId);
64
+ await this.Client.set(key, JSON.stringify(stored));
65
+ // Drive the Redis TTL from the session's real expiration; a never-expiring
66
+ // session simply has no TTL.
67
+ if (session.Expiration !== undefined) {
68
+ await this.Client.pexpireat(key, session.Expiration.toMillis());
69
+ }
70
+ else {
71
+ await this.Client.persist(key);
72
+ }
73
+ // Maintain the per-user index set so deleteByUser / listByUser stay O(members).
74
+ await this.Client.sadd(this.userKey(session.UserId), session.SessionId);
75
+ }
76
+ async touch(session) {
77
+ const current = session.Expiration;
78
+ const renewed = this.Expiration.renew(session);
79
+ // unchanged (e.g. AbsoluteExpiration) — skip the write, report false
80
+ if (this.expirationEquals(current, renewed)) {
81
+ return false;
82
+ }
83
+ session.Expiration = renewed;
84
+ // re-persist verbatim + reset the Redis TTL (save handles PEXPIREAT/PERSIST)
85
+ await this.save(session);
86
+ return true;
87
+ }
88
+ async delete(sessionId) {
89
+ // read first so we know which user index set to prune
90
+ const raw = await this.Client.get(this.sessionKey(sessionId));
91
+ await this.Client.del(this.sessionKey(sessionId));
92
+ if (raw) {
93
+ const stored = JSON.parse(raw);
94
+ await this.Client.srem(this.userKey(stored.UserId), sessionId);
95
+ }
96
+ }
97
+ async deleteByUser(userId) {
98
+ const ids = await this.Client.smembers(this.userKey(userId));
99
+ if (ids.length > 0) {
100
+ await this.Client.del(...ids.map((id) => this.sessionKey(id)));
101
+ }
102
+ await this.Client.del(this.userKey(userId));
103
+ }
104
+ async listByUser(userId) {
105
+ const ids = await this.Client.smembers(this.userKey(userId));
106
+ const sessions = [];
107
+ for (const id of ids) {
108
+ const raw = await this.Client.get(this.sessionKey(id));
109
+ // key gone (TTL-evicted) or expired → prune the dead id from the set
110
+ if (!raw) {
111
+ await this.Client.srem(this.userKey(userId), id);
112
+ continue;
113
+ }
114
+ const session = this.toSession(id, raw);
115
+ if (this.isExpired(session)) {
116
+ await this.delete(id);
117
+ continue;
118
+ }
119
+ sessions.push(session);
120
+ }
121
+ return sessions;
122
+ }
123
+ async truncate() {
124
+ // Prefix-scoped delete — only keys this store owns (session:* covers both the
125
+ // session keys and the per-user index sets). Never a blind FLUSHDB.
126
+ const prefix = this.Client.options.keyPrefix ?? '';
127
+ const pattern = `${prefix}session:*`;
128
+ let cursor = '0';
129
+ do {
130
+ const [next, keys] = await this.Client.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
131
+ cursor = next;
132
+ if (keys.length > 0) {
133
+ // SCAN returns full keys (incl. the connection keyPrefix); strip it so the
134
+ // client does not double-prefix on DEL.
135
+ const stripped = keys.map((k) => (prefix && k.startsWith(prefix) ? k.slice(prefix.length) : k));
136
+ await this.Client.del(...stripped);
137
+ }
138
+ } while (cursor !== '0');
139
+ }
140
+ sessionKey(sessionId) {
141
+ return `session:${sessionId}`;
142
+ }
143
+ userKey(userId) {
144
+ return `session:user:${userId}`;
145
+ }
146
+ toSession(sessionId, raw) {
147
+ const stored = JSON.parse(raw);
148
+ return new UserSession({
149
+ SessionId: sessionId,
150
+ UserId: stored.UserId,
151
+ Creation: DateTime.fromISO(stored.Creation),
152
+ Expiration: stored.Expiration ? DateTime.fromISO(stored.Expiration) : undefined,
153
+ Data: decodeSessionData(stored.Data),
154
+ });
155
+ }
156
+ expirationEquals(a, b) {
157
+ if (a === undefined || b === undefined) {
158
+ return a === b;
159
+ }
160
+ return a.toMillis() === b.toMillis();
161
+ }
162
+ };
163
+ __decorate([
164
+ Logger('redis-session-store'),
165
+ __metadata("design:type", Log)
166
+ ], RedisSessionStore.prototype, "Log", void 0);
167
+ __decorate([
168
+ Config('rbac.session.redis'),
169
+ __metadata("design:type", Object)
170
+ ], RedisSessionStore.prototype, "RedisConfig", void 0);
171
+ RedisSessionStore = __decorate([
172
+ Injectable(SessionProvider)
173
+ ], RedisSessionStore);
174
+ export { RedisSessionStore };
175
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"type":"module"}