@chidchanun/bcp 0.2.4 → 0.2.6

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,318 @@
1
+ export interface AuthSessionStoreRecord {
2
+ sid: string;
3
+ userId: string;
4
+ createdAt: number;
5
+ expiresAt: number;
6
+ lastSeenAt: number;
7
+ revokedAt?: number | null;
8
+ }
9
+
10
+ export interface AuthSessionStore {
11
+ set(
12
+ record: AuthSessionStoreRecord
13
+ ): Promise<void> | void;
14
+ get(
15
+ sid: string
16
+ ): Promise<AuthSessionStoreRecord | null> | AuthSessionStoreRecord | null;
17
+ touch(
18
+ sid: string,
19
+ lastSeenAt: number
20
+ ): Promise<void> | void;
21
+ revoke(
22
+ sid: string,
23
+ revokedAt?: number
24
+ ): Promise<boolean> | boolean;
25
+ revokeUser(
26
+ userId: string,
27
+ revokedAt?: number
28
+ ): Promise<number> | number;
29
+ }
30
+
31
+ export interface MemoryAuthSessionStore
32
+ extends AuthSessionStore {
33
+ clearExpired(
34
+ now?: number
35
+ ): number;
36
+ size(): number;
37
+ }
38
+
39
+ export function createMemoryAuthSessionStore(): MemoryAuthSessionStore {
40
+ const sessions =
41
+ new Map<
42
+ string,
43
+ AuthSessionStoreRecord
44
+ >();
45
+
46
+ const clearExpired = (
47
+ now = currentUnixTime()
48
+ ): number => {
49
+ let removed = 0;
50
+
51
+ for (
52
+ const [
53
+ sid,
54
+ record,
55
+ ]
56
+ of sessions
57
+ ) {
58
+ if (
59
+ record.expiresAt <= now
60
+ ) {
61
+ sessions.delete(
62
+ sid
63
+ );
64
+ removed += 1;
65
+ }
66
+ }
67
+
68
+ return removed;
69
+ };
70
+
71
+ return {
72
+ set(record) {
73
+ assertRecord(
74
+ record
75
+ );
76
+ clearExpired();
77
+
78
+ sessions.set(
79
+ record.sid,
80
+ cloneRecord(
81
+ record
82
+ )
83
+ );
84
+ },
85
+
86
+ get(sid) {
87
+ assertSid(
88
+ sid
89
+ );
90
+ clearExpired();
91
+
92
+ const record =
93
+ sessions.get(
94
+ sid
95
+ );
96
+
97
+ return record
98
+ ? cloneRecord(
99
+ record
100
+ )
101
+ : null;
102
+ },
103
+
104
+ touch(
105
+ sid,
106
+ lastSeenAt
107
+ ) {
108
+ assertSid(
109
+ sid
110
+ );
111
+ assertTimestamp(
112
+ lastSeenAt,
113
+ "lastSeenAt"
114
+ );
115
+ clearExpired(
116
+ lastSeenAt
117
+ );
118
+
119
+ const record =
120
+ sessions.get(
121
+ sid
122
+ );
123
+
124
+ if (
125
+ !record ||
126
+ record.revokedAt !== undefined &&
127
+ record.revokedAt !== null
128
+ ) {
129
+ return;
130
+ }
131
+
132
+ record.lastSeenAt =
133
+ Math.max(
134
+ record.lastSeenAt,
135
+ lastSeenAt
136
+ );
137
+ },
138
+
139
+ revoke(
140
+ sid,
141
+ revokedAt = currentUnixTime()
142
+ ) {
143
+ assertSid(
144
+ sid
145
+ );
146
+ assertTimestamp(
147
+ revokedAt,
148
+ "revokedAt"
149
+ );
150
+
151
+ const record =
152
+ sessions.get(
153
+ sid
154
+ );
155
+
156
+ if (!record) {
157
+ return false;
158
+ }
159
+
160
+ record.revokedAt =
161
+ revokedAt;
162
+
163
+ return true;
164
+ },
165
+
166
+ revokeUser(
167
+ userId,
168
+ revokedAt = currentUnixTime()
169
+ ) {
170
+ assertUserId(
171
+ userId
172
+ );
173
+ assertTimestamp(
174
+ revokedAt,
175
+ "revokedAt"
176
+ );
177
+ clearExpired(
178
+ revokedAt
179
+ );
180
+
181
+ let revoked = 0;
182
+
183
+ for (
184
+ const record
185
+ of sessions.values()
186
+ ) {
187
+ if (
188
+ record.userId !== userId ||
189
+ record.revokedAt !== undefined &&
190
+ record.revokedAt !== null
191
+ ) {
192
+ continue;
193
+ }
194
+
195
+ record.revokedAt =
196
+ revokedAt;
197
+ revoked += 1;
198
+ }
199
+
200
+ return revoked;
201
+ },
202
+
203
+ clearExpired,
204
+
205
+ size() {
206
+ clearExpired();
207
+ return sessions.size;
208
+ },
209
+ };
210
+ }
211
+
212
+ function cloneRecord(
213
+ record: AuthSessionStoreRecord
214
+ ): AuthSessionStoreRecord {
215
+ return {
216
+ ...record,
217
+ };
218
+ }
219
+
220
+ function assertRecord(
221
+ record: AuthSessionStoreRecord
222
+ ): void {
223
+ if (
224
+ !record ||
225
+ typeof record !== "object"
226
+ ) {
227
+ throw new TypeError(
228
+ "BCP Auth Store: session record must be an object."
229
+ );
230
+ }
231
+
232
+ assertSid(
233
+ record.sid
234
+ );
235
+ assertUserId(
236
+ record.userId
237
+ );
238
+ assertTimestamp(
239
+ record.createdAt,
240
+ "createdAt"
241
+ );
242
+ assertTimestamp(
243
+ record.expiresAt,
244
+ "expiresAt"
245
+ );
246
+ assertTimestamp(
247
+ record.lastSeenAt,
248
+ "lastSeenAt"
249
+ );
250
+
251
+ if (
252
+ record.expiresAt <=
253
+ record.createdAt
254
+ ) {
255
+ throw new TypeError(
256
+ "BCP Auth Store: expiresAt must be later than createdAt."
257
+ );
258
+ }
259
+
260
+ if (
261
+ record.revokedAt !== undefined &&
262
+ record.revokedAt !== null
263
+ ) {
264
+ assertTimestamp(
265
+ record.revokedAt,
266
+ "revokedAt"
267
+ );
268
+ }
269
+ }
270
+
271
+ function assertSid(
272
+ value: string
273
+ ): void {
274
+ if (
275
+ typeof value !== "string" ||
276
+ value.trim() === ""
277
+ ) {
278
+ throw new TypeError(
279
+ "BCP Auth Store: sid must be a non-empty string."
280
+ );
281
+ }
282
+ }
283
+
284
+ function assertUserId(
285
+ value: string
286
+ ): void {
287
+ if (
288
+ typeof value !== "string" ||
289
+ value.trim() === ""
290
+ ) {
291
+ throw new TypeError(
292
+ "BCP Auth Store: userId must be a non-empty string."
293
+ );
294
+ }
295
+ }
296
+
297
+ function assertTimestamp(
298
+ value: number,
299
+ label: string
300
+ ): void {
301
+ if (
302
+ !Number.isFinite(
303
+ value
304
+ ) ||
305
+ value < 0
306
+ ) {
307
+ throw new TypeError(
308
+ `BCP Auth Store: ${label} must be a non-negative finite Unix timestamp.`
309
+ );
310
+ }
311
+ }
312
+
313
+ function currentUnixTime(): number {
314
+ return Math.floor(
315
+ Date.now() /
316
+ 1000
317
+ );
318
+ }