@getstrata/core 0.5.79 → 0.5.81
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 +8 -0
- package/README.md +1 -1
- package/dist/core/auth/intendedUrlCookie.d.ts +10 -0
- package/dist/core/runtime/appKeyPrefix.d.ts +3 -1
- package/dist/entries/audit/exportAuditLogs.js +6 -0
- package/dist/entries/audit/siemFormatter.js +6 -0
- package/dist/entries/auth/intendedUrlCookie.js +176 -0
- package/dist/entries/auth/oauth/providers.js +6 -0
- package/dist/entries/auth/oauth/samlProvider.js +6 -0
- package/dist/entries/auth/passwordConfirmCookie.js +58 -2
- package/dist/entries/auth/sessionCookie.js +58 -2
- package/dist/entries/auth/sessionGuard.js +58 -2
- package/dist/entries/auth/tokenHash.js +58 -2
- package/dist/entries/cache/createCacheStore.js +6 -0
- package/dist/entries/facades.js +6 -0
- package/dist/entries/http/csrfMiddleware.js +56 -2
- package/dist/entries/http/csrfToken.js +56 -2
- package/dist/entries/http/flashMiddleware.js +58 -2
- package/dist/entries/http/flashSession.js +58 -2
- package/dist/entries/http/loginThrottleMiddleware.js +6 -0
- package/dist/entries/http/memoryThrottleMiddleware.js +6 -0
- package/dist/entries/http/requireVerifiedMiddleware.js +7 -1
- package/dist/entries/http/scimThrottleMiddleware.js +6 -0
- package/dist/entries/http/securityHeadersMiddleware.js +6 -0
- package/dist/entries/http/signedUrl.js +57 -1
- package/dist/entries/http/throttleMiddleware.js +6 -0
- package/dist/entries/jobs/exportAuditLogsJob.js +6 -0
- package/dist/entries/mail/mailer.js +6 -0
- package/dist/entries/openapi/generator.js +6 -0
- package/dist/entries/queue/createAppQueue.js +6 -0
- package/dist/entries/queue/publicQueue.js +6 -0
- package/dist/entries/queue/queueMetrics.js +6 -0
- package/dist/entries/queue/redisQueue.js +6 -0
- package/dist/entries/runtime/appKeyPrefix.js +8 -0
- package/dist/entries/security/oauthState.js +57 -1
- package/dist/entries/security/safeFetch.js +6 -0
- package/dist/entries/tracing/tracingMiddleware.js +6 -0
- package/dist/entries/view.js +58 -4
- package/dist/index.js +11 -7
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @getstrata/core changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.81
|
|
4
|
+
|
|
5
|
+
- `@getstrata/core/auth/intendedUrlCookie` (`createIntendedUrlCookie`, `readIntendedUrl`, `clearIntendedUrlCookie`, `createIntendedUrlCookieFromRequest`). Fortify intended URL after HTML email verification: register with `redirect=` and Laravel `verified` HTML redirects stash `${APP_KEY_PREFIX}_intended` (`INTENDED_URL_COOKIE_NAME`, default `workhub_intended`; TTL `INTENDED_URL_TTL_SECONDS`, default 86400s). `GET /verify-email` honors and clears it.
|
|
6
|
+
|
|
7
|
+
## 0.5.80
|
|
8
|
+
|
|
9
|
+
- `appCookieName()` / `appDevSecret()` on `@getstrata/core/runtime/appKeyPrefix`. HMAC session, CSRF, flash, password-confirm, signed-URL, OAuth-state, and token-pepper fallbacks follow `APP_KEY_PREFIX` (WorkHub defaults unchanged).
|
|
10
|
+
|
|
3
11
|
## 0.5.79
|
|
4
12
|
|
|
5
13
|
- `readSession()` returns `{ userId, issuedAt }` from the HMAC session cookie. `isSessionInvalidated(issuedAt, session_valid_after)` lets `SessionGuard` reject cookies issued before `AuthUserRecord.session_valid_after` (Jetstream logout-other-devices / password change).
|
package/README.md
CHANGED
|
@@ -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.83 && git push origin v0.5.83`
|
|
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.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare const INTENDED_URL_COOKIE = "workhub_intended";
|
|
2
|
+
declare const DEFAULT_INTENDED_URL_TTL_SECONDS: number;
|
|
3
|
+
declare function intendedUrlCookieName(): string;
|
|
4
|
+
declare function intendedUrlTtlSeconds(): number;
|
|
5
|
+
declare function isStashableIntendedPath(path: string): boolean;
|
|
6
|
+
declare function createIntendedUrlCookie(path: string): string | null;
|
|
7
|
+
declare function createIntendedUrlCookieFromRequest(request: Request): string | null;
|
|
8
|
+
declare function readIntendedUrl(request: Request): string | null;
|
|
9
|
+
declare function clearIntendedUrlCookie(): string;
|
|
10
|
+
export { clearIntendedUrlCookie, createIntendedUrlCookie, createIntendedUrlCookieFromRequest, DEFAULT_INTENDED_URL_TTL_SECONDS, INTENDED_URL_COOKIE, intendedUrlCookieName, intendedUrlTtlSeconds, isStashableIntendedPath, readIntendedUrl, };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
declare function appKeyPrefix(): string;
|
|
2
|
+
declare function appCookieName(kind: string): string;
|
|
3
|
+
declare function appDevSecret(kind: string): string;
|
|
2
4
|
declare function namespacedRedisKey(kind: string): string;
|
|
3
5
|
declare function smtpEhloHost(): string;
|
|
4
6
|
declare function siemEventType(): string;
|
|
@@ -10,4 +12,4 @@ declare function appEnv(): string;
|
|
|
10
12
|
declare function appUrl(): string;
|
|
11
13
|
declare function apiPrefix(): string;
|
|
12
14
|
declare function sdkClientClassName(): string;
|
|
13
|
-
export { apiPrefix, appDisplayName, appEnv, appKeyPrefix, appUrl, appUserAgent, namespacedRedisKey, otelServiceName, sdkClientClassName, siemEventType, smtpEhloHost, webhookSignatureHeader, };
|
|
15
|
+
export { apiPrefix, appCookieName, appDevSecret, appDisplayName, appEnv, appKeyPrefix, appUrl, appUserAgent, namespacedRedisKey, otelServiceName, sdkClientClassName, siemEventType, smtpEhloHost, webhookSignatureHeader, };
|
|
@@ -6,6 +6,12 @@ import { repositoryConnection as db2 } from "@getstrata/core/database/repository
|
|
|
6
6
|
function appKeyPrefix() {
|
|
7
7
|
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
8
8
|
}
|
|
9
|
+
function appCookieName(kind) {
|
|
10
|
+
return `${appKeyPrefix()}_${kind}`;
|
|
11
|
+
}
|
|
12
|
+
function appDevSecret(kind) {
|
|
13
|
+
return `${appKeyPrefix()}-dev-${kind}`;
|
|
14
|
+
}
|
|
9
15
|
function namespacedRedisKey(kind) {
|
|
10
16
|
return `${appKeyPrefix()}:${kind}`;
|
|
11
17
|
}
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
function appKeyPrefix() {
|
|
4
4
|
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
5
5
|
}
|
|
6
|
+
function appCookieName(kind) {
|
|
7
|
+
return `${appKeyPrefix()}_${kind}`;
|
|
8
|
+
}
|
|
9
|
+
function appDevSecret(kind) {
|
|
10
|
+
return `${appKeyPrefix()}-dev-${kind}`;
|
|
11
|
+
}
|
|
6
12
|
function namespacedRedisKey(kind) {
|
|
7
13
|
return `${appKeyPrefix()}:${kind}`;
|
|
8
14
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../../src/core/http/safeInternalPath.ts
|
|
3
|
+
function looksLikeExternalTarget(value) {
|
|
4
|
+
const trimmed = value.trim();
|
|
5
|
+
if (!trimmed.startsWith("/") || trimmed.startsWith("//") || trimmed.includes("\\")) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
if (trimmed.includes("://") || trimmed.includes(":/") || trimmed.includes(":\\")) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const decoded = decodeURIComponent(trimmed);
|
|
13
|
+
if (decoded.startsWith("//") || decoded.includes("\\") || /https?:/i.test(decoded)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
function sanitizeInternalPath(raw, fallback = "/") {
|
|
22
|
+
if (looksLikeExternalTarget(raw)) {
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
return raw;
|
|
26
|
+
}
|
|
27
|
+
function safeInternalRedirectPath(request, fallback = "/") {
|
|
28
|
+
const url = new URL(request.url);
|
|
29
|
+
return sanitizeInternalPath(`${url.pathname}${url.search}`, fallback);
|
|
30
|
+
}
|
|
31
|
+
function loginRedirectLocation(request) {
|
|
32
|
+
return `/login?redirect=${encodeURIComponent(safeInternalRedirectPath(request))}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
36
|
+
function appKeyPrefix() {
|
|
37
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
38
|
+
}
|
|
39
|
+
function appCookieName(kind) {
|
|
40
|
+
return `${appKeyPrefix()}_${kind}`;
|
|
41
|
+
}
|
|
42
|
+
function appDevSecret(kind) {
|
|
43
|
+
return `${appKeyPrefix()}-dev-${kind}`;
|
|
44
|
+
}
|
|
45
|
+
function namespacedRedisKey(kind) {
|
|
46
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
47
|
+
}
|
|
48
|
+
function smtpEhloHost() {
|
|
49
|
+
const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
|
|
50
|
+
const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
|
|
51
|
+
return safe || "strata.local";
|
|
52
|
+
}
|
|
53
|
+
function siemEventType() {
|
|
54
|
+
return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
|
|
55
|
+
}
|
|
56
|
+
function appUserAgent() {
|
|
57
|
+
return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
|
|
58
|
+
}
|
|
59
|
+
function otelServiceName() {
|
|
60
|
+
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
61
|
+
}
|
|
62
|
+
function webhookSignatureHeader() {
|
|
63
|
+
return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
|
|
64
|
+
}
|
|
65
|
+
function appDisplayName() {
|
|
66
|
+
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
67
|
+
}
|
|
68
|
+
function appEnv() {
|
|
69
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
70
|
+
}
|
|
71
|
+
function appUrl() {
|
|
72
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
73
|
+
}
|
|
74
|
+
function apiPrefix() {
|
|
75
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
76
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
77
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
78
|
+
return trimmed || "/api/v1";
|
|
79
|
+
}
|
|
80
|
+
function sdkClientClassName() {
|
|
81
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
82
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
83
|
+
return override;
|
|
84
|
+
}
|
|
85
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
86
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ../../src/core/auth/intendedUrlCookie.ts
|
|
90
|
+
var INTENDED_URL_COOKIE = "workhub_intended";
|
|
91
|
+
var DEFAULT_INTENDED_URL_TTL_SECONDS = 60 * 60 * 24;
|
|
92
|
+
var SKIP_EXACT_PATHS = new Set([
|
|
93
|
+
"/email/verify",
|
|
94
|
+
"/verify-email",
|
|
95
|
+
"/login",
|
|
96
|
+
"/register",
|
|
97
|
+
"/logout",
|
|
98
|
+
"/forgot-password",
|
|
99
|
+
"/reset-password",
|
|
100
|
+
"/two-factor-challenge",
|
|
101
|
+
"/confirm-password",
|
|
102
|
+
"/api"
|
|
103
|
+
]);
|
|
104
|
+
function intendedUrlCookieName() {
|
|
105
|
+
return process.env.INTENDED_URL_COOKIE_NAME?.trim() || appCookieName("intended");
|
|
106
|
+
}
|
|
107
|
+
function intendedUrlTtlSeconds() {
|
|
108
|
+
const parsed = Number.parseInt(process.env.INTENDED_URL_TTL_SECONDS ?? "", 10);
|
|
109
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_INTENDED_URL_TTL_SECONDS;
|
|
110
|
+
}
|
|
111
|
+
function cookieSecureFlag() {
|
|
112
|
+
return process.env.APP_ENV === "production" ? "; Secure" : "";
|
|
113
|
+
}
|
|
114
|
+
function pathnameOf(path) {
|
|
115
|
+
const pathname = path.split("?")[0] ?? path;
|
|
116
|
+
const trimmed = pathname.replace(/\/+$/, "");
|
|
117
|
+
return trimmed || "/";
|
|
118
|
+
}
|
|
119
|
+
function isStashableIntendedPath(path) {
|
|
120
|
+
if (!path.startsWith("/") || path.startsWith("//")) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
const pathname = pathnameOf(path);
|
|
124
|
+
if (SKIP_EXACT_PATHS.has(pathname) || pathname.startsWith("/oauth") || pathname.startsWith("/api/")) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
function readCookieValue(request, cookieName) {
|
|
130
|
+
const cookieHeader = request.headers.get("cookie");
|
|
131
|
+
if (!cookieHeader) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
for (const part of cookieHeader.split(";")) {
|
|
135
|
+
const [name, ...rest] = part.trim().split("=");
|
|
136
|
+
if (name === cookieName) {
|
|
137
|
+
return decodeURIComponent(rest.join("="));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function createIntendedUrlCookie(path) {
|
|
143
|
+
const sanitized = sanitizeInternalPath(path, "");
|
|
144
|
+
if (!sanitized || !isStashableIntendedPath(sanitized)) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return `${intendedUrlCookieName()}=${encodeURIComponent(sanitized)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${intendedUrlTtlSeconds()}${cookieSecureFlag()}`;
|
|
148
|
+
}
|
|
149
|
+
function createIntendedUrlCookieFromRequest(request) {
|
|
150
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return createIntendedUrlCookie(safeInternalRedirectPath(request, ""));
|
|
154
|
+
}
|
|
155
|
+
function readIntendedUrl(request) {
|
|
156
|
+
const cookieValue = readCookieValue(request, intendedUrlCookieName());
|
|
157
|
+
if (!cookieValue) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const sanitized = sanitizeInternalPath(cookieValue, "");
|
|
161
|
+
return sanitized && isStashableIntendedPath(sanitized) ? sanitized : null;
|
|
162
|
+
}
|
|
163
|
+
function clearIntendedUrlCookie() {
|
|
164
|
+
return `${intendedUrlCookieName()}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${cookieSecureFlag()}`;
|
|
165
|
+
}
|
|
166
|
+
export {
|
|
167
|
+
DEFAULT_INTENDED_URL_TTL_SECONDS,
|
|
168
|
+
INTENDED_URL_COOKIE,
|
|
169
|
+
clearIntendedUrlCookie,
|
|
170
|
+
createIntendedUrlCookie,
|
|
171
|
+
createIntendedUrlCookieFromRequest,
|
|
172
|
+
intendedUrlCookieName,
|
|
173
|
+
intendedUrlTtlSeconds,
|
|
174
|
+
isStashableIntendedPath,
|
|
175
|
+
readIntendedUrl
|
|
176
|
+
};
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
function appKeyPrefix() {
|
|
4
4
|
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
5
5
|
}
|
|
6
|
+
function appCookieName(kind) {
|
|
7
|
+
return `${appKeyPrefix()}_${kind}`;
|
|
8
|
+
}
|
|
9
|
+
function appDevSecret(kind) {
|
|
10
|
+
return `${appKeyPrefix()}-dev-${kind}`;
|
|
11
|
+
}
|
|
6
12
|
function namespacedRedisKey(kind) {
|
|
7
13
|
return `${appKeyPrefix()}:${kind}`;
|
|
8
14
|
}
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
function appKeyPrefix() {
|
|
4
4
|
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
5
5
|
}
|
|
6
|
+
function appCookieName(kind) {
|
|
7
|
+
return `${appKeyPrefix()}_${kind}`;
|
|
8
|
+
}
|
|
9
|
+
function appDevSecret(kind) {
|
|
10
|
+
return `${appKeyPrefix()}-dev-${kind}`;
|
|
11
|
+
}
|
|
6
12
|
function namespacedRedisKey(kind) {
|
|
7
13
|
return `${appKeyPrefix()}:${kind}`;
|
|
8
14
|
}
|
|
@@ -1,17 +1,73 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/core/auth/passwordConfirmCookie.ts
|
|
3
3
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
4
|
+
|
|
5
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
6
|
+
function appKeyPrefix() {
|
|
7
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
8
|
+
}
|
|
9
|
+
function appCookieName(kind) {
|
|
10
|
+
return `${appKeyPrefix()}_${kind}`;
|
|
11
|
+
}
|
|
12
|
+
function appDevSecret(kind) {
|
|
13
|
+
return `${appKeyPrefix()}-dev-${kind}`;
|
|
14
|
+
}
|
|
15
|
+
function namespacedRedisKey(kind) {
|
|
16
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
17
|
+
}
|
|
18
|
+
function smtpEhloHost() {
|
|
19
|
+
const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
|
|
20
|
+
const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
|
|
21
|
+
return safe || "strata.local";
|
|
22
|
+
}
|
|
23
|
+
function siemEventType() {
|
|
24
|
+
return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
|
|
25
|
+
}
|
|
26
|
+
function appUserAgent() {
|
|
27
|
+
return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
|
|
28
|
+
}
|
|
29
|
+
function otelServiceName() {
|
|
30
|
+
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
31
|
+
}
|
|
32
|
+
function webhookSignatureHeader() {
|
|
33
|
+
return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
|
|
34
|
+
}
|
|
35
|
+
function appDisplayName() {
|
|
36
|
+
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
37
|
+
}
|
|
38
|
+
function appEnv() {
|
|
39
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
40
|
+
}
|
|
41
|
+
function appUrl() {
|
|
42
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
43
|
+
}
|
|
44
|
+
function apiPrefix() {
|
|
45
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
46
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
47
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
48
|
+
return trimmed || "/api/v1";
|
|
49
|
+
}
|
|
50
|
+
function sdkClientClassName() {
|
|
51
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
52
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
53
|
+
return override;
|
|
54
|
+
}
|
|
55
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
56
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ../../src/core/auth/passwordConfirmCookie.ts
|
|
4
60
|
var PASSWORD_CONFIRM_COOKIE = "workhub_password_confirmed";
|
|
5
61
|
var DEFAULT_PASSWORD_CONFIRM_TTL_SECONDS = 3 * 60 * 60;
|
|
6
62
|
function passwordConfirmCookieName() {
|
|
7
|
-
return process.env.PASSWORD_CONFIRM_COOKIE_NAME?.trim() ||
|
|
63
|
+
return process.env.PASSWORD_CONFIRM_COOKIE_NAME?.trim() || appCookieName("password_confirmed");
|
|
8
64
|
}
|
|
9
65
|
function passwordConfirmTtlSeconds() {
|
|
10
66
|
const parsed = Number.parseInt(process.env.PASSWORD_CONFIRM_TIMEOUT ?? "", 10);
|
|
11
67
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_PASSWORD_CONFIRM_TTL_SECONDS;
|
|
12
68
|
}
|
|
13
69
|
function resolvePasswordConfirmSecret() {
|
|
14
|
-
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || "
|
|
70
|
+
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || appDevSecret("session-secret");
|
|
15
71
|
}
|
|
16
72
|
function signPasswordConfirm(userId, confirmedAt) {
|
|
17
73
|
const payload = `${userId}.${confirmedAt}`;
|
|
@@ -1,11 +1,67 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/core/auth/sessionCookie.ts
|
|
3
3
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
4
|
+
|
|
5
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
6
|
+
function appKeyPrefix() {
|
|
7
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
8
|
+
}
|
|
9
|
+
function appCookieName(kind) {
|
|
10
|
+
return `${appKeyPrefix()}_${kind}`;
|
|
11
|
+
}
|
|
12
|
+
function appDevSecret(kind) {
|
|
13
|
+
return `${appKeyPrefix()}-dev-${kind}`;
|
|
14
|
+
}
|
|
15
|
+
function namespacedRedisKey(kind) {
|
|
16
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
17
|
+
}
|
|
18
|
+
function smtpEhloHost() {
|
|
19
|
+
const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
|
|
20
|
+
const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
|
|
21
|
+
return safe || "strata.local";
|
|
22
|
+
}
|
|
23
|
+
function siemEventType() {
|
|
24
|
+
return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
|
|
25
|
+
}
|
|
26
|
+
function appUserAgent() {
|
|
27
|
+
return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
|
|
28
|
+
}
|
|
29
|
+
function otelServiceName() {
|
|
30
|
+
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
31
|
+
}
|
|
32
|
+
function webhookSignatureHeader() {
|
|
33
|
+
return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
|
|
34
|
+
}
|
|
35
|
+
function appDisplayName() {
|
|
36
|
+
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
37
|
+
}
|
|
38
|
+
function appEnv() {
|
|
39
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
40
|
+
}
|
|
41
|
+
function appUrl() {
|
|
42
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
43
|
+
}
|
|
44
|
+
function apiPrefix() {
|
|
45
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
46
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
47
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
48
|
+
return trimmed || "/api/v1";
|
|
49
|
+
}
|
|
50
|
+
function sdkClientClassName() {
|
|
51
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
52
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
53
|
+
return override;
|
|
54
|
+
}
|
|
55
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
56
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ../../src/core/auth/sessionCookie.ts
|
|
4
60
|
var SESSION_COOKIE = "workhub_session";
|
|
5
61
|
var SESSION_TTL_SECONDS = 60 * 60 * 24 * 7;
|
|
6
62
|
var SESSION_REMEMBER_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
7
63
|
function sessionCookieName() {
|
|
8
|
-
return process.env.SESSION_COOKIE_NAME?.trim() ||
|
|
64
|
+
return process.env.SESSION_COOKIE_NAME?.trim() || appCookieName("session");
|
|
9
65
|
}
|
|
10
66
|
function parsePositiveSeconds(raw, fallback) {
|
|
11
67
|
const parsed = Number.parseInt(raw ?? "", 10);
|
|
@@ -18,7 +74,7 @@ function sessionRememberTtlSeconds() {
|
|
|
18
74
|
return parsePositiveSeconds(process.env.SESSION_REMEMBER_TTL_SECONDS, SESSION_REMEMBER_TTL_SECONDS);
|
|
19
75
|
}
|
|
20
76
|
function resolveSessionSecret() {
|
|
21
|
-
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "
|
|
77
|
+
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("session-secret");
|
|
22
78
|
}
|
|
23
79
|
function signSession(userId, issuedAt, ttlSeconds) {
|
|
24
80
|
const payload = ttlSeconds === undefined ? `${userId}.${issuedAt}` : `${userId}.${issuedAt}.${ttlSeconds}`;
|
|
@@ -84,11 +84,67 @@ function resolveAbilitiesForRole(role) {
|
|
|
84
84
|
|
|
85
85
|
// ../../src/core/auth/sessionCookie.ts
|
|
86
86
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
87
|
+
|
|
88
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
89
|
+
function appKeyPrefix() {
|
|
90
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
91
|
+
}
|
|
92
|
+
function appCookieName(kind) {
|
|
93
|
+
return `${appKeyPrefix()}_${kind}`;
|
|
94
|
+
}
|
|
95
|
+
function appDevSecret(kind) {
|
|
96
|
+
return `${appKeyPrefix()}-dev-${kind}`;
|
|
97
|
+
}
|
|
98
|
+
function namespacedRedisKey(kind) {
|
|
99
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
100
|
+
}
|
|
101
|
+
function smtpEhloHost() {
|
|
102
|
+
const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
|
|
103
|
+
const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
|
|
104
|
+
return safe || "strata.local";
|
|
105
|
+
}
|
|
106
|
+
function siemEventType() {
|
|
107
|
+
return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
|
|
108
|
+
}
|
|
109
|
+
function appUserAgent() {
|
|
110
|
+
return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
|
|
111
|
+
}
|
|
112
|
+
function otelServiceName() {
|
|
113
|
+
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
114
|
+
}
|
|
115
|
+
function webhookSignatureHeader() {
|
|
116
|
+
return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
|
|
117
|
+
}
|
|
118
|
+
function appDisplayName() {
|
|
119
|
+
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
120
|
+
}
|
|
121
|
+
function appEnv() {
|
|
122
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
123
|
+
}
|
|
124
|
+
function appUrl() {
|
|
125
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
126
|
+
}
|
|
127
|
+
function apiPrefix() {
|
|
128
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
129
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
130
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
131
|
+
return trimmed || "/api/v1";
|
|
132
|
+
}
|
|
133
|
+
function sdkClientClassName() {
|
|
134
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
135
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
136
|
+
return override;
|
|
137
|
+
}
|
|
138
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
139
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ../../src/core/auth/sessionCookie.ts
|
|
87
143
|
var SESSION_COOKIE = "workhub_session";
|
|
88
144
|
var SESSION_TTL_SECONDS = 60 * 60 * 24 * 7;
|
|
89
145
|
var SESSION_REMEMBER_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
90
146
|
function sessionCookieName() {
|
|
91
|
-
return process.env.SESSION_COOKIE_NAME?.trim() ||
|
|
147
|
+
return process.env.SESSION_COOKIE_NAME?.trim() || appCookieName("session");
|
|
92
148
|
}
|
|
93
149
|
function parsePositiveSeconds(raw, fallback) {
|
|
94
150
|
const parsed = Number.parseInt(raw ?? "", 10);
|
|
@@ -101,7 +157,7 @@ function sessionRememberTtlSeconds() {
|
|
|
101
157
|
return parsePositiveSeconds(process.env.SESSION_REMEMBER_TTL_SECONDS, SESSION_REMEMBER_TTL_SECONDS);
|
|
102
158
|
}
|
|
103
159
|
function resolveSessionSecret() {
|
|
104
|
-
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "
|
|
160
|
+
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("session-secret");
|
|
105
161
|
}
|
|
106
162
|
function signSession(userId, issuedAt, ttlSeconds) {
|
|
107
163
|
const payload = ttlSeconds === undefined ? `${userId}.${issuedAt}` : `${userId}.${issuedAt}.${ttlSeconds}`;
|
|
@@ -1,12 +1,68 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/core/auth/tokenHash.ts
|
|
3
3
|
import { createHash, createHmac } from "crypto";
|
|
4
|
+
|
|
5
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
6
|
+
function appKeyPrefix() {
|
|
7
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
8
|
+
}
|
|
9
|
+
function appCookieName(kind) {
|
|
10
|
+
return `${appKeyPrefix()}_${kind}`;
|
|
11
|
+
}
|
|
12
|
+
function appDevSecret(kind) {
|
|
13
|
+
return `${appKeyPrefix()}-dev-${kind}`;
|
|
14
|
+
}
|
|
15
|
+
function namespacedRedisKey(kind) {
|
|
16
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
17
|
+
}
|
|
18
|
+
function smtpEhloHost() {
|
|
19
|
+
const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
|
|
20
|
+
const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
|
|
21
|
+
return safe || "strata.local";
|
|
22
|
+
}
|
|
23
|
+
function siemEventType() {
|
|
24
|
+
return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
|
|
25
|
+
}
|
|
26
|
+
function appUserAgent() {
|
|
27
|
+
return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
|
|
28
|
+
}
|
|
29
|
+
function otelServiceName() {
|
|
30
|
+
return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
|
|
31
|
+
}
|
|
32
|
+
function webhookSignatureHeader() {
|
|
33
|
+
return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
|
|
34
|
+
}
|
|
35
|
+
function appDisplayName() {
|
|
36
|
+
return process.env.APP_NAME?.trim() || "WorkHub";
|
|
37
|
+
}
|
|
38
|
+
function appEnv() {
|
|
39
|
+
return process.env.APP_ENV?.trim() || "local";
|
|
40
|
+
}
|
|
41
|
+
function appUrl() {
|
|
42
|
+
return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
|
|
43
|
+
}
|
|
44
|
+
function apiPrefix() {
|
|
45
|
+
const raw = process.env.API_PREFIX?.trim() || "/api/v1";
|
|
46
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
47
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
48
|
+
return trimmed || "/api/v1";
|
|
49
|
+
}
|
|
50
|
+
function sdkClientClassName() {
|
|
51
|
+
const override = process.env.APP_SDK_CLASS?.trim();
|
|
52
|
+
if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
|
|
53
|
+
return override;
|
|
54
|
+
}
|
|
55
|
+
const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
|
|
56
|
+
return fromName ? `${fromName}Client` : "AppClient";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ../../src/core/auth/tokenHash.ts
|
|
4
60
|
function resolveTokenPepper() {
|
|
5
|
-
return process.env.TOKEN_HASH_PEPPER?.trim() ?? "
|
|
61
|
+
return process.env.TOKEN_HASH_PEPPER?.trim() ?? appDevSecret("token-pepper");
|
|
6
62
|
}
|
|
7
63
|
function hashApiToken(token) {
|
|
8
64
|
const pepper = resolveTokenPepper();
|
|
9
|
-
if (pepper && pepper !== "
|
|
65
|
+
if (pepper && pepper !== appDevSecret("token-pepper")) {
|
|
10
66
|
return createHmac("sha256", pepper).update(token).digest("hex");
|
|
11
67
|
}
|
|
12
68
|
return createHash("sha256").update(token).digest("hex");
|
|
@@ -6,6 +6,12 @@ var {RedisClient } = globalThis.Bun;
|
|
|
6
6
|
function appKeyPrefix() {
|
|
7
7
|
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
8
8
|
}
|
|
9
|
+
function appCookieName(kind) {
|
|
10
|
+
return `${appKeyPrefix()}_${kind}`;
|
|
11
|
+
}
|
|
12
|
+
function appDevSecret(kind) {
|
|
13
|
+
return `${appKeyPrefix()}-dev-${kind}`;
|
|
14
|
+
}
|
|
9
15
|
function namespacedRedisKey(kind) {
|
|
10
16
|
return `${appKeyPrefix()}:${kind}`;
|
|
11
17
|
}
|
package/dist/entries/facades.js
CHANGED
|
@@ -15,6 +15,12 @@ import {
|
|
|
15
15
|
function appKeyPrefix() {
|
|
16
16
|
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
17
17
|
}
|
|
18
|
+
function appCookieName(kind) {
|
|
19
|
+
return `${appKeyPrefix()}_${kind}`;
|
|
20
|
+
}
|
|
21
|
+
function appDevSecret(kind) {
|
|
22
|
+
return `${appKeyPrefix()}-dev-${kind}`;
|
|
23
|
+
}
|
|
18
24
|
function namespacedRedisKey(kind) {
|
|
19
25
|
return `${appKeyPrefix()}:${kind}`;
|
|
20
26
|
}
|