@getstrata/core 0.5.58 → 0.5.59
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/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/dist/bootstrap/config.d.ts +1 -1
- package/dist/core/auth/sessionCookie.d.ts +2 -1
- package/dist/core/contracts/serviceTokens.d.ts +10 -1
- package/dist/core/http/csrfToken.d.ts +2 -1
- package/dist/core/view/htmlResponse.d.ts +9 -1
- package/dist/core/view/index.d.ts +3 -3
- package/dist/core/view/webLayoutData.d.ts +15 -4
- package/dist/entries/auth/sessionCookie.js +8 -4
- package/dist/entries/auth/sessionGuard.js +44 -5
- package/dist/entries/contracts/serviceTokens.js +43 -1
- package/dist/entries/http/csrfMiddleware.js +6 -3
- package/dist/entries/http/csrfToken.js +7 -3
- package/dist/entries/http/response.js +104 -26
- package/dist/entries/http/webErrorResponse.js +104 -26
- package/dist/entries/queue/createAppQueue.js +2 -0
- package/dist/entries/queue/jobRunner.js +2 -0
- package/dist/entries/queue/publicQueue.js +2 -0
- package/dist/entries/queue/queueMetrics.js +2 -0
- package/dist/entries/queue/redisQueue.js +2 -0
- package/dist/entries/view.js +110 -27
- package/dist/framework/public-api.d.ts +2 -2
- package/dist/index.js +98 -29
- package/package.json +4 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @getstrata/core changelog
|
|
2
|
+
|
|
3
|
+
## 0.5.59
|
|
4
|
+
|
|
5
|
+
Sibling HTMX apps can consume published packages without WorkHub-only glue.
|
|
6
|
+
|
|
7
|
+
- Emit `@getstrata/core/facades` types at `dist/core/facades/index.d.ts` (CI checks every `exports.*.types` path after build).
|
|
8
|
+
- Session and CSRF cookie names are configurable (`SESSION_COOKIE_NAME`, `CSRF_COOKIE_NAME`). Defaults remain `workhub_session` and `workhub_csrf` for WorkHub.
|
|
9
|
+
- Split `CORE_ABILITY_CHECKER_TOKEN` and `CORE_AUTH_USER_DIRECTORY_TOKEN` from `CORE_TOKEN_SERVICE_TOKEN`. HttpKernel prefers the ability-checker token; HMAC `SessionGuard` / layout data prefer the user directory. A compatibility shim uses `CORE_TOKEN_SERVICE_TOKEN` only when the bound value matches the requested type.
|
|
10
|
+
- `configureWebLayoutData` lets apps choose `currentUser` vs `authUser`, a custom user loader, and extra template fields. WorkHub still gets `{ authUser, csrfToken, flash }`.
|
|
11
|
+
- `redirectResponse`, `notFoundHtmlResponse`, `textResponse`, and `xmlResponse` join `htmlResponse` / `isHtmxRequest` on `@getstrata/core/view`.
|
|
12
|
+
- `LOGIN_RATE_LIMIT_WINDOW_MS` is a deprecated alias for `LOGIN_RATE_LIMIT_WINDOW_SECONDS`.
|
|
13
|
+
|
|
14
|
+
HMAC `SessionGuard` is unchanged for WorkHub API/token apps. Sibling HTMX apps should use `@getstrata/bootstrap` `CookieSessionStore` + `createCookieSessionAuthManager`.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Stable **Strata** framework surface for application modules.
|
|
|
5
5
|
**Source:** `src/framework/public-api.ts` (monorepo)
|
|
6
6
|
**Repository:** [EyK-26/strata](https://github.com/EyK-26/strata), directory `packages/strata-core`
|
|
7
7
|
|
|
8
|
-
WorkHub is the reference application built on Strata; import the framework from this package in your own modules.
|
|
8
|
+
WorkHub is the reference application built on Strata; import the framework from this package in your own modules. Sibling HTMX apps (Eta, cookie sessions, no API tokens) should follow [docs/SIBLING-HTMX.md](../../docs/SIBLING-HTMX.md).
|
|
9
9
|
|
|
10
10
|
## Usage
|
|
11
11
|
|
|
@@ -82,7 +82,7 @@ import type { Migration } from "@getstrata/core/database/migrations/types";
|
|
|
82
82
|
Package name: **`@getstrata/core`** (npm org [`@getstrata`](https://www.npmjs.com/org/getstrata)).
|
|
83
83
|
|
|
84
84
|
1. Add `NPM_TOKEN` to GitHub repository secrets.
|
|
85
|
-
2. Tag a release: `git tag v0.5.
|
|
85
|
+
2. Tag a release: `git tag v0.5.60 && git push origin v0.5.60`
|
|
86
86
|
3. [Release workflow](../../.github/workflows/release.yml) builds and runs `npm publish --access public`.
|
|
87
87
|
|
|
88
88
|
Previously published as `@eyk-workhub/framework@0.1.0`, deprecated in favor of this package.
|
|
@@ -4,7 +4,7 @@ declare const CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
|
|
|
4
4
|
declare const CACHE_DRIVER_CONFIG_KEY = "cache.driver";
|
|
5
5
|
declare const REDIS_URL_CONFIG_KEY = "cache.redisUrl";
|
|
6
6
|
declare const DATABASE_URL_CONFIG_KEY = "database.url";
|
|
7
|
-
export { CORE_AUTH_TOKEN, CORE_CACHE_TOKEN, CORE_CONFIG_TOKEN, CORE_EVENT_BUS_TOKEN, CORE_POLICY_GATE_TOKEN, CORE_QUEUE_TOKEN, CORE_TOKEN_SERVICE_TOKEN, } from "@getstrata/core/contracts/serviceTokens";
|
|
7
|
+
export { CORE_ABILITY_CHECKER_TOKEN, CORE_AUTH_TOKEN, CORE_AUTH_USER_DIRECTORY_TOKEN, CORE_CACHE_TOKEN, CORE_CONFIG_TOKEN, CORE_EVENT_BUS_TOKEN, CORE_POLICY_GATE_TOKEN, CORE_QUEUE_TOKEN, CORE_TOKEN_SERVICE_TOKEN, } from "@getstrata/core/contracts/serviceTokens";
|
|
8
8
|
declare const AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
|
|
9
9
|
declare const DEFAULT_APP_PORT = 3000;
|
|
10
10
|
declare const DEFAULT_CACHE_TTL_MS = 3600000;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
declare const SESSION_COOKIE = "workhub_session";
|
|
2
2
|
declare const SESSION_TTL_SECONDS: number;
|
|
3
|
+
declare function sessionCookieName(): string;
|
|
3
4
|
declare function readSessionUserId(request: Request): number | null;
|
|
4
5
|
declare function createSessionCookie(userId: number): string;
|
|
5
6
|
declare function clearSessionCookie(): string;
|
|
6
|
-
export { clearSessionCookie, createSessionCookie, readSessionUserId, SESSION_COOKIE, SESSION_TTL_SECONDS, };
|
|
7
|
+
export { clearSessionCookie, createSessionCookie, readSessionUserId, SESSION_COOKIE, SESSION_TTL_SECONDS, sessionCookieName, };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { AbilityChecker } from "../auth/abilityChecker";
|
|
2
|
+
import type { AuthUserDirectory } from "./authUserDirectory";
|
|
3
|
+
import type { ServiceContainerLike } from "./container";
|
|
1
4
|
declare const CORE_CONFIG_TOKEN = "core.config";
|
|
2
5
|
declare const CORE_CACHE_TOKEN = "core.cache";
|
|
3
6
|
declare const CORE_QUEUE_TOKEN = "core.queue";
|
|
@@ -5,4 +8,10 @@ declare const CORE_EVENT_BUS_TOKEN = "core.eventBus";
|
|
|
5
8
|
declare const CORE_POLICY_GATE_TOKEN = "core.policyGate";
|
|
6
9
|
declare const CORE_AUTH_TOKEN = "core.auth";
|
|
7
10
|
declare const CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
|
|
8
|
-
|
|
11
|
+
declare const CORE_ABILITY_CHECKER_TOKEN = "core.abilityChecker";
|
|
12
|
+
declare const CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
|
|
13
|
+
declare function isAbilityChecker(value: unknown): value is AbilityChecker;
|
|
14
|
+
declare function isAuthUserDirectory(value: unknown): value is AuthUserDirectory;
|
|
15
|
+
declare function resolveAbilityChecker(container: ServiceContainerLike): AbilityChecker;
|
|
16
|
+
declare function resolveAuthUserDirectory(container: ServiceContainerLike): AuthUserDirectory | null;
|
|
17
|
+
export { CORE_ABILITY_CHECKER_TOKEN, CORE_AUTH_TOKEN, CORE_AUTH_USER_DIRECTORY_TOKEN, CORE_CACHE_TOKEN, CORE_CONFIG_TOKEN, CORE_EVENT_BUS_TOKEN, CORE_POLICY_GATE_TOKEN, CORE_QUEUE_TOKEN, CORE_TOKEN_SERVICE_TOKEN, isAbilityChecker, isAuthUserDirectory, resolveAbilityChecker, resolveAuthUserDirectory, };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
declare const CSRF_COOKIE = "workhub_csrf";
|
|
2
|
+
declare function csrfCookieName(): string;
|
|
2
3
|
declare function createCsrfTokenCookie(): {
|
|
3
4
|
token: string;
|
|
4
5
|
cookie: string;
|
|
@@ -11,4 +12,4 @@ declare function readSubmittedCsrfToken(request: Request): string | null;
|
|
|
11
12
|
declare function readSubmittedCsrfTokenFromBody(request: Request): Promise<string | null>;
|
|
12
13
|
declare function verifyCsrfToken(request: Request, submittedToken: string | null): boolean;
|
|
13
14
|
declare function resolveCsrfTokenForRequest(request: Request): string;
|
|
14
|
-
export { CSRF_COOKIE, createCsrfTokenCookie, readSubmittedCsrfToken, readSubmittedCsrfTokenFromBody, resolveCsrfToken, resolveCsrfTokenForRequest, verifyCsrfToken, };
|
|
15
|
+
export { CSRF_COOKIE, createCsrfTokenCookie, csrfCookieName, readSubmittedCsrfToken, readSubmittedCsrfTokenFromBody, resolveCsrfToken, resolveCsrfTokenForRequest, verifyCsrfToken, };
|
|
@@ -3,4 +3,12 @@ declare function htmlResponse(html: string, init?: {
|
|
|
3
3
|
statusText?: string;
|
|
4
4
|
}): Response;
|
|
5
5
|
declare function isHtmxRequest(request: Request): boolean;
|
|
6
|
-
|
|
6
|
+
declare function redirectResponse(location: string, status?: number): Response;
|
|
7
|
+
declare function notFoundHtmlResponse(body?: string): Response;
|
|
8
|
+
declare function textResponse(body: string, init?: {
|
|
9
|
+
status?: number;
|
|
10
|
+
}): Response;
|
|
11
|
+
declare function xmlResponse(body: string, init?: {
|
|
12
|
+
status?: number;
|
|
13
|
+
}): Response;
|
|
14
|
+
export { htmlResponse, isHtmxRequest, notFoundHtmlResponse, redirectResponse, textResponse, xmlResponse, };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { DEFAULT_VIEWS_DIRECTORY, EtaViewEngine } from "./etaViewEngine";
|
|
2
|
-
export { htmlResponse, isHtmxRequest } from "./htmlResponse";
|
|
2
|
+
export { htmlResponse, isHtmxRequest, notFoundHtmlResponse, redirectResponse, textResponse, xmlResponse, } from "./htmlResponse";
|
|
3
3
|
export type { ViewEngine } from "./viewEngine";
|
|
4
|
-
export type { WebLayoutAuthUser, WebLayoutData } from "./webLayoutData";
|
|
5
|
-
export { resolveWebLayoutData } from "./webLayoutData";
|
|
4
|
+
export type { WebLayoutAuthUser, WebLayoutData, WebLayoutDataOptions, WebLayoutUserKey, } from "./webLayoutData";
|
|
5
|
+
export { configureWebLayoutData, resolveWebLayoutData } from "./webLayoutData";
|
|
@@ -3,15 +3,26 @@ interface WebLayoutAuthUser {
|
|
|
3
3
|
id: number;
|
|
4
4
|
email: string;
|
|
5
5
|
role: string;
|
|
6
|
+
[key: string]: unknown;
|
|
6
7
|
}
|
|
7
8
|
interface WebLayoutData {
|
|
8
|
-
authUser
|
|
9
|
+
authUser?: WebLayoutAuthUser | null;
|
|
10
|
+
currentUser?: WebLayoutAuthUser | null;
|
|
9
11
|
csrfToken: string;
|
|
10
12
|
flash: {
|
|
11
13
|
level: string;
|
|
12
14
|
message: string;
|
|
13
15
|
} | null;
|
|
16
|
+
[key: string]: unknown;
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
type WebLayoutUserKey = "authUser" | "currentUser";
|
|
19
|
+
interface WebLayoutDataOptions {
|
|
20
|
+
userKey?: WebLayoutUserKey;
|
|
21
|
+
loadUser?: (container: ServiceContainerLike, request: Request | undefined) => Promise<Record<string, unknown> | null>;
|
|
22
|
+
extra?: Record<string, unknown> | ((user: Record<string, unknown> | null) => Record<string, unknown> | Promise<Record<string, unknown>>);
|
|
23
|
+
}
|
|
24
|
+
declare function configureWebLayoutData(options: WebLayoutDataOptions): void;
|
|
25
|
+
declare function resetWebLayoutDataConfigForTests(): void;
|
|
26
|
+
declare function resolveWebLayoutData(container: ServiceContainerLike, request?: Request, options?: WebLayoutDataOptions): Promise<Record<string, unknown>>;
|
|
27
|
+
export type { WebLayoutAuthUser, WebLayoutData, WebLayoutDataOptions, WebLayoutUserKey };
|
|
28
|
+
export { configureWebLayoutData, resetWebLayoutDataConfigForTests, resolveWebLayoutData };
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
4
4
|
var SESSION_COOKIE = "workhub_session";
|
|
5
5
|
var SESSION_TTL_SECONDS = 60 * 60 * 24 * 7;
|
|
6
|
+
function sessionCookieName() {
|
|
7
|
+
return process.env.SESSION_COOKIE_NAME?.trim() || SESSION_COOKIE;
|
|
8
|
+
}
|
|
6
9
|
function resolveSessionSecret() {
|
|
7
10
|
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-session-secret";
|
|
8
11
|
}
|
|
@@ -25,7 +28,7 @@ function readCookieValue(request, cookieName) {
|
|
|
25
28
|
return null;
|
|
26
29
|
}
|
|
27
30
|
function readSessionUserId(request) {
|
|
28
|
-
const cookieValue = readCookieValue(request,
|
|
31
|
+
const cookieValue = readCookieValue(request, sessionCookieName());
|
|
29
32
|
if (!cookieValue) {
|
|
30
33
|
return null;
|
|
31
34
|
}
|
|
@@ -60,16 +63,17 @@ function createSessionCookie(userId) {
|
|
|
60
63
|
const issuedAt = Date.now();
|
|
61
64
|
const value = signSession(userId, issuedAt);
|
|
62
65
|
const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
|
|
63
|
-
return `${
|
|
66
|
+
return `${sessionCookieName()}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${SESSION_TTL_SECONDS}${secure}`;
|
|
64
67
|
}
|
|
65
68
|
function clearSessionCookie() {
|
|
66
69
|
const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
|
|
67
|
-
return `${
|
|
70
|
+
return `${sessionCookieName()}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${secure}`;
|
|
68
71
|
}
|
|
69
72
|
export {
|
|
70
73
|
SESSION_COOKIE,
|
|
71
74
|
SESSION_TTL_SECONDS,
|
|
72
75
|
clearSessionCookie,
|
|
73
76
|
createSessionCookie,
|
|
74
|
-
readSessionUserId
|
|
77
|
+
readSessionUserId,
|
|
78
|
+
sessionCookieName
|
|
75
79
|
};
|
|
@@ -7,6 +7,42 @@ var CORE_EVENT_BUS_TOKEN = "core.eventBus";
|
|
|
7
7
|
var CORE_POLICY_GATE_TOKEN = "core.policyGate";
|
|
8
8
|
var CORE_AUTH_TOKEN = "core.auth";
|
|
9
9
|
var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
|
|
10
|
+
var CORE_ABILITY_CHECKER_TOKEN = "core.abilityChecker";
|
|
11
|
+
var CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
|
|
12
|
+
function isAbilityChecker(value) {
|
|
13
|
+
if (!value || typeof value !== "object") {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const candidate = value;
|
|
17
|
+
return typeof candidate.tokenCan === "function" && typeof candidate.requireAbility === "function";
|
|
18
|
+
}
|
|
19
|
+
function isAuthUserDirectory(value) {
|
|
20
|
+
if (!value || typeof value !== "object") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return typeof value.findByIdOrThrow === "function";
|
|
24
|
+
}
|
|
25
|
+
function resolveAbilityChecker(container) {
|
|
26
|
+
if (container.has(CORE_ABILITY_CHECKER_TOKEN)) {
|
|
27
|
+
return container.resolve(CORE_ABILITY_CHECKER_TOKEN);
|
|
28
|
+
}
|
|
29
|
+
return container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
30
|
+
}
|
|
31
|
+
function resolveAuthUserDirectory(container) {
|
|
32
|
+
if (container.has(CORE_AUTH_USER_DIRECTORY_TOKEN)) {
|
|
33
|
+
const directory = container.resolve(CORE_AUTH_USER_DIRECTORY_TOKEN);
|
|
34
|
+
if (isAuthUserDirectory(directory)) {
|
|
35
|
+
return directory;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (container.has(CORE_TOKEN_SERVICE_TOKEN)) {
|
|
39
|
+
const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
40
|
+
if (isAuthUserDirectory(tokenService)) {
|
|
41
|
+
return tokenService;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
10
46
|
|
|
11
47
|
// ../../src/core/auth/abilityCatalog.ts
|
|
12
48
|
var MEMBER_ABILITIES = [
|
|
@@ -50,6 +86,9 @@ function resolveAbilitiesForRole(role) {
|
|
|
50
86
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
51
87
|
var SESSION_COOKIE = "workhub_session";
|
|
52
88
|
var SESSION_TTL_SECONDS = 60 * 60 * 24 * 7;
|
|
89
|
+
function sessionCookieName() {
|
|
90
|
+
return process.env.SESSION_COOKIE_NAME?.trim() || SESSION_COOKIE;
|
|
91
|
+
}
|
|
53
92
|
function resolveSessionSecret() {
|
|
54
93
|
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-session-secret";
|
|
55
94
|
}
|
|
@@ -72,7 +111,7 @@ function readCookieValue(request, cookieName) {
|
|
|
72
111
|
return null;
|
|
73
112
|
}
|
|
74
113
|
function readSessionUserId(request) {
|
|
75
|
-
const cookieValue = readCookieValue(request,
|
|
114
|
+
const cookieValue = readCookieValue(request, sessionCookieName());
|
|
76
115
|
if (!cookieValue) {
|
|
77
116
|
return null;
|
|
78
117
|
}
|
|
@@ -107,11 +146,11 @@ function createSessionCookie(userId) {
|
|
|
107
146
|
const issuedAt = Date.now();
|
|
108
147
|
const value = signSession(userId, issuedAt);
|
|
109
148
|
const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
|
|
110
|
-
return `${
|
|
149
|
+
return `${sessionCookieName()}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${SESSION_TTL_SECONDS}${secure}`;
|
|
111
150
|
}
|
|
112
151
|
function clearSessionCookie() {
|
|
113
152
|
const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
|
|
114
|
-
return `${
|
|
153
|
+
return `${sessionCookieName()}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${secure}`;
|
|
115
154
|
}
|
|
116
155
|
|
|
117
156
|
// ../../src/core/auth/sessionGuard.ts
|
|
@@ -125,10 +164,10 @@ class SessionGuard {
|
|
|
125
164
|
if (!userId) {
|
|
126
165
|
return null;
|
|
127
166
|
}
|
|
128
|
-
|
|
167
|
+
const tokenService = resolveAuthUserDirectory(this.container);
|
|
168
|
+
if (!tokenService) {
|
|
129
169
|
return null;
|
|
130
170
|
}
|
|
131
|
-
const tokenService = this.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
132
171
|
try {
|
|
133
172
|
const user = await tokenService.findByIdOrThrow(userId);
|
|
134
173
|
return {
|
|
@@ -7,12 +7,54 @@ var CORE_EVENT_BUS_TOKEN = "core.eventBus";
|
|
|
7
7
|
var CORE_POLICY_GATE_TOKEN = "core.policyGate";
|
|
8
8
|
var CORE_AUTH_TOKEN = "core.auth";
|
|
9
9
|
var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
|
|
10
|
+
var CORE_ABILITY_CHECKER_TOKEN = "core.abilityChecker";
|
|
11
|
+
var CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
|
|
12
|
+
function isAbilityChecker(value) {
|
|
13
|
+
if (!value || typeof value !== "object") {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const candidate = value;
|
|
17
|
+
return typeof candidate.tokenCan === "function" && typeof candidate.requireAbility === "function";
|
|
18
|
+
}
|
|
19
|
+
function isAuthUserDirectory(value) {
|
|
20
|
+
if (!value || typeof value !== "object") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return typeof value.findByIdOrThrow === "function";
|
|
24
|
+
}
|
|
25
|
+
function resolveAbilityChecker(container) {
|
|
26
|
+
if (container.has(CORE_ABILITY_CHECKER_TOKEN)) {
|
|
27
|
+
return container.resolve(CORE_ABILITY_CHECKER_TOKEN);
|
|
28
|
+
}
|
|
29
|
+
return container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
30
|
+
}
|
|
31
|
+
function resolveAuthUserDirectory(container) {
|
|
32
|
+
if (container.has(CORE_AUTH_USER_DIRECTORY_TOKEN)) {
|
|
33
|
+
const directory = container.resolve(CORE_AUTH_USER_DIRECTORY_TOKEN);
|
|
34
|
+
if (isAuthUserDirectory(directory)) {
|
|
35
|
+
return directory;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (container.has(CORE_TOKEN_SERVICE_TOKEN)) {
|
|
39
|
+
const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
40
|
+
if (isAuthUserDirectory(tokenService)) {
|
|
41
|
+
return tokenService;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
10
46
|
export {
|
|
47
|
+
CORE_ABILITY_CHECKER_TOKEN,
|
|
11
48
|
CORE_AUTH_TOKEN,
|
|
49
|
+
CORE_AUTH_USER_DIRECTORY_TOKEN,
|
|
12
50
|
CORE_CACHE_TOKEN,
|
|
13
51
|
CORE_CONFIG_TOKEN,
|
|
14
52
|
CORE_EVENT_BUS_TOKEN,
|
|
15
53
|
CORE_POLICY_GATE_TOKEN,
|
|
16
54
|
CORE_QUEUE_TOKEN,
|
|
17
|
-
CORE_TOKEN_SERVICE_TOKEN
|
|
55
|
+
CORE_TOKEN_SERVICE_TOKEN,
|
|
56
|
+
isAbilityChecker,
|
|
57
|
+
isAuthUserDirectory,
|
|
58
|
+
resolveAbilityChecker,
|
|
59
|
+
resolveAuthUserDirectory
|
|
18
60
|
};
|
|
@@ -62,6 +62,9 @@ function currentRequestMeta() {
|
|
|
62
62
|
// ../../src/core/http/csrfToken.ts
|
|
63
63
|
var CSRF_COOKIE = "workhub_csrf";
|
|
64
64
|
var CSRF_TTL_MS = 60 * 60 * 1000;
|
|
65
|
+
function csrfCookieName() {
|
|
66
|
+
return process.env.CSRF_COOKIE_NAME?.trim() || CSRF_COOKIE;
|
|
67
|
+
}
|
|
65
68
|
function resolveCsrfSecret() {
|
|
66
69
|
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
|
|
67
70
|
}
|
|
@@ -81,11 +84,11 @@ function createCsrfTokenCookie() {
|
|
|
81
84
|
const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
|
|
82
85
|
return {
|
|
83
86
|
token,
|
|
84
|
-
cookie: `${
|
|
87
|
+
cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
|
|
85
88
|
};
|
|
86
89
|
}
|
|
87
90
|
function resolveCsrfToken(request) {
|
|
88
|
-
const cookieValue = readRequestCookie(request,
|
|
91
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
89
92
|
if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
|
|
90
93
|
return { token: cookieValue };
|
|
91
94
|
}
|
|
@@ -121,7 +124,7 @@ function verifyCsrfToken(request, submittedToken) {
|
|
|
121
124
|
if (!submittedToken) {
|
|
122
125
|
return false;
|
|
123
126
|
}
|
|
124
|
-
const cookieValue = readRequestCookie(request,
|
|
127
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
125
128
|
if (!cookieValue) {
|
|
126
129
|
return false;
|
|
127
130
|
}
|
|
@@ -59,6 +59,9 @@ function currentRequestMeta() {
|
|
|
59
59
|
// ../../src/core/http/csrfToken.ts
|
|
60
60
|
var CSRF_COOKIE = "workhub_csrf";
|
|
61
61
|
var CSRF_TTL_MS = 60 * 60 * 1000;
|
|
62
|
+
function csrfCookieName() {
|
|
63
|
+
return process.env.CSRF_COOKIE_NAME?.trim() || CSRF_COOKIE;
|
|
64
|
+
}
|
|
62
65
|
function resolveCsrfSecret() {
|
|
63
66
|
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
|
|
64
67
|
}
|
|
@@ -78,11 +81,11 @@ function createCsrfTokenCookie() {
|
|
|
78
81
|
const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
|
|
79
82
|
return {
|
|
80
83
|
token,
|
|
81
|
-
cookie: `${
|
|
84
|
+
cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
|
|
82
85
|
};
|
|
83
86
|
}
|
|
84
87
|
function resolveCsrfToken(request) {
|
|
85
|
-
const cookieValue = readRequestCookie(request,
|
|
88
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
86
89
|
if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
|
|
87
90
|
return { token: cookieValue };
|
|
88
91
|
}
|
|
@@ -118,7 +121,7 @@ function verifyCsrfToken(request, submittedToken) {
|
|
|
118
121
|
if (!submittedToken) {
|
|
119
122
|
return false;
|
|
120
123
|
}
|
|
121
|
-
const cookieValue = readRequestCookie(request,
|
|
124
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
122
125
|
if (!cookieValue) {
|
|
123
126
|
return false;
|
|
124
127
|
}
|
|
@@ -137,6 +140,7 @@ function resolveCsrfTokenForRequest(request) {
|
|
|
137
140
|
export {
|
|
138
141
|
CSRF_COOKIE,
|
|
139
142
|
createCsrfTokenCookie,
|
|
143
|
+
csrfCookieName,
|
|
140
144
|
readSubmittedCsrfToken,
|
|
141
145
|
readSubmittedCsrfTokenFromBody,
|
|
142
146
|
resolveCsrfToken,
|
|
@@ -309,6 +309,33 @@ function htmlResponse(html, init = {}) {
|
|
|
309
309
|
function isHtmxRequest(request) {
|
|
310
310
|
return request.headers.get("HX-Request") === "true";
|
|
311
311
|
}
|
|
312
|
+
function redirectResponse(location, status = 302) {
|
|
313
|
+
return new Response(null, {
|
|
314
|
+
status,
|
|
315
|
+
headers: {
|
|
316
|
+
Location: location
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
function notFoundHtmlResponse(body = "Not Found") {
|
|
321
|
+
return htmlResponse(body, { status: 404 });
|
|
322
|
+
}
|
|
323
|
+
function textResponse(body, init = {}) {
|
|
324
|
+
return new Response(body, {
|
|
325
|
+
status: init.status ?? 200,
|
|
326
|
+
headers: {
|
|
327
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
function xmlResponse(body, init = {}) {
|
|
332
|
+
return new Response(body, {
|
|
333
|
+
status: init.status ?? 200,
|
|
334
|
+
headers: {
|
|
335
|
+
"Content-Type": "application/xml; charset=utf-8"
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
312
339
|
// ../../src/core/view/webLayoutData.ts
|
|
313
340
|
import { currentAuthUser } from "@getstrata/core/auth/authContext";
|
|
314
341
|
import { currentRequestMeta as currentRequestMeta2 } from "@getstrata/core/http/requestMetaContext";
|
|
@@ -321,6 +348,42 @@ var CORE_EVENT_BUS_TOKEN = "core.eventBus";
|
|
|
321
348
|
var CORE_POLICY_GATE_TOKEN = "core.policyGate";
|
|
322
349
|
var CORE_AUTH_TOKEN = "core.auth";
|
|
323
350
|
var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
|
|
351
|
+
var CORE_ABILITY_CHECKER_TOKEN = "core.abilityChecker";
|
|
352
|
+
var CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
|
|
353
|
+
function isAbilityChecker(value) {
|
|
354
|
+
if (!value || typeof value !== "object") {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
const candidate = value;
|
|
358
|
+
return typeof candidate.tokenCan === "function" && typeof candidate.requireAbility === "function";
|
|
359
|
+
}
|
|
360
|
+
function isAuthUserDirectory(value) {
|
|
361
|
+
if (!value || typeof value !== "object") {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
return typeof value.findByIdOrThrow === "function";
|
|
365
|
+
}
|
|
366
|
+
function resolveAbilityChecker(container) {
|
|
367
|
+
if (container.has(CORE_ABILITY_CHECKER_TOKEN)) {
|
|
368
|
+
return container.resolve(CORE_ABILITY_CHECKER_TOKEN);
|
|
369
|
+
}
|
|
370
|
+
return container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
371
|
+
}
|
|
372
|
+
function resolveAuthUserDirectory(container) {
|
|
373
|
+
if (container.has(CORE_AUTH_USER_DIRECTORY_TOKEN)) {
|
|
374
|
+
const directory = container.resolve(CORE_AUTH_USER_DIRECTORY_TOKEN);
|
|
375
|
+
if (isAuthUserDirectory(directory)) {
|
|
376
|
+
return directory;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (container.has(CORE_TOKEN_SERVICE_TOKEN)) {
|
|
380
|
+
const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
381
|
+
if (isAuthUserDirectory(tokenService)) {
|
|
382
|
+
return tokenService;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
324
387
|
|
|
325
388
|
// ../../src/core/http/csrfToken.ts
|
|
326
389
|
import { timingSafeEqual } from "crypto";
|
|
@@ -382,6 +445,9 @@ function currentRequestMeta() {
|
|
|
382
445
|
// ../../src/core/http/csrfToken.ts
|
|
383
446
|
var CSRF_COOKIE = "workhub_csrf";
|
|
384
447
|
var CSRF_TTL_MS = 60 * 60 * 1000;
|
|
448
|
+
function csrfCookieName() {
|
|
449
|
+
return process.env.CSRF_COOKIE_NAME?.trim() || CSRF_COOKIE;
|
|
450
|
+
}
|
|
385
451
|
function resolveCsrfSecret() {
|
|
386
452
|
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
|
|
387
453
|
}
|
|
@@ -401,11 +467,11 @@ function createCsrfTokenCookie() {
|
|
|
401
467
|
const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
|
|
402
468
|
return {
|
|
403
469
|
token,
|
|
404
|
-
cookie: `${
|
|
470
|
+
cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
|
|
405
471
|
};
|
|
406
472
|
}
|
|
407
473
|
function resolveCsrfToken(request) {
|
|
408
|
-
const cookieValue = readRequestCookie(request,
|
|
474
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
409
475
|
if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
|
|
410
476
|
return { token: cookieValue };
|
|
411
477
|
}
|
|
@@ -441,7 +507,7 @@ function verifyCsrfToken(request, submittedToken) {
|
|
|
441
507
|
if (!submittedToken) {
|
|
442
508
|
return false;
|
|
443
509
|
}
|
|
444
|
-
const cookieValue = readRequestCookie(request,
|
|
510
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
445
511
|
if (!cookieValue) {
|
|
446
512
|
return false;
|
|
447
513
|
}
|
|
@@ -558,44 +624,56 @@ function withFlashClear(response) {
|
|
|
558
624
|
}
|
|
559
625
|
|
|
560
626
|
// ../../src/core/view/webLayoutData.ts
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
627
|
+
var configuredLayoutOptions = {};
|
|
628
|
+
function configureWebLayoutData(options) {
|
|
629
|
+
configuredLayoutOptions = { ...options };
|
|
630
|
+
}
|
|
631
|
+
function layoutUserKey(options) {
|
|
632
|
+
return options.userKey ?? "authUser";
|
|
633
|
+
}
|
|
634
|
+
async function defaultLoadLayoutUser(container, _request) {
|
|
564
635
|
const authUser = currentAuthUser();
|
|
565
636
|
if (!authUser) {
|
|
566
|
-
return
|
|
637
|
+
return null;
|
|
567
638
|
}
|
|
568
639
|
const userId = Number(authUser.id);
|
|
569
640
|
if (!Number.isInteger(userId) || userId <= 0) {
|
|
570
|
-
return
|
|
641
|
+
return null;
|
|
571
642
|
}
|
|
572
|
-
|
|
643
|
+
const directory = resolveAuthUserDirectory(container);
|
|
644
|
+
if (!directory) {
|
|
573
645
|
return {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
role: authUser.role ?? "member"
|
|
578
|
-
},
|
|
579
|
-
csrfToken,
|
|
580
|
-
flash
|
|
646
|
+
id: userId,
|
|
647
|
+
email: "",
|
|
648
|
+
role: authUser.role ?? "member"
|
|
581
649
|
};
|
|
582
650
|
}
|
|
583
|
-
const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
584
651
|
try {
|
|
585
|
-
const user = await
|
|
652
|
+
const user = await directory.findByIdOrThrow(userId);
|
|
586
653
|
return {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
role: authUser.role ?? user.role ?? "member"
|
|
591
|
-
},
|
|
592
|
-
csrfToken,
|
|
593
|
-
flash
|
|
654
|
+
id: userId,
|
|
655
|
+
email: user.email ?? "",
|
|
656
|
+
role: authUser.role ?? user.role ?? "member"
|
|
594
657
|
};
|
|
595
658
|
} catch {
|
|
596
|
-
return
|
|
659
|
+
return null;
|
|
597
660
|
}
|
|
598
661
|
}
|
|
662
|
+
async function resolveWebLayoutData(container, request, options = {}) {
|
|
663
|
+
const resolved = { ...configuredLayoutOptions, ...options };
|
|
664
|
+
const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
|
|
665
|
+
const flash = request ? currentRequestMeta2().flash ?? pullFlash(request) : null;
|
|
666
|
+
const userKey = layoutUserKey(resolved);
|
|
667
|
+
const loadUser = resolved.loadUser ?? defaultLoadLayoutUser;
|
|
668
|
+
const user = await loadUser(container, request);
|
|
669
|
+
const extra = typeof resolved.extra === "function" ? await resolved.extra(user) : resolved.extra ?? {};
|
|
670
|
+
return {
|
|
671
|
+
[userKey]: user,
|
|
672
|
+
csrfToken,
|
|
673
|
+
flash,
|
|
674
|
+
...extra
|
|
675
|
+
};
|
|
676
|
+
}
|
|
599
677
|
// ../../src/core/http/contentNegotiation.ts
|
|
600
678
|
function requestPrefersJson(request) {
|
|
601
679
|
if (!request) {
|
|
@@ -306,6 +306,33 @@ function htmlResponse(html, init = {}) {
|
|
|
306
306
|
function isHtmxRequest(request) {
|
|
307
307
|
return request.headers.get("HX-Request") === "true";
|
|
308
308
|
}
|
|
309
|
+
function redirectResponse(location, status = 302) {
|
|
310
|
+
return new Response(null, {
|
|
311
|
+
status,
|
|
312
|
+
headers: {
|
|
313
|
+
Location: location
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function notFoundHtmlResponse(body = "Not Found") {
|
|
318
|
+
return htmlResponse(body, { status: 404 });
|
|
319
|
+
}
|
|
320
|
+
function textResponse(body, init = {}) {
|
|
321
|
+
return new Response(body, {
|
|
322
|
+
status: init.status ?? 200,
|
|
323
|
+
headers: {
|
|
324
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function xmlResponse(body, init = {}) {
|
|
329
|
+
return new Response(body, {
|
|
330
|
+
status: init.status ?? 200,
|
|
331
|
+
headers: {
|
|
332
|
+
"Content-Type": "application/xml; charset=utf-8"
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
309
336
|
// ../../src/core/view/webLayoutData.ts
|
|
310
337
|
import { currentAuthUser } from "@getstrata/core/auth/authContext";
|
|
311
338
|
import { currentRequestMeta as currentRequestMeta2 } from "@getstrata/core/http/requestMetaContext";
|
|
@@ -318,6 +345,42 @@ var CORE_EVENT_BUS_TOKEN = "core.eventBus";
|
|
|
318
345
|
var CORE_POLICY_GATE_TOKEN = "core.policyGate";
|
|
319
346
|
var CORE_AUTH_TOKEN = "core.auth";
|
|
320
347
|
var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
|
|
348
|
+
var CORE_ABILITY_CHECKER_TOKEN = "core.abilityChecker";
|
|
349
|
+
var CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
|
|
350
|
+
function isAbilityChecker(value) {
|
|
351
|
+
if (!value || typeof value !== "object") {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
const candidate = value;
|
|
355
|
+
return typeof candidate.tokenCan === "function" && typeof candidate.requireAbility === "function";
|
|
356
|
+
}
|
|
357
|
+
function isAuthUserDirectory(value) {
|
|
358
|
+
if (!value || typeof value !== "object") {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
return typeof value.findByIdOrThrow === "function";
|
|
362
|
+
}
|
|
363
|
+
function resolveAbilityChecker(container) {
|
|
364
|
+
if (container.has(CORE_ABILITY_CHECKER_TOKEN)) {
|
|
365
|
+
return container.resolve(CORE_ABILITY_CHECKER_TOKEN);
|
|
366
|
+
}
|
|
367
|
+
return container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
368
|
+
}
|
|
369
|
+
function resolveAuthUserDirectory(container) {
|
|
370
|
+
if (container.has(CORE_AUTH_USER_DIRECTORY_TOKEN)) {
|
|
371
|
+
const directory = container.resolve(CORE_AUTH_USER_DIRECTORY_TOKEN);
|
|
372
|
+
if (isAuthUserDirectory(directory)) {
|
|
373
|
+
return directory;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (container.has(CORE_TOKEN_SERVICE_TOKEN)) {
|
|
377
|
+
const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
378
|
+
if (isAuthUserDirectory(tokenService)) {
|
|
379
|
+
return tokenService;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
321
384
|
|
|
322
385
|
// ../../src/core/http/csrfToken.ts
|
|
323
386
|
import { timingSafeEqual } from "crypto";
|
|
@@ -379,6 +442,9 @@ function currentRequestMeta() {
|
|
|
379
442
|
// ../../src/core/http/csrfToken.ts
|
|
380
443
|
var CSRF_COOKIE = "workhub_csrf";
|
|
381
444
|
var CSRF_TTL_MS = 60 * 60 * 1000;
|
|
445
|
+
function csrfCookieName() {
|
|
446
|
+
return process.env.CSRF_COOKIE_NAME?.trim() || CSRF_COOKIE;
|
|
447
|
+
}
|
|
382
448
|
function resolveCsrfSecret() {
|
|
383
449
|
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
|
|
384
450
|
}
|
|
@@ -398,11 +464,11 @@ function createCsrfTokenCookie() {
|
|
|
398
464
|
const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
|
|
399
465
|
return {
|
|
400
466
|
token,
|
|
401
|
-
cookie: `${
|
|
467
|
+
cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
|
|
402
468
|
};
|
|
403
469
|
}
|
|
404
470
|
function resolveCsrfToken(request) {
|
|
405
|
-
const cookieValue = readRequestCookie(request,
|
|
471
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
406
472
|
if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
|
|
407
473
|
return { token: cookieValue };
|
|
408
474
|
}
|
|
@@ -438,7 +504,7 @@ function verifyCsrfToken(request, submittedToken) {
|
|
|
438
504
|
if (!submittedToken) {
|
|
439
505
|
return false;
|
|
440
506
|
}
|
|
441
|
-
const cookieValue = readRequestCookie(request,
|
|
507
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
442
508
|
if (!cookieValue) {
|
|
443
509
|
return false;
|
|
444
510
|
}
|
|
@@ -555,44 +621,56 @@ function withFlashClear(response) {
|
|
|
555
621
|
}
|
|
556
622
|
|
|
557
623
|
// ../../src/core/view/webLayoutData.ts
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
624
|
+
var configuredLayoutOptions = {};
|
|
625
|
+
function configureWebLayoutData(options) {
|
|
626
|
+
configuredLayoutOptions = { ...options };
|
|
627
|
+
}
|
|
628
|
+
function layoutUserKey(options) {
|
|
629
|
+
return options.userKey ?? "authUser";
|
|
630
|
+
}
|
|
631
|
+
async function defaultLoadLayoutUser(container, _request) {
|
|
561
632
|
const authUser = currentAuthUser();
|
|
562
633
|
if (!authUser) {
|
|
563
|
-
return
|
|
634
|
+
return null;
|
|
564
635
|
}
|
|
565
636
|
const userId = Number(authUser.id);
|
|
566
637
|
if (!Number.isInteger(userId) || userId <= 0) {
|
|
567
|
-
return
|
|
638
|
+
return null;
|
|
568
639
|
}
|
|
569
|
-
|
|
640
|
+
const directory = resolveAuthUserDirectory(container);
|
|
641
|
+
if (!directory) {
|
|
570
642
|
return {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
role: authUser.role ?? "member"
|
|
575
|
-
},
|
|
576
|
-
csrfToken,
|
|
577
|
-
flash
|
|
643
|
+
id: userId,
|
|
644
|
+
email: "",
|
|
645
|
+
role: authUser.role ?? "member"
|
|
578
646
|
};
|
|
579
647
|
}
|
|
580
|
-
const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
581
648
|
try {
|
|
582
|
-
const user = await
|
|
649
|
+
const user = await directory.findByIdOrThrow(userId);
|
|
583
650
|
return {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
role: authUser.role ?? user.role ?? "member"
|
|
588
|
-
},
|
|
589
|
-
csrfToken,
|
|
590
|
-
flash
|
|
651
|
+
id: userId,
|
|
652
|
+
email: user.email ?? "",
|
|
653
|
+
role: authUser.role ?? user.role ?? "member"
|
|
591
654
|
};
|
|
592
655
|
} catch {
|
|
593
|
-
return
|
|
656
|
+
return null;
|
|
594
657
|
}
|
|
595
658
|
}
|
|
659
|
+
async function resolveWebLayoutData(container, request, options = {}) {
|
|
660
|
+
const resolved = { ...configuredLayoutOptions, ...options };
|
|
661
|
+
const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
|
|
662
|
+
const flash = request ? currentRequestMeta2().flash ?? pullFlash(request) : null;
|
|
663
|
+
const userKey = layoutUserKey(resolved);
|
|
664
|
+
const loadUser = resolved.loadUser ?? defaultLoadLayoutUser;
|
|
665
|
+
const user = await loadUser(container, request);
|
|
666
|
+
const extra = typeof resolved.extra === "function" ? await resolved.extra(user) : resolved.extra ?? {};
|
|
667
|
+
return {
|
|
668
|
+
[userKey]: user,
|
|
669
|
+
csrfToken,
|
|
670
|
+
flash,
|
|
671
|
+
...extra
|
|
672
|
+
};
|
|
673
|
+
}
|
|
596
674
|
// ../../src/core/http/contentNegotiation.ts
|
|
597
675
|
function requestPrefersJson(request) {
|
|
598
676
|
if (!request) {
|
|
@@ -102,7 +102,9 @@ var jobRegistry = readSharedJobRegistry();
|
|
|
102
102
|
|
|
103
103
|
// ../../src/bootstrap/config.ts
|
|
104
104
|
import {
|
|
105
|
+
CORE_ABILITY_CHECKER_TOKEN,
|
|
105
106
|
CORE_AUTH_TOKEN,
|
|
107
|
+
CORE_AUTH_USER_DIRECTORY_TOKEN,
|
|
106
108
|
CORE_CACHE_TOKEN,
|
|
107
109
|
CORE_CONFIG_TOKEN,
|
|
108
110
|
CORE_EVENT_BUS_TOKEN,
|
|
@@ -102,7 +102,9 @@ var jobRegistry = readSharedJobRegistry();
|
|
|
102
102
|
|
|
103
103
|
// ../../src/bootstrap/config.ts
|
|
104
104
|
import {
|
|
105
|
+
CORE_ABILITY_CHECKER_TOKEN,
|
|
105
106
|
CORE_AUTH_TOKEN,
|
|
107
|
+
CORE_AUTH_USER_DIRECTORY_TOKEN,
|
|
106
108
|
CORE_CACHE_TOKEN,
|
|
107
109
|
CORE_CONFIG_TOKEN,
|
|
108
110
|
CORE_EVENT_BUS_TOKEN,
|
package/dist/entries/view.js
CHANGED
|
@@ -225,6 +225,33 @@ function htmlResponse(html, init = {}) {
|
|
|
225
225
|
function isHtmxRequest(request) {
|
|
226
226
|
return request.headers.get("HX-Request") === "true";
|
|
227
227
|
}
|
|
228
|
+
function redirectResponse(location, status = 302) {
|
|
229
|
+
return new Response(null, {
|
|
230
|
+
status,
|
|
231
|
+
headers: {
|
|
232
|
+
Location: location
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function notFoundHtmlResponse(body = "Not Found") {
|
|
237
|
+
return htmlResponse(body, { status: 404 });
|
|
238
|
+
}
|
|
239
|
+
function textResponse(body, init = {}) {
|
|
240
|
+
return new Response(body, {
|
|
241
|
+
status: init.status ?? 200,
|
|
242
|
+
headers: {
|
|
243
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function xmlResponse(body, init = {}) {
|
|
248
|
+
return new Response(body, {
|
|
249
|
+
status: init.status ?? 200,
|
|
250
|
+
headers: {
|
|
251
|
+
"Content-Type": "application/xml; charset=utf-8"
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
228
255
|
// ../../src/core/view/webLayoutData.ts
|
|
229
256
|
import { currentAuthUser } from "@getstrata/core/auth/authContext";
|
|
230
257
|
import { currentRequestMeta as currentRequestMeta2 } from "@getstrata/core/http/requestMetaContext";
|
|
@@ -237,6 +264,42 @@ var CORE_EVENT_BUS_TOKEN = "core.eventBus";
|
|
|
237
264
|
var CORE_POLICY_GATE_TOKEN = "core.policyGate";
|
|
238
265
|
var CORE_AUTH_TOKEN = "core.auth";
|
|
239
266
|
var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
|
|
267
|
+
var CORE_ABILITY_CHECKER_TOKEN = "core.abilityChecker";
|
|
268
|
+
var CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
|
|
269
|
+
function isAbilityChecker(value) {
|
|
270
|
+
if (!value || typeof value !== "object") {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
const candidate = value;
|
|
274
|
+
return typeof candidate.tokenCan === "function" && typeof candidate.requireAbility === "function";
|
|
275
|
+
}
|
|
276
|
+
function isAuthUserDirectory(value) {
|
|
277
|
+
if (!value || typeof value !== "object") {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
return typeof value.findByIdOrThrow === "function";
|
|
281
|
+
}
|
|
282
|
+
function resolveAbilityChecker(container) {
|
|
283
|
+
if (container.has(CORE_ABILITY_CHECKER_TOKEN)) {
|
|
284
|
+
return container.resolve(CORE_ABILITY_CHECKER_TOKEN);
|
|
285
|
+
}
|
|
286
|
+
return container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
287
|
+
}
|
|
288
|
+
function resolveAuthUserDirectory(container) {
|
|
289
|
+
if (container.has(CORE_AUTH_USER_DIRECTORY_TOKEN)) {
|
|
290
|
+
const directory = container.resolve(CORE_AUTH_USER_DIRECTORY_TOKEN);
|
|
291
|
+
if (isAuthUserDirectory(directory)) {
|
|
292
|
+
return directory;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (container.has(CORE_TOKEN_SERVICE_TOKEN)) {
|
|
296
|
+
const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
297
|
+
if (isAuthUserDirectory(tokenService)) {
|
|
298
|
+
return tokenService;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
240
303
|
|
|
241
304
|
// ../../src/core/http/csrfToken.ts
|
|
242
305
|
import { timingSafeEqual } from "crypto";
|
|
@@ -298,6 +361,9 @@ function currentRequestMeta() {
|
|
|
298
361
|
// ../../src/core/http/csrfToken.ts
|
|
299
362
|
var CSRF_COOKIE = "workhub_csrf";
|
|
300
363
|
var CSRF_TTL_MS = 60 * 60 * 1000;
|
|
364
|
+
function csrfCookieName() {
|
|
365
|
+
return process.env.CSRF_COOKIE_NAME?.trim() || CSRF_COOKIE;
|
|
366
|
+
}
|
|
301
367
|
function resolveCsrfSecret() {
|
|
302
368
|
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
|
|
303
369
|
}
|
|
@@ -317,11 +383,11 @@ function createCsrfTokenCookie() {
|
|
|
317
383
|
const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
|
|
318
384
|
return {
|
|
319
385
|
token,
|
|
320
|
-
cookie: `${
|
|
386
|
+
cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
|
|
321
387
|
};
|
|
322
388
|
}
|
|
323
389
|
function resolveCsrfToken(request) {
|
|
324
|
-
const cookieValue = readRequestCookie(request,
|
|
390
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
325
391
|
if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
|
|
326
392
|
return { token: cookieValue };
|
|
327
393
|
}
|
|
@@ -357,7 +423,7 @@ function verifyCsrfToken(request, submittedToken) {
|
|
|
357
423
|
if (!submittedToken) {
|
|
358
424
|
return false;
|
|
359
425
|
}
|
|
360
|
-
const cookieValue = readRequestCookie(request,
|
|
426
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
361
427
|
if (!cookieValue) {
|
|
362
428
|
return false;
|
|
363
429
|
}
|
|
@@ -474,48 +540,65 @@ function withFlashClear(response) {
|
|
|
474
540
|
}
|
|
475
541
|
|
|
476
542
|
// ../../src/core/view/webLayoutData.ts
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
543
|
+
var configuredLayoutOptions = {};
|
|
544
|
+
function configureWebLayoutData(options) {
|
|
545
|
+
configuredLayoutOptions = { ...options };
|
|
546
|
+
}
|
|
547
|
+
function layoutUserKey(options) {
|
|
548
|
+
return options.userKey ?? "authUser";
|
|
549
|
+
}
|
|
550
|
+
async function defaultLoadLayoutUser(container, _request) {
|
|
480
551
|
const authUser = currentAuthUser();
|
|
481
552
|
if (!authUser) {
|
|
482
|
-
return
|
|
553
|
+
return null;
|
|
483
554
|
}
|
|
484
555
|
const userId = Number(authUser.id);
|
|
485
556
|
if (!Number.isInteger(userId) || userId <= 0) {
|
|
486
|
-
return
|
|
557
|
+
return null;
|
|
487
558
|
}
|
|
488
|
-
|
|
559
|
+
const directory = resolveAuthUserDirectory(container);
|
|
560
|
+
if (!directory) {
|
|
489
561
|
return {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
role: authUser.role ?? "member"
|
|
494
|
-
},
|
|
495
|
-
csrfToken,
|
|
496
|
-
flash
|
|
562
|
+
id: userId,
|
|
563
|
+
email: "",
|
|
564
|
+
role: authUser.role ?? "member"
|
|
497
565
|
};
|
|
498
566
|
}
|
|
499
|
-
const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
500
567
|
try {
|
|
501
|
-
const user = await
|
|
568
|
+
const user = await directory.findByIdOrThrow(userId);
|
|
502
569
|
return {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
role: authUser.role ?? user.role ?? "member"
|
|
507
|
-
},
|
|
508
|
-
csrfToken,
|
|
509
|
-
flash
|
|
570
|
+
id: userId,
|
|
571
|
+
email: user.email ?? "",
|
|
572
|
+
role: authUser.role ?? user.role ?? "member"
|
|
510
573
|
};
|
|
511
574
|
} catch {
|
|
512
|
-
return
|
|
575
|
+
return null;
|
|
513
576
|
}
|
|
514
577
|
}
|
|
578
|
+
async function resolveWebLayoutData(container, request, options = {}) {
|
|
579
|
+
const resolved = { ...configuredLayoutOptions, ...options };
|
|
580
|
+
const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
|
|
581
|
+
const flash = request ? currentRequestMeta2().flash ?? pullFlash(request) : null;
|
|
582
|
+
const userKey = layoutUserKey(resolved);
|
|
583
|
+
const loadUser = resolved.loadUser ?? defaultLoadLayoutUser;
|
|
584
|
+
const user = await loadUser(container, request);
|
|
585
|
+
const extra = typeof resolved.extra === "function" ? await resolved.extra(user) : resolved.extra ?? {};
|
|
586
|
+
return {
|
|
587
|
+
[userKey]: user,
|
|
588
|
+
csrfToken,
|
|
589
|
+
flash,
|
|
590
|
+
...extra
|
|
591
|
+
};
|
|
592
|
+
}
|
|
515
593
|
export {
|
|
516
594
|
DEFAULT_VIEWS_DIRECTORY,
|
|
517
595
|
EtaViewEngine,
|
|
596
|
+
configureWebLayoutData,
|
|
518
597
|
htmlResponse,
|
|
519
598
|
isHtmxRequest,
|
|
520
|
-
|
|
599
|
+
notFoundHtmlResponse,
|
|
600
|
+
redirectResponse,
|
|
601
|
+
resolveWebLayoutData,
|
|
602
|
+
textResponse,
|
|
603
|
+
xmlResponse
|
|
521
604
|
};
|
|
@@ -118,6 +118,6 @@ export { currentTraceId, runWithTraceContext } from "../core/tracing/traceContex
|
|
|
118
118
|
export { createTracingMiddleware } from "../core/tracing/tracingMiddleware.ts";
|
|
119
119
|
export type { ValidationRule, ValidationSchema } from "../core/validation/rules.ts";
|
|
120
120
|
export { emailRule, maxLength, minLength, required, stringRule, validateObject, } from "../core/validation/rules.ts";
|
|
121
|
-
export { DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, htmlResponse, isHtmxRequest, resolveWebLayoutData, } from "../core/view/index.ts";
|
|
121
|
+
export { configureWebLayoutData, DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, htmlResponse, isHtmxRequest, notFoundHtmlResponse, redirectResponse, resolveWebLayoutData, textResponse, xmlResponse, } from "../core/view/index.ts";
|
|
122
122
|
export type { ViewEngine } from "../core/view/viewEngine.ts";
|
|
123
|
-
export type { WebLayoutAuthUser, WebLayoutData } from "../core/view/webLayoutData.ts";
|
|
123
|
+
export type { WebLayoutAuthUser, WebLayoutData, WebLayoutDataOptions, WebLayoutUserKey, } from "../core/view/webLayoutData.ts";
|
package/dist/index.js
CHANGED
|
@@ -172,6 +172,28 @@ var CORE_EVENT_BUS_TOKEN = "core.eventBus";
|
|
|
172
172
|
var CORE_POLICY_GATE_TOKEN = "core.policyGate";
|
|
173
173
|
var CORE_AUTH_TOKEN = "core.auth";
|
|
174
174
|
var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
|
|
175
|
+
var CORE_AUTH_USER_DIRECTORY_TOKEN = "core.authUserDirectory";
|
|
176
|
+
function isAuthUserDirectory(value) {
|
|
177
|
+
if (!value || typeof value !== "object") {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
return typeof value.findByIdOrThrow === "function";
|
|
181
|
+
}
|
|
182
|
+
function resolveAuthUserDirectory(container) {
|
|
183
|
+
if (container.has(CORE_AUTH_USER_DIRECTORY_TOKEN)) {
|
|
184
|
+
const directory = container.resolve(CORE_AUTH_USER_DIRECTORY_TOKEN);
|
|
185
|
+
if (isAuthUserDirectory(directory)) {
|
|
186
|
+
return directory;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (container.has(CORE_TOKEN_SERVICE_TOKEN)) {
|
|
190
|
+
const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
191
|
+
if (isAuthUserDirectory(tokenService)) {
|
|
192
|
+
return tokenService;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
175
197
|
|
|
176
198
|
// ../../src/core/auth/abilityCatalog.ts
|
|
177
199
|
var MEMBER_ABILITIES = [
|
|
@@ -276,10 +298,10 @@ class DatabaseTokenGuard {
|
|
|
276
298
|
if (!token) {
|
|
277
299
|
return null;
|
|
278
300
|
}
|
|
279
|
-
|
|
301
|
+
const tokenService = resolveAuthUserDirectory(this.container);
|
|
302
|
+
if (!tokenService || typeof tokenService.resolveUserFromToken !== "function") {
|
|
280
303
|
return null;
|
|
281
304
|
}
|
|
282
|
-
const tokenService = this.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
283
305
|
return await tokenService.resolveUserFromToken(token);
|
|
284
306
|
}
|
|
285
307
|
}
|
|
@@ -4366,6 +4388,9 @@ function currentRequestMeta() {
|
|
|
4366
4388
|
// ../../src/core/http/csrfToken.ts
|
|
4367
4389
|
var CSRF_COOKIE = "workhub_csrf";
|
|
4368
4390
|
var CSRF_TTL_MS = 60 * 60 * 1000;
|
|
4391
|
+
function csrfCookieName() {
|
|
4392
|
+
return process.env.CSRF_COOKIE_NAME?.trim() || CSRF_COOKIE;
|
|
4393
|
+
}
|
|
4369
4394
|
function resolveCsrfSecret() {
|
|
4370
4395
|
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
|
|
4371
4396
|
}
|
|
@@ -4385,11 +4410,11 @@ function createCsrfTokenCookie() {
|
|
|
4385
4410
|
const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
|
|
4386
4411
|
return {
|
|
4387
4412
|
token,
|
|
4388
|
-
cookie: `${
|
|
4413
|
+
cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
|
|
4389
4414
|
};
|
|
4390
4415
|
}
|
|
4391
4416
|
function resolveCsrfToken(request) {
|
|
4392
|
-
const cookieValue = readRequestCookie(request,
|
|
4417
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
4393
4418
|
if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
|
|
4394
4419
|
return { token: cookieValue };
|
|
4395
4420
|
}
|
|
@@ -4425,7 +4450,7 @@ function verifyCsrfToken(request, submittedToken) {
|
|
|
4425
4450
|
if (!submittedToken) {
|
|
4426
4451
|
return false;
|
|
4427
4452
|
}
|
|
4428
|
-
const cookieValue = readRequestCookie(request,
|
|
4453
|
+
const cookieValue = readRequestCookie(request, csrfCookieName());
|
|
4429
4454
|
if (!cookieValue) {
|
|
4430
4455
|
return false;
|
|
4431
4456
|
}
|
|
@@ -5094,45 +5119,84 @@ function htmlResponse(html, init = {}) {
|
|
|
5094
5119
|
function isHtmxRequest(request) {
|
|
5095
5120
|
return request.headers.get("HX-Request") === "true";
|
|
5096
5121
|
}
|
|
5122
|
+
function redirectResponse(location, status = 302) {
|
|
5123
|
+
return new Response(null, {
|
|
5124
|
+
status,
|
|
5125
|
+
headers: {
|
|
5126
|
+
Location: location
|
|
5127
|
+
}
|
|
5128
|
+
});
|
|
5129
|
+
}
|
|
5130
|
+
function notFoundHtmlResponse(body = "Not Found") {
|
|
5131
|
+
return htmlResponse(body, { status: 404 });
|
|
5132
|
+
}
|
|
5133
|
+
function textResponse(body, init = {}) {
|
|
5134
|
+
return new Response(body, {
|
|
5135
|
+
status: init.status ?? 200,
|
|
5136
|
+
headers: {
|
|
5137
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
5138
|
+
}
|
|
5139
|
+
});
|
|
5140
|
+
}
|
|
5141
|
+
function xmlResponse(body, init = {}) {
|
|
5142
|
+
return new Response(body, {
|
|
5143
|
+
status: init.status ?? 200,
|
|
5144
|
+
headers: {
|
|
5145
|
+
"Content-Type": "application/xml; charset=utf-8"
|
|
5146
|
+
}
|
|
5147
|
+
});
|
|
5148
|
+
}
|
|
5097
5149
|
// ../../src/core/view/webLayoutData.ts
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5150
|
+
var configuredLayoutOptions = {};
|
|
5151
|
+
function configureWebLayoutData(options) {
|
|
5152
|
+
configuredLayoutOptions = { ...options };
|
|
5153
|
+
}
|
|
5154
|
+
function layoutUserKey(options) {
|
|
5155
|
+
return options.userKey ?? "authUser";
|
|
5156
|
+
}
|
|
5157
|
+
async function defaultLoadLayoutUser(container, _request) {
|
|
5101
5158
|
const authUser = currentAuthUser();
|
|
5102
5159
|
if (!authUser) {
|
|
5103
|
-
return
|
|
5160
|
+
return null;
|
|
5104
5161
|
}
|
|
5105
5162
|
const userId = Number(authUser.id);
|
|
5106
5163
|
if (!Number.isInteger(userId) || userId <= 0) {
|
|
5107
|
-
return
|
|
5164
|
+
return null;
|
|
5108
5165
|
}
|
|
5109
|
-
|
|
5166
|
+
const directory = resolveAuthUserDirectory(container);
|
|
5167
|
+
if (!directory) {
|
|
5110
5168
|
return {
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
role: authUser.role ?? "member"
|
|
5115
|
-
},
|
|
5116
|
-
csrfToken,
|
|
5117
|
-
flash
|
|
5169
|
+
id: userId,
|
|
5170
|
+
email: "",
|
|
5171
|
+
role: authUser.role ?? "member"
|
|
5118
5172
|
};
|
|
5119
5173
|
}
|
|
5120
|
-
const tokenService = container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
5121
5174
|
try {
|
|
5122
|
-
const user = await
|
|
5175
|
+
const user = await directory.findByIdOrThrow(userId);
|
|
5123
5176
|
return {
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
role: authUser.role ?? user.role ?? "member"
|
|
5128
|
-
},
|
|
5129
|
-
csrfToken,
|
|
5130
|
-
flash
|
|
5177
|
+
id: userId,
|
|
5178
|
+
email: user.email ?? "",
|
|
5179
|
+
role: authUser.role ?? user.role ?? "member"
|
|
5131
5180
|
};
|
|
5132
5181
|
} catch {
|
|
5133
|
-
return
|
|
5182
|
+
return null;
|
|
5134
5183
|
}
|
|
5135
5184
|
}
|
|
5185
|
+
async function resolveWebLayoutData(container, request, options = {}) {
|
|
5186
|
+
const resolved = { ...configuredLayoutOptions, ...options };
|
|
5187
|
+
const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
|
|
5188
|
+
const flash = request ? currentRequestMeta().flash ?? pullFlash(request) : null;
|
|
5189
|
+
const userKey = layoutUserKey(resolved);
|
|
5190
|
+
const loadUser = resolved.loadUser ?? defaultLoadLayoutUser;
|
|
5191
|
+
const user = await loadUser(container, request);
|
|
5192
|
+
const extra = typeof resolved.extra === "function" ? await resolved.extra(user) : resolved.extra ?? {};
|
|
5193
|
+
return {
|
|
5194
|
+
[userKey]: user,
|
|
5195
|
+
csrfToken,
|
|
5196
|
+
flash,
|
|
5197
|
+
...extra
|
|
5198
|
+
};
|
|
5199
|
+
}
|
|
5136
5200
|
// ../../src/core/http/contentNegotiation.ts
|
|
5137
5201
|
function requestPrefersJson(request) {
|
|
5138
5202
|
if (!request) {
|
|
@@ -6683,6 +6747,7 @@ export {
|
|
|
6683
6747
|
conditionalJsonResponse,
|
|
6684
6748
|
config,
|
|
6685
6749
|
configureMembershipLookup,
|
|
6750
|
+
configureWebLayoutData,
|
|
6686
6751
|
createAuthMiddleware,
|
|
6687
6752
|
createAuthorizeMiddleware,
|
|
6688
6753
|
createBodySizeLimitMiddleware,
|
|
@@ -6780,6 +6845,7 @@ export {
|
|
|
6780
6845
|
morphTo,
|
|
6781
6846
|
noContentResponse,
|
|
6782
6847
|
normalizeMetricPath,
|
|
6848
|
+
notFoundHtmlResponse,
|
|
6783
6849
|
paginatedResponse,
|
|
6784
6850
|
parseMultipartUpload,
|
|
6785
6851
|
parsePaginationQuery,
|
|
@@ -6794,6 +6860,7 @@ export {
|
|
|
6794
6860
|
readSubmittedCsrfToken,
|
|
6795
6861
|
readSubmittedCsrfTokenFromBody,
|
|
6796
6862
|
readTenancyDriver,
|
|
6863
|
+
redirectResponse,
|
|
6797
6864
|
registerDefaultDatabasePool,
|
|
6798
6865
|
registerModelRepository,
|
|
6799
6866
|
registerShutdownHandler,
|
|
@@ -6841,6 +6908,7 @@ export {
|
|
|
6841
6908
|
storageFacade as storage,
|
|
6842
6909
|
stringRule,
|
|
6843
6910
|
stripMarkdown,
|
|
6911
|
+
textResponse,
|
|
6844
6912
|
toPaginatedResourceCollection,
|
|
6845
6913
|
toResourceCollection,
|
|
6846
6914
|
trustForwardedFor,
|
|
@@ -6850,5 +6918,6 @@ export {
|
|
|
6850
6918
|
withMiddleware,
|
|
6851
6919
|
withMigrationLock,
|
|
6852
6920
|
wrapMarkdownMailLayout,
|
|
6853
|
-
wrapRouteHandler
|
|
6921
|
+
wrapRouteHandler,
|
|
6922
|
+
xmlResponse
|
|
6854
6923
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getstrata/core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.59",
|
|
4
4
|
"description": "Strata — Laravel-inspired Bun framework public API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -306,7 +306,7 @@
|
|
|
306
306
|
"default": "./dist/entries/events.js"
|
|
307
307
|
},
|
|
308
308
|
"./facades": {
|
|
309
|
-
"types": "./dist/core/facades.d.ts",
|
|
309
|
+
"types": "./dist/core/facades/index.d.ts",
|
|
310
310
|
"import": "./dist/entries/facades.js",
|
|
311
311
|
"default": "./dist/entries/facades.js"
|
|
312
312
|
},
|
|
@@ -765,7 +765,8 @@
|
|
|
765
765
|
"types": "./dist/framework/public-api.d.ts",
|
|
766
766
|
"files": [
|
|
767
767
|
"dist",
|
|
768
|
-
"README.md"
|
|
768
|
+
"README.md",
|
|
769
|
+
"CHANGELOG.md"
|
|
769
770
|
],
|
|
770
771
|
"scripts": {
|
|
771
772
|
"build": "bun run build:bundle && bun run build:shims && bun run build:subpaths && bun run build:types",
|