alinea 2.0.0-preview.3 → 2.0.0-preview.5
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/dist/.init/content/pages/welcome.json +1 -1
- package/dist/.init/src/cms.ts +1 -4
- package/dist/LICENSES.md +148 -94
- package/dist/adapter/next/DevRemote.js +10 -2
- package/dist/adapter/next/cms.js +5 -1
- package/dist/adapter/next/handler.js +35 -6
- package/dist/adapter/next/previews.js +7 -0
- package/dist/adapter/next/syncCheck.d.ts +12 -0
- package/dist/adapter/next/syncCheck.js +59 -0
- package/dist/adapter/next/with-alinea.d.ts +0 -8
- package/dist/adapter/next/with-alinea.js +43 -19
- package/dist/adapter/vite/plugin.js +9 -4
- package/dist/backend/Handler.js +120 -7
- package/dist/backend/api/BasicAuth.d.ts +1 -1
- package/dist/backend/api/BasicAuth.js +14 -14
- package/dist/backend/api/OAuth2.d.ts +3 -3
- package/dist/backend/api/OAuth2.js +7 -4
- package/dist/backend/api/ProxyMedia.d.ts +3 -0
- package/dist/backend/api/ProxyMedia.js +39 -0
- package/dist/backend/api/S3Uploads.d.ts +1 -1
- package/dist/backend/router/Router.js +2 -0
- package/dist/backend/util/Syncable.js +1 -1
- package/dist/bundled.d.ts +260 -95
- package/dist/chunks/{chunk-HFBZMKIN.js → chunk-A6M3PNJT.js} +1 -1
- package/dist/chunks/{chunk-Y7XOMJOC.js → chunk-CFNRVI3Y.js} +220 -247
- package/dist/chunks/chunk-D6UJHBJJ.js +183 -0
- package/dist/chunks/chunk-TIJTT5ZI.js +228 -0
- package/dist/cli/Generate.js +1 -3
- package/dist/cli/Serve.js +3 -2
- package/dist/cli/bin.js +1 -1
- package/dist/cli/generate/GenerateDashboard.js +1 -0
- package/dist/cli/serve/CreateLocalServer.d.ts +1 -1
- package/dist/cli/serve/CreateLocalServer.js +42 -34
- package/dist/cli/serve/LocalAuth.js +1 -1
- package/dist/cli/static/init/cms.js +1 -4
- package/dist/cli/util/ContentType.d.ts +1 -0
- package/dist/cli/util/ContentType.js +36 -0
- package/dist/cloud/AuthResult.d.ts +2 -0
- package/dist/cloud/CloudHandshake.d.ts +13 -0
- package/dist/cloud/CloudHandshake.js +121 -0
- package/dist/cloud/CloudRemote.d.ts +4 -1
- package/dist/cloud/CloudRemote.js +40 -9
- package/dist/components/Calendar.js +7 -7
- package/dist/components/Checkbox.js +2 -2
- package/dist/components/ColorSwatchPicker.js +2 -2
- package/dist/components/ComboBox.js +12 -12
- package/dist/components/DateField.js +4 -4
- package/dist/components/DatePicker.js +4 -4
- package/dist/components/DateRangePicker.js +8 -8
- package/dist/components/Disclosure.js +7 -7
- package/dist/components/Label.js +5 -5
- package/dist/components/List.d.ts +1 -1
- package/dist/components/List.js +29 -28
- package/dist/components/Menu.js +2 -2
- package/dist/components/MultipleSelect.js +25 -25
- package/dist/components/NumberField.js +3 -3
- package/dist/components/ProgressCircle.js +4 -4
- package/dist/components/SearchField.js +6 -6
- package/dist/components/Select.js +7 -7
- package/dist/components/Surface.js +3 -3
- package/dist/components/Table.js +6 -6
- package/dist/components/Tabs.js +2 -2
- package/dist/components/TagGroup.js +2 -2
- package/dist/components/TextField.js +4 -4
- package/dist/components/TimeField.js +1 -1
- package/dist/components/Toggle.d.ts +1 -1
- package/dist/components/Toolbar.js +1 -1
- package/dist/components/Tree.js +7 -7
- package/dist/core/Config.d.ts +10 -3
- package/dist/core/Config.js +16 -1
- package/dist/core/Connection.d.ts +7 -4
- package/dist/core/Entry.d.ts +6 -0
- package/dist/core/Entry.js +10 -0
- package/dist/core/Graph.d.ts +2 -2
- package/dist/core/Id.d.ts +1 -0
- package/dist/core/Id.js +8 -0
- package/dist/core/Role.js +16 -14
- package/dist/core/TextDoc.d.ts +2 -0
- package/dist/core/TextDoc.js +1 -0
- package/dist/core/Workspace.d.ts +6 -5
- package/dist/core/db/EntryIndex.d.ts +3 -0
- package/dist/core/db/EntryIndex.js +62 -0
- package/dist/core/db/EntryTransaction.js +33 -12
- package/dist/core/db/EntryUrlConflictError.js +1 -1
- package/dist/core/db/LinkResolver.d.ts +8 -2
- package/dist/core/db/LinkResolver.js +27 -5
- package/dist/core/db/Operation.js +4 -3
- package/dist/core/field/RichTextField.js +16 -10
- package/dist/core/media/MediaLocation.d.ts +9 -15
- package/dist/core/media/MediaLocation.js +29 -19
- package/dist/core/media/MediaTypes.js +1 -2
- package/dist/dashboard/App.js +30 -16
- package/dist/dashboard/StoryProvider.js +2 -2
- package/dist/dashboard/app/AccessDenied.js +4 -4
- package/dist/dashboard/app/ActivityStatus.js +22 -21
- package/dist/dashboard/app/AppShell.js +2 -2
- package/dist/dashboard/app/AppearanceToggle.d.ts +1 -0
- package/dist/dashboard/app/AppearanceToggle.js +43 -0
- package/dist/dashboard/app/AuthView.d.ts +1 -1
- package/dist/dashboard/app/AuthView.js +19 -21
- package/dist/dashboard/app/CompactField.js +13 -13
- package/dist/dashboard/app/DashboardErrorBoundary.js +26 -14
- package/dist/dashboard/app/DashboardLayout.js +15 -8
- package/dist/dashboard/app/DashboardMeta.d.ts +2 -1
- package/dist/dashboard/app/DashboardMeta.js +20 -5
- package/dist/dashboard/app/EntryFields.d.ts +3 -1
- package/dist/dashboard/app/EntryFields.js +17 -14
- package/dist/dashboard/app/EntryHeader.js +134 -102
- package/dist/dashboard/app/EntryHeaderActions.d.ts +21 -4
- package/dist/dashboard/app/EntryHeaderActions.js +48 -27
- package/dist/dashboard/app/EntryReferences.js +16 -16
- package/dist/dashboard/app/EntrySidebar.js +24 -24
- package/dist/dashboard/app/EntrySidebarPreview.js +16 -15
- package/dist/dashboard/app/EntryTranslationBanner.d.ts +3 -1
- package/dist/dashboard/app/EntryTranslationBanner.js +24 -21
- package/dist/dashboard/app/Explorer.d.ts +4 -2
- package/dist/dashboard/app/Explorer.js +70 -66
- package/dist/dashboard/app/ExplorerCards.js +49 -49
- package/dist/dashboard/app/ExplorerFileCard.js +18 -18
- package/dist/dashboard/app/ExplorerList.js +21 -22
- package/dist/dashboard/app/ExplorerModal.js +1 -1
- package/dist/dashboard/app/ExplorerPickerContent.js +2 -2
- package/dist/dashboard/app/ExplorerTable.js +68 -68
- package/dist/dashboard/app/ImagePicker.js +7 -7
- package/dist/dashboard/app/LinkPicker.js +17 -22
- package/dist/dashboard/app/LocaleMenu.d.ts +7 -0
- package/dist/dashboard/app/LocaleMenu.js +31 -17
- package/dist/dashboard/app/ReadOnlyBadge.d.ts +4 -0
- package/dist/dashboard/app/ReadOnlyBadge.js +33 -0
- package/dist/dashboard/app/SidebarLayout.js +4 -4
- package/dist/dashboard/app/SidebarTree.d.ts +3 -6
- package/dist/dashboard/app/SidebarTree.js +66 -61
- package/dist/dashboard/app/WorkspaceMenu.d.ts +10 -3
- package/dist/dashboard/app/WorkspaceMenu.js +87 -66
- package/dist/dashboard/app/WorkspaceMenu.stories.d.ts +2 -0
- package/dist/dashboard/app/WorkspaceRoots.d.ts +3 -1
- package/dist/dashboard/app/WorkspaceRoots.js +88 -113
- package/dist/dashboard/app/editor/FileEditor.d.ts +2 -3
- package/dist/dashboard/app/editor/FileEditor.js +35 -38
- package/dist/dashboard/app/editor/FilePreview.js +3 -3
- package/dist/dashboard/app/editor/MediaAltField.view.js +4 -4
- package/dist/dashboard/app/modals/CreateEntry.js +10 -10
- package/dist/dashboard/app/pages/EntryPage.js +85 -44
- package/dist/dashboard/app/pages/RootPage.js +4 -4
- package/dist/dashboard/app/pages/SplashPage.d.ts +3 -0
- package/dist/dashboard/app/pages/SplashPage.js +414 -0
- package/dist/dashboard/app/pages/UsersPage.js +38 -38
- package/dist/dashboard/app/ui/DashboardModal.js +16 -16
- package/dist/dashboard/app/ui/Rail.js +2 -2
- package/dist/dashboard/app/ui/Sidebar.js +2 -2
- package/dist/dashboard/atoms/ReactiveNode.js +1 -1
- package/dist/dashboard/atoms/activity.d.ts +0 -1
- package/dist/dashboard/atoms/activity.js +3 -5
- package/dist/dashboard/atoms/auth.d.ts +2 -0
- package/dist/dashboard/atoms/auth.js +6 -2
- package/dist/dashboard/atoms/config.js +1 -1
- package/dist/dashboard/atoms/core.js +2 -2
- package/dist/dashboard/atoms/create.js +1 -1
- package/dist/dashboard/atoms/dashboard.js +2 -2
- package/dist/dashboard/atoms/editor.d.ts +2 -1
- package/dist/dashboard/atoms/editor.js +5 -3
- package/dist/dashboard/atoms/entry.d.ts +2 -0
- package/dist/dashboard/atoms/entry.js +34 -16
- package/dist/dashboard/atoms/explorer.d.ts +5 -6
- package/dist/dashboard/atoms/explorer.js +68 -31
- package/dist/dashboard/atoms/graph.d.ts +1 -0
- package/dist/dashboard/atoms/graph.js +7 -2
- package/dist/dashboard/atoms/link.d.ts +1 -1
- package/dist/dashboard/atoms/link.js +41 -57
- package/dist/dashboard/atoms/nav.d.ts +4 -3
- package/dist/dashboard/atoms/nav.js +10 -5
- package/dist/dashboard/atoms/preview.js +1 -1
- package/dist/dashboard/atoms/root.d.ts +7 -2
- package/dist/dashboard/atoms/root.js +53 -18
- package/dist/dashboard/atoms/upload.d.ts +1 -0
- package/dist/dashboard/atoms/upload.js +3 -2
- package/dist/dashboard/atoms/user.d.ts +1 -1
- package/dist/dashboard/atoms/user.js +8 -6
- package/dist/dashboard/atoms/utils.js +2 -2
- package/dist/dashboard/boot/Boot.js +2 -4
- package/dist/dashboard/boot/DashboardWorker.js +35 -29
- package/dist/dashboard/editor/UseField.js +6 -6
- package/dist/dashboard/fixture/cms.d.ts +12 -0
- package/dist/dashboard/fixture/cms.js +54 -6
- package/dist/dashboard/hook/UseGraph.js +4 -4
- package/dist/dashboard/hook/UseRoot.js +4 -4
- package/dist/dashboard/hook/UseSaveShortcut.d.ts +1 -0
- package/dist/dashboard/hook/UseSaveShortcut.js +24 -0
- package/dist/dashboard/hooks.d.ts +1 -1
- package/dist/dashboard/hooks.js +28 -19
- package/dist/field/code/CodeField.view.js +12 -12
- package/dist/field/link/ImageLink.js +1 -3
- package/dist/field/link/LinkField.view.js +97 -71
- package/dist/field/list/ListField.view.js +23 -23
- package/dist/field/localiser/LocalisedField.view.js +76 -5
- package/dist/field/metadata/MetadataField.d.ts +3 -3
- package/dist/field/metadata/MetadataField.view.js +27 -20
- package/dist/field/object/ObjectField.view.js +11 -4
- package/dist/field/richtext/PickTextLink.js +11 -8
- package/dist/field/richtext/ReferenceLink.d.ts +2 -0
- package/dist/field/richtext/ReferenceLink.js +5 -0
- package/dist/field/richtext/RichTextBlock.js +6 -6
- package/dist/field/richtext/RichTextBlocks.js +1 -1
- package/dist/field/richtext/RichTextExtensions.js +22 -22
- package/dist/field/richtext/RichTextField.view.js +9 -9
- package/dist/field/richtext/RichTextInsertMenu.js +3 -3
- package/dist/field/richtext/RichTextState.js +1 -1
- package/dist/field/richtext/extensions/Link.d.ts +1 -0
- package/dist/field/richtext/extensions/Link.js +1 -0
- package/dist/field/text/TextField.view.js +2 -2
- package/dist/field/time/TimeField.d.ts +0 -2
- package/dist/field/time/TimeField.view.js +1 -1
- package/dist/index.css +459 -66
- package/dist/next.cjs +96 -19
- package/dist/picker/entry/EntryPicker.js +10 -12
- package/dist/picker/entry/EntryReference.d.ts +2 -0
- package/dist/picker/entry/EntryReference.js +1 -0
- package/dist/preview/RegisterPreview.d.ts +1 -1
- package/dist/preview/RegisterPreview.js +4 -6
- package/dist/preview/react.d.ts +5 -1
- package/dist/preview/react.js +12 -9
- package/dist/types.d.ts +0 -1
- package/package.json +1 -1
- package/dist/chunks/chunk-STAJCW4Z.js +0 -322
- package/dist/chunks/chunk-U4VOHNR2.js +0 -210
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "../../chunks/chunk-CQ52GEHY.js";
|
|
2
2
|
|
|
3
3
|
// src/adapter/next/with-alinea.ts
|
|
4
|
+
import { join } from "alinea/core/util/Paths";
|
|
4
5
|
import { readFileSync } from "node:fs";
|
|
5
6
|
import { createRequire } from "node:module";
|
|
6
7
|
import { resolve } from "node:path";
|
|
@@ -26,23 +27,13 @@ function withAlinea(config = {}) {
|
|
|
26
27
|
} catch {
|
|
27
28
|
console.warn("Alinea could not determine Next.js version, assuming 15+");
|
|
28
29
|
}
|
|
29
|
-
const imagesConfig = config.images ?? {};
|
|
30
|
-
const remotePatterns = [
|
|
31
|
-
...imagesConfig.remotePatterns ?? [],
|
|
32
|
-
{
|
|
33
|
-
protocol: "https",
|
|
34
|
-
hostname: "uploads.alinea.cloud"
|
|
35
|
-
}
|
|
36
|
-
];
|
|
37
|
-
const images = {
|
|
38
|
-
...imagesConfig,
|
|
39
|
-
remotePatterns
|
|
40
|
-
};
|
|
41
30
|
const redirects = adminPath ? createRedirects(config, adminPath) : config.redirects;
|
|
42
|
-
const rewrites = adminPath ? createRewrites(config, adminPath) : config.rewrites;
|
|
31
|
+
const rewrites = adminPath ? createRewrites(config, adminPath, settings.handlerUrl) : config.rewrites;
|
|
32
|
+
const images = adminPath ? createImages(config, adminPath) : config.images;
|
|
43
33
|
const env = settings ? {
|
|
44
34
|
...config.env,
|
|
45
|
-
ALINEA_ADMIN_PATH: settings.adminPath
|
|
35
|
+
ALINEA_ADMIN_PATH: settings.adminPath,
|
|
36
|
+
ALINEA_HANDLER_URL: settings.handlerUrl
|
|
46
37
|
} : config.env;
|
|
47
38
|
if (nextVersion < 15)
|
|
48
39
|
return {
|
|
@@ -54,9 +45,9 @@ function withAlinea(config = {}) {
|
|
|
54
45
|
"@alinea/generated"
|
|
55
46
|
]
|
|
56
47
|
},
|
|
57
|
-
images,
|
|
58
48
|
redirects,
|
|
59
49
|
rewrites,
|
|
50
|
+
images,
|
|
60
51
|
env
|
|
61
52
|
};
|
|
62
53
|
return {
|
|
@@ -65,12 +56,32 @@ function withAlinea(config = {}) {
|
|
|
65
56
|
...config.serverExternalPackages ?? [],
|
|
66
57
|
"@alinea/generated"
|
|
67
58
|
],
|
|
68
|
-
images,
|
|
69
59
|
redirects,
|
|
70
60
|
rewrites,
|
|
61
|
+
images,
|
|
71
62
|
env
|
|
72
63
|
};
|
|
73
64
|
}
|
|
65
|
+
function createImages(config, adminPath) {
|
|
66
|
+
const filePattern = `${adminPath}/file/**`;
|
|
67
|
+
const localPatterns = config.images?.localPatterns;
|
|
68
|
+
if (!localPatterns)
|
|
69
|
+
return {
|
|
70
|
+
...config.images,
|
|
71
|
+
localPatterns: [{ pathname: filePattern }, { pathname: "/**" }]
|
|
72
|
+
};
|
|
73
|
+
if (localPatterns.some((pattern) => pattern.pathname === filePattern))
|
|
74
|
+
return config.images;
|
|
75
|
+
return {
|
|
76
|
+
...config.images,
|
|
77
|
+
localPatterns: [
|
|
78
|
+
...localPatterns,
|
|
79
|
+
{
|
|
80
|
+
pathname: filePattern
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
};
|
|
84
|
+
}
|
|
74
85
|
function createRedirects(config, adminPath) {
|
|
75
86
|
const dev = process.env.ALINEA_DEV_SERVER;
|
|
76
87
|
if (!dev) return config.redirects;
|
|
@@ -91,7 +102,7 @@ var emptyRewrites = {
|
|
|
91
102
|
afterFiles: [],
|
|
92
103
|
fallback: []
|
|
93
104
|
};
|
|
94
|
-
function createRewrites(config, adminPath) {
|
|
105
|
+
function createRewrites(config, adminPath, handlerUrl) {
|
|
95
106
|
return async () => {
|
|
96
107
|
const devServer = process.env.ALINEA_DEV_SERVER;
|
|
97
108
|
const nodeEnv = process.env.NODE_ENV;
|
|
@@ -112,6 +123,10 @@ function createRewrites(config, adminPath) {
|
|
|
112
123
|
...rewrites,
|
|
113
124
|
beforeFiles: [
|
|
114
125
|
...rewrites.beforeFiles,
|
|
126
|
+
{
|
|
127
|
+
source: `${adminPath}/file/:file*`,
|
|
128
|
+
destination: `${devServer}/api?file=:file*&delivery=proxy`
|
|
129
|
+
},
|
|
115
130
|
{
|
|
116
131
|
source: `${adminPath}/:path*`,
|
|
117
132
|
destination: `${devServer}${adminPath}/:path*`
|
|
@@ -121,6 +136,14 @@ function createRewrites(config, adminPath) {
|
|
|
121
136
|
}
|
|
122
137
|
return {
|
|
123
138
|
...rewrites,
|
|
139
|
+
beforeFiles: [
|
|
140
|
+
...rewrites.beforeFiles,
|
|
141
|
+
{
|
|
142
|
+
source: `${adminPath}/file/:file*`,
|
|
143
|
+
// Next's internal image optimizer does not follow redirects.
|
|
144
|
+
destination: `${handlerUrl}?file=:file*&delivery=proxy`
|
|
145
|
+
}
|
|
146
|
+
],
|
|
124
147
|
afterFiles: [
|
|
125
148
|
...rewrites.afterFiles,
|
|
126
149
|
{
|
|
@@ -135,11 +158,12 @@ function resolveSettings(config) {
|
|
|
135
158
|
const adminPath = config.env?.ALINEA_ADMIN_PATH ?? process.env.ALINEA_ADMIN_PATH;
|
|
136
159
|
if (!adminPath) return;
|
|
137
160
|
return {
|
|
138
|
-
adminPath: normalizeBasePath(adminPath)
|
|
161
|
+
adminPath: normalizeBasePath(adminPath),
|
|
162
|
+
handlerUrl: config.env?.ALINEA_HANDLER_URL ?? process.env.ALINEA_HANDLER_URL ?? "/api/cms"
|
|
139
163
|
};
|
|
140
164
|
}
|
|
141
165
|
function normalizeBasePath(value) {
|
|
142
|
-
return
|
|
166
|
+
return join("/", value, ".");
|
|
143
167
|
}
|
|
144
168
|
export {
|
|
145
169
|
createCMS,
|
|
@@ -3,6 +3,7 @@ import "../../chunks/chunk-CQ52GEHY.js";
|
|
|
3
3
|
// src/adapter/vite/plugin.ts
|
|
4
4
|
import { nodeHandler } from "alinea/backend/router/NodeHandler";
|
|
5
5
|
import { createDevServer } from "alinea/cli/serve/DevServer";
|
|
6
|
+
import { Config } from "alinea/core/Config";
|
|
6
7
|
function createFallbackUrl(server) {
|
|
7
8
|
const address = server.httpServer?.address();
|
|
8
9
|
const protocol = server.config.server.https ? "https" : "http";
|
|
@@ -28,19 +29,23 @@ function waitForServerUrl(server) {
|
|
|
28
29
|
server.httpServer?.once("listening", resolveUrl);
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
|
-
function isAlineaRequest(request) {
|
|
32
|
+
function isAlineaRequest(request, adminPath) {
|
|
32
33
|
const url = new URL(request.url);
|
|
33
|
-
return url.pathname ===
|
|
34
|
+
return url.pathname === adminPath || url.pathname.startsWith(`${adminPath}/`) || url.pathname === "/api" || url.pathname.startsWith("/api/") || url.pathname === "/~dev" || url.pathname.startsWith("/~dev/") || url.search.startsWith("?/upload");
|
|
34
35
|
}
|
|
35
36
|
function alineaPlugin(dir) {
|
|
36
37
|
let devServer;
|
|
38
|
+
let adminPath = "/admin";
|
|
37
39
|
return {
|
|
38
40
|
name: "alinea-plugin",
|
|
39
41
|
configureServer(server) {
|
|
40
42
|
const serverUrl = waitForServerUrl(server);
|
|
41
43
|
const devServerPromise = createDevServer(dir, {
|
|
42
44
|
cmd: "dev",
|
|
43
|
-
dashboardUrl: serverUrl
|
|
45
|
+
dashboardUrl: serverUrl,
|
|
46
|
+
onAfterGenerate(_, config) {
|
|
47
|
+
adminPath = Config.adminPath(config);
|
|
48
|
+
}
|
|
44
49
|
}).then((result) => {
|
|
45
50
|
devServer = result;
|
|
46
51
|
return result;
|
|
@@ -50,7 +55,7 @@ function alineaPlugin(dir) {
|
|
|
50
55
|
});
|
|
51
56
|
server.middlewares.use(
|
|
52
57
|
nodeHandler(async (request) => {
|
|
53
|
-
if (!isAlineaRequest(request)) return void 0;
|
|
58
|
+
if (!isAlineaRequest(request, adminPath)) return void 0;
|
|
54
59
|
const localServer = await devServerPromise;
|
|
55
60
|
return localServer.handle(request);
|
|
56
61
|
})
|
package/dist/backend/Handler.js
CHANGED
|
@@ -17,16 +17,20 @@ import {
|
|
|
17
17
|
BLOB_SEQUENCE_CONTENT_TYPE,
|
|
18
18
|
encodeBlobSequence
|
|
19
19
|
} from "alinea/core/BlobTransport";
|
|
20
|
+
import { Config } from "alinea/core/Config";
|
|
20
21
|
import { developmentKeyHeader } from "alinea/core/Connection";
|
|
21
22
|
import { ErrorCode, HttpError } from "alinea/core/HttpError";
|
|
23
|
+
import { MediaLocation } from "alinea/core/media/MediaLocation";
|
|
22
24
|
import { assertUploadSize } from "alinea/core/media/UploadLimits";
|
|
23
25
|
import { Permission, Policy } from "alinea/core/Role";
|
|
24
26
|
import { getScope } from "alinea/core/Scope";
|
|
25
27
|
import { ShaMismatchError } from "alinea/core/source/ShaMismatchError";
|
|
26
28
|
import { base64 } from "alinea/core/util/Encoding";
|
|
27
29
|
import { isRecord } from "alinea/core/util/Objects";
|
|
30
|
+
import { isAbsolute, normalize } from "alinea/core/util/Paths";
|
|
28
31
|
import { InvalidCredentialsError, MissingCredentialsError } from "./Auth.js";
|
|
29
32
|
import { HandleAction } from "./HandleAction.js";
|
|
33
|
+
import { isRedirectStatus, proxyMediaUrl } from "./api/ProxyMedia.js";
|
|
30
34
|
import { applyPreview, decodePreviewRequest } from "./resolver/ParsePreview.js";
|
|
31
35
|
import { compressResponse } from "./router/Router.js";
|
|
32
36
|
import { createThrottledSync } from "./util/Syncable.js";
|
|
@@ -59,12 +63,89 @@ function createHandler({
|
|
|
59
63
|
let cnx = remote(context);
|
|
60
64
|
let userCtx;
|
|
61
65
|
if (auth) {
|
|
62
|
-
return cnx.authenticate(request, {
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
return await cnx.authenticate(request, {
|
|
67
|
+
async authenticated(user) {
|
|
68
|
+
try {
|
|
69
|
+
return {
|
|
70
|
+
user: await cnx.enrichUser(user),
|
|
71
|
+
capabilities: await cnx.capabilities()
|
|
72
|
+
};
|
|
73
|
+
} catch (cause) {
|
|
74
|
+
if (cause instanceof HttpError) throw cause;
|
|
75
|
+
throw new HttpError(500, "Failed to complete authentication", {
|
|
76
|
+
cause
|
|
77
|
+
});
|
|
78
|
+
}
|
|
65
79
|
}
|
|
66
80
|
});
|
|
67
81
|
}
|
|
82
|
+
if (!params.has("action") && params.has("file") && (request.method === "GET" || request.method === "HEAD")) {
|
|
83
|
+
const file = string(params.get("file"));
|
|
84
|
+
const proxy = params.get("delivery") === "proxy";
|
|
85
|
+
const normalized = normalize(file);
|
|
86
|
+
if (isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../"))
|
|
87
|
+
throw new HttpError(400, "Invalid file path");
|
|
88
|
+
await periodicSync(cnx);
|
|
89
|
+
const requestedUrl = Config.filePathname(cms.config, normalized);
|
|
90
|
+
const entry = local.index.findByUrl(
|
|
91
|
+
requestedUrl,
|
|
92
|
+
(entry2) => entry2.type === "MediaFile" && entry2.status === "published" && entry2.main
|
|
93
|
+
);
|
|
94
|
+
if (!entry) return new Response("Not found", { status: 404 });
|
|
95
|
+
if (entry.url !== requestedUrl && !proxy)
|
|
96
|
+
return new Response(null, {
|
|
97
|
+
status: 308,
|
|
98
|
+
headers: { location: entry.url }
|
|
99
|
+
});
|
|
100
|
+
const wasBuilt = local.index.initialSync?.has(entry.filePath) ?? false;
|
|
101
|
+
const previewUrl = entry.data.previewUrl;
|
|
102
|
+
const location = entry.data.location;
|
|
103
|
+
if (typeof location !== "string")
|
|
104
|
+
return new Response("Not found", { status: 404 });
|
|
105
|
+
const previewSource = typeof previewUrl === "string" && previewUrl ? new URL(previewUrl, request.url).href : void 0;
|
|
106
|
+
async function proxyMedia(source2, cacheControl, isAllowed) {
|
|
107
|
+
const previewEntryId = uploadPreviewEntryId(
|
|
108
|
+
source2,
|
|
109
|
+
context.handlerUrl
|
|
110
|
+
);
|
|
111
|
+
let response;
|
|
112
|
+
if (previewEntryId && cnx.previewUpload) {
|
|
113
|
+
response = await cnx.previewUpload(previewEntryId);
|
|
114
|
+
} else {
|
|
115
|
+
response = await proxyMediaUrl(
|
|
116
|
+
request,
|
|
117
|
+
new URL(source2, request.url),
|
|
118
|
+
isAllowed
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return mediaResponse(request, response, cacheControl);
|
|
122
|
+
}
|
|
123
|
+
if (!wasBuilt && previewSource) {
|
|
124
|
+
if (proxy) return proxyMedia(previewSource, "private, no-store");
|
|
125
|
+
return redirectFile(previewSource, "private, no-store");
|
|
126
|
+
}
|
|
127
|
+
const source = MediaLocation.sourceUrl(
|
|
128
|
+
cms.config,
|
|
129
|
+
entry.workspace,
|
|
130
|
+
location
|
|
131
|
+
);
|
|
132
|
+
if (!source || source === requestedUrl)
|
|
133
|
+
return new Response("Not found", { status: 404 });
|
|
134
|
+
if (!proxy) return redirectFile(source, "public, max-age=60");
|
|
135
|
+
const configuredBase = Config.baseUrl(
|
|
136
|
+
cms.config,
|
|
137
|
+
context.isDev ? "development" : "production"
|
|
138
|
+
);
|
|
139
|
+
const deliveryBase = configuredBase ?? context.handlerUrl;
|
|
140
|
+
const sourceUrl = new URL(source, deliveryBase);
|
|
141
|
+
const deliveryOrigin = new URL(deliveryBase).origin;
|
|
142
|
+
const handlerPath = context.handlerUrl.pathname;
|
|
143
|
+
return proxyMedia(
|
|
144
|
+
sourceUrl.href,
|
|
145
|
+
"public, max-age=60",
|
|
146
|
+
(url2) => url2.origin === deliveryOrigin && url2.pathname !== handlerPath && !url2.pathname.startsWith(`${handlerPath}/`)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
68
149
|
const action = params.get("action");
|
|
69
150
|
const expectJson = () => {
|
|
70
151
|
const acceptsJson = request.headers.get("accept")?.includes("application/json");
|
|
@@ -72,10 +153,7 @@ function createHandler({
|
|
|
72
153
|
};
|
|
73
154
|
if (action === HandleAction.Capabilities && request.method === "GET") {
|
|
74
155
|
expectJson();
|
|
75
|
-
|
|
76
|
-
return Response.json(
|
|
77
|
-
capabilities ? await capabilities() : { users: typeof cnx.listUsers === "function" }
|
|
78
|
-
);
|
|
156
|
+
return Response.json(await cnx.capabilities());
|
|
79
157
|
}
|
|
80
158
|
if (action === HandleAction.Upload && request.method === "GET") {
|
|
81
159
|
const entryId = url.searchParams.get("entryId");
|
|
@@ -340,6 +418,41 @@ function createHandler({
|
|
|
340
418
|
}
|
|
341
419
|
};
|
|
342
420
|
}
|
|
421
|
+
function redirectFile(location, cacheControl) {
|
|
422
|
+
return new Response(null, {
|
|
423
|
+
status: 307,
|
|
424
|
+
headers: { "cache-control": cacheControl, location }
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function uploadPreviewEntryId(previewSource, handlerUrl) {
|
|
428
|
+
const previewUrl = new URL(previewSource);
|
|
429
|
+
if (previewUrl.origin !== handlerUrl.origin || previewUrl.pathname !== handlerUrl.pathname || previewUrl.searchParams.get("action") !== HandleAction.Upload)
|
|
430
|
+
return;
|
|
431
|
+
return previewUrl.searchParams.get("entryId") ?? void 0;
|
|
432
|
+
}
|
|
433
|
+
function mediaResponse(request, upstream, cacheControl) {
|
|
434
|
+
if (isRedirectStatus(upstream.status))
|
|
435
|
+
return new Response("Media backend returned a redirect", { status: 502 });
|
|
436
|
+
const responseHeaders = new Headers({
|
|
437
|
+
"cache-control": upstream.ok || upstream.status === 304 ? cacheControl : "no-store"
|
|
438
|
+
});
|
|
439
|
+
for (const name of [
|
|
440
|
+
"accept-ranges",
|
|
441
|
+
"content-disposition",
|
|
442
|
+
"content-length",
|
|
443
|
+
"content-range",
|
|
444
|
+
"content-type",
|
|
445
|
+
"etag",
|
|
446
|
+
"last-modified"
|
|
447
|
+
]) {
|
|
448
|
+
const value = upstream.headers.get(name);
|
|
449
|
+
if (value) responseHeaders.set(name, value);
|
|
450
|
+
}
|
|
451
|
+
return new Response(request.method === "HEAD" ? null : upstream.body, {
|
|
452
|
+
status: upstream.status,
|
|
453
|
+
headers: responseHeaders
|
|
454
|
+
});
|
|
455
|
+
}
|
|
343
456
|
function parseUser(input) {
|
|
344
457
|
if (!isRecord(input)) throw new HttpError(400, "Expected user object");
|
|
345
458
|
const { sub, name, email, roles } = input;
|
|
@@ -7,6 +7,6 @@ export interface Verifier {
|
|
|
7
7
|
export declare class BasicAuth implements AuthApi {
|
|
8
8
|
#private;
|
|
9
9
|
constructor(context: RequestContext, verify: Verifier);
|
|
10
|
-
authenticate(request: Request, options
|
|
10
|
+
authenticate(request: Request, options: AuthOptions): Promise<Response>;
|
|
11
11
|
verify(request: Request): Promise<AuthedContext>;
|
|
12
12
|
}
|
|
@@ -16,24 +16,24 @@ var BasicAuth = class {
|
|
|
16
16
|
this.#verify = verify;
|
|
17
17
|
}
|
|
18
18
|
async authenticate(request, options) {
|
|
19
|
+
let verified;
|
|
19
20
|
try {
|
|
20
|
-
|
|
21
|
-
const url = new URL(request.url);
|
|
22
|
-
const action = url.searchParams.get("auth");
|
|
23
|
-
switch (action) {
|
|
24
|
-
case AuthAction.Status: {
|
|
25
|
-
const user = options?.enrichUser ? await options.enrichUser(verified.user) : verified.user;
|
|
26
|
-
return Response.json({
|
|
27
|
-
type: AuthResultType.Authenticated,
|
|
28
|
-
user
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
default:
|
|
32
|
-
return new Response("Bad request", { status: 400 });
|
|
33
|
-
}
|
|
21
|
+
verified = await this.verify(request);
|
|
34
22
|
} catch {
|
|
35
23
|
return unauthorized();
|
|
36
24
|
}
|
|
25
|
+
const url = new URL(request.url);
|
|
26
|
+
const action = url.searchParams.get("auth");
|
|
27
|
+
switch (action) {
|
|
28
|
+
case AuthAction.Status: {
|
|
29
|
+
return Response.json({
|
|
30
|
+
type: AuthResultType.Authenticated,
|
|
31
|
+
...await options.authenticated(verified.user)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
default:
|
|
35
|
+
return new Response("Bad request", { status: 400 });
|
|
36
|
+
}
|
|
37
37
|
}
|
|
38
38
|
async verify(request) {
|
|
39
39
|
const ctx = this.#context;
|
|
@@ -38,14 +38,14 @@ export interface OAuth2Options {
|
|
|
38
38
|
*/
|
|
39
39
|
revocationEndpoint?: string;
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* Validate provider-specific claims, including `iss` and `aud`.
|
|
42
42
|
* If the claims are invalid, an error should be thrown.
|
|
43
43
|
*/
|
|
44
|
-
validateClaims
|
|
44
|
+
validateClaims(claims: JWTPayload): void | Promise<void>;
|
|
45
45
|
}
|
|
46
46
|
export declare class OAuth2 implements AuthApi {
|
|
47
47
|
#private;
|
|
48
48
|
constructor(context: RequestContext, config: Config, options: OAuth2Options);
|
|
49
|
-
authenticate(request: Request, options
|
|
49
|
+
authenticate(request: Request, options: AuthOptions): Promise<Response>;
|
|
50
50
|
verify(request: Request): Promise<AuthedContext>;
|
|
51
51
|
}
|
|
@@ -469,6 +469,10 @@ var OAuth2 = class {
|
|
|
469
469
|
#jwks;
|
|
470
470
|
#validateClaims;
|
|
471
471
|
constructor(context, config, options) {
|
|
472
|
+
assert(
|
|
473
|
+
typeof options.validateClaims === "function",
|
|
474
|
+
"OAuth2 validateClaims is required"
|
|
475
|
+
);
|
|
472
476
|
this.#context = context;
|
|
473
477
|
this.#config = config;
|
|
474
478
|
this.#validateClaims = options.validateClaims;
|
|
@@ -514,10 +518,9 @@ var OAuth2 = class {
|
|
|
514
518
|
const [ctx, err] = await outcome(this.verify(request));
|
|
515
519
|
if (err instanceof Response) return err;
|
|
516
520
|
if (ctx) {
|
|
517
|
-
const user = options?.enrichUser ? await options.enrichUser(ctx.user) : ctx.user;
|
|
518
521
|
return Response.json({
|
|
519
522
|
type: AuthResultType.Authenticated,
|
|
520
|
-
user
|
|
523
|
+
...await options.authenticated(ctx.user)
|
|
521
524
|
});
|
|
522
525
|
}
|
|
523
526
|
const codeVerifier = await generateCodeVerifier();
|
|
@@ -615,7 +618,7 @@ var OAuth2 = class {
|
|
|
615
618
|
throw new MissingCredentialsError("Missing access token cookie");
|
|
616
619
|
const key = selectKey(jwks, accessToken);
|
|
617
620
|
const user = await verify(accessToken, key);
|
|
618
|
-
this.#validateClaims
|
|
621
|
+
await this.#validateClaims(user);
|
|
619
622
|
assert(user.exp, "Missing exp claim in access token");
|
|
620
623
|
const expiresSoon = user.exp - Math.floor(Date.now() / 1e3) < 30;
|
|
621
624
|
if (expiresSoon && refreshToken)
|
|
@@ -637,7 +640,7 @@ var OAuth2 = class {
|
|
|
637
640
|
cause: [cause, error]
|
|
638
641
|
});
|
|
639
642
|
});
|
|
640
|
-
this.#validateClaims
|
|
643
|
+
await this.#validateClaims(user);
|
|
641
644
|
throw Response.json(
|
|
642
645
|
{ type: AuthResultType.NeedsRefresh },
|
|
643
646
|
{
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import "../../chunks/chunk-CQ52GEHY.js";
|
|
2
|
+
|
|
3
|
+
// src/backend/api/ProxyMedia.ts
|
|
4
|
+
import { Headers, Response } from "@alinea/iso";
|
|
5
|
+
async function proxyMediaUrl(request, source, isAllowed = (url) => url.origin === source.origin) {
|
|
6
|
+
const headers = new Headers();
|
|
7
|
+
for (const name of [
|
|
8
|
+
"range",
|
|
9
|
+
"if-range",
|
|
10
|
+
"if-none-match",
|
|
11
|
+
"if-modified-since"
|
|
12
|
+
]) {
|
|
13
|
+
const value = request.headers.get(name);
|
|
14
|
+
if (value) headers.set(name, value);
|
|
15
|
+
}
|
|
16
|
+
let current = source;
|
|
17
|
+
for (let redirects = 0; ; redirects++) {
|
|
18
|
+
if (!isAllowed(current))
|
|
19
|
+
return new Response("Invalid media source", { status: 502 });
|
|
20
|
+
const response = await fetch(current, {
|
|
21
|
+
method: request.method,
|
|
22
|
+
headers,
|
|
23
|
+
redirect: "manual",
|
|
24
|
+
signal: request.signal
|
|
25
|
+
});
|
|
26
|
+
const location = response.headers.get("location");
|
|
27
|
+
if (!isRedirectStatus(response.status) || !location) return response;
|
|
28
|
+
if (redirects === 3)
|
|
29
|
+
return new Response("Too many media redirects", { status: 502 });
|
|
30
|
+
current = new URL(location, current);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function isRedirectStatus(status) {
|
|
34
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
isRedirectStatus,
|
|
38
|
+
proxyMediaUrl
|
|
39
|
+
};
|
|
@@ -195,6 +195,8 @@ function callHandler(handler, input) {
|
|
|
195
195
|
function compressResponse(request, response) {
|
|
196
196
|
const body = response.body;
|
|
197
197
|
if (!body) return response;
|
|
198
|
+
if (response.status === 206 || response.headers.has("content-range"))
|
|
199
|
+
return response;
|
|
198
200
|
const isCompressed = response.headers.get("content-encoding");
|
|
199
201
|
if (isCompressed) return response;
|
|
200
202
|
const accept = request.headers.get("accept-encoding");
|
|
@@ -5,7 +5,7 @@ import "../../chunks/chunk-CQ52GEHY.js";
|
|
|
5
5
|
|
|
6
6
|
// src/backend/util/Syncable.ts
|
|
7
7
|
function createThrottledSync() {
|
|
8
|
-
let lastSync =
|
|
8
|
+
let lastSync = 0;
|
|
9
9
|
const limit = pLimit(1);
|
|
10
10
|
return async function throttle(sync, intervalSeconds = 60) {
|
|
11
11
|
return limit(async () => {
|