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.
Files changed (225) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/.init/src/cms.ts +1 -4
  3. package/dist/LICENSES.md +148 -94
  4. package/dist/adapter/next/DevRemote.js +10 -2
  5. package/dist/adapter/next/cms.js +5 -1
  6. package/dist/adapter/next/handler.js +35 -6
  7. package/dist/adapter/next/previews.js +7 -0
  8. package/dist/adapter/next/syncCheck.d.ts +12 -0
  9. package/dist/adapter/next/syncCheck.js +59 -0
  10. package/dist/adapter/next/with-alinea.d.ts +0 -8
  11. package/dist/adapter/next/with-alinea.js +43 -19
  12. package/dist/adapter/vite/plugin.js +9 -4
  13. package/dist/backend/Handler.js +120 -7
  14. package/dist/backend/api/BasicAuth.d.ts +1 -1
  15. package/dist/backend/api/BasicAuth.js +14 -14
  16. package/dist/backend/api/OAuth2.d.ts +3 -3
  17. package/dist/backend/api/OAuth2.js +7 -4
  18. package/dist/backend/api/ProxyMedia.d.ts +3 -0
  19. package/dist/backend/api/ProxyMedia.js +39 -0
  20. package/dist/backend/api/S3Uploads.d.ts +1 -1
  21. package/dist/backend/router/Router.js +2 -0
  22. package/dist/backend/util/Syncable.js +1 -1
  23. package/dist/bundled.d.ts +260 -95
  24. package/dist/chunks/{chunk-HFBZMKIN.js → chunk-A6M3PNJT.js} +1 -1
  25. package/dist/chunks/{chunk-Y7XOMJOC.js → chunk-CFNRVI3Y.js} +220 -247
  26. package/dist/chunks/chunk-D6UJHBJJ.js +183 -0
  27. package/dist/chunks/chunk-TIJTT5ZI.js +228 -0
  28. package/dist/cli/Generate.js +1 -3
  29. package/dist/cli/Serve.js +3 -2
  30. package/dist/cli/bin.js +1 -1
  31. package/dist/cli/generate/GenerateDashboard.js +1 -0
  32. package/dist/cli/serve/CreateLocalServer.d.ts +1 -1
  33. package/dist/cli/serve/CreateLocalServer.js +42 -34
  34. package/dist/cli/serve/LocalAuth.js +1 -1
  35. package/dist/cli/static/init/cms.js +1 -4
  36. package/dist/cli/util/ContentType.d.ts +1 -0
  37. package/dist/cli/util/ContentType.js +36 -0
  38. package/dist/cloud/AuthResult.d.ts +2 -0
  39. package/dist/cloud/CloudHandshake.d.ts +13 -0
  40. package/dist/cloud/CloudHandshake.js +121 -0
  41. package/dist/cloud/CloudRemote.d.ts +4 -1
  42. package/dist/cloud/CloudRemote.js +40 -9
  43. package/dist/components/Calendar.js +7 -7
  44. package/dist/components/Checkbox.js +2 -2
  45. package/dist/components/ColorSwatchPicker.js +2 -2
  46. package/dist/components/ComboBox.js +12 -12
  47. package/dist/components/DateField.js +4 -4
  48. package/dist/components/DatePicker.js +4 -4
  49. package/dist/components/DateRangePicker.js +8 -8
  50. package/dist/components/Disclosure.js +7 -7
  51. package/dist/components/Label.js +5 -5
  52. package/dist/components/List.d.ts +1 -1
  53. package/dist/components/List.js +29 -28
  54. package/dist/components/Menu.js +2 -2
  55. package/dist/components/MultipleSelect.js +25 -25
  56. package/dist/components/NumberField.js +3 -3
  57. package/dist/components/ProgressCircle.js +4 -4
  58. package/dist/components/SearchField.js +6 -6
  59. package/dist/components/Select.js +7 -7
  60. package/dist/components/Surface.js +3 -3
  61. package/dist/components/Table.js +6 -6
  62. package/dist/components/Tabs.js +2 -2
  63. package/dist/components/TagGroup.js +2 -2
  64. package/dist/components/TextField.js +4 -4
  65. package/dist/components/TimeField.js +1 -1
  66. package/dist/components/Toggle.d.ts +1 -1
  67. package/dist/components/Toolbar.js +1 -1
  68. package/dist/components/Tree.js +7 -7
  69. package/dist/core/Config.d.ts +10 -3
  70. package/dist/core/Config.js +16 -1
  71. package/dist/core/Connection.d.ts +7 -4
  72. package/dist/core/Entry.d.ts +6 -0
  73. package/dist/core/Entry.js +10 -0
  74. package/dist/core/Graph.d.ts +2 -2
  75. package/dist/core/Id.d.ts +1 -0
  76. package/dist/core/Id.js +8 -0
  77. package/dist/core/Role.js +16 -14
  78. package/dist/core/TextDoc.d.ts +2 -0
  79. package/dist/core/TextDoc.js +1 -0
  80. package/dist/core/Workspace.d.ts +6 -5
  81. package/dist/core/db/EntryIndex.d.ts +3 -0
  82. package/dist/core/db/EntryIndex.js +62 -0
  83. package/dist/core/db/EntryTransaction.js +33 -12
  84. package/dist/core/db/EntryUrlConflictError.js +1 -1
  85. package/dist/core/db/LinkResolver.d.ts +8 -2
  86. package/dist/core/db/LinkResolver.js +27 -5
  87. package/dist/core/db/Operation.js +4 -3
  88. package/dist/core/field/RichTextField.js +16 -10
  89. package/dist/core/media/MediaLocation.d.ts +9 -15
  90. package/dist/core/media/MediaLocation.js +29 -19
  91. package/dist/core/media/MediaTypes.js +1 -2
  92. package/dist/dashboard/App.js +30 -16
  93. package/dist/dashboard/StoryProvider.js +2 -2
  94. package/dist/dashboard/app/AccessDenied.js +4 -4
  95. package/dist/dashboard/app/ActivityStatus.js +22 -21
  96. package/dist/dashboard/app/AppShell.js +2 -2
  97. package/dist/dashboard/app/AppearanceToggle.d.ts +1 -0
  98. package/dist/dashboard/app/AppearanceToggle.js +43 -0
  99. package/dist/dashboard/app/AuthView.d.ts +1 -1
  100. package/dist/dashboard/app/AuthView.js +19 -21
  101. package/dist/dashboard/app/CompactField.js +13 -13
  102. package/dist/dashboard/app/DashboardErrorBoundary.js +26 -14
  103. package/dist/dashboard/app/DashboardLayout.js +15 -8
  104. package/dist/dashboard/app/DashboardMeta.d.ts +2 -1
  105. package/dist/dashboard/app/DashboardMeta.js +20 -5
  106. package/dist/dashboard/app/EntryFields.d.ts +3 -1
  107. package/dist/dashboard/app/EntryFields.js +17 -14
  108. package/dist/dashboard/app/EntryHeader.js +134 -102
  109. package/dist/dashboard/app/EntryHeaderActions.d.ts +21 -4
  110. package/dist/dashboard/app/EntryHeaderActions.js +48 -27
  111. package/dist/dashboard/app/EntryReferences.js +16 -16
  112. package/dist/dashboard/app/EntrySidebar.js +24 -24
  113. package/dist/dashboard/app/EntrySidebarPreview.js +16 -15
  114. package/dist/dashboard/app/EntryTranslationBanner.d.ts +3 -1
  115. package/dist/dashboard/app/EntryTranslationBanner.js +24 -21
  116. package/dist/dashboard/app/Explorer.d.ts +4 -2
  117. package/dist/dashboard/app/Explorer.js +70 -66
  118. package/dist/dashboard/app/ExplorerCards.js +49 -49
  119. package/dist/dashboard/app/ExplorerFileCard.js +18 -18
  120. package/dist/dashboard/app/ExplorerList.js +21 -22
  121. package/dist/dashboard/app/ExplorerModal.js +1 -1
  122. package/dist/dashboard/app/ExplorerPickerContent.js +2 -2
  123. package/dist/dashboard/app/ExplorerTable.js +68 -68
  124. package/dist/dashboard/app/ImagePicker.js +7 -7
  125. package/dist/dashboard/app/LinkPicker.js +17 -22
  126. package/dist/dashboard/app/LocaleMenu.d.ts +7 -0
  127. package/dist/dashboard/app/LocaleMenu.js +31 -17
  128. package/dist/dashboard/app/ReadOnlyBadge.d.ts +4 -0
  129. package/dist/dashboard/app/ReadOnlyBadge.js +33 -0
  130. package/dist/dashboard/app/SidebarLayout.js +4 -4
  131. package/dist/dashboard/app/SidebarTree.d.ts +3 -6
  132. package/dist/dashboard/app/SidebarTree.js +66 -61
  133. package/dist/dashboard/app/WorkspaceMenu.d.ts +10 -3
  134. package/dist/dashboard/app/WorkspaceMenu.js +87 -66
  135. package/dist/dashboard/app/WorkspaceMenu.stories.d.ts +2 -0
  136. package/dist/dashboard/app/WorkspaceRoots.d.ts +3 -1
  137. package/dist/dashboard/app/WorkspaceRoots.js +88 -113
  138. package/dist/dashboard/app/editor/FileEditor.d.ts +2 -3
  139. package/dist/dashboard/app/editor/FileEditor.js +35 -38
  140. package/dist/dashboard/app/editor/FilePreview.js +3 -3
  141. package/dist/dashboard/app/editor/MediaAltField.view.js +4 -4
  142. package/dist/dashboard/app/modals/CreateEntry.js +10 -10
  143. package/dist/dashboard/app/pages/EntryPage.js +85 -44
  144. package/dist/dashboard/app/pages/RootPage.js +4 -4
  145. package/dist/dashboard/app/pages/SplashPage.d.ts +3 -0
  146. package/dist/dashboard/app/pages/SplashPage.js +414 -0
  147. package/dist/dashboard/app/pages/UsersPage.js +38 -38
  148. package/dist/dashboard/app/ui/DashboardModal.js +16 -16
  149. package/dist/dashboard/app/ui/Rail.js +2 -2
  150. package/dist/dashboard/app/ui/Sidebar.js +2 -2
  151. package/dist/dashboard/atoms/ReactiveNode.js +1 -1
  152. package/dist/dashboard/atoms/activity.d.ts +0 -1
  153. package/dist/dashboard/atoms/activity.js +3 -5
  154. package/dist/dashboard/atoms/auth.d.ts +2 -0
  155. package/dist/dashboard/atoms/auth.js +6 -2
  156. package/dist/dashboard/atoms/config.js +1 -1
  157. package/dist/dashboard/atoms/core.js +2 -2
  158. package/dist/dashboard/atoms/create.js +1 -1
  159. package/dist/dashboard/atoms/dashboard.js +2 -2
  160. package/dist/dashboard/atoms/editor.d.ts +2 -1
  161. package/dist/dashboard/atoms/editor.js +5 -3
  162. package/dist/dashboard/atoms/entry.d.ts +2 -0
  163. package/dist/dashboard/atoms/entry.js +34 -16
  164. package/dist/dashboard/atoms/explorer.d.ts +5 -6
  165. package/dist/dashboard/atoms/explorer.js +68 -31
  166. package/dist/dashboard/atoms/graph.d.ts +1 -0
  167. package/dist/dashboard/atoms/graph.js +7 -2
  168. package/dist/dashboard/atoms/link.d.ts +1 -1
  169. package/dist/dashboard/atoms/link.js +41 -57
  170. package/dist/dashboard/atoms/nav.d.ts +4 -3
  171. package/dist/dashboard/atoms/nav.js +10 -5
  172. package/dist/dashboard/atoms/preview.js +1 -1
  173. package/dist/dashboard/atoms/root.d.ts +7 -2
  174. package/dist/dashboard/atoms/root.js +53 -18
  175. package/dist/dashboard/atoms/upload.d.ts +1 -0
  176. package/dist/dashboard/atoms/upload.js +3 -2
  177. package/dist/dashboard/atoms/user.d.ts +1 -1
  178. package/dist/dashboard/atoms/user.js +8 -6
  179. package/dist/dashboard/atoms/utils.js +2 -2
  180. package/dist/dashboard/boot/Boot.js +2 -4
  181. package/dist/dashboard/boot/DashboardWorker.js +35 -29
  182. package/dist/dashboard/editor/UseField.js +6 -6
  183. package/dist/dashboard/fixture/cms.d.ts +12 -0
  184. package/dist/dashboard/fixture/cms.js +54 -6
  185. package/dist/dashboard/hook/UseGraph.js +4 -4
  186. package/dist/dashboard/hook/UseRoot.js +4 -4
  187. package/dist/dashboard/hook/UseSaveShortcut.d.ts +1 -0
  188. package/dist/dashboard/hook/UseSaveShortcut.js +24 -0
  189. package/dist/dashboard/hooks.d.ts +1 -1
  190. package/dist/dashboard/hooks.js +28 -19
  191. package/dist/field/code/CodeField.view.js +12 -12
  192. package/dist/field/link/ImageLink.js +1 -3
  193. package/dist/field/link/LinkField.view.js +97 -71
  194. package/dist/field/list/ListField.view.js +23 -23
  195. package/dist/field/localiser/LocalisedField.view.js +76 -5
  196. package/dist/field/metadata/MetadataField.d.ts +3 -3
  197. package/dist/field/metadata/MetadataField.view.js +27 -20
  198. package/dist/field/object/ObjectField.view.js +11 -4
  199. package/dist/field/richtext/PickTextLink.js +11 -8
  200. package/dist/field/richtext/ReferenceLink.d.ts +2 -0
  201. package/dist/field/richtext/ReferenceLink.js +5 -0
  202. package/dist/field/richtext/RichTextBlock.js +6 -6
  203. package/dist/field/richtext/RichTextBlocks.js +1 -1
  204. package/dist/field/richtext/RichTextExtensions.js +22 -22
  205. package/dist/field/richtext/RichTextField.view.js +9 -9
  206. package/dist/field/richtext/RichTextInsertMenu.js +3 -3
  207. package/dist/field/richtext/RichTextState.js +1 -1
  208. package/dist/field/richtext/extensions/Link.d.ts +1 -0
  209. package/dist/field/richtext/extensions/Link.js +1 -0
  210. package/dist/field/text/TextField.view.js +2 -2
  211. package/dist/field/time/TimeField.d.ts +0 -2
  212. package/dist/field/time/TimeField.view.js +1 -1
  213. package/dist/index.css +459 -66
  214. package/dist/next.cjs +96 -19
  215. package/dist/picker/entry/EntryPicker.js +10 -12
  216. package/dist/picker/entry/EntryReference.d.ts +2 -0
  217. package/dist/picker/entry/EntryReference.js +1 -0
  218. package/dist/preview/RegisterPreview.d.ts +1 -1
  219. package/dist/preview/RegisterPreview.js +4 -6
  220. package/dist/preview/react.d.ts +5 -1
  221. package/dist/preview/react.js +12 -9
  222. package/dist/types.d.ts +0 -1
  223. package/package.json +1 -1
  224. package/dist/chunks/chunk-STAJCW4Z.js +0 -322
  225. 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 value.startsWith("/") ? value : `/${value}`;
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 === "/admin" || url.pathname.startsWith("/admin/") || url.pathname === "/api" || url.pathname.startsWith("/api/") || url.pathname === "/~dev" || url.pathname.startsWith("/~dev/") || url.search.startsWith("?/upload");
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
  })
@@ -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
- enrichUser(user) {
64
- return cnx.enrichUser(user);
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
- const capabilities = cnx.capabilities;
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?: AuthOptions): Promise<Response>;
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
- const verified = await this.verify(request);
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
- * Extra claims to validate, such as `iss` or `aud`,
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?(claims: JWTPayload): void;
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?: AuthOptions): Promise<Response>;
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?.(user);
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?.(user);
643
+ await this.#validateClaims(user);
641
644
  throw Response.json(
642
645
  { type: AuthResultType.NeedsRefresh },
643
646
  {
@@ -0,0 +1,3 @@
1
+ import { type Request, Response } from '@alinea/iso';
2
+ export declare function proxyMediaUrl(request: Request, source: URL, isAllowed?: (url: URL) => boolean): Promise<Response>;
3
+ export declare function isRedirectStatus(status: number): boolean;
@@ -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
+ };
@@ -13,7 +13,7 @@ export interface S3UploadsOptions {
13
13
  previewExpiresIn?: number;
14
14
  }
15
15
  interface S3PresignOptions {
16
- method: 'GET' | 'PUT';
16
+ method: 'GET' | 'HEAD' | 'PUT';
17
17
  url: URL;
18
18
  region: string;
19
19
  accessKeyId: string;
@@ -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 = Date.now();
8
+ let lastSync = 0;
9
9
  const limit = pLimit(1);
10
10
  return async function throttle(sync, intervalSeconds = 60) {
11
11
  return limit(async () => {