@spinajs/session-provider-db 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 +27 -6
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +91 -43
- package/lib/cjs/index.js.map +1 -1
- package/lib/mjs/index.d.ts +27 -6
- package/lib/mjs/index.d.ts.map +1 -1
- package/lib/mjs/index.js +92 -41
- package/lib/mjs/index.js.map +1 -1
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +18 -19
package/lib/cjs/index.d.ts
CHANGED
|
@@ -1,17 +1,38 @@
|
|
|
1
|
-
import { SessionProvider, ISession
|
|
1
|
+
import { SessionProvider, ISession } from '@spinajs/rbac';
|
|
2
2
|
import { Log } from '@spinajs/log';
|
|
3
3
|
export * from './models/DbSession.js';
|
|
4
4
|
export * from './migrations/UserSessionDBSqlMigration_2022_06_28_01_01_01.js';
|
|
5
|
+
/**
|
|
6
|
+
* Relational-db backed session store. 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
|
+
*/
|
|
5
11
|
export declare class DbSessionStore extends SessionProvider {
|
|
6
12
|
protected Log: Log;
|
|
7
|
-
|
|
8
|
-
|
|
13
|
+
/**
|
|
14
|
+
* How often expired rows are swept, in milliseconds. Reads the correctly
|
|
15
|
+
* spelled `rbac.session.db.cleanupInterval` key (fixes B2 — the historic typo
|
|
16
|
+
* `cleanupInteval` meant the configured value was ignored).
|
|
17
|
+
*/
|
|
18
|
+
protected CleanupInterval: number;
|
|
9
19
|
resolve(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Removes every session whose expiration has passed.
|
|
22
|
+
*
|
|
23
|
+
* Extracted from the cleanup timer so it can be exercised directly.
|
|
24
|
+
*
|
|
25
|
+
* @returns how many sessions were removed
|
|
26
|
+
*/
|
|
27
|
+
cleanupExpired(): Promise<number>;
|
|
10
28
|
restore(sessionId: string): Promise<ISession | null>;
|
|
29
|
+
save(session: ISession): Promise<void>;
|
|
30
|
+
touch(session: ISession): Promise<boolean>;
|
|
11
31
|
delete(sessionId: string): Promise<void>;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
logsOut(user: User): Promise<void>;
|
|
32
|
+
deleteByUser(userId: number): Promise<void>;
|
|
33
|
+
listByUser(userId: number): Promise<ISession[]>;
|
|
15
34
|
truncate(): Promise<void>;
|
|
35
|
+
private toSession;
|
|
36
|
+
private expirationEquals;
|
|
16
37
|
}
|
|
17
38
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAqD,MAAM,eAAe,CAAC;AAE7G,OAAO,EAAU,GAAG,EAAE,MAAM,cAAc,CAAC;AAM3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,+DAA+D,CAAC;AAE9E;;;;;GAKG;AACH,qBACa,cAAe,SAAQ,eAAe;IAEjD,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAEnB;;;;OAIG;IAIH,SAAS,CAAC,eAAe,EAAE,MAAM,CAAC;IAErB,OAAO;IAWpB;;;;;;OAMG;IACU,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAgBjC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAmBpD,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBtC,KAAK,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAiB1C,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc3C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAM/C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,gBAAgB;CAMzB"}
|
package/lib/cjs/index.js
CHANGED
|
@@ -22,9 +22,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
22
22
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
23
23
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
24
24
|
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
26
|
exports.DbSessionStore = void 0;
|
|
30
27
|
const rbac_1 = require("@spinajs/rbac");
|
|
@@ -34,67 +31,122 @@ const DbSession_js_1 = require("./models/DbSession.js");
|
|
|
34
31
|
const orm_1 = require("@spinajs/orm");
|
|
35
32
|
const configuration_1 = require("@spinajs/configuration");
|
|
36
33
|
const luxon_1 = require("luxon");
|
|
37
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
38
|
-
const util_1 = require("@spinajs/util");
|
|
39
34
|
__exportStar(require("./models/DbSession.js"), exports);
|
|
40
35
|
__exportStar(require("./migrations/UserSessionDBSqlMigration_2022_06_28_01_01_01.js"), exports);
|
|
36
|
+
/**
|
|
37
|
+
* Relational-db backed session store. Conforms to the `@spinajs/rbac`
|
|
38
|
+
* `SessionProvider` contract: ownership is the numeric `UserId`, expiration is
|
|
39
|
+
* owned by the injected strategy (`this.Expiration`) and persisted verbatim, and
|
|
40
|
+
* `Data` is (de)serialized with the shared session codec.
|
|
41
|
+
*/
|
|
41
42
|
let DbSessionStore = class DbSessionStore extends rbac_1.SessionProvider {
|
|
42
43
|
async resolve() {
|
|
43
|
-
setInterval(async () => {
|
|
44
|
-
const
|
|
45
|
-
this.Log.info(`Cleaned up expired session, count: ${
|
|
44
|
+
const timer = setInterval(async () => {
|
|
45
|
+
const count = await this.cleanupExpired();
|
|
46
|
+
this.Log.info(`Cleaned up expired session, count: ${count}`);
|
|
46
47
|
}, this.CleanupInterval);
|
|
48
|
+
// do not keep the process alive solely for the cleanup timer
|
|
49
|
+
timer.unref?.();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Removes every session whose expiration has passed.
|
|
53
|
+
*
|
|
54
|
+
* Extracted from the cleanup timer so it can be exercised directly.
|
|
55
|
+
*
|
|
56
|
+
* @returns how many sessions were removed
|
|
57
|
+
*/
|
|
58
|
+
async cleanupExpired() {
|
|
59
|
+
// NOTE: same reason as in deleteByUser — destroy() only builds a DELETE
|
|
60
|
+
// bounded by primary keys, so the expired rows are looked up first.
|
|
61
|
+
// `destroy().where(...)` threw on every tick and no session was ever
|
|
62
|
+
// cleaned up.
|
|
63
|
+
const expired = await DbSession_js_1.DbSession.where('Expiration', '<=', luxon_1.DateTime.now());
|
|
64
|
+
if (expired.length === 0) {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
await DbSession_js_1.DbSession.destroy(expired.map((s) => s.SessionId));
|
|
68
|
+
return expired.length;
|
|
47
69
|
}
|
|
48
70
|
async restore(sessionId) {
|
|
49
|
-
const
|
|
71
|
+
const row = await DbSession_js_1.DbSession.where({
|
|
50
72
|
SessionId: sessionId,
|
|
51
73
|
}).first();
|
|
52
|
-
if (!
|
|
74
|
+
if (!row) {
|
|
53
75
|
return null;
|
|
54
76
|
}
|
|
55
|
-
|
|
77
|
+
const session = this.toSession(row);
|
|
78
|
+
// an expired row is treated as absent
|
|
79
|
+
if (this.isExpired(session)) {
|
|
56
80
|
return null;
|
|
57
81
|
}
|
|
58
|
-
|
|
59
|
-
return new rbac_1.UserSession({
|
|
60
|
-
SessionId: session.SessionId,
|
|
61
|
-
Creation: session.CreatedAt,
|
|
62
|
-
Data: new Map(Object.entries(sData)),
|
|
63
|
-
Expiration: session.Expiration,
|
|
64
|
-
});
|
|
82
|
+
return session;
|
|
65
83
|
}
|
|
66
|
-
async
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const sData = lodash_1.default.isString(sessionOrId) ? data : sessionOrId.Data;
|
|
73
|
-
let sCreationTime = luxon_1.DateTime.now();
|
|
74
|
-
let sExpirationTime = luxon_1.DateTime.now().plus({ minutes: this.DefaultExpirationTime });
|
|
84
|
+
async save(session) {
|
|
85
|
+
// Persist `Expiration` verbatim (fixes B3). Only a brand-new session with no
|
|
86
|
+
// scheduled expiration is given its initial expiry via the strategy.
|
|
87
|
+
if (session.Expiration === undefined) {
|
|
88
|
+
this.applyInitialExpiration(session);
|
|
89
|
+
}
|
|
75
90
|
const s = await DbSession_js_1.DbSession.getOrNew({
|
|
76
|
-
SessionId:
|
|
77
|
-
CreatedAt: sCreationTime,
|
|
78
|
-
Expiration: sExpirationTime,
|
|
79
|
-
UserId: userId
|
|
91
|
+
SessionId: session.SessionId,
|
|
80
92
|
});
|
|
81
|
-
s.
|
|
93
|
+
s.SessionId = session.SessionId;
|
|
94
|
+
s.CreatedAt = session.Creation;
|
|
95
|
+
// column is nullable; a never-expiring session persists a NULL Expiration
|
|
96
|
+
s.Expiration = (session.Expiration ?? null);
|
|
97
|
+
s.UserId = session.UserId;
|
|
98
|
+
s.Data = (0, rbac_1.encodeSessionData)(session.Data);
|
|
82
99
|
await s.insert(orm_1.InsertBehaviour.InsertOrUpdate);
|
|
83
100
|
}
|
|
84
101
|
async touch(session) {
|
|
102
|
+
const current = session.Expiration;
|
|
103
|
+
const renewed = this.Expiration.renew(session);
|
|
104
|
+
// unchanged (e.g. AbsoluteExpiration) — skip the write, report false
|
|
105
|
+
if (this.expirationEquals(current, renewed)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
session.Expiration = renewed;
|
|
85
109
|
await DbSession_js_1.DbSession.update({
|
|
86
|
-
Expiration:
|
|
110
|
+
Expiration: renewed,
|
|
87
111
|
}).where('SessionId', session.SessionId);
|
|
112
|
+
return true;
|
|
88
113
|
}
|
|
89
|
-
async
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
114
|
+
async delete(sessionId) {
|
|
115
|
+
await DbSession_js_1.DbSession.destroy(sessionId);
|
|
116
|
+
}
|
|
117
|
+
async deleteByUser(userId) {
|
|
118
|
+
// NOTE: destroy() refuses to build an unbounded DELETE, so the sessions of
|
|
119
|
+
// the user are resolved to their primary keys first. `destroy().where(...)`
|
|
120
|
+
// threw "Cannot destroy without primary keys", which broke every forced
|
|
121
|
+
// logout ( admin "log out user" route included ).
|
|
122
|
+
const sessions = await DbSession_js_1.DbSession.where('UserId', userId);
|
|
123
|
+
if (sessions.length === 0) {
|
|
124
|
+
return;
|
|
93
125
|
}
|
|
126
|
+
await DbSession_js_1.DbSession.destroy(sessions.map((s) => s.SessionId));
|
|
127
|
+
}
|
|
128
|
+
async listByUser(userId) {
|
|
129
|
+
const rows = await DbSession_js_1.DbSession.where('UserId', userId);
|
|
130
|
+
return rows.map((r) => this.toSession(r)).filter((s) => !this.isExpired(s));
|
|
94
131
|
}
|
|
95
132
|
async truncate() {
|
|
96
133
|
await DbSession_js_1.DbSession.truncate();
|
|
97
134
|
}
|
|
135
|
+
toSession(row) {
|
|
136
|
+
return new rbac_1.UserSession({
|
|
137
|
+
SessionId: row.SessionId,
|
|
138
|
+
UserId: row.UserId,
|
|
139
|
+
Creation: row.CreatedAt,
|
|
140
|
+
Expiration: row.Expiration ?? undefined,
|
|
141
|
+
Data: (0, rbac_1.decodeSessionData)(row.Data),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
expirationEquals(a, b) {
|
|
145
|
+
if (a === undefined || b === undefined) {
|
|
146
|
+
return a === b;
|
|
147
|
+
}
|
|
148
|
+
return a.toMillis() === b.toMillis();
|
|
149
|
+
}
|
|
98
150
|
};
|
|
99
151
|
exports.DbSessionStore = DbSessionStore;
|
|
100
152
|
__decorate([
|
|
@@ -102,15 +154,11 @@ __decorate([
|
|
|
102
154
|
__metadata("design:type", log_1.Log)
|
|
103
155
|
], DbSessionStore.prototype, "Log", void 0);
|
|
104
156
|
__decorate([
|
|
105
|
-
(0, configuration_1.Config)('rbac.session.db.
|
|
157
|
+
(0, configuration_1.Config)('rbac.session.db.cleanupInterval', {
|
|
106
158
|
defaultValue: 100000,
|
|
107
159
|
}),
|
|
108
|
-
__metadata("design:type", Object)
|
|
109
|
-
], DbSessionStore.prototype, "CleanupInterval", void 0);
|
|
110
|
-
__decorate([
|
|
111
|
-
(0, configuration_1.Config)('rbac.session.expiration'),
|
|
112
160
|
__metadata("design:type", Number)
|
|
113
|
-
], DbSessionStore.prototype, "
|
|
161
|
+
], DbSessionStore.prototype, "CleanupInterval", void 0);
|
|
114
162
|
exports.DbSessionStore = DbSessionStore = __decorate([
|
|
115
163
|
(0, di_1.Injectable)(rbac_1.SessionProvider)
|
|
116
164
|
], DbSessionStore);
|
package/lib/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/mjs/index.d.ts
CHANGED
|
@@ -1,17 +1,38 @@
|
|
|
1
|
-
import { SessionProvider, ISession
|
|
1
|
+
import { SessionProvider, ISession } from '@spinajs/rbac';
|
|
2
2
|
import { Log } from '@spinajs/log';
|
|
3
3
|
export * from './models/DbSession.js';
|
|
4
4
|
export * from './migrations/UserSessionDBSqlMigration_2022_06_28_01_01_01.js';
|
|
5
|
+
/**
|
|
6
|
+
* Relational-db backed session store. 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
|
+
*/
|
|
5
11
|
export declare class DbSessionStore extends SessionProvider {
|
|
6
12
|
protected Log: Log;
|
|
7
|
-
|
|
8
|
-
|
|
13
|
+
/**
|
|
14
|
+
* How often expired rows are swept, in milliseconds. Reads the correctly
|
|
15
|
+
* spelled `rbac.session.db.cleanupInterval` key (fixes B2 — the historic typo
|
|
16
|
+
* `cleanupInteval` meant the configured value was ignored).
|
|
17
|
+
*/
|
|
18
|
+
protected CleanupInterval: number;
|
|
9
19
|
resolve(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Removes every session whose expiration has passed.
|
|
22
|
+
*
|
|
23
|
+
* Extracted from the cleanup timer so it can be exercised directly.
|
|
24
|
+
*
|
|
25
|
+
* @returns how many sessions were removed
|
|
26
|
+
*/
|
|
27
|
+
cleanupExpired(): Promise<number>;
|
|
10
28
|
restore(sessionId: string): Promise<ISession | null>;
|
|
29
|
+
save(session: ISession): Promise<void>;
|
|
30
|
+
touch(session: ISession): Promise<boolean>;
|
|
11
31
|
delete(sessionId: string): Promise<void>;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
logsOut(user: User): Promise<void>;
|
|
32
|
+
deleteByUser(userId: number): Promise<void>;
|
|
33
|
+
listByUser(userId: number): Promise<ISession[]>;
|
|
15
34
|
truncate(): Promise<void>;
|
|
35
|
+
private toSession;
|
|
36
|
+
private expirationEquals;
|
|
16
37
|
}
|
|
17
38
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/mjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAqD,MAAM,eAAe,CAAC;AAE7G,OAAO,EAAU,GAAG,EAAE,MAAM,cAAc,CAAC;AAM3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,+DAA+D,CAAC;AAE9E;;;;;GAKG;AACH,qBACa,cAAe,SAAQ,eAAe;IAEjD,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAEnB;;;;OAIG;IAIH,SAAS,CAAC,eAAe,EAAE,MAAM,CAAC;IAErB,OAAO;IAWpB;;;;;;OAMG;IACU,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAgBjC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAmBpD,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBtC,KAAK,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAiB1C,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc3C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAM/C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,gBAAgB;CAMzB"}
|
package/lib/mjs/index.js
CHANGED
|
@@ -7,89 +7,140 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
import { SessionProvider, UserSession } from '@spinajs/rbac';
|
|
10
|
+
import { SessionProvider, UserSession, encodeSessionData, decodeSessionData } from '@spinajs/rbac';
|
|
11
11
|
import { Injectable } from '@spinajs/di';
|
|
12
12
|
import { Logger, Log } from '@spinajs/log';
|
|
13
13
|
import { DbSession } from './models/DbSession.js';
|
|
14
14
|
import { InsertBehaviour } from '@spinajs/orm';
|
|
15
15
|
import { Config } from '@spinajs/configuration';
|
|
16
16
|
import { DateTime } from 'luxon';
|
|
17
|
-
import _ from 'lodash';
|
|
18
|
-
import { replacer } from '@spinajs/util';
|
|
19
17
|
export * from './models/DbSession.js';
|
|
20
18
|
export * from './migrations/UserSessionDBSqlMigration_2022_06_28_01_01_01.js';
|
|
19
|
+
/**
|
|
20
|
+
* Relational-db backed session store. 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
|
+
*/
|
|
21
25
|
let DbSessionStore = class DbSessionStore extends SessionProvider {
|
|
22
26
|
async resolve() {
|
|
23
|
-
setInterval(async () => {
|
|
24
|
-
const
|
|
25
|
-
this.Log.info(`Cleaned up expired session, count: ${
|
|
27
|
+
const timer = setInterval(async () => {
|
|
28
|
+
const count = await this.cleanupExpired();
|
|
29
|
+
this.Log.info(`Cleaned up expired session, count: ${count}`);
|
|
26
30
|
}, this.CleanupInterval);
|
|
31
|
+
// do not keep the process alive solely for the cleanup timer
|
|
32
|
+
timer.unref?.();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Removes every session whose expiration has passed.
|
|
36
|
+
*
|
|
37
|
+
* Extracted from the cleanup timer so it can be exercised directly.
|
|
38
|
+
*
|
|
39
|
+
* @returns how many sessions were removed
|
|
40
|
+
*/
|
|
41
|
+
async cleanupExpired() {
|
|
42
|
+
// NOTE: same reason as in deleteByUser — destroy() only builds a DELETE
|
|
43
|
+
// bounded by primary keys, so the expired rows are looked up first.
|
|
44
|
+
// `destroy().where(...)` threw on every tick and no session was ever
|
|
45
|
+
// cleaned up.
|
|
46
|
+
const expired = await DbSession.where('Expiration', '<=', DateTime.now());
|
|
47
|
+
if (expired.length === 0) {
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
await DbSession.destroy(expired.map((s) => s.SessionId));
|
|
51
|
+
return expired.length;
|
|
27
52
|
}
|
|
28
53
|
async restore(sessionId) {
|
|
29
|
-
const
|
|
54
|
+
const row = await DbSession.where({
|
|
30
55
|
SessionId: sessionId,
|
|
31
56
|
}).first();
|
|
32
|
-
if (!
|
|
57
|
+
if (!row) {
|
|
33
58
|
return null;
|
|
34
59
|
}
|
|
35
|
-
|
|
60
|
+
const session = this.toSession(row);
|
|
61
|
+
// an expired row is treated as absent
|
|
62
|
+
if (this.isExpired(session)) {
|
|
36
63
|
return null;
|
|
37
64
|
}
|
|
38
|
-
|
|
39
|
-
return new UserSession({
|
|
40
|
-
SessionId: session.SessionId,
|
|
41
|
-
Creation: session.CreatedAt,
|
|
42
|
-
Data: new Map(Object.entries(sData)),
|
|
43
|
-
Expiration: session.Expiration,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
async delete(sessionId) {
|
|
47
|
-
await DbSession.destroy(sessionId);
|
|
65
|
+
return session;
|
|
48
66
|
}
|
|
49
|
-
async save(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
67
|
+
async save(session) {
|
|
68
|
+
// Persist `Expiration` verbatim (fixes B3). Only a brand-new session with no
|
|
69
|
+
// scheduled expiration is given its initial expiry via the strategy.
|
|
70
|
+
if (session.Expiration === undefined) {
|
|
71
|
+
this.applyInitialExpiration(session);
|
|
72
|
+
}
|
|
55
73
|
const s = await DbSession.getOrNew({
|
|
56
|
-
SessionId:
|
|
57
|
-
CreatedAt: sCreationTime,
|
|
58
|
-
Expiration: sExpirationTime,
|
|
59
|
-
UserId: userId
|
|
74
|
+
SessionId: session.SessionId,
|
|
60
75
|
});
|
|
61
|
-
s.
|
|
76
|
+
s.SessionId = session.SessionId;
|
|
77
|
+
s.CreatedAt = session.Creation;
|
|
78
|
+
// column is nullable; a never-expiring session persists a NULL Expiration
|
|
79
|
+
s.Expiration = (session.Expiration ?? null);
|
|
80
|
+
s.UserId = session.UserId;
|
|
81
|
+
s.Data = encodeSessionData(session.Data);
|
|
62
82
|
await s.insert(InsertBehaviour.InsertOrUpdate);
|
|
63
83
|
}
|
|
64
84
|
async touch(session) {
|
|
85
|
+
const current = session.Expiration;
|
|
86
|
+
const renewed = this.Expiration.renew(session);
|
|
87
|
+
// unchanged (e.g. AbsoluteExpiration) — skip the write, report false
|
|
88
|
+
if (this.expirationEquals(current, renewed)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
session.Expiration = renewed;
|
|
65
92
|
await DbSession.update({
|
|
66
|
-
Expiration:
|
|
93
|
+
Expiration: renewed,
|
|
67
94
|
}).where('SessionId', session.SessionId);
|
|
95
|
+
return true;
|
|
68
96
|
}
|
|
69
|
-
async
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
97
|
+
async delete(sessionId) {
|
|
98
|
+
await DbSession.destroy(sessionId);
|
|
99
|
+
}
|
|
100
|
+
async deleteByUser(userId) {
|
|
101
|
+
// NOTE: destroy() refuses to build an unbounded DELETE, so the sessions of
|
|
102
|
+
// the user are resolved to their primary keys first. `destroy().where(...)`
|
|
103
|
+
// threw "Cannot destroy without primary keys", which broke every forced
|
|
104
|
+
// logout ( admin "log out user" route included ).
|
|
105
|
+
const sessions = await DbSession.where('UserId', userId);
|
|
106
|
+
if (sessions.length === 0) {
|
|
107
|
+
return;
|
|
73
108
|
}
|
|
109
|
+
await DbSession.destroy(sessions.map((s) => s.SessionId));
|
|
110
|
+
}
|
|
111
|
+
async listByUser(userId) {
|
|
112
|
+
const rows = await DbSession.where('UserId', userId);
|
|
113
|
+
return rows.map((r) => this.toSession(r)).filter((s) => !this.isExpired(s));
|
|
74
114
|
}
|
|
75
115
|
async truncate() {
|
|
76
116
|
await DbSession.truncate();
|
|
77
117
|
}
|
|
118
|
+
toSession(row) {
|
|
119
|
+
return new UserSession({
|
|
120
|
+
SessionId: row.SessionId,
|
|
121
|
+
UserId: row.UserId,
|
|
122
|
+
Creation: row.CreatedAt,
|
|
123
|
+
Expiration: row.Expiration ?? undefined,
|
|
124
|
+
Data: decodeSessionData(row.Data),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
expirationEquals(a, b) {
|
|
128
|
+
if (a === undefined || b === undefined) {
|
|
129
|
+
return a === b;
|
|
130
|
+
}
|
|
131
|
+
return a.toMillis() === b.toMillis();
|
|
132
|
+
}
|
|
78
133
|
};
|
|
79
134
|
__decorate([
|
|
80
135
|
Logger('db-session-store'),
|
|
81
136
|
__metadata("design:type", Log)
|
|
82
137
|
], DbSessionStore.prototype, "Log", void 0);
|
|
83
138
|
__decorate([
|
|
84
|
-
Config('rbac.session.db.
|
|
139
|
+
Config('rbac.session.db.cleanupInterval', {
|
|
85
140
|
defaultValue: 100000,
|
|
86
141
|
}),
|
|
87
|
-
__metadata("design:type", Object)
|
|
88
|
-
], DbSessionStore.prototype, "CleanupInterval", void 0);
|
|
89
|
-
__decorate([
|
|
90
|
-
Config('rbac.session.expiration'),
|
|
91
142
|
__metadata("design:type", Number)
|
|
92
|
-
], DbSessionStore.prototype, "
|
|
143
|
+
], DbSessionStore.prototype, "CleanupInterval", void 0);
|
|
93
144
|
DbSessionStore = __decorate([
|
|
94
145
|
Injectable(SessionProvider)
|
|
95
146
|
], DbSessionStore);
|
package/lib/mjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,eAAe,EAAY,WAAW,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|