@antelopejs/dms-frontend 0.5.6 → 0.6.1

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.
Files changed (71) hide show
  1. package/README.md +376 -18
  2. package/dist/app-configs.d.ts +9 -0
  3. package/dist/app-configs.js +52 -0
  4. package/dist/asset-roots.d.ts +7 -0
  5. package/dist/asset-roots.js +34 -0
  6. package/dist/commands/build-action.js +9 -0
  7. package/dist/commands/dev-action.js +88 -19
  8. package/dist/commands/start-action.js +2 -0
  9. package/dist/common.d.ts +1 -0
  10. package/dist/common.js +1 -0
  11. package/dist/config.d.ts +2 -0
  12. package/dist/config.js +8 -3
  13. package/dist/derived-outputs.d.ts +2 -0
  14. package/dist/derived-outputs.js +38 -31
  15. package/dist/help.js +10 -3
  16. package/dist/layer-watch.js +123 -131
  17. package/dist/layers.js +2 -0
  18. package/dist/manifest-secrets.js +5 -5
  19. package/dist/manifest.js +7 -1
  20. package/dist/materialize.js +21 -1
  21. package/dist/module-set.d.ts +16 -0
  22. package/dist/module-set.js +84 -0
  23. package/dist/server-process.d.ts +1 -0
  24. package/dist/server-process.js +28 -2
  25. package/dist/verify-source-runner.js +12 -0
  26. package/dist/workspace.d.ts +9 -0
  27. package/package.json +2 -1
  28. package/templates/vue/app-runtime.ts +58 -16
  29. package/templates/vue/client-server-guard.mjs +73 -0
  30. package/templates/vue/email-renderer.ts +17 -4
  31. package/templates/vue/email-runtime.ts +1 -1
  32. package/templates/vue/frontend-module.ts +1817 -259
  33. package/templates/vue/globals.d.ts +1 -1
  34. package/templates/vue/icon-hydration.ts +106 -1
  35. package/templates/vue/index.html +0 -1
  36. package/templates/vue/layer-app-config.ts +92 -0
  37. package/templates/vue/main.ts +10 -2
  38. package/templates/vue/package.json +4 -1
  39. package/templates/vue/server/auth/backend.mjs +13 -19
  40. package/templates/vue/server/auth/client-ip.mjs +27 -0
  41. package/templates/vue/server/auth/establish-endpoints.mjs +70 -0
  42. package/templates/vue/server/auth/session.mjs +6 -97
  43. package/templates/vue/server/auth-hooks.mjs +84 -0
  44. package/templates/vue/server/backend-proxies.mjs +110 -0
  45. package/templates/vue/server/dev-private-files.mjs +99 -0
  46. package/templates/vue/server/email.mjs +11 -33
  47. package/templates/vue/server/favicon.mjs +20 -0
  48. package/templates/vue/server/inertia.mjs +48 -3
  49. package/templates/vue/server/listen-host.mjs +19 -0
  50. package/templates/vue/server/module-event.mjs +265 -0
  51. package/templates/vue/server/module-routes.mjs +513 -0
  52. package/templates/vue/server/module-server.mjs +225 -0
  53. package/templates/vue/server/module-testing.mjs +65 -0
  54. package/templates/vue/server/page-props.mjs +151 -0
  55. package/templates/vue/server/path-patterns.mjs +38 -0
  56. package/templates/vue/server/public-endpoints.mjs +69 -11
  57. package/templates/vue/server/request-routing.mjs +47 -0
  58. package/templates/vue/server/route-rules.mjs +129 -0
  59. package/templates/vue/server-module.ts +294 -0
  60. package/templates/vue/server.mjs +177 -161
  61. package/templates/vue/ssr-renderer.ts +60 -19
  62. package/templates/vue/tsconfig.json +2 -1
  63. package/templates/vue/ui-app-config.ts +18 -6
  64. package/templates/vue/ui-inertia-page.mjs +37 -0
  65. package/templates/vue/vite.config.ts +31 -10
  66. package/templates/vue/vite.email.config.ts +1 -1
  67. package/templates/vue/vite.server.config.ts +57 -0
  68. package/templates/vue/server/auth/oauth.mjs +0 -233
  69. package/templates/vue/server/auth/routes.mjs +0 -444
  70. package/templates/vue/server/render-token.mjs +0 -81
  71. package/templates/vue/server/tester.mjs +0 -227
@@ -1,227 +0,0 @@
1
- import { body, json, RequestBodyError } from "./auth/backend.mjs";
2
- import { CROSS_ORIGIN_ERROR, isSameOrigin } from "./auth/client-ip.mjs";
3
- import { readSession } from "./auth/session.mjs";
4
-
5
- const RESPONSE_LIMIT = 64 * 1024;
6
- const REQUEST_TIMEOUT_MS = 30_000;
7
- const METHODS = new Set([
8
- "GET",
9
- "HEAD",
10
- "POST",
11
- "PUT",
12
- "PATCH",
13
- "DELETE",
14
- "OPTIONS",
15
- ]);
16
- const BLOCKED_HEADERS = new Set([
17
- "host",
18
- "cookie",
19
- "cookie2",
20
- "set-cookie",
21
- "set-cookie2",
22
- "connection",
23
- "keep-alive",
24
- "proxy-authenticate",
25
- "proxy-authorization",
26
- "te",
27
- "trailer",
28
- "transfer-encoding",
29
- "upgrade",
30
- "content-length",
31
- "expect",
32
- "forwarded",
33
- "origin",
34
- "referer",
35
- "via",
36
- "authorization",
37
- ]);
38
- const CONTROL_HEADER =
39
- /^(?:sec-|proxy-|x-forwarded-|x-dms-|x-inertia|x-http-method|x-method-override|x-original-|x-rewrite-)/i;
40
- // oxlint-disable-next-line eslint/no-control-regex -- URL paths must reject literal control characters before forwarding.
41
- const UNSAFE_PATH = /[\\#\u0000-\u0020\u007f]/;
42
- // oxlint-disable-next-line eslint/no-control-regex -- Decoded path segments need the same boundary after every decoding pass.
43
- const UNSAFE_DECODED_SEGMENT = /[\\/#\u0000-\u0020\u007f]/;
44
-
45
- function invalidInput() {
46
- throw new RequestBodyError(400, "Invalid tester request");
47
- }
48
-
49
- function validatePathSegment(segment) {
50
- let decoded = segment;
51
- for (;;) {
52
- if ([".", ".."].includes(decoded) || UNSAFE_DECODED_SEGMENT.test(decoded))
53
- return invalidInput();
54
- let next;
55
- try {
56
- next = decodeURIComponent(decoded);
57
- } catch {
58
- return invalidInput();
59
- }
60
- if (next === decoded) break;
61
- decoded = next;
62
- }
63
- }
64
-
65
- function targetUrl(path) {
66
- if (
67
- typeof path !== "string" ||
68
- !path.startsWith("/") ||
69
- path.startsWith("//") ||
70
- UNSAFE_PATH.test(path)
71
- )
72
- return invalidInput();
73
- path.split("?", 1)[0].split("/").forEach(validatePathSegment);
74
- const base = new URL(process.env.DMS_API_BASE_URL);
75
- if (
76
- !["http:", "https:"].includes(base.protocol) ||
77
- base.username ||
78
- base.password
79
- )
80
- throw new Error("Invalid backend configuration");
81
- const target = new URL(path, base);
82
- if (target.origin !== base.origin || target.hash) return invalidInput();
83
- return target;
84
- }
85
-
86
- function filteredHeaders(input, allowAuthorization = false) {
87
- const source = new Headers(input);
88
- const connection = new Set(
89
- (source.get("connection") ?? "")
90
- .toLowerCase()
91
- .split(",")
92
- .map((name) => name.trim()),
93
- );
94
- return new Headers(
95
- [...source].filter(
96
- ([name]) =>
97
- !connection.has(name) &&
98
- !CONTROL_HEADER.test(name) &&
99
- (!BLOCKED_HEADERS.has(name) ||
100
- (allowAuthorization && name === "authorization")),
101
- ),
102
- );
103
- }
104
-
105
- function validateInput(input) {
106
- if (!input || typeof input !== "object" || Array.isArray(input))
107
- return invalidInput();
108
- if (!METHODS.has(input.method) || typeof input.useSession !== "boolean")
109
- return invalidInput();
110
- if (
111
- !input.headers ||
112
- typeof input.headers !== "object" ||
113
- Array.isArray(input.headers)
114
- )
115
- return invalidInput();
116
- if (Object.values(input.headers).some((value) => typeof value !== "string"))
117
- return invalidInput();
118
- if (
119
- input.body !== undefined &&
120
- (typeof input.body !== "string" || ["GET", "HEAD"].includes(input.method))
121
- )
122
- return invalidInput();
123
- try {
124
- new Headers(input.headers);
125
- } catch {
126
- return invalidInput();
127
- }
128
- return targetUrl(input.path);
129
- }
130
-
131
- async function limitedText(response) {
132
- if (!response.body) return "";
133
- const chunks = [];
134
- let size = 0;
135
- for await (const chunk of response.body) {
136
- size += chunk.length;
137
- if (size > RESPONSE_LIMIT) throw new Error("Backend response too large");
138
- chunks.push(chunk);
139
- }
140
- return Buffer.concat(chunks).toString("utf8");
141
- }
142
-
143
- async function isOwner(session, signal) {
144
- const response = await fetch(targetUrl("/api/auth/me"), {
145
- headers: {
146
- authorization: `Bearer ${session.accessToken}`,
147
- "x-antelopejs-namespace": "default",
148
- },
149
- redirect: "manual",
150
- signal,
151
- });
152
- const text = await limitedText(response);
153
- if (!response.ok) return false;
154
- return JSON.parse(text)?.owner === true;
155
- }
156
-
157
- function redact(value, session) {
158
- const secrets = [session.accessToken, session.refreshToken].filter(
159
- (secret) => typeof secret === "string" && secret.length,
160
- );
161
- return secrets.reduce((result, secret) => {
162
- const variants = [
163
- secret,
164
- encodeURIComponent(secret),
165
- JSON.stringify(secret).slice(1, -1),
166
- ];
167
- return variants.reduce(
168
- (text, variant) => text.replaceAll(variant, "[REDACTED]"),
169
- result,
170
- );
171
- }, value);
172
- }
173
-
174
- async function forward(input, target, session, signal) {
175
- const headers = filteredHeaders(input.headers, true);
176
- if (input.useSession && !headers.has("authorization")) {
177
- headers.set("authorization", `Bearer ${session.accessToken}`);
178
- if (!headers.has("x-antelopejs-namespace"))
179
- headers.set("x-antelopejs-namespace", "default");
180
- }
181
- const requestOptions = {
182
- method: input.method,
183
- headers,
184
- redirect: "manual",
185
- signal,
186
- };
187
- if (input.body !== undefined) requestOptions.body = input.body;
188
- const upstream = await fetch(target, requestOptions);
189
- const responseHeaders = Object.fromEntries(
190
- [...filteredHeaders(upstream.headers)].map(([name, value]) => [
191
- redact(name, session),
192
- redact(value, session),
193
- ]),
194
- );
195
- return {
196
- status: upstream.status,
197
- headers: responseHeaders,
198
- body: redact(await limitedText(upstream), session),
199
- };
200
- }
201
-
202
- /** Forwards owner-only API tester requests without returning private session credentials. */
203
- export async function handleTester(request, response) {
204
- response.setHeader("cache-control", "no-store");
205
- if (request.method !== "POST") {
206
- response.setHeader("allow", "POST");
207
- return json(response, 405, { error: "Method Not Allowed" });
208
- }
209
- if (!isSameOrigin(request)) return json(response, 403, CROSS_ORIGIN_ERROR);
210
- try {
211
- const session = readSession(request);
212
- if (!session?.accessToken)
213
- return json(response, 401, { error: "Not authenticated" });
214
- const input = await body(request);
215
- const target = validateInput(input);
216
- const signal = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
217
- if (!(await isOwner(session, signal)))
218
- return json(response, 403, { error: "Forbidden" });
219
- return json(response, 200, await forward(input, target, session, signal));
220
- } catch (error) {
221
- const status = error instanceof RequestBodyError ? error.status : 502;
222
- return json(response, status, {
223
- error:
224
- status === 502 ? "DMS tester request failed" : "Invalid tester request",
225
- });
226
- }
227
- }