@getstrata/core 0.5.58 → 0.5.60
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 +21 -0
- package/README.md +3 -3
- 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/database/bindConnection.d.ts +1 -3
- package/dist/core/database/bunSql.d.ts +13 -0
- package/dist/core/http/csrfToken.d.ts +2 -1
- package/dist/core/http/memoryThrottleMiddleware.d.ts +2 -1
- package/dist/core/view/htmlResponse.d.ts +13 -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/database/bunSql.js +1 -0
- package/dist/entries/http/csrfMiddleware.js +6 -3
- package/dist/entries/http/csrfToken.js +7 -3
- package/dist/entries/http/memoryThrottleMiddleware.js +10 -2
- package/dist/entries/http/response.js +110 -26
- package/dist/entries/http/webErrorResponse.js +110 -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 +117 -27
- package/dist/framework/public-api.d.ts +5 -4
- package/dist/index.js +139 -38
- package/package.json +10 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @getstrata/core changelog
|
|
2
|
+
|
|
3
|
+
## 0.5.60
|
|
4
|
+
|
|
5
|
+
- `@getstrata/core/database/boundConnection` publishes `getBoundDatabaseConnection` and `resetBoundDatabaseConnection` on the JS entry (not only the types file). Tests can reset the bound pool after `closeDatabase()` without a postinstall patch.
|
|
6
|
+
- `bindBunSql` / `createBunSqlPool` on `@getstrata/core/database/bunSql` create a Bun `SQL` pool and register it as the bound connection plus default pool.
|
|
7
|
+
- `xmlResponse` accepts `{ contentType }`. `rssResponse` sends `application/rss+xml`.
|
|
8
|
+
- Memory throttle buckets are per middleware instance. `resetMemoryThrottleForTests()` clears every instance map so nested HTTP tests do not leak.
|
|
9
|
+
|
|
10
|
+
## 0.5.59
|
|
11
|
+
|
|
12
|
+
Sibling HTMX apps can consume published packages without WorkHub-only glue.
|
|
13
|
+
|
|
14
|
+
- Emit `@getstrata/core/facades` types at `dist/core/facades/index.d.ts` (CI checks every `exports.*.types` path after build).
|
|
15
|
+
- Session and CSRF cookie names are configurable (`SESSION_COOKIE_NAME`, `CSRF_COOKIE_NAME`). Defaults remain `workhub_session` and `workhub_csrf` for WorkHub.
|
|
16
|
+
- 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.
|
|
17
|
+
- `configureWebLayoutData` lets apps choose `currentUser` vs `authUser`, a custom user loader, and extra template fields. WorkHub still gets `{ authUser, csrfToken, flash }`.
|
|
18
|
+
- `redirectResponse`, `notFoundHtmlResponse`, `textResponse`, and `xmlResponse` join `htmlResponse` / `isHtmxRequest` on `@getstrata/core/view`.
|
|
19
|
+
- `LOGIN_RATE_LIMIT_WINDOW_MS` is a deprecated alias for `LOGIN_RATE_LIMIT_WINDOW_SECONDS`.
|
|
20
|
+
|
|
21
|
+
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
|
|
|
@@ -28,7 +28,7 @@ import { EtaViewEngine } from "@getstrata/core/view";
|
|
|
28
28
|
|
|
29
29
|
`.eta` files are **HTML + Eta tags** (`<% %>`, `<%= %>`, `<%~ include() %>`), not Pug. Class/attribute shorthand such as `section.section` or `a href=` fails at render time with the template name.
|
|
30
30
|
|
|
31
|
-
**Dependency:** `eta` is bundled as a direct dependency of `@getstrata/core`. Apps do not need to list it separately. The database driver is your app's choice. WorkHub and getstrata use **Bun's built-in `Bun.sql`** client; bind it with `bindDatabaseConnection()
|
|
31
|
+
**Dependency:** `eta` is bundled as a direct dependency of `@getstrata/core`. Apps do not need to list it separately. The database driver is your app's choice. WorkHub and getstrata use **Bun's built-in `Bun.sql`** client; create and bind it with `createBunSqlPool()` / `bindBunSql()`, or call `bindDatabaseConnection()` yourself.
|
|
32
32
|
|
|
33
33
|
`orderBy` accepts explicit `{ column, direction }` objects or column shorthand such as `{ published_at: "desc" }`.
|
|
34
34
|
|
|
@@ -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,3 +1 @@
|
|
|
1
|
-
|
|
2
|
-
declare function bindDatabaseConnection(connection: DatabaseConnection): void;
|
|
3
|
-
export { bindDatabaseConnection };
|
|
1
|
+
export { bindDatabaseConnection, getBoundDatabaseConnection, resetBoundDatabaseConnection, } from "./boundConnection";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SQL } from "bun";
|
|
2
|
+
import type { DatabaseConnection } from "./baseRepository";
|
|
3
|
+
interface CreateBunSqlPoolOptions {
|
|
4
|
+
url: string;
|
|
5
|
+
max?: number;
|
|
6
|
+
idleTimeout?: number;
|
|
7
|
+
maxLifetime?: number;
|
|
8
|
+
connectionTimeout?: number;
|
|
9
|
+
}
|
|
10
|
+
declare function createBunSqlPool(options: CreateBunSqlPoolOptions): InstanceType<typeof SQL>;
|
|
11
|
+
declare function bindBunSql(sql: DatabaseConnection): DatabaseConnection;
|
|
12
|
+
export type { CreateBunSqlPoolOptions };
|
|
13
|
+
export { bindBunSql, createBunSqlPool };
|
|
@@ -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, };
|
|
@@ -5,5 +5,6 @@ interface MemoryThrottleOptions {
|
|
|
5
5
|
keyPrefix?: string;
|
|
6
6
|
}
|
|
7
7
|
declare function createMemoryThrottleMiddleware(options: MemoryThrottleOptions): Middleware;
|
|
8
|
+
declare function resetMemoryThrottleForTests(): void;
|
|
8
9
|
export type { MemoryThrottleOptions };
|
|
9
|
-
export { createMemoryThrottleMiddleware };
|
|
10
|
+
export { createMemoryThrottleMiddleware, resetMemoryThrottleForTests };
|
|
@@ -3,4 +3,16 @@ 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
|
+
contentType?: string;
|
|
14
|
+
}): Response;
|
|
15
|
+
declare function rssResponse(body: string, init?: {
|
|
16
|
+
status?: number;
|
|
17
|
+
}): Response;
|
|
18
|
+
export { htmlResponse, isHtmxRequest, notFoundHtmlResponse, redirectResponse, rssResponse, 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, rssResponse, 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
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../index.js";
|
|
@@ -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,
|
|
@@ -15,9 +15,11 @@ function readClientIp(request, env = process.env) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// ../../src/core/http/memoryThrottleMiddleware.ts
|
|
18
|
-
var
|
|
18
|
+
var throttleBucketRegistries = new Set;
|
|
19
19
|
function createMemoryThrottleMiddleware(options) {
|
|
20
20
|
const prefix = options.keyPrefix ?? "workhub:memory-throttle:";
|
|
21
|
+
const buckets = new Map;
|
|
22
|
+
throttleBucketRegistries.add(buckets);
|
|
21
23
|
return async (request, next) => {
|
|
22
24
|
const path = new URL(request.url).pathname;
|
|
23
25
|
const identity = readClientIp(request) ?? request.headers.get("authorization")?.slice(0, 32) ?? "unknown";
|
|
@@ -40,6 +42,12 @@ function createMemoryThrottleMiddleware(options) {
|
|
|
40
42
|
return await next();
|
|
41
43
|
};
|
|
42
44
|
}
|
|
45
|
+
function resetMemoryThrottleForTests() {
|
|
46
|
+
for (const buckets of throttleBucketRegistries) {
|
|
47
|
+
buckets.clear();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
43
50
|
export {
|
|
44
|
-
createMemoryThrottleMiddleware
|
|
51
|
+
createMemoryThrottleMiddleware,
|
|
52
|
+
resetMemoryThrottleForTests
|
|
45
53
|
};
|