@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.
- package/lib/cjs/index.d.ts +37 -0
- package/lib/cjs/index.d.ts.map +1 -0
- package/lib/cjs/index.js +178 -0
- package/lib/cjs/index.js.map +1 -0
- package/lib/cjs/package.json +1 -0
- package/lib/mjs/index.d.ts +37 -0
- package/lib/mjs/index.d.ts.map +1 -0
- package/lib/mjs/index.js +175 -0
- package/lib/mjs/index.js.map +1 -0
- package/lib/mjs/package.json +1 -0
- package/lib/tsconfig.cjs.tsbuildinfo +1 -0
- package/lib/tsconfig.mjs.tsbuildinfo +1 -0
- package/package.json +17 -15
|
@@ -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"}
|
package/lib/cjs/index.js
ADDED
|
@@ -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"}
|
package/lib/mjs/index.js
ADDED
|
@@ -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"}
|