@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
|
@@ -4,6 +4,11 @@ import {
|
|
|
4
4
|
type AuthSession,
|
|
5
5
|
type AuthUser,
|
|
6
6
|
} from "./auth.js";
|
|
7
|
+
import {
|
|
8
|
+
hasPermission,
|
|
9
|
+
type AuthorizationMatch,
|
|
10
|
+
type PermissionRequirement,
|
|
11
|
+
} from "./authorization.js";
|
|
7
12
|
import {
|
|
8
13
|
redirect,
|
|
9
14
|
type RedirectStatus,
|
|
@@ -18,21 +23,29 @@ export interface AuthGuardData<
|
|
|
18
23
|
|
|
19
24
|
export interface RequireAuthOptions
|
|
20
25
|
extends AuthOptions {
|
|
21
|
-
redirectTo?:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
redirectTo?: string | URL | null;
|
|
27
|
+
redirectStatus?: RedirectStatus;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RequireGuestOptions
|
|
31
|
+
extends AuthOptions {
|
|
32
|
+
redirectTo?: string | URL | null;
|
|
25
33
|
redirectStatus?: RedirectStatus;
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
export interface RequireRoleOptions
|
|
29
37
|
extends RequireAuthOptions {
|
|
30
38
|
roleField?: string;
|
|
31
|
-
match?:
|
|
32
|
-
forbiddenRedirectTo?:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
match?: AuthorizationMatch;
|
|
40
|
+
forbiddenRedirectTo?: string | URL | null;
|
|
41
|
+
forbiddenRedirectStatus?: RedirectStatus;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RequirePermissionOptions
|
|
45
|
+
extends RequireAuthOptions {
|
|
46
|
+
permissionField?: string;
|
|
47
|
+
match?: AuthorizationMatch;
|
|
48
|
+
forbiddenRedirectTo?: string | URL | null;
|
|
36
49
|
forbiddenRedirectStatus?: RedirectStatus;
|
|
37
50
|
}
|
|
38
51
|
|
|
@@ -47,16 +60,20 @@ export type AuthGuardResult<
|
|
|
47
60
|
| AuthGuardData<TUser, TData>
|
|
48
61
|
| Response;
|
|
49
62
|
|
|
63
|
+
export type GuestGuardResult =
|
|
64
|
+
| Record<string, never>
|
|
65
|
+
| Response;
|
|
66
|
+
|
|
50
67
|
export type AuthGuardFunction<
|
|
51
68
|
TUser extends AuthUser = AuthUser,
|
|
52
69
|
TData extends object = Record<string, never>
|
|
53
70
|
> = () => Promise<
|
|
54
|
-
AuthGuardResult<
|
|
55
|
-
TUser,
|
|
56
|
-
TData
|
|
57
|
-
>
|
|
71
|
+
AuthGuardResult<TUser, TData>
|
|
58
72
|
>;
|
|
59
73
|
|
|
74
|
+
export type GuestGuardFunction =
|
|
75
|
+
() => Promise<GuestGuardResult>;
|
|
76
|
+
|
|
60
77
|
export async function requireAuth<
|
|
61
78
|
TUser extends AuthUser = AuthUser,
|
|
62
79
|
TData extends object = Record<string, never>
|
|
@@ -69,23 +86,18 @@ export async function requireAuth<
|
|
|
69
86
|
...authOptions
|
|
70
87
|
} = options;
|
|
71
88
|
const session =
|
|
72
|
-
await auth<
|
|
73
|
-
TUser,
|
|
74
|
-
TData
|
|
75
|
-
>(
|
|
89
|
+
await auth<TUser, TData>(
|
|
76
90
|
authOptions
|
|
77
91
|
);
|
|
78
92
|
|
|
79
93
|
if (!session) {
|
|
80
94
|
if (
|
|
81
|
-
redirectTo ===
|
|
82
|
-
null
|
|
95
|
+
redirectTo === null
|
|
83
96
|
) {
|
|
84
97
|
return new Response(
|
|
85
98
|
"Unauthorized",
|
|
86
99
|
{
|
|
87
|
-
status:
|
|
88
|
-
401,
|
|
100
|
+
status: 401,
|
|
89
101
|
}
|
|
90
102
|
);
|
|
91
103
|
}
|
|
@@ -97,11 +109,47 @@ export async function requireAuth<
|
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
return {
|
|
100
|
-
auth:
|
|
101
|
-
session,
|
|
112
|
+
auth: session,
|
|
102
113
|
};
|
|
103
114
|
}
|
|
104
115
|
|
|
116
|
+
export async function requireGuest<
|
|
117
|
+
TUser extends AuthUser = AuthUser,
|
|
118
|
+
TData extends object = Record<string, never>
|
|
119
|
+
>(
|
|
120
|
+
options: RequireGuestOptions = {}
|
|
121
|
+
): Promise<GuestGuardResult> {
|
|
122
|
+
const {
|
|
123
|
+
redirectTo = "/",
|
|
124
|
+
redirectStatus = 303,
|
|
125
|
+
...authOptions
|
|
126
|
+
} = options;
|
|
127
|
+
const session =
|
|
128
|
+
await auth<TUser, TData>(
|
|
129
|
+
authOptions
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (!session) {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
redirectTo === null
|
|
138
|
+
) {
|
|
139
|
+
return new Response(
|
|
140
|
+
"Already authenticated",
|
|
141
|
+
{
|
|
142
|
+
status: 409,
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return redirect(
|
|
148
|
+
redirectTo,
|
|
149
|
+
redirectStatus
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
105
153
|
export async function requireRole<
|
|
106
154
|
TUser extends AuthUser = AuthUser,
|
|
107
155
|
TData extends object = Record<string, never>
|
|
@@ -121,16 +169,12 @@ export async function requireRole<
|
|
|
121
169
|
...authOptions
|
|
122
170
|
} = options;
|
|
123
171
|
const authenticated =
|
|
124
|
-
await requireAuth<
|
|
125
|
-
TUser,
|
|
126
|
-
TData
|
|
127
|
-
>(
|
|
172
|
+
await requireAuth<TUser, TData>(
|
|
128
173
|
authOptions
|
|
129
174
|
);
|
|
130
175
|
|
|
131
176
|
if (
|
|
132
|
-
authenticated instanceof
|
|
133
|
-
Response
|
|
177
|
+
authenticated instanceof Response
|
|
134
178
|
) {
|
|
135
179
|
return authenticated;
|
|
136
180
|
}
|
|
@@ -155,27 +199,59 @@ export async function requireRole<
|
|
|
155
199
|
)
|
|
156
200
|
);
|
|
157
201
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
forbiddenRedirectStatus
|
|
166
|
-
);
|
|
167
|
-
}
|
|
202
|
+
return finalizeAuthorization(
|
|
203
|
+
allowed,
|
|
204
|
+
authenticated,
|
|
205
|
+
forbiddenRedirectTo,
|
|
206
|
+
forbiddenRedirectStatus
|
|
207
|
+
);
|
|
208
|
+
}
|
|
168
209
|
|
|
169
|
-
|
|
170
|
-
|
|
210
|
+
export async function requirePermission<
|
|
211
|
+
TUser extends AuthUser = AuthUser,
|
|
212
|
+
TData extends object = Record<string, never>
|
|
213
|
+
>(
|
|
214
|
+
requiredPermission:
|
|
215
|
+
PermissionRequirement,
|
|
216
|
+
options:
|
|
217
|
+
RequirePermissionOptions = {}
|
|
218
|
+
): Promise<AuthGuardResult<TUser, TData>> {
|
|
219
|
+
const {
|
|
220
|
+
permissionField =
|
|
221
|
+
"permissions",
|
|
222
|
+
match = "any",
|
|
223
|
+
forbiddenRedirectTo = null,
|
|
224
|
+
forbiddenRedirectStatus = 303,
|
|
225
|
+
...authOptions
|
|
226
|
+
} = options;
|
|
227
|
+
const authenticated =
|
|
228
|
+
await requireAuth<TUser, TData>(
|
|
229
|
+
authOptions
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (
|
|
233
|
+
authenticated instanceof Response
|
|
234
|
+
) {
|
|
235
|
+
return authenticated;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const allowed =
|
|
239
|
+
hasPermission(
|
|
240
|
+
authenticated.auth.user,
|
|
241
|
+
requiredPermission,
|
|
171
242
|
{
|
|
172
|
-
|
|
173
|
-
|
|
243
|
+
field:
|
|
244
|
+
permissionField,
|
|
245
|
+
match,
|
|
174
246
|
}
|
|
175
247
|
);
|
|
176
|
-
}
|
|
177
248
|
|
|
178
|
-
return
|
|
249
|
+
return finalizeAuthorization(
|
|
250
|
+
allowed,
|
|
251
|
+
authenticated,
|
|
252
|
+
forbiddenRedirectTo,
|
|
253
|
+
forbiddenRedirectStatus
|
|
254
|
+
);
|
|
179
255
|
}
|
|
180
256
|
|
|
181
257
|
export function createAuthGuard<
|
|
@@ -185,10 +261,19 @@ export function createAuthGuard<
|
|
|
185
261
|
options: RequireAuthOptions = {}
|
|
186
262
|
): AuthGuardFunction<TUser, TData> {
|
|
187
263
|
return () =>
|
|
188
|
-
requireAuth<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
264
|
+
requireAuth<TUser, TData>(
|
|
265
|
+
options
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function createGuestGuard<
|
|
270
|
+
TUser extends AuthUser = AuthUser,
|
|
271
|
+
TData extends object = Record<string, never>
|
|
272
|
+
>(
|
|
273
|
+
options: RequireGuestOptions = {}
|
|
274
|
+
): GuestGuardFunction {
|
|
275
|
+
return () =>
|
|
276
|
+
requireGuest<TUser, TData>(
|
|
192
277
|
options
|
|
193
278
|
);
|
|
194
279
|
}
|
|
@@ -201,31 +286,42 @@ export function createRoleGuard<
|
|
|
201
286
|
options: RequireRoleOptions = {}
|
|
202
287
|
): AuthGuardFunction<TUser, TData> {
|
|
203
288
|
return () =>
|
|
204
|
-
requireRole<
|
|
205
|
-
TUser,
|
|
206
|
-
TData
|
|
207
|
-
>(
|
|
289
|
+
requireRole<TUser, TData>(
|
|
208
290
|
requiredRole,
|
|
209
291
|
options
|
|
210
292
|
);
|
|
211
293
|
}
|
|
212
294
|
|
|
295
|
+
export function createPermissionGuard<
|
|
296
|
+
TUser extends AuthUser = AuthUser,
|
|
297
|
+
TData extends object = Record<string, never>
|
|
298
|
+
>(
|
|
299
|
+
requiredPermission:
|
|
300
|
+
PermissionRequirement,
|
|
301
|
+
options:
|
|
302
|
+
RequirePermissionOptions = {}
|
|
303
|
+
): AuthGuardFunction<TUser, TData> {
|
|
304
|
+
return () =>
|
|
305
|
+
requirePermission<TUser, TData>(
|
|
306
|
+
requiredPermission,
|
|
307
|
+
options
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
213
311
|
export function getGuardAuth<
|
|
214
312
|
TUser extends AuthUser = AuthUser,
|
|
215
313
|
TData extends object = Record<string, never>
|
|
216
314
|
>(
|
|
217
|
-
guardData:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}>
|
|
315
|
+
guardData: Readonly<{
|
|
316
|
+
auth?: unknown;
|
|
317
|
+
}>
|
|
221
318
|
): AuthSession<TUser, TData> | null {
|
|
222
319
|
const value =
|
|
223
320
|
guardData.auth;
|
|
224
321
|
|
|
225
322
|
if (
|
|
226
323
|
!value ||
|
|
227
|
-
typeof value !==
|
|
228
|
-
"object" ||
|
|
324
|
+
typeof value !== "object" ||
|
|
229
325
|
Array.isArray(
|
|
230
326
|
value
|
|
231
327
|
)
|
|
@@ -235,24 +331,17 @@ export function getGuardAuth<
|
|
|
235
331
|
|
|
236
332
|
const session =
|
|
237
333
|
value as Partial<
|
|
238
|
-
AuthSession<
|
|
239
|
-
TUser,
|
|
240
|
-
TData
|
|
241
|
-
>
|
|
334
|
+
AuthSession<TUser, TData>
|
|
242
335
|
>;
|
|
243
336
|
|
|
244
337
|
if (
|
|
245
|
-
typeof session.sid !==
|
|
246
|
-
|
|
247
|
-
session.
|
|
248
|
-
0 ||
|
|
249
|
-
typeof session.iat !==
|
|
250
|
-
"number" ||
|
|
338
|
+
typeof session.sid !== "string" ||
|
|
339
|
+
session.sid.trim().length === 0 ||
|
|
340
|
+
typeof session.iat !== "number" ||
|
|
251
341
|
!Number.isFinite(
|
|
252
342
|
session.iat
|
|
253
343
|
) ||
|
|
254
|
-
typeof session.exp !==
|
|
255
|
-
"number" ||
|
|
344
|
+
typeof session.exp !== "number" ||
|
|
256
345
|
!Number.isFinite(
|
|
257
346
|
session.exp
|
|
258
347
|
) ||
|
|
@@ -264,18 +353,47 @@ export function getGuardAuth<
|
|
|
264
353
|
}
|
|
265
354
|
|
|
266
355
|
return value as
|
|
267
|
-
AuthSession<
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
356
|
+
AuthSession<TUser, TData>;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function finalizeAuthorization<
|
|
360
|
+
TUser extends AuthUser,
|
|
361
|
+
TData extends object
|
|
362
|
+
>(
|
|
363
|
+
allowed: boolean,
|
|
364
|
+
authenticated:
|
|
365
|
+
AuthGuardData<TUser, TData>,
|
|
366
|
+
forbiddenRedirectTo:
|
|
367
|
+
string | URL | null,
|
|
368
|
+
forbiddenRedirectStatus:
|
|
369
|
+
RedirectStatus
|
|
370
|
+
): Promise<AuthGuardResult<TUser, TData>> | AuthGuardData<TUser, TData> | Response {
|
|
371
|
+
if (allowed) {
|
|
372
|
+
return authenticated;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (
|
|
376
|
+
forbiddenRedirectTo !== null
|
|
377
|
+
) {
|
|
378
|
+
return redirect(
|
|
379
|
+
forbiddenRedirectTo,
|
|
380
|
+
forbiddenRedirectStatus
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return new Response(
|
|
385
|
+
"Forbidden",
|
|
386
|
+
{
|
|
387
|
+
status: 403,
|
|
388
|
+
}
|
|
389
|
+
);
|
|
271
390
|
}
|
|
272
391
|
|
|
273
392
|
function normalizeRequiredRoles(
|
|
274
393
|
value: RequiredRole
|
|
275
394
|
): string[] {
|
|
276
395
|
const roles =
|
|
277
|
-
typeof value ===
|
|
278
|
-
"string"
|
|
396
|
+
typeof value === "string"
|
|
279
397
|
? [
|
|
280
398
|
value,
|
|
281
399
|
]
|
|
@@ -289,12 +407,10 @@ function normalizeRequiredRoles(
|
|
|
289
407
|
);
|
|
290
408
|
|
|
291
409
|
if (
|
|
292
|
-
normalized.length ===
|
|
293
|
-
0 ||
|
|
410
|
+
normalized.length === 0 ||
|
|
294
411
|
normalized.some(
|
|
295
412
|
(role) =>
|
|
296
|
-
role.length ===
|
|
297
|
-
0
|
|
413
|
+
role.length === 0
|
|
298
414
|
)
|
|
299
415
|
) {
|
|
300
416
|
throw new TypeError(
|
|
@@ -324,14 +440,12 @@ function readAssignedRoles(
|
|
|
324
440
|
|
|
325
441
|
const value =
|
|
326
442
|
(
|
|
327
|
-
user as
|
|
328
|
-
unknown as
|
|
443
|
+
user as unknown as
|
|
329
444
|
Record<string, unknown>
|
|
330
445
|
)[field];
|
|
331
446
|
|
|
332
447
|
if (
|
|
333
|
-
typeof value ===
|
|
334
|
-
"string"
|
|
448
|
+
typeof value === "string"
|
|
335
449
|
) {
|
|
336
450
|
const role =
|
|
337
451
|
value.trim();
|
|
@@ -353,8 +467,7 @@ function readAssignedRoles(
|
|
|
353
467
|
(
|
|
354
468
|
role
|
|
355
469
|
): role is string =>
|
|
356
|
-
typeof role ===
|
|
357
|
-
"string"
|
|
470
|
+
typeof role === "string"
|
|
358
471
|
)
|
|
359
472
|
.map(
|
|
360
473
|
(role) =>
|
|
@@ -373,8 +486,7 @@ function isGuardAuthUser(
|
|
|
373
486
|
): value is AuthUser {
|
|
374
487
|
if (
|
|
375
488
|
!value ||
|
|
376
|
-
typeof value !==
|
|
377
|
-
"object" ||
|
|
489
|
+
typeof value !== "object" ||
|
|
378
490
|
Array.isArray(
|
|
379
491
|
value
|
|
380
492
|
)
|
|
@@ -389,13 +501,10 @@ function isGuardAuthUser(
|
|
|
389
501
|
).id;
|
|
390
502
|
|
|
391
503
|
return (
|
|
392
|
-
typeof id ===
|
|
393
|
-
|
|
394
|
-
id.trim().length >
|
|
395
|
-
0
|
|
504
|
+
typeof id === "string" &&
|
|
505
|
+
id.trim().length > 0
|
|
396
506
|
) || (
|
|
397
|
-
typeof id ===
|
|
398
|
-
"number" &&
|
|
507
|
+
typeof id === "number" &&
|
|
399
508
|
Number.isFinite(
|
|
400
509
|
id
|
|
401
510
|
)
|