@chidchanun/bcp 0.2.3 → 0.2.5
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 +136 -152
- package/docs/README.md +77 -126
- package/docs/api-manifest.json +3 -2
- package/docs/api-reference.md +22 -2
- package/docs/application-packaging.md +243 -0
- package/docs/auth-route-guards.md +72 -37
- package/docs/auth-session-store.md +204 -0
- package/docs/authentication.md +128 -44
- package/docs/deployment.md +94 -7
- package/docs/docs-web-manifest.json +8 -4
- package/docs/platform-manifest.json +22 -5
- package/docs/releases/0.2.4.md +152 -0
- package/docs/releases/0.2.5.md +152 -0
- package/package.json +1 -1
- package/packages/cli/src/application-packaging.ts +1162 -0
- package/packages/cli/src/args.ts +2 -0
- package/packages/cli/src/bootstrap.ts +1 -0
- package/packages/cli/src/index.ts +60 -2
- package/packages/client/src/auth.ts +16 -0
- package/packages/server/src/auth-guard.ts +95 -82
- package/packages/server/src/auth-session-store.ts +318 -0
- package/packages/server/src/auth.ts +443 -50
package/packages/cli/src/args.ts
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
export type CliCommand =
|
|
5
5
|
| "dev"
|
|
6
6
|
| "build"
|
|
7
|
+
| "package"
|
|
7
8
|
| "start"
|
|
8
9
|
| "routes"
|
|
9
10
|
| "update"
|
|
@@ -417,6 +418,7 @@ export function parseCliArgs(
|
|
|
417
418
|
if (
|
|
418
419
|
argument === "dev" ||
|
|
419
420
|
argument === "build" ||
|
|
421
|
+
argument === "package" ||
|
|
420
422
|
argument === "start" ||
|
|
421
423
|
argument === "routes" ||
|
|
422
424
|
argument === "update" ||
|
|
@@ -51,6 +51,11 @@ switch (cliOptions.command) {
|
|
|
51
51
|
break;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
case "package": {
|
|
55
|
+
await runPackage();
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
case "start": {
|
|
55
60
|
await runStart();
|
|
56
61
|
break;
|
|
@@ -356,6 +361,57 @@ async function runBuild() {
|
|
|
356
361
|
console.log("");
|
|
357
362
|
}
|
|
358
363
|
|
|
364
|
+
async function runPackage(): Promise<void> {
|
|
365
|
+
process.env.NODE_ENV =
|
|
366
|
+
"production";
|
|
367
|
+
|
|
368
|
+
await runBuild();
|
|
369
|
+
|
|
370
|
+
const rootDirectory =
|
|
371
|
+
resolveProjectRoot(
|
|
372
|
+
cliOptions.rootDirectory
|
|
373
|
+
);
|
|
374
|
+
const {
|
|
375
|
+
packageApplication,
|
|
376
|
+
} =
|
|
377
|
+
await import(
|
|
378
|
+
"./application-packaging.js"
|
|
379
|
+
);
|
|
380
|
+
const result =
|
|
381
|
+
packageApplication({
|
|
382
|
+
rootDirectory,
|
|
383
|
+
frameworkVersion:
|
|
384
|
+
FRAMEWORK_VERSION,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
console.log(
|
|
388
|
+
"[BCP Package] Application package created."
|
|
389
|
+
);
|
|
390
|
+
console.log(
|
|
391
|
+
`[BCP Package] Output: ${result.outputDirectory}`
|
|
392
|
+
);
|
|
393
|
+
console.log(
|
|
394
|
+
`[BCP Package] Target: ${result.packageManifest.target}`
|
|
395
|
+
);
|
|
396
|
+
console.log(
|
|
397
|
+
`[BCP Package] Files: ${result.packageManifest.files.length}`
|
|
398
|
+
);
|
|
399
|
+
console.log(
|
|
400
|
+
`[BCP Package] Install: ${result.packageManifest.install.command}`
|
|
401
|
+
);
|
|
402
|
+
console.log(
|
|
403
|
+
`[BCP Package] Start: ${result.packageManifest.runtime.startCommand}`
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
for (const warning of result.warnings) {
|
|
407
|
+
console.warn(
|
|
408
|
+
`[BCP Package] Warning: ${warning}`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
console.log("");
|
|
413
|
+
}
|
|
414
|
+
|
|
359
415
|
async function runStart() {
|
|
360
416
|
process.env.NODE_ENV =
|
|
361
417
|
"production";
|
|
@@ -866,6 +922,7 @@ Usage:
|
|
|
866
922
|
Commands:
|
|
867
923
|
dev Start the development server
|
|
868
924
|
build Create optimized client assets and standalone server.mjs
|
|
925
|
+
package Build and create a production deployment package
|
|
869
926
|
start Start the standalone production build
|
|
870
927
|
routes Print discovered page and API routes
|
|
871
928
|
update [target] Update BCP Framework (default target: latest)
|
|
@@ -890,12 +947,13 @@ Options:
|
|
|
890
947
|
-v, --version Show version
|
|
891
948
|
|
|
892
949
|
Config precedence:
|
|
893
|
-
dev/build: CLI > BCP_* environment > bcp.config.ts > defaults
|
|
894
|
-
start:
|
|
950
|
+
dev/build/package: CLI > BCP_* environment > bcp.config.ts > defaults
|
|
951
|
+
start: CLI > runtime BCP_* environment > frozen build config
|
|
895
952
|
|
|
896
953
|
Examples:
|
|
897
954
|
bcp dev
|
|
898
955
|
bcp build
|
|
956
|
+
bcp package
|
|
899
957
|
bcp start
|
|
900
958
|
bcp routes
|
|
901
959
|
bcp generate page dashboard/users
|
|
@@ -4,6 +4,9 @@ export {
|
|
|
4
4
|
getSession,
|
|
5
5
|
login,
|
|
6
6
|
logout,
|
|
7
|
+
logoutAll,
|
|
8
|
+
revokeSession,
|
|
9
|
+
revokeUserSessions,
|
|
7
10
|
rotateSession,
|
|
8
11
|
|
|
9
12
|
type AuthApi,
|
|
@@ -13,17 +16,30 @@ export {
|
|
|
13
16
|
type AuthUser,
|
|
14
17
|
} from "../../server/src/auth.js";
|
|
15
18
|
|
|
19
|
+
export {
|
|
20
|
+
createMemoryAuthSessionStore,
|
|
21
|
+
|
|
22
|
+
type AuthSessionStore,
|
|
23
|
+
type AuthSessionStoreRecord,
|
|
24
|
+
type MemoryAuthSessionStore,
|
|
25
|
+
} from "../../server/src/auth-session-store.js";
|
|
26
|
+
|
|
16
27
|
export {
|
|
17
28
|
createAuthGuard,
|
|
29
|
+
createGuestGuard,
|
|
18
30
|
createRoleGuard,
|
|
19
31
|
getGuardAuth,
|
|
20
32
|
requireAuth,
|
|
33
|
+
requireGuest,
|
|
21
34
|
requireRole,
|
|
22
35
|
|
|
23
36
|
type AuthGuardData,
|
|
24
37
|
type AuthGuardFunction,
|
|
25
38
|
type AuthGuardResult,
|
|
39
|
+
type GuestGuardFunction,
|
|
40
|
+
type GuestGuardResult,
|
|
26
41
|
type RequireAuthOptions,
|
|
42
|
+
type RequireGuestOptions,
|
|
27
43
|
type RequireRoleOptions,
|
|
28
44
|
type RequiredRole,
|
|
29
45
|
} from "../../server/src/auth-guard.js";
|
|
@@ -18,10 +18,13 @@ export interface AuthGuardData<
|
|
|
18
18
|
|
|
19
19
|
export interface RequireAuthOptions
|
|
20
20
|
extends AuthOptions {
|
|
21
|
-
redirectTo?:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
redirectTo?: string | URL | null;
|
|
22
|
+
redirectStatus?: RedirectStatus;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RequireGuestOptions
|
|
26
|
+
extends AuthOptions {
|
|
27
|
+
redirectTo?: string | URL | null;
|
|
25
28
|
redirectStatus?: RedirectStatus;
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -29,10 +32,7 @@ export interface RequireRoleOptions
|
|
|
29
32
|
extends RequireAuthOptions {
|
|
30
33
|
roleField?: string;
|
|
31
34
|
match?: "any" | "all";
|
|
32
|
-
forbiddenRedirectTo?:
|
|
33
|
-
string |
|
|
34
|
-
URL |
|
|
35
|
-
null;
|
|
35
|
+
forbiddenRedirectTo?: string | URL | null;
|
|
36
36
|
forbiddenRedirectStatus?: RedirectStatus;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -47,16 +47,20 @@ export type AuthGuardResult<
|
|
|
47
47
|
| AuthGuardData<TUser, TData>
|
|
48
48
|
| Response;
|
|
49
49
|
|
|
50
|
+
export type GuestGuardResult =
|
|
51
|
+
| Record<string, never>
|
|
52
|
+
| Response;
|
|
53
|
+
|
|
50
54
|
export type AuthGuardFunction<
|
|
51
55
|
TUser extends AuthUser = AuthUser,
|
|
52
56
|
TData extends object = Record<string, never>
|
|
53
57
|
> = () => Promise<
|
|
54
|
-
AuthGuardResult<
|
|
55
|
-
TUser,
|
|
56
|
-
TData
|
|
57
|
-
>
|
|
58
|
+
AuthGuardResult<TUser, TData>
|
|
58
59
|
>;
|
|
59
60
|
|
|
61
|
+
export type GuestGuardFunction =
|
|
62
|
+
() => Promise<GuestGuardResult>;
|
|
63
|
+
|
|
60
64
|
export async function requireAuth<
|
|
61
65
|
TUser extends AuthUser = AuthUser,
|
|
62
66
|
TData extends object = Record<string, never>
|
|
@@ -69,23 +73,18 @@ export async function requireAuth<
|
|
|
69
73
|
...authOptions
|
|
70
74
|
} = options;
|
|
71
75
|
const session =
|
|
72
|
-
await auth<
|
|
73
|
-
TUser,
|
|
74
|
-
TData
|
|
75
|
-
>(
|
|
76
|
+
await auth<TUser, TData>(
|
|
76
77
|
authOptions
|
|
77
78
|
);
|
|
78
79
|
|
|
79
80
|
if (!session) {
|
|
80
81
|
if (
|
|
81
|
-
redirectTo ===
|
|
82
|
-
null
|
|
82
|
+
redirectTo === null
|
|
83
83
|
) {
|
|
84
84
|
return new Response(
|
|
85
85
|
"Unauthorized",
|
|
86
86
|
{
|
|
87
|
-
status:
|
|
88
|
-
401,
|
|
87
|
+
status: 401,
|
|
89
88
|
}
|
|
90
89
|
);
|
|
91
90
|
}
|
|
@@ -97,11 +96,47 @@ export async function requireAuth<
|
|
|
97
96
|
}
|
|
98
97
|
|
|
99
98
|
return {
|
|
100
|
-
auth:
|
|
101
|
-
session,
|
|
99
|
+
auth: session,
|
|
102
100
|
};
|
|
103
101
|
}
|
|
104
102
|
|
|
103
|
+
export async function requireGuest<
|
|
104
|
+
TUser extends AuthUser = AuthUser,
|
|
105
|
+
TData extends object = Record<string, never>
|
|
106
|
+
>(
|
|
107
|
+
options: RequireGuestOptions = {}
|
|
108
|
+
): Promise<GuestGuardResult> {
|
|
109
|
+
const {
|
|
110
|
+
redirectTo = "/",
|
|
111
|
+
redirectStatus = 303,
|
|
112
|
+
...authOptions
|
|
113
|
+
} = options;
|
|
114
|
+
const session =
|
|
115
|
+
await auth<TUser, TData>(
|
|
116
|
+
authOptions
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (!session) {
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (
|
|
124
|
+
redirectTo === null
|
|
125
|
+
) {
|
|
126
|
+
return new Response(
|
|
127
|
+
"Already authenticated",
|
|
128
|
+
{
|
|
129
|
+
status: 409,
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return redirect(
|
|
135
|
+
redirectTo,
|
|
136
|
+
redirectStatus
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
105
140
|
export async function requireRole<
|
|
106
141
|
TUser extends AuthUser = AuthUser,
|
|
107
142
|
TData extends object = Record<string, never>
|
|
@@ -121,16 +156,12 @@ export async function requireRole<
|
|
|
121
156
|
...authOptions
|
|
122
157
|
} = options;
|
|
123
158
|
const authenticated =
|
|
124
|
-
await requireAuth<
|
|
125
|
-
TUser,
|
|
126
|
-
TData
|
|
127
|
-
>(
|
|
159
|
+
await requireAuth<TUser, TData>(
|
|
128
160
|
authOptions
|
|
129
161
|
);
|
|
130
162
|
|
|
131
163
|
if (
|
|
132
|
-
authenticated instanceof
|
|
133
|
-
Response
|
|
164
|
+
authenticated instanceof Response
|
|
134
165
|
) {
|
|
135
166
|
return authenticated;
|
|
136
167
|
}
|
|
@@ -157,8 +188,7 @@ export async function requireRole<
|
|
|
157
188
|
|
|
158
189
|
if (!allowed) {
|
|
159
190
|
if (
|
|
160
|
-
forbiddenRedirectTo !==
|
|
161
|
-
null
|
|
191
|
+
forbiddenRedirectTo !== null
|
|
162
192
|
) {
|
|
163
193
|
return redirect(
|
|
164
194
|
forbiddenRedirectTo,
|
|
@@ -169,8 +199,7 @@ export async function requireRole<
|
|
|
169
199
|
return new Response(
|
|
170
200
|
"Forbidden",
|
|
171
201
|
{
|
|
172
|
-
status:
|
|
173
|
-
403,
|
|
202
|
+
status: 403,
|
|
174
203
|
}
|
|
175
204
|
);
|
|
176
205
|
}
|
|
@@ -185,10 +214,19 @@ export function createAuthGuard<
|
|
|
185
214
|
options: RequireAuthOptions = {}
|
|
186
215
|
): AuthGuardFunction<TUser, TData> {
|
|
187
216
|
return () =>
|
|
188
|
-
requireAuth<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
217
|
+
requireAuth<TUser, TData>(
|
|
218
|
+
options
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function createGuestGuard<
|
|
223
|
+
TUser extends AuthUser = AuthUser,
|
|
224
|
+
TData extends object = Record<string, never>
|
|
225
|
+
>(
|
|
226
|
+
options: RequireGuestOptions = {}
|
|
227
|
+
): GuestGuardFunction {
|
|
228
|
+
return () =>
|
|
229
|
+
requireGuest<TUser, TData>(
|
|
192
230
|
options
|
|
193
231
|
);
|
|
194
232
|
}
|
|
@@ -201,10 +239,7 @@ export function createRoleGuard<
|
|
|
201
239
|
options: RequireRoleOptions = {}
|
|
202
240
|
): AuthGuardFunction<TUser, TData> {
|
|
203
241
|
return () =>
|
|
204
|
-
requireRole<
|
|
205
|
-
TUser,
|
|
206
|
-
TData
|
|
207
|
-
>(
|
|
242
|
+
requireRole<TUser, TData>(
|
|
208
243
|
requiredRole,
|
|
209
244
|
options
|
|
210
245
|
);
|
|
@@ -214,18 +249,16 @@ export function getGuardAuth<
|
|
|
214
249
|
TUser extends AuthUser = AuthUser,
|
|
215
250
|
TData extends object = Record<string, never>
|
|
216
251
|
>(
|
|
217
|
-
guardData:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}>
|
|
252
|
+
guardData: Readonly<{
|
|
253
|
+
auth?: unknown;
|
|
254
|
+
}>
|
|
221
255
|
): AuthSession<TUser, TData> | null {
|
|
222
256
|
const value =
|
|
223
257
|
guardData.auth;
|
|
224
258
|
|
|
225
259
|
if (
|
|
226
260
|
!value ||
|
|
227
|
-
typeof value !==
|
|
228
|
-
"object" ||
|
|
261
|
+
typeof value !== "object" ||
|
|
229
262
|
Array.isArray(
|
|
230
263
|
value
|
|
231
264
|
)
|
|
@@ -235,24 +268,17 @@ export function getGuardAuth<
|
|
|
235
268
|
|
|
236
269
|
const session =
|
|
237
270
|
value as Partial<
|
|
238
|
-
AuthSession<
|
|
239
|
-
TUser,
|
|
240
|
-
TData
|
|
241
|
-
>
|
|
271
|
+
AuthSession<TUser, TData>
|
|
242
272
|
>;
|
|
243
273
|
|
|
244
274
|
if (
|
|
245
|
-
typeof session.sid !==
|
|
246
|
-
|
|
247
|
-
session.
|
|
248
|
-
0 ||
|
|
249
|
-
typeof session.iat !==
|
|
250
|
-
"number" ||
|
|
275
|
+
typeof session.sid !== "string" ||
|
|
276
|
+
session.sid.trim().length === 0 ||
|
|
277
|
+
typeof session.iat !== "number" ||
|
|
251
278
|
!Number.isFinite(
|
|
252
279
|
session.iat
|
|
253
280
|
) ||
|
|
254
|
-
typeof session.exp !==
|
|
255
|
-
"number" ||
|
|
281
|
+
typeof session.exp !== "number" ||
|
|
256
282
|
!Number.isFinite(
|
|
257
283
|
session.exp
|
|
258
284
|
) ||
|
|
@@ -264,18 +290,14 @@ export function getGuardAuth<
|
|
|
264
290
|
}
|
|
265
291
|
|
|
266
292
|
return value as
|
|
267
|
-
AuthSession<
|
|
268
|
-
TUser,
|
|
269
|
-
TData
|
|
270
|
-
>;
|
|
293
|
+
AuthSession<TUser, TData>;
|
|
271
294
|
}
|
|
272
295
|
|
|
273
296
|
function normalizeRequiredRoles(
|
|
274
297
|
value: RequiredRole
|
|
275
298
|
): string[] {
|
|
276
299
|
const roles =
|
|
277
|
-
typeof value ===
|
|
278
|
-
"string"
|
|
300
|
+
typeof value === "string"
|
|
279
301
|
? [
|
|
280
302
|
value,
|
|
281
303
|
]
|
|
@@ -289,12 +311,10 @@ function normalizeRequiredRoles(
|
|
|
289
311
|
);
|
|
290
312
|
|
|
291
313
|
if (
|
|
292
|
-
normalized.length ===
|
|
293
|
-
0 ||
|
|
314
|
+
normalized.length === 0 ||
|
|
294
315
|
normalized.some(
|
|
295
316
|
(role) =>
|
|
296
|
-
role.length ===
|
|
297
|
-
0
|
|
317
|
+
role.length === 0
|
|
298
318
|
)
|
|
299
319
|
) {
|
|
300
320
|
throw new TypeError(
|
|
@@ -324,14 +344,12 @@ function readAssignedRoles(
|
|
|
324
344
|
|
|
325
345
|
const value =
|
|
326
346
|
(
|
|
327
|
-
user as
|
|
328
|
-
unknown as
|
|
347
|
+
user as unknown as
|
|
329
348
|
Record<string, unknown>
|
|
330
349
|
)[field];
|
|
331
350
|
|
|
332
351
|
if (
|
|
333
|
-
typeof value ===
|
|
334
|
-
"string"
|
|
352
|
+
typeof value === "string"
|
|
335
353
|
) {
|
|
336
354
|
const role =
|
|
337
355
|
value.trim();
|
|
@@ -353,8 +371,7 @@ function readAssignedRoles(
|
|
|
353
371
|
(
|
|
354
372
|
role
|
|
355
373
|
): role is string =>
|
|
356
|
-
typeof role ===
|
|
357
|
-
"string"
|
|
374
|
+
typeof role === "string"
|
|
358
375
|
)
|
|
359
376
|
.map(
|
|
360
377
|
(role) =>
|
|
@@ -373,8 +390,7 @@ function isGuardAuthUser(
|
|
|
373
390
|
): value is AuthUser {
|
|
374
391
|
if (
|
|
375
392
|
!value ||
|
|
376
|
-
typeof value !==
|
|
377
|
-
"object" ||
|
|
393
|
+
typeof value !== "object" ||
|
|
378
394
|
Array.isArray(
|
|
379
395
|
value
|
|
380
396
|
)
|
|
@@ -389,13 +405,10 @@ function isGuardAuthUser(
|
|
|
389
405
|
).id;
|
|
390
406
|
|
|
391
407
|
return (
|
|
392
|
-
typeof id ===
|
|
393
|
-
|
|
394
|
-
id.trim().length >
|
|
395
|
-
0
|
|
408
|
+
typeof id === "string" &&
|
|
409
|
+
id.trim().length > 0
|
|
396
410
|
) || (
|
|
397
|
-
typeof id ===
|
|
398
|
-
"number" &&
|
|
411
|
+
typeof id === "number" &&
|
|
399
412
|
Number.isFinite(
|
|
400
413
|
id
|
|
401
414
|
)
|