@antelopejs/dms-frontend 0.1.7 → 0.1.8
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/README.md +28 -8
- package/dist/config.js +2 -1
- package/dist/layers.js +1 -0
- package/dist/materialize.js +22 -0
- package/package.json +1 -1
- package/templates/vue/server/auth/routes.mjs +49 -8
package/README.md
CHANGED
|
@@ -190,18 +190,38 @@ never sends a token and never receives one: it gets back the same
|
|
|
190
190
|
tenant-assignment outcomes are handled identically.
|
|
191
191
|
|
|
192
192
|
Because the route turns a backend endpoint into a login, it only calls the
|
|
193
|
-
endpoints
|
|
193
|
+
endpoints that were declared for it.
|
|
194
|
+
|
|
195
|
+
Backend modules declare their own. A module registering its frontend names the
|
|
196
|
+
routes that finish its flow, the DMS aggregates them into the frontend manifest
|
|
197
|
+
it serves with the bootstrap secret, and `dev`/`build` write them into the
|
|
198
|
+
workspace as it is materialized:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
// in the backend module
|
|
202
|
+
await AddFrontendModule({
|
|
203
|
+
name: "@antelopejs/dms-saas-frontend-vue",
|
|
204
|
+
sourcePath: path.join(__dirname, "../frontend-vue"),
|
|
205
|
+
renderer: { name: "vue", version: "3" },
|
|
206
|
+
authEstablishEndpoints: ["/api/saas/register/finalize"],
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
A standard deployment therefore needs no configuration at all. The environment
|
|
211
|
+
variable stays as an override, for a backend route no module declares — or one
|
|
212
|
+
declared by a DMS too old to carry the field:
|
|
194
213
|
|
|
195
214
|
```bash
|
|
196
215
|
# .env
|
|
197
|
-
DMS_AUTH_ESTABLISH_ENDPOINTS=/api/
|
|
216
|
+
DMS_AUTH_ESTABLISH_ENDPOINTS=/api/invites/redeem
|
|
198
217
|
```
|
|
199
218
|
|
|
200
|
-
The list is
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
is `
|
|
219
|
+
The effective allow-list is the union of the two. Every entry, from either
|
|
220
|
+
source, is matched verbatim against absolute `/api/…` paths — no prefixes, no
|
|
221
|
+
query strings, no traversal — so no backend route that happens to mint a token
|
|
222
|
+
pair can be turned into a login by a request from the browser. An undeclared
|
|
223
|
+
endpoint is answered `403` and never called. The route is `POST`-only and
|
|
224
|
+
same-origin, like every other auth action.
|
|
205
225
|
|
|
206
226
|
### Workspaces
|
|
207
227
|
|
|
@@ -246,7 +266,7 @@ frontend-module registry drives server and client entries.
|
|
|
246
266
|
| | `DMS_COOKIE_SECURE` | Secure cookies (`true` by default; `ajs dms dev` defaults to `false`) |
|
|
247
267
|
| | `DMS_TRUSTED_PROXY_HOPS` | Number of trusted, rightmost reverse-proxy hops (default `0`) |
|
|
248
268
|
| | `DMS_SESSION_SECRET` | Session cookie encryption key, 32 characters or more (required for login) |
|
|
249
|
-
| | `DMS_AUTH_ESTABLISH_ENDPOINTS` |
|
|
269
|
+
| | `DMS_AUTH_ESTABLISH_ENDPOINTS` | Extra backend endpoints `/auth/establish` may open a session from, on top of those the backend's modules declare (comma-separated, empty by default) |
|
|
250
270
|
| | `DMS_CLIENT_BASE_URL` | Public frontend URL used in generated links and emails |
|
|
251
271
|
|
|
252
272
|
All of these can be set in the project's `.env` instead of the environment; see [Configuration](#configuration).
|
package/dist/config.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.Options = exports.TAILWIND_SOURCE_GLOB = exports.PNPM_LIFECYCLE_SCRIPTS = exports.layerCopyIgnore = exports.LAYER_COPY_BLOCKLIST = exports.FRONTEND_MODULE_ENTRY = exports.LAYERS_SUBDIR = exports.TEMPLATE_FILES = exports.WORKSPACE_DIR_MODE = exports.DEPS_HASH_FILE = exports.DMS_FRONTEND_HOME = void 0;
|
|
6
|
+
exports.Options = exports.TAILWIND_SOURCE_GLOB = exports.PNPM_LIFECYCLE_SCRIPTS = exports.layerCopyIgnore = exports.LAYER_COPY_BLOCKLIST = exports.AUTH_ESTABLISH_FILE = exports.FRONTEND_MODULE_ENTRY = exports.LAYERS_SUBDIR = exports.TEMPLATE_FILES = exports.WORKSPACE_DIR_MODE = exports.DEPS_HASH_FILE = exports.DMS_FRONTEND_HOME = void 0;
|
|
7
7
|
exports.writeSecretBearingFile = writeSecretBearingFile;
|
|
8
8
|
exports.normalizeBootstrapSecret = normalizeBootstrapSecret;
|
|
9
9
|
exports.resolveBootstrapSecret = resolveBootstrapSecret;
|
|
@@ -52,6 +52,7 @@ exports.TEMPLATE_FILES = [
|
|
|
52
52
|
];
|
|
53
53
|
exports.LAYERS_SUBDIR = "frontend-modules";
|
|
54
54
|
exports.FRONTEND_MODULE_ENTRY = "dms.frontend.ts";
|
|
55
|
+
exports.AUTH_ESTABLISH_FILE = "generated-auth-establish.json";
|
|
55
56
|
exports.LAYER_COPY_BLOCKLIST = [
|
|
56
57
|
"node_modules",
|
|
57
58
|
"dist",
|
package/dist/layers.js
CHANGED
package/dist/materialize.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.copyStaticTemplates = copyStaticTemplates;
|
|
|
5
5
|
exports.writeWorkspacePackageJson = writeWorkspacePackageJson;
|
|
6
6
|
exports.materializeLayers = materializeLayers;
|
|
7
7
|
exports.createFrontendModuleRegistry = createFrontendModuleRegistry;
|
|
8
|
+
exports.collectAuthEstablishEndpoints = collectAuthEstablishEndpoints;
|
|
8
9
|
exports.writeFrontendModuleRegistry = writeFrontendModuleRegistry;
|
|
9
10
|
exports.writeDmsMainCss = writeDmsMainCss;
|
|
10
11
|
const node_fs_1 = require("node:fs");
|
|
@@ -107,9 +108,30 @@ function createFrontendModuleRegistry(workspaceDir, layers) {
|
|
|
107
108
|
validateFrontendModuleRegistry(registry, workspaceDir);
|
|
108
109
|
return registry;
|
|
109
110
|
}
|
|
111
|
+
const BACKEND_API_PATH = /^\/api\/[A-Za-z0-9][A-Za-z0-9._~-]*(?:\/[A-Za-z0-9][A-Za-z0-9._~-]*)*$/;
|
|
112
|
+
function collectAuthEstablishEndpoints(layers) {
|
|
113
|
+
const endpoints = new Set();
|
|
114
|
+
for (const layer of layers) {
|
|
115
|
+
for (const endpoint of layer.authEstablishEndpoints ?? []) {
|
|
116
|
+
if (typeof endpoint === "string" && BACKEND_API_PATH.test(endpoint)) {
|
|
117
|
+
endpoints.add(endpoint);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
console.warn(`⚠ Ignoring malformed authEstablishEndpoints entry ${JSON.stringify(endpoint)} ` +
|
|
121
|
+
`declared by ${layer.packageName ?? layer.path}: expected an absolute ` +
|
|
122
|
+
"backend API path under /api/ with no query string.");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return [...endpoints];
|
|
126
|
+
}
|
|
127
|
+
function writeAuthEstablishEndpoints(workspaceDir, layers) {
|
|
128
|
+
const endpoints = collectAuthEstablishEndpoints(layers);
|
|
129
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, config_1.AUTH_ESTABLISH_FILE), `${JSON.stringify({ endpoints }, null, 2)}\n`);
|
|
130
|
+
}
|
|
110
131
|
function writeFrontendModuleRegistry(workspaceDir, layers) {
|
|
111
132
|
const registry = createFrontendModuleRegistry(workspaceDir, layers);
|
|
112
133
|
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspaceDir, "generated-frontend-modules.json"), `${JSON.stringify(registry, null, 2)}\n`);
|
|
134
|
+
writeAuthEstablishEndpoints(workspaceDir, layers);
|
|
113
135
|
writeFrontendTypePaths(workspaceDir, registry);
|
|
114
136
|
writeFrontendModuleLoader(workspaceDir, registry);
|
|
115
137
|
writeLocaleMessages(workspaceDir, registry);
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import { backend, body, json, UpstreamError } from "./backend.mjs";
|
|
3
4
|
import { CROSS_ORIGIN_ERROR, isSameOrigin } from "./client-ip.mjs";
|
|
4
5
|
import {
|
|
@@ -28,6 +29,36 @@ const PASSTHROUGH = new Map([
|
|
|
28
29
|
const BACKEND_API_PATH =
|
|
29
30
|
/^\/api\/[A-Za-z0-9][A-Za-z0-9._~-]*(?:\/[A-Za-z0-9][A-Za-z0-9._~-]*)*$/;
|
|
30
31
|
|
|
32
|
+
// Written at the workspace root by the loader, from the endpoints each backend
|
|
33
|
+
// module declared in the frontend manifest. This file sits two directories up.
|
|
34
|
+
const DECLARED_ENDPOINTS_FILE = new URL(
|
|
35
|
+
"../../generated-auth-establish.json",
|
|
36
|
+
import.meta.url,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
let declaredEndpoints;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Backend endpoints the modules of this deployment declared as session-opening.
|
|
43
|
+
*
|
|
44
|
+
* Read from the workspace rather than from the backend: the file is generated
|
|
45
|
+
* when the workspace is materialized, so the server needs no backend round trip
|
|
46
|
+
* — and no operator — to know which module flows may end on a login. A
|
|
47
|
+
* workspace built from a DMS that predates the declaration simply has no file,
|
|
48
|
+
* and the list falls back to whatever the environment names.
|
|
49
|
+
*
|
|
50
|
+
* @param file Generated file to read
|
|
51
|
+
* @returns The declared paths, unfiltered
|
|
52
|
+
*/
|
|
53
|
+
export function readDeclaredEstablishEndpoints(file = DECLARED_ENDPOINTS_FILE) {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
56
|
+
return Array.isArray(parsed?.endpoints) ? parsed.endpoints : [];
|
|
57
|
+
} catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
31
62
|
export function publicSession(session) {
|
|
32
63
|
if (!session) return {};
|
|
33
64
|
return {
|
|
@@ -88,20 +119,30 @@ async function establish(request, response, endpoint) {
|
|
|
88
119
|
/**
|
|
89
120
|
* Backend endpoints this deployment lets a module open a session from.
|
|
90
121
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
122
|
+
* The union of what the backend's modules declared — the standard case, which
|
|
123
|
+
* needs no configuration — and what the environment adds on top, which is how
|
|
124
|
+
* an operator extends the list for a route no module owns. Everything else
|
|
125
|
+
* stays refused, so no backend endpoint that happens to mint a token pair can
|
|
126
|
+
* be turned into a login by a request from the browser.
|
|
94
127
|
*
|
|
95
|
-
* @param declaration Comma-separated absolute backend paths
|
|
96
|
-
* @
|
|
128
|
+
* @param declaration Comma-separated absolute backend paths from the environment
|
|
129
|
+
* @param declared Paths declared by the backend's frontend modules
|
|
130
|
+
* @returns The well-formed backend API paths, without duplicates
|
|
97
131
|
*/
|
|
98
132
|
export function allowedEstablishEndpoints(
|
|
99
133
|
declaration = process.env.DMS_AUTH_ESTABLISH_ENDPOINTS,
|
|
134
|
+
declared = (declaredEndpoints ??= readDeclaredEstablishEndpoints()),
|
|
100
135
|
) {
|
|
101
|
-
|
|
136
|
+
const fromEnvironment = (declaration ?? "")
|
|
102
137
|
.split(",")
|
|
103
|
-
.map((entry) => entry.trim())
|
|
104
|
-
|
|
138
|
+
.map((entry) => entry.trim());
|
|
139
|
+
return [
|
|
140
|
+
...new Set(
|
|
141
|
+
[...declared, ...fromEnvironment].filter(
|
|
142
|
+
(entry) => typeof entry === "string" && BACKEND_API_PATH.test(entry),
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
];
|
|
105
146
|
}
|
|
106
147
|
|
|
107
148
|
/**
|