@getstrata/core 0.5.101 → 0.7.3
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 +63 -32
- package/README.md +16 -35
- package/dist/core/auth/abilityCatalog.d.ts +2 -2
- package/dist/core/auth/basicAuthGuard.d.ts +9 -0
- package/dist/core/auth/guard.d.ts +6 -0
- package/dist/core/auth/jwt.d.ts +19 -0
- package/dist/core/auth/jwtGuard.d.ts +14 -0
- package/dist/core/auth/tokenAbilityChecker.d.ts +5 -0
- package/dist/core/cache/tags.d.ts +6 -0
- package/dist/core/contracts/authUserDirectory.d.ts +4 -0
- package/dist/core/database/dialect.d.ts +18 -0
- package/dist/core/database/factory.d.ts +1 -0
- package/dist/core/database/index.d.ts +8 -0
- package/dist/core/database/mysqlConnection.d.ts +12 -0
- package/dist/core/database/namedConnections.d.ts +15 -0
- package/dist/core/database/repositoryQuery.d.ts +1 -0
- package/dist/core/database/sqliteConnection.d.ts +7 -0
- package/dist/core/http/loginThrottleMiddleware.d.ts +5 -2
- package/dist/core/http/resources.d.ts +2 -2
- package/dist/core/http/response.d.ts +2 -1
- package/dist/core/http/statelessAuth.d.ts +8 -0
- package/dist/core/http/throttleResponse.d.ts +2 -0
- package/dist/core/runtime/frontendMode.d.ts +10 -2
- package/dist/entries/auth/basicAuthGuard.js +137 -0
- package/dist/entries/auth/jwt.js +135 -0
- package/dist/entries/auth/jwtGuard.js +203 -0
- package/dist/entries/auth/sessionGuard.js +3 -21
- package/dist/entries/auth/tokenAbilityChecker.js +24 -0
- package/dist/entries/cache/tags.js +7 -1
- package/dist/entries/database/connectionContext.js +1 -0
- package/dist/entries/database/dialect.js +1 -0
- package/dist/entries/database/factory.js +5 -4
- package/dist/entries/database/model.js +49 -32
- package/dist/entries/database/mysqlConnection.js +35 -0
- package/dist/entries/database/namedConnections.js +1 -0
- package/dist/entries/database/query.js +28 -15
- package/dist/entries/database/relationships.js +14 -6
- package/dist/entries/database/repositoryQuery.js +96 -81
- package/dist/entries/database/schema.js +28 -15
- package/dist/entries/database/sqliteConnection.js +34 -0
- package/dist/entries/facades.js +1 -1
- package/dist/entries/http/contentNegotiation.js +5 -2
- package/dist/entries/http/csrfMiddleware.js +45 -0
- package/dist/entries/http/loginThrottleMiddleware.js +246 -7
- package/dist/entries/http/memoryThrottleMiddleware.js +208 -6
- package/dist/entries/http/requireAbilityMiddleware.js +35 -11
- package/dist/entries/http/requirePasswordConfirmMiddleware.js +5 -2
- package/dist/entries/http/requireVerifiedMiddleware.js +5 -2
- package/dist/entries/http/requireWebAuthMiddleware.js +12 -3
- package/dist/entries/http/resources.js +4 -1
- package/dist/entries/http/response.js +46 -12
- package/dist/entries/http/statelessAuth.js +48 -0
- package/dist/entries/http/throttleMiddleware.js +208 -6
- package/dist/entries/http/webErrorResponse.js +35 -11
- package/dist/entries/http/webFormRequest.js +5 -2
- package/dist/entries/mail/mailer.js +1 -1
- package/dist/entries/openapi/generator.js +26 -3
- package/dist/entries/runtime/frontendMode.js +39 -10
- package/dist/framework/public-api.d.ts +11 -1
- package/dist/index.js +839 -252
- package/package.json +56 -5
|
@@ -73,6 +73,213 @@ function readClientIp(request, env = process.env) {
|
|
|
73
73
|
return request.headers.get("x-real-ip")?.trim() || undefined;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// ../../src/core/runtime/frontendMode.ts
|
|
77
|
+
var FRONTEND_MODES = ["api", "server-htmx", "spa-react", "hybrid"];
|
|
78
|
+
var DEFAULT_SPA_PREFIX = "/app";
|
|
79
|
+
var FRONTEND_MODE_PATTERN = new RegExp(`^(${FRONTEND_MODES.join("|")})$`);
|
|
80
|
+
function parseFrontendMode(value) {
|
|
81
|
+
const mode = (value ?? "api").trim();
|
|
82
|
+
if (mode === "server-htmx" || mode === "spa-react" || mode === "hybrid") {
|
|
83
|
+
return mode;
|
|
84
|
+
}
|
|
85
|
+
return "api";
|
|
86
|
+
}
|
|
87
|
+
function readFrontendMode() {
|
|
88
|
+
return parseFrontendMode(process.env.FRONTEND_MODE);
|
|
89
|
+
}
|
|
90
|
+
function isViewsMode(mode) {
|
|
91
|
+
return mode === "server-htmx" || mode === "hybrid";
|
|
92
|
+
}
|
|
93
|
+
function isSpaMode(mode) {
|
|
94
|
+
return mode === "spa-react" || mode === "hybrid";
|
|
95
|
+
}
|
|
96
|
+
function isViewsEnabled() {
|
|
97
|
+
return isViewsMode(readFrontendMode());
|
|
98
|
+
}
|
|
99
|
+
function isSpaEnabled() {
|
|
100
|
+
return isSpaMode(readFrontendMode());
|
|
101
|
+
}
|
|
102
|
+
function normalizeSpaPrefix(value) {
|
|
103
|
+
const raw = (value ?? DEFAULT_SPA_PREFIX).trim() || DEFAULT_SPA_PREFIX;
|
|
104
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
105
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
106
|
+
if (trimmed.length === 0 || trimmed === "/") {
|
|
107
|
+
return DEFAULT_SPA_PREFIX;
|
|
108
|
+
}
|
|
109
|
+
return trimmed;
|
|
110
|
+
}
|
|
111
|
+
function readSpaPrefix() {
|
|
112
|
+
return normalizeSpaPrefix(process.env.SPA_PREFIX);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ../../src/core/view/webErrorView.ts
|
|
116
|
+
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
117
|
+
|
|
118
|
+
// ../../src/core/view/htmlResponse.ts
|
|
119
|
+
function withCharset(contentType) {
|
|
120
|
+
return contentType.includes("charset=") ? contentType : `${contentType}; charset=utf-8`;
|
|
121
|
+
}
|
|
122
|
+
function htmlResponse(html, init = {}) {
|
|
123
|
+
return new Response(html, {
|
|
124
|
+
status: init.status ?? 200,
|
|
125
|
+
statusText: init.statusText,
|
|
126
|
+
headers: {
|
|
127
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function isHtmxRequest(request) {
|
|
132
|
+
return request.headers.get("HX-Request") === "true";
|
|
133
|
+
}
|
|
134
|
+
function redirectResponse(location, status = 302) {
|
|
135
|
+
return new Response(null, {
|
|
136
|
+
status,
|
|
137
|
+
headers: {
|
|
138
|
+
Location: location
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function textResponse(body, init = {}) {
|
|
143
|
+
return new Response(body, {
|
|
144
|
+
status: init.status ?? 200,
|
|
145
|
+
headers: {
|
|
146
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function xmlResponse(body, init = {}) {
|
|
151
|
+
return new Response(body, {
|
|
152
|
+
status: init.status ?? 200,
|
|
153
|
+
headers: {
|
|
154
|
+
"Content-Type": withCharset(init.contentType ?? "application/xml")
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function rssResponse(body, init = {}) {
|
|
159
|
+
return xmlResponse(body, { ...init, contentType: "application/rss+xml" });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ../../src/core/view/webErrorView.ts
|
|
163
|
+
var configuredErrorView = {};
|
|
164
|
+
function configureWebErrorView(options) {
|
|
165
|
+
configuredErrorView = { ...options };
|
|
166
|
+
}
|
|
167
|
+
function escapeHtml(value) {
|
|
168
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
169
|
+
}
|
|
170
|
+
function renderKernelErrorChrome(input) {
|
|
171
|
+
const title = escapeHtml(input.title);
|
|
172
|
+
const message = escapeHtml(input.message);
|
|
173
|
+
const errorLines = Object.entries(input.errors ?? {}).flatMap(([field, messages]) => messages.map((item) => `${field}: ${item}`)).map((line) => `<li>${escapeHtml(line)}</li>`).join("");
|
|
174
|
+
const details = errorLines ? `<ul class="error-list">${errorLines}</ul>` : "";
|
|
175
|
+
const goBack = input.status === 422 ? `<p><a href="javascript:history.back()">Go back</a></p>` : "";
|
|
176
|
+
return `<!doctype html>
|
|
177
|
+
<html lang="en">
|
|
178
|
+
<head>
|
|
179
|
+
<meta charset="UTF-8" />
|
|
180
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
181
|
+
<title>${title}</title>
|
|
182
|
+
<link rel="stylesheet" href="/assets/app.css" />
|
|
183
|
+
</head>
|
|
184
|
+
<body>
|
|
185
|
+
<header class="site-header">
|
|
186
|
+
<a class="brand" href="/">Home</a>
|
|
187
|
+
</header>
|
|
188
|
+
<main class="site-main">
|
|
189
|
+
<section class="page-header">
|
|
190
|
+
<h1>${title}</h1>
|
|
191
|
+
<p>${message}</p>
|
|
192
|
+
${details}
|
|
193
|
+
${goBack}
|
|
194
|
+
</section>
|
|
195
|
+
</main>
|
|
196
|
+
</body>
|
|
197
|
+
</html>
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
function errorTemplateName(status) {
|
|
201
|
+
if (status === 404) {
|
|
202
|
+
return "errors/not-found";
|
|
203
|
+
}
|
|
204
|
+
if (status === 403) {
|
|
205
|
+
return "errors/forbidden";
|
|
206
|
+
}
|
|
207
|
+
return "errors/error";
|
|
208
|
+
}
|
|
209
|
+
async function renderWebErrorHtml(input) {
|
|
210
|
+
const render = configuredErrorView.render;
|
|
211
|
+
if (!render) {
|
|
212
|
+
return renderKernelErrorChrome(input);
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
return await render({
|
|
216
|
+
...input,
|
|
217
|
+
request: input.request ?? currentRequestMeta().request
|
|
218
|
+
});
|
|
219
|
+
} catch {
|
|
220
|
+
return renderKernelErrorChrome(input);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async function htmlErrorResponse(input) {
|
|
224
|
+
return htmlResponse(await renderWebErrorHtml(input), { status: input.status });
|
|
225
|
+
}
|
|
226
|
+
async function notFoundHtmlResponse(body) {
|
|
227
|
+
if (body !== undefined) {
|
|
228
|
+
return htmlResponse(body, { status: 404 });
|
|
229
|
+
}
|
|
230
|
+
return htmlErrorResponse({
|
|
231
|
+
status: 404,
|
|
232
|
+
title: "Not Found",
|
|
233
|
+
message: "The page you requested was not found."
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ../../src/core/http/contentNegotiation.ts
|
|
238
|
+
function requestPrefersJson(request) {
|
|
239
|
+
if (!request) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
if (request.headers.get("HX-Request") === "true") {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
const pathname = new URL(request.url).pathname;
|
|
246
|
+
if (pathname.startsWith("/api/")) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
const accept = request.headers.get("accept")?.toLowerCase() ?? "";
|
|
250
|
+
if (accept.includes("text/html")) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
if (accept.includes("application/json")) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
const contentType = request.headers.get("content-type")?.toLowerCase() ?? "";
|
|
257
|
+
if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ../../src/core/http/throttleResponse.ts
|
|
264
|
+
async function tooManyRequestsResponse(request, message, decaySeconds) {
|
|
265
|
+
const retryAfter = { "retry-after": String(decaySeconds) };
|
|
266
|
+
if (requestPrefersJson(request) || !isViewsEnabled()) {
|
|
267
|
+
return Response.json({ error: message }, {
|
|
268
|
+
status: 429,
|
|
269
|
+
headers: retryAfter
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const html = await htmlErrorResponse({
|
|
273
|
+
status: 429,
|
|
274
|
+
title: "Too Many Requests",
|
|
275
|
+
message,
|
|
276
|
+
request
|
|
277
|
+
});
|
|
278
|
+
const headers = new Headers(html.headers);
|
|
279
|
+
headers.set("retry-after", String(decaySeconds));
|
|
280
|
+
return new Response(html.body, { status: 429, headers });
|
|
281
|
+
}
|
|
282
|
+
|
|
76
283
|
// ../../src/core/http/throttleMiddleware.ts
|
|
77
284
|
function resolveThrottleIdentity(request) {
|
|
78
285
|
const user = currentAuthUser();
|
|
@@ -97,12 +304,7 @@ function createThrottleMiddleware(options) {
|
|
|
97
304
|
}
|
|
98
305
|
const maxAttempts = options.maxAttempts * rateLimitMultiplierForPlan(currentTenant()?.plan ?? "free");
|
|
99
306
|
if (attempts > maxAttempts) {
|
|
100
|
-
return
|
|
101
|
-
status: 429,
|
|
102
|
-
headers: {
|
|
103
|
-
"retry-after": String(options.decaySeconds)
|
|
104
|
-
}
|
|
105
|
-
});
|
|
307
|
+
return await tooManyRequestsResponse(request, "Too many requests.", options.decaySeconds);
|
|
106
308
|
}
|
|
107
309
|
return await next();
|
|
108
310
|
};
|
|
@@ -67,21 +67,42 @@ async function withDatabaseErrorHandling(operation) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// ../../src/core/runtime/frontendMode.ts
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (mode === "spa-react") {
|
|
76
|
-
return
|
|
70
|
+
var FRONTEND_MODES = ["api", "server-htmx", "spa-react", "hybrid"];
|
|
71
|
+
var DEFAULT_SPA_PREFIX = "/app";
|
|
72
|
+
var FRONTEND_MODE_PATTERN = new RegExp(`^(${FRONTEND_MODES.join("|")})$`);
|
|
73
|
+
function parseFrontendMode(value) {
|
|
74
|
+
const mode = (value ?? "api").trim();
|
|
75
|
+
if (mode === "server-htmx" || mode === "spa-react" || mode === "hybrid") {
|
|
76
|
+
return mode;
|
|
77
77
|
}
|
|
78
78
|
return "api";
|
|
79
79
|
}
|
|
80
|
+
function readFrontendMode() {
|
|
81
|
+
return parseFrontendMode(process.env.FRONTEND_MODE);
|
|
82
|
+
}
|
|
83
|
+
function isViewsMode(mode) {
|
|
84
|
+
return mode === "server-htmx" || mode === "hybrid";
|
|
85
|
+
}
|
|
86
|
+
function isSpaMode(mode) {
|
|
87
|
+
return mode === "spa-react" || mode === "hybrid";
|
|
88
|
+
}
|
|
80
89
|
function isViewsEnabled() {
|
|
81
|
-
return readFrontendMode()
|
|
90
|
+
return isViewsMode(readFrontendMode());
|
|
82
91
|
}
|
|
83
92
|
function isSpaEnabled() {
|
|
84
|
-
return readFrontendMode()
|
|
93
|
+
return isSpaMode(readFrontendMode());
|
|
94
|
+
}
|
|
95
|
+
function normalizeSpaPrefix(value) {
|
|
96
|
+
const raw = (value ?? DEFAULT_SPA_PREFIX).trim() || DEFAULT_SPA_PREFIX;
|
|
97
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
98
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
99
|
+
if (trimmed.length === 0 || trimmed === "/") {
|
|
100
|
+
return DEFAULT_SPA_PREFIX;
|
|
101
|
+
}
|
|
102
|
+
return trimmed;
|
|
103
|
+
}
|
|
104
|
+
function readSpaPrefix() {
|
|
105
|
+
return normalizeSpaPrefix(process.env.SPA_PREFIX);
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
// ../../src/core/view/webErrorView.ts
|
|
@@ -214,6 +235,10 @@ function requestPrefersJson(request) {
|
|
|
214
235
|
if (request.headers.get("HX-Request") === "true") {
|
|
215
236
|
return false;
|
|
216
237
|
}
|
|
238
|
+
const pathname = new URL(request.url).pathname;
|
|
239
|
+
if (pathname.startsWith("/api/")) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
217
242
|
const accept = request.headers.get("accept")?.toLowerCase() ?? "";
|
|
218
243
|
if (accept.includes("text/html")) {
|
|
219
244
|
return false;
|
|
@@ -225,8 +250,7 @@ function requestPrefersJson(request) {
|
|
|
225
250
|
if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
|
|
226
251
|
return false;
|
|
227
252
|
}
|
|
228
|
-
|
|
229
|
-
return pathname.startsWith("/api/");
|
|
253
|
+
return false;
|
|
230
254
|
}
|
|
231
255
|
|
|
232
256
|
// ../../src/core/http/safeInternalPath.ts
|
|
@@ -10,6 +10,10 @@ function requestPrefersJson(request) {
|
|
|
10
10
|
if (request.headers.get("HX-Request") === "true") {
|
|
11
11
|
return false;
|
|
12
12
|
}
|
|
13
|
+
const pathname = new URL(request.url).pathname;
|
|
14
|
+
if (pathname.startsWith("/api/")) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
13
17
|
const accept = request.headers.get("accept")?.toLowerCase() ?? "";
|
|
14
18
|
if (accept.includes("text/html")) {
|
|
15
19
|
return false;
|
|
@@ -21,8 +25,7 @@ function requestPrefersJson(request) {
|
|
|
21
25
|
if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
|
|
22
26
|
return false;
|
|
23
27
|
}
|
|
24
|
-
|
|
25
|
-
return pathname.startsWith("/api/");
|
|
28
|
+
return false;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
// ../../src/core/http/parseFormBody.ts
|
|
@@ -65,6 +65,12 @@ var PUBLIC_ROUTE_DESCRIPTIONS = {
|
|
|
65
65
|
"GET /auth/tokens": "List API tokens",
|
|
66
66
|
"POST /auth/tokens": "Create API token",
|
|
67
67
|
"DELETE /auth/tokens/:id": "Revoke API token",
|
|
68
|
+
"POST /auth/token": "Mint a short-lived JWT with email and password",
|
|
69
|
+
"POST /login": "Login with email and password",
|
|
70
|
+
"GET /careers": "List public career postings",
|
|
71
|
+
"GET /careers/:id": "Show a public career posting",
|
|
72
|
+
"GET /integrations/ping": "Partner heartbeat (requires integrations:ping)",
|
|
73
|
+
"GET /audit-logs/export": "Download audit events as JSON or CEF",
|
|
68
74
|
"GET /users/me/export": "GDPR export of user data",
|
|
69
75
|
"GET /users/me/current-organization": "Current organization",
|
|
70
76
|
"PUT /users/me/current-organization": "Switch current organization",
|
|
@@ -99,6 +105,23 @@ var PUBLIC_ROUTE_DESCRIPTIONS = {
|
|
|
99
105
|
"GET /metrics": "Prometheus metrics",
|
|
100
106
|
"GET /api/user": "Current authenticated HiroApp user",
|
|
101
107
|
"POST /api/login": "Login with email and password",
|
|
108
|
+
"POST /api/auth/token": "Mint a short-lived JWT with email and password",
|
|
109
|
+
"POST /api/apply/login": "Candidate portal login (opaque token)",
|
|
110
|
+
"POST /api/apply/logout": "Revoke the candidate portal token",
|
|
111
|
+
"GET /api/apply/me": "Candidate portal current user",
|
|
112
|
+
"GET /api/apply/positions": "Published jobs for the candidate portal",
|
|
113
|
+
"GET /api/apply/applications": "Candidate portal applications",
|
|
114
|
+
"POST /api/apply/applications": "Apply from the candidate portal",
|
|
115
|
+
"GET /api/apply/interviews": "Candidate portal interviews",
|
|
116
|
+
"GET /api/apply/offers": "Candidate portal offers",
|
|
117
|
+
"PATCH /api/apply/profile": "Update candidate portal profile",
|
|
118
|
+
"GET /api/kiosk/scorecards": "List on-site kiosk scorecards",
|
|
119
|
+
"POST /api/kiosk/scorecards": "Store an on-site kiosk scorecard",
|
|
120
|
+
"POST /api/kiosk/sync": "Sync kiosk scorecards into Postgres",
|
|
121
|
+
"GET /api/integrations/ping": "Partner heartbeat (requires integrations:ping)",
|
|
122
|
+
"GET /api/audit-logs/export": "Download audit events as JSON or CEF",
|
|
123
|
+
"GET /api/careers": "List public career postings",
|
|
124
|
+
"GET /api/careers/:id": "Show a public career posting",
|
|
102
125
|
"POST /api/logout": "Log out the current cookie session",
|
|
103
126
|
"POST /api/auth/two-factor-challenge": "Complete staff two-factor login challenge",
|
|
104
127
|
"GET /api/users/me": "Current user profile",
|
|
@@ -134,7 +157,7 @@ function toRelativeApiPath(path) {
|
|
|
134
157
|
}
|
|
135
158
|
function requiresBearerAuth(path, method) {
|
|
136
159
|
const relative = toRelativeApiPath(path);
|
|
137
|
-
if (relative.startsWith("/auth/login") || relative.startsWith("/auth/two-factor-challenge") || relative.startsWith("/auth/register") || relative.startsWith("/auth/forgot-password") || relative.startsWith("/auth/reset-password") || relative.startsWith("/auth/email/verification-notification") || relative.startsWith("/auth/oauth")) {
|
|
160
|
+
if (relative.startsWith("/auth/login") || relative.startsWith("/auth/two-factor-challenge") || relative.startsWith("/auth/register") || relative.startsWith("/auth/forgot-password") || relative.startsWith("/auth/reset-password") || relative.startsWith("/auth/email/verification-notification") || relative.startsWith("/auth/oauth") || relative === "/auth/token" || relative === "/apply/login" || relative === "/login" || path === "/login" || path === "/api/login" || path === "/api/apply/login") {
|
|
138
161
|
return false;
|
|
139
162
|
}
|
|
140
163
|
if (relative.startsWith("/scim/") || relative.startsWith("/billing/webhooks/") || path.startsWith("/scim/") || path.startsWith("/billing/webhooks/")) {
|
|
@@ -143,10 +166,10 @@ function requiresBearerAuth(path, method) {
|
|
|
143
166
|
if (["/health", "/ready", "/metrics", "/"].includes(relative) || ["/health", "/ready", "/metrics", "/"].includes(path)) {
|
|
144
167
|
return false;
|
|
145
168
|
}
|
|
146
|
-
if (method === "GET" && ["/organizations", "/projects", "/tasks", "/search"].some((prefix) => relative.startsWith(prefix) || path.startsWith(prefix))) {
|
|
169
|
+
if (method === "GET" && ["/organizations", "/projects", "/tasks", "/search", "/careers"].some((prefix) => relative.startsWith(prefix) || path.startsWith(prefix))) {
|
|
147
170
|
return false;
|
|
148
171
|
}
|
|
149
|
-
return relative.startsWith("/auth/") || relative.startsWith("/users/me") || path.startsWith("/users/me") || ["POST", "PATCH", "PUT", "DELETE"].includes(method);
|
|
172
|
+
return relative.startsWith("/auth/") || relative.startsWith("/users/me") || path.startsWith("/users/me") || relative === "/user" || relative.startsWith("/integrations/") || relative.startsWith("/audit-logs") || ["POST", "PATCH", "PUT", "DELETE"].includes(method);
|
|
150
173
|
}
|
|
151
174
|
function generateOpenApiSpec(routes) {
|
|
152
175
|
const paths = {};
|
|
@@ -1,23 +1,52 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/core/runtime/frontendMode.ts
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (mode === "spa-react") {
|
|
9
|
-
return
|
|
3
|
+
var FRONTEND_MODES = ["api", "server-htmx", "spa-react", "hybrid"];
|
|
4
|
+
var DEFAULT_SPA_PREFIX = "/app";
|
|
5
|
+
var FRONTEND_MODE_PATTERN = new RegExp(`^(${FRONTEND_MODES.join("|")})$`);
|
|
6
|
+
function parseFrontendMode(value) {
|
|
7
|
+
const mode = (value ?? "api").trim();
|
|
8
|
+
if (mode === "server-htmx" || mode === "spa-react" || mode === "hybrid") {
|
|
9
|
+
return mode;
|
|
10
10
|
}
|
|
11
11
|
return "api";
|
|
12
12
|
}
|
|
13
|
+
function readFrontendMode() {
|
|
14
|
+
return parseFrontendMode(process.env.FRONTEND_MODE);
|
|
15
|
+
}
|
|
16
|
+
function isViewsMode(mode) {
|
|
17
|
+
return mode === "server-htmx" || mode === "hybrid";
|
|
18
|
+
}
|
|
19
|
+
function isSpaMode(mode) {
|
|
20
|
+
return mode === "spa-react" || mode === "hybrid";
|
|
21
|
+
}
|
|
13
22
|
function isViewsEnabled() {
|
|
14
|
-
return readFrontendMode()
|
|
23
|
+
return isViewsMode(readFrontendMode());
|
|
15
24
|
}
|
|
16
25
|
function isSpaEnabled() {
|
|
17
|
-
return readFrontendMode()
|
|
26
|
+
return isSpaMode(readFrontendMode());
|
|
27
|
+
}
|
|
28
|
+
function normalizeSpaPrefix(value) {
|
|
29
|
+
const raw = (value ?? DEFAULT_SPA_PREFIX).trim() || DEFAULT_SPA_PREFIX;
|
|
30
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
31
|
+
const trimmed = withSlash.replace(/\/+$/, "");
|
|
32
|
+
if (trimmed.length === 0 || trimmed === "/") {
|
|
33
|
+
return DEFAULT_SPA_PREFIX;
|
|
34
|
+
}
|
|
35
|
+
return trimmed;
|
|
36
|
+
}
|
|
37
|
+
function readSpaPrefix() {
|
|
38
|
+
return normalizeSpaPrefix(process.env.SPA_PREFIX);
|
|
18
39
|
}
|
|
19
40
|
export {
|
|
41
|
+
DEFAULT_SPA_PREFIX,
|
|
42
|
+
FRONTEND_MODES,
|
|
43
|
+
FRONTEND_MODE_PATTERN,
|
|
20
44
|
isSpaEnabled,
|
|
45
|
+
isSpaMode,
|
|
21
46
|
isViewsEnabled,
|
|
22
|
-
|
|
47
|
+
isViewsMode,
|
|
48
|
+
normalizeSpaPrefix,
|
|
49
|
+
parseFrontendMode,
|
|
50
|
+
readFrontendMode,
|
|
51
|
+
readSpaPrefix
|
|
23
52
|
};
|
|
@@ -8,14 +8,19 @@ export type { AbilityChecker } from "../core/auth/abilityChecker.ts";
|
|
|
8
8
|
export { isGlobalAdmin, resolveUserId } from "../core/auth/accessControl.ts";
|
|
9
9
|
export type { AuthUser } from "../core/auth/authContext.ts";
|
|
10
10
|
export { authContext, currentAuthUser, runWithAuthUser } from "../core/auth/authContext.ts";
|
|
11
|
+
export { BasicAuthGuard } from "../core/auth/basicAuthGuard.ts";
|
|
11
12
|
export type { AuthGuard } from "../core/auth/guard.ts";
|
|
12
13
|
export { ApiTokenGuard, AuthManager, CompositeGuard, DatabaseTokenGuard, GuestGuard, } from "../core/auth/guard.ts";
|
|
14
|
+
export type { JwtPayload } from "../core/auth/jwt.ts";
|
|
15
|
+
export { jwtTtlSeconds, signJwt, verifyJwt } from "../core/auth/jwt.ts";
|
|
16
|
+
export { JwtGuard } from "../core/auth/jwtGuard.ts";
|
|
13
17
|
export { configureMembershipLookup, currentOrganizationIds, currentOrgRole, hasMinimumOrgRole, hasOrgMembership, resolveMembershipLookup, runWithMembershipContext, } from "../core/auth/membershipContext.ts";
|
|
14
18
|
export { createMembershipMiddleware } from "../core/auth/membershipMiddleware.ts";
|
|
15
19
|
export { appendOrganizationScope, appendProjectScope, assertOrganizationReadable, assertResourceInCurrentTenant, emptyPaginateResult, resolveOrganizationScope, scopedOrganizationIds, } from "../core/auth/membershipScope.ts";
|
|
16
20
|
export { default as MembershipService, resolveMembershipService, } from "../core/auth/membershipService.ts";
|
|
17
21
|
export { Policy, PolicyGate } from "../core/auth/policy.ts";
|
|
18
22
|
export { createScimAuthMiddleware } from "../core/auth/scimAuthMiddleware.ts";
|
|
23
|
+
export { createTokenAbilityChecker } from "../core/auth/tokenAbilityChecker.ts";
|
|
19
24
|
export { type CacheDriver, type CreateCacheStoreOptions, createCacheStore, } from "../core/cache/createCacheStore.ts";
|
|
20
25
|
export { default as CacheRepository } from "../core/cache/repository.ts";
|
|
21
26
|
export { CACHE_TAGS } from "../core/cache/tags.ts";
|
|
@@ -27,8 +32,10 @@ export { default as BaseRepository } from "../core/database/baseRepository.ts";
|
|
|
27
32
|
export { bindDatabaseConnection, getBoundDatabaseConnection, resetBoundDatabaseConnection, } from "../core/database/bindConnection.ts";
|
|
28
33
|
export { bindBunSql, createBunSqlPool } from "../core/database/bunSql.ts";
|
|
29
34
|
export { createDatabaseConnection } from "../core/database/connection.ts";
|
|
30
|
-
export { getActiveDatabaseConnection, runWithDatabaseConnection, } from "../core/database/connectionContext.ts";
|
|
35
|
+
export { getActiveDatabaseConnection, hasActiveDatabaseConnection, runWithDatabaseConnection, } from "../core/database/connectionContext.ts";
|
|
31
36
|
export { getDefaultDatabasePool, getDefaultDatabaseQuery, registerDefaultDatabasePool, } from "../core/database/defaultConnection.ts";
|
|
37
|
+
export type { SqlDialect } from "../core/database/dialect.ts";
|
|
38
|
+
export { currentSqlDialect, dialectFor, resetSqlDialect, runWithSqlDialect, useSqlDialect, } from "../core/database/dialect.ts";
|
|
32
39
|
export { Factory } from "../core/database/factory.ts";
|
|
33
40
|
export { foreignKeyFromTable, pivotTableName, singularize } from "../core/database/inflection.ts";
|
|
34
41
|
export { withMigrationLock } from "../core/database/migrations/advisoryLock.ts";
|
|
@@ -36,6 +43,8 @@ export { freshDatabase, getMigrationStatus, loadMigrationsFromDirectory, migrate
|
|
|
36
43
|
export type { Migration, MigrationDatabase, MigrationStatus, } from "../core/database/migrations/types.ts";
|
|
37
44
|
export type { CastType, GlobalScopeFn, ModelConstructor } from "../core/database/model.ts";
|
|
38
45
|
export { applyCasts, BelongsToManyRelationQuery, BelongsToRelationQuery, dehydrateValue, filterMassAssignable, HasManyRelationQuery, HasOneRelationQuery, hydrateValue, Model, ModelQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, registerModelClass, registerModelRepository, } from "../core/database/model.ts";
|
|
46
|
+
export { createMysqlConnection, createMysqlConnectionFromPool, } from "../core/database/mysqlConnection.ts";
|
|
47
|
+
export { getNamedConnection, hasNamedConnection, registerNamedConnection, resetNamedConnections, runOnNamedConnection, unregisterNamedConnection, } from "../core/database/namedConnections.ts";
|
|
39
48
|
export { createDatabaseQueryProxy } from "../core/database/queryProxy.ts";
|
|
40
49
|
export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, } from "../core/database/relationships.ts";
|
|
41
50
|
export { belongsTo, belongsToMany, hasMany, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, } from "../core/database/relationships.ts";
|
|
@@ -45,6 +54,7 @@ export type { BlueprintAction, BlueprintCallback, ColumnKind, DatabaseDriver, Fo
|
|
|
45
54
|
export { Blueprint, ColumnDefinition, compileBlueprint, createSchemaBuilder, ForeignIdColumnDefinition, grammarForDriver, inferReferencedTable, MySqlGrammar, PostgresGrammar, resolveDatabaseDriver, Schema, SqliteGrammar, UnsupportedSchemaFeatureError, } from "../core/database/schema/index.ts";
|
|
46
55
|
export { loadSeedersFromDirectory, runSeedersFromDirectory, } from "../core/database/seeders/runner.ts";
|
|
47
56
|
export type { Seeder, SeederDatabase } from "../core/database/seeders/types.ts";
|
|
57
|
+
export { createSqliteConnection } from "../core/database/sqliteConnection.ts";
|
|
48
58
|
export { defineTable } from "../core/database/table.ts";
|
|
49
59
|
export { runInTransaction } from "../core/database/transaction.ts";
|
|
50
60
|
export type { QueryJoin, QueryJoinOn, QueryOptions, QueryOrder, QuerySelectItem, QueryWhere, } from "../core/database/types.ts";
|