@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.
- package/README.md +181 -77
- package/docs/README.md +84 -127
- package/docs/api-manifest.json +6 -3
- package/docs/api-reference.md +60 -4
- package/docs/auth-route-guards.md +104 -61
- package/docs/auth-session-store.md +204 -0
- package/docs/authentication.md +128 -44
- package/docs/authorization-security.md +298 -0
- package/docs/docs-web-manifest.json +8 -4
- package/docs/platform-manifest.json +19 -4
- package/docs/releases/0.2.5.md +152 -0
- package/docs/releases/0.2.6.md +194 -0
- package/docs/security.md +55 -1
- package/package.json +1 -1
- package/packages/client/src/auth.ts +36 -0
- package/packages/client/src/server.ts +16 -0
- package/packages/server/src/auth-guard.ts +204 -95
- package/packages/server/src/auth-session-store.ts +318 -0
- package/packages/server/src/auth.ts +443 -50
- package/packages/server/src/authorization.ts +368 -0
- package/packages/server/src/request-security.ts +596 -0
|
@@ -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
|
+
}
|