alinea 2.0.0-preview.10 → 2.0.0-preview.12

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 (140) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/.mcp/later/dev.json +1 -1
  3. package/dist/.mcp/online/dev.json +1 -1
  4. package/dist/adapter/next/handler.js +6 -4
  5. package/dist/backend/Handler.d.ts +3 -0
  6. package/dist/backend/Handler.js +5 -5
  7. package/dist/backend/api/OAuth2.js +21 -26
  8. package/dist/backend/util/JWKS.d.ts +4 -0
  9. package/dist/backend/util/JWKS.js +40 -0
  10. package/dist/bundled.d.ts +129 -31
  11. package/dist/chunks/{chunk-HXXBAY3B.js → chunk-OHGAVV4G.js} +1 -1
  12. package/dist/cli/Mcp.js +1 -1
  13. package/dist/cli/Serve.d.ts +1 -0
  14. package/dist/cli/Serve.js +5 -2
  15. package/dist/cli/bin.js +3 -2
  16. package/dist/cli/generate/GenerateDashboard.js +17 -5
  17. package/dist/cli/serve/DevServer.d.ts +2 -0
  18. package/dist/cli/serve/DevServer.js +14 -2
  19. package/dist/cli/serve/mcp/DevMcp.js +1 -1
  20. package/dist/cli/static/dashboard/entry.js +2 -1
  21. package/dist/cloud/CloudHandshake.js +9 -46
  22. package/dist/cloud/CloudRemote.js +1 -1
  23. package/dist/components/Alert.js +2 -2
  24. package/dist/components/Badge.js +3 -3
  25. package/dist/components/Breadcrumbs.js +5 -5
  26. package/dist/components/Button.js +2 -2
  27. package/dist/components/Calendar.js +17 -17
  28. package/dist/components/Checkbox.js +7 -7
  29. package/dist/components/Collapsible.js +3 -3
  30. package/dist/components/ComboBox.js +8 -8
  31. package/dist/components/Command.js +7 -7
  32. package/dist/components/ContentCard.js +25 -25
  33. package/dist/components/ContentGrid.js +8 -8
  34. package/dist/components/DataList.js +2 -2
  35. package/dist/components/Dialog.js +6 -6
  36. package/dist/components/DropZone.js +3 -3
  37. package/dist/components/DropdownMenu.js +9 -9
  38. package/dist/components/Empty.js +1 -1
  39. package/dist/components/Field.js +7 -7
  40. package/dist/components/List.js +26 -26
  41. package/dist/components/MediaPreview.js +3 -3
  42. package/dist/components/MultipleSelect.js +19 -19
  43. package/dist/components/NavRail.js +5 -5
  44. package/dist/components/NumberField.js +1 -1
  45. package/dist/components/Page.js +5 -5
  46. package/dist/components/PreviewFrame.js +6 -6
  47. package/dist/components/RadioGroup.js +6 -6
  48. package/dist/components/Resizable.js +1 -1
  49. package/dist/components/SearchField.js +4 -4
  50. package/dist/components/Select.js +8 -8
  51. package/dist/components/Sidebar.js +4 -4
  52. package/dist/components/SortableList.js +15 -15
  53. package/dist/components/Spinner.js +3 -3
  54. package/dist/components/Surface.js +3 -3
  55. package/dist/components/Switch.js +5 -5
  56. package/dist/components/Table.js +40 -40
  57. package/dist/components/Tabs.js +4 -4
  58. package/dist/components/TagGroup.js +8 -8
  59. package/dist/components/TextField.js +8 -8
  60. package/dist/components/TimeField.js +6 -6
  61. package/dist/components/Toolbar.js +2 -2
  62. package/dist/components/Tree.js +21 -21
  63. package/dist/components/internal/DateControl.js +9 -9
  64. package/dist/components/internal/ListBoxOption.js +7 -7
  65. package/dist/components/internal/SelectionCheckbox.js +3 -3
  66. package/dist/components/internal/ToggleButton.js +4 -4
  67. package/dist/core/Role.d.ts +4 -5
  68. package/dist/core/Role.js +55 -54
  69. package/dist/core/Scope.d.ts +2 -0
  70. package/dist/core/Scope.js +36 -2
  71. package/dist/core/Type.d.ts +7 -4
  72. package/dist/core/Type.js +5 -1
  73. package/dist/core/Validation.js +3 -3
  74. package/dist/core/media/CompressPdf.browser.js +1 -1
  75. package/dist/core/media/CompressPdf.js +1 -1
  76. package/dist/core/media/CreatePreview.browser.js +1 -1
  77. package/dist/core/util/JWT.d.ts +1 -1
  78. package/dist/dashboard/app/ActivityStatus.js +12 -12
  79. package/dist/dashboard/app/AuthView.js +4 -4
  80. package/dist/dashboard/app/CompactField.js +15 -15
  81. package/dist/dashboard/app/CroppedImage.js +2 -2
  82. package/dist/dashboard/app/DashboardErrorBoundary.js +2 -2
  83. package/dist/dashboard/app/EntryHeader.js +5 -5
  84. package/dist/dashboard/app/EntryReferences.js +3 -3
  85. package/dist/dashboard/app/EntrySidebar.js +12 -12
  86. package/dist/dashboard/app/EntrySidebarPreview.js +2 -2
  87. package/dist/dashboard/app/Explorer.js +32 -39
  88. package/dist/dashboard/app/ExplorerBatchActions.js +3 -3
  89. package/dist/dashboard/app/ExplorerControls.js +5 -5
  90. package/dist/dashboard/app/ExplorerList.js +4 -4
  91. package/dist/dashboard/app/ExplorerModal.js +3 -3
  92. package/dist/dashboard/app/ExplorerPickerContent.d.ts +1 -1
  93. package/dist/dashboard/app/ExplorerPickerContent.js +17 -3
  94. package/dist/dashboard/app/ExplorerTable.js +7 -7
  95. package/dist/dashboard/app/ImageEditor.js +25 -25
  96. package/dist/dashboard/app/ImagePicker.js +3 -2
  97. package/dist/dashboard/app/LinkPicker.js +6 -5
  98. package/dist/dashboard/app/LocaleMenu.js +7 -7
  99. package/dist/dashboard/app/MediaPreviewCell.js +3 -3
  100. package/dist/dashboard/app/OverviewCell.js +7 -7
  101. package/dist/dashboard/app/PendingUploadsView.js +4 -4
  102. package/dist/dashboard/app/SidebarTree.js +21 -23
  103. package/dist/dashboard/app/WorkspaceMenu.js +7 -7
  104. package/dist/dashboard/app/WorkspaceRoots.js +9 -9
  105. package/dist/dashboard/app/editor/FileEditor.js +20 -20
  106. package/dist/dashboard/app/modals/CreateEntry.js +5 -5
  107. package/dist/dashboard/app/pages/EntryPage.js +8 -8
  108. package/dist/dashboard/app/pages/SplashPage.js +37 -37
  109. package/dist/dashboard/app/pages/UsersPage.js +12 -12
  110. package/dist/dashboard/app/ui/DashboardModal.js +15 -15
  111. package/dist/dashboard/atoms/config.d.ts +1 -1
  112. package/dist/dashboard/atoms/entry.d.ts +11 -4
  113. package/dist/dashboard/atoms/entry.js +103 -86
  114. package/dist/dashboard/atoms/entryTable.js +2 -1
  115. package/dist/dashboard/atoms/explorer.d.ts +17 -2
  116. package/dist/dashboard/atoms/explorer.js +38 -16
  117. package/dist/dashboard/atoms/link.d.ts +2 -0
  118. package/dist/dashboard/atoms/link.js +6 -3
  119. package/dist/dashboard/atoms/root.d.ts +15 -3
  120. package/dist/dashboard/atoms/root.js +111 -52
  121. package/dist/dashboard/boot/BootProd.d.ts +6 -1
  122. package/dist/dashboard/boot/BootProd.js +2 -2
  123. package/dist/dashboard/boot/DashboardWorker.js +51 -15
  124. package/dist/dashboard/cms/EntryTable.js +2 -2
  125. package/dist/database/BrowserEntryStore.d.ts +47 -9
  126. package/dist/database/BrowserEntryStore.js +137 -66
  127. package/dist/database/EntryTransaction.js +17 -6
  128. package/dist/field/check/CheckField.view.js +2 -2
  129. package/dist/field/code/CodeField.view.js +11 -11
  130. package/dist/field/link/LinkField.view.js +33 -32
  131. package/dist/field/list/ListField.view.js +4 -4
  132. package/dist/field/metadata/MetadataField.view.js +16 -16
  133. package/dist/field/richtext/RichTextBlock.js +2 -2
  134. package/dist/field/richtext/RichTextExtensions.js +31 -31
  135. package/dist/field/richtext/RichTextField.view.js +5 -5
  136. package/docs/deploy.md +1 -1
  137. package/docs/roles-permissions.md +7 -7
  138. package/llms-full.txt +8 -8
  139. package/package.json +1 -1
  140. /package/dist/chunks/{dist-UDQZOKRG.js → dist-OU2RRDGP.js} +0 -0
@@ -1,5 +1,5 @@
1
1
  {
2
- "_id": "3K5v72iiSXMjBJwIX9mSIj8QqhI",
2
+ "_id": "3KPEwd5jxca0cmXOExux5RBSXpo",
3
3
  "_type": "Page",
4
4
  "_index": "a0",
5
5
  "_seeded": "welcome.json",
@@ -1 +1 @@
1
- {"url":"http://localhost:1","pid":3102}
1
+ {"url":"http://localhost:1","pid":2819}
@@ -1 +1 @@
1
- {"url":"http://localhost:40115","pid":3102}
1
+ {"url":"http://localhost:40589","pid":2819}
@@ -67,8 +67,6 @@ function createHandlerWithDatabase(input, openGeneratedDatabase) {
67
67
  return failure(400, "Expected JSON");
68
68
  }
69
69
  if (!Array.isArray(mutations)) return failure(400, "Expected mutations");
70
- const adjusted = await options.beforeCommit?.({ mutations });
71
- if (adjusted) mutations = [...adjusted];
72
70
  const devServer = new Client({
73
71
  config,
74
72
  url: context.handlerUrl.href,
@@ -80,15 +78,19 @@ function createHandlerWithDatabase(input, openGeneratedDatabase) {
80
78
  }
81
79
  });
82
80
  let sha;
81
+ let user;
83
82
  try {
84
- ;
83
+ user = await devServer.user();
84
+ if (!user) return failure(401, "Unauthorized");
85
+ const adjusted = await options.beforeCommit?.({ mutations, user });
86
+ if (adjusted) mutations = [...adjusted];
85
87
  ({ sha } = await devServer.mutate(mutations));
86
88
  } catch (error) {
87
89
  if (error instanceof HttpError) return failure(error.code, error.message);
88
90
  throw error;
89
91
  }
90
92
  try {
91
- await afterCommit({ mutations, sha });
93
+ await afterCommit({ mutations, sha, user });
92
94
  } catch (error) {
93
95
  console.error("Alinea afterCommit hook failed", error);
94
96
  }
@@ -2,12 +2,15 @@ import type { CMS } from 'alinea/core/CMS';
2
2
  import type { AuthedContext, RemoteConnection, RequestContext } from 'alinea/core/Connection';
3
3
  import type { LocalStore } from 'alinea/core/db/LocalStore';
4
4
  import type { Mutation } from 'alinea/core/db/Mutation';
5
+ import type { User } from 'alinea/core/User';
5
6
  export interface Handler {
6
7
  (request: Request, context: RequestContext): Promise<Response>;
7
8
  }
8
9
  export type HookResponse<T = void> = void | T | Promise<void | T>;
9
10
  export interface BeforeCommitContext {
10
11
  mutations: ReadonlyArray<Mutation>;
12
+ /** The user committing the mutations */
13
+ user: User;
11
14
  }
12
15
  export interface AfterCommitContext extends BeforeCommitContext {
13
16
  sha: string;
@@ -273,7 +273,10 @@ function createHandler({
273
273
  const policy = await user.policy;
274
274
  let mutations = await body;
275
275
  await local.syncWith(cnx);
276
- const adjusted = await hooks.beforeCommit?.({ mutations });
276
+ const adjusted = await hooks.beforeCommit?.({
277
+ mutations,
278
+ user: user.claims
279
+ });
277
280
  if (adjusted) mutations = adjusted;
278
281
  const attempt = async (retry = 0) => {
279
282
  if (retry > 0) await local.syncWith(cnx);
@@ -297,10 +300,7 @@ function createHandler({
297
300
  };
298
301
  const sha = await attempt();
299
302
  try {
300
- await hooks.afterCommit?.({
301
- mutations,
302
- sha
303
- });
303
+ await hooks.afterCommit?.({ mutations, sha, user: user.claims });
304
304
  } catch (error) {
305
305
  console.error("Alinea afterCommit hook failed", error);
306
306
  }
@@ -1,15 +1,12 @@
1
1
  import {
2
2
  parse
3
3
  } from "../../chunks/chunk-BYJRQTFR.js";
4
- import {
5
- PLazy
6
- } from "../../chunks/chunk-IKINPSS5.js";
7
4
  import "../../chunks/chunk-CQ52GEHY.js";
8
5
 
9
6
  // src/backend/api/OAuth2.ts
10
7
  import { AuthResultType } from "alinea/cloud/AuthResult";
11
8
  import { Config } from "alinea/core/Config";
12
- import { HttpError } from "alinea/core/HttpError";
9
+ import { ErrorCode, HttpError } from "alinea/core/HttpError";
13
10
  import { createId } from "alinea/core/Id";
14
11
  import { outcome } from "alinea/core/Outcome";
15
12
  import { assert } from "alinea/core/util/Assert";
@@ -459,6 +456,7 @@ import {
459
456
  MissingCredentialsError
460
457
  } from "../Auth.js";
461
458
  import { router } from "../router/Router.js";
459
+ import { jwks } from "../util/JWKS.js";
462
460
  var COOKIE_VERIFIER = "alinea.cv";
463
461
  var COOKIE_ACCESS_TOKEN = "alinea.at";
464
462
  var COOKIE_REFRESH_TOKEN = "alinea.rt";
@@ -466,7 +464,7 @@ var OAuth2 = class {
466
464
  #context;
467
465
  #config;
468
466
  #client;
469
- #jwks;
467
+ #jwksUri;
470
468
  #validateClaims;
471
469
  constructor(context, config, options) {
472
470
  assert(
@@ -489,19 +487,16 @@ var OAuth2 = class {
489
487
  return response;
490
488
  })
491
489
  });
492
- const loadJwks = async () => {
493
- try {
494
- const res = await fetch(options.jwksUri);
495
- if (res.status !== 200)
496
- throw new HttpError(res.status, await res.text());
497
- const jwks = await res.json();
498
- return jwks.keys;
499
- } catch (cause) {
500
- this.#jwks = PLazy.from(loadJwks);
501
- throw new Error("Remote unavailable", { cause });
502
- }
503
- };
504
- this.#jwks = PLazy.from(loadJwks);
490
+ this.#jwksUri = options.jwksUri;
491
+ }
492
+ async #key(token) {
493
+ const { header } = decode(token);
494
+ const keys = await jwks(this.#jwksUri, header.kid).catch((cause) => {
495
+ throw new HttpError(ErrorCode.ServiceUnavailable, "Remote unavailable", {
496
+ cause
497
+ });
498
+ });
499
+ return selectKey(keys, header);
505
500
  }
506
501
  get #redirectUri() {
507
502
  const url = new URL(this.#context.handlerUrl);
@@ -612,11 +607,10 @@ var OAuth2 = class {
612
607
  [COOKIE_ACCESS_TOKEN]: accessToken,
613
608
  [COOKIE_REFRESH_TOKEN]: refreshToken
614
609
  } = parse(cookieHeader);
615
- const jwks = await this.#jwks;
616
610
  try {
617
611
  if (!accessToken)
618
612
  throw new MissingCredentialsError("Missing access token cookie");
619
- const key = selectKey(jwks, accessToken);
613
+ const key = await this.#key(accessToken);
620
614
  const user = await verify(accessToken, key);
621
615
  await this.#validateClaims(user);
622
616
  assert(user.exp, "Missing exp claim in access token");
@@ -626,13 +620,15 @@ var OAuth2 = class {
626
620
  return { ...ctx, user, token: accessToken };
627
621
  } catch (error) {
628
622
  if (!refreshToken) throw error;
623
+ if (error instanceof HttpError && error.code === ErrorCode.ServiceUnavailable)
624
+ throw error;
629
625
  const token = { accessToken, refreshToken, expiresAt: null };
630
626
  const newToken = await this.#client.refreshToken(token).catch((cause) => {
631
627
  throw new InvalidCredentialsError("Failed to refresh token", {
632
628
  cause: [cause, error]
633
629
  });
634
630
  });
635
- const key = selectKey(jwks, newToken.accessToken);
631
+ const key = await this.#key(newToken.accessToken);
636
632
  const user = await verify(newToken.accessToken, key, {
637
633
  clockTolerance: 30
638
634
  }).catch((cause) => {
@@ -653,13 +649,12 @@ var OAuth2 = class {
653
649
  }
654
650
  }
655
651
  };
656
- function selectKey(jwks, token) {
657
- const { header } = decode(token);
652
+ function selectKey(jwks2, header) {
658
653
  const kid = header.kid;
659
654
  const alg = header.alg;
660
655
  if (!kid) {
661
- if (jwks.length === 1) {
662
- const key2 = jwks[0];
656
+ if (jwks2.length === 1) {
657
+ const key2 = jwks2[0];
663
658
  if (key2.alg && alg && key2.alg !== alg) {
664
659
  throw new Error(
665
660
  `Algorithm mismatch: token uses ${alg}, but key specifies ${key2.alg}`
@@ -671,7 +666,7 @@ function selectKey(jwks, token) {
671
666
  'Token is missing "kid" header, and JWKS contains multiple keys'
672
667
  );
673
668
  }
674
- const key = jwks.find((k) => k.kid === kid);
669
+ const key = jwks2.find((k) => k.kid === kid);
675
670
  if (!key) throw new Error(`No key found for kid: ${kid}`);
676
671
  if (key.alg && alg && key.alg !== alg)
677
672
  throw new Error(
@@ -0,0 +1,4 @@
1
+ export interface WebKey extends JsonWebKey {
2
+ kid: string;
3
+ }
4
+ export declare function jwks(uri: string, kid?: string): Promise<Array<WebKey>>;
@@ -0,0 +1,40 @@
1
+ import "../../chunks/chunk-CQ52GEHY.js";
2
+
3
+ // src/backend/util/JWKS.ts
4
+ import { HttpError } from "alinea/core/HttpError";
5
+ import { isRecord } from "alinea/core/util/Objects";
6
+ var lifetime = 10 * 60 * 1e3;
7
+ var refreshInterval = 60 * 1e3;
8
+ var cache = /* @__PURE__ */ new Map();
9
+ async function jwks(uri, kid) {
10
+ const cached = cache.get(uri);
11
+ if (!cached || Date.now() >= cached.expires) return load(uri, cached);
12
+ const keys = await cached.keys;
13
+ if (!kid || keys.some((key) => key.kid === kid)) return keys;
14
+ const current = cache.get(uri);
15
+ if (current && current !== cached) return current.keys;
16
+ if (Date.now() - cached.loaded < refreshInterval) return keys;
17
+ return load(uri, cached);
18
+ }
19
+ function load(uri, previous) {
20
+ const fetched = fetch(uri).then(async (response) => {
21
+ if (!response.ok) throw new HttpError(response.status);
22
+ const body = await response.json();
23
+ if (!isRecord(body) || !Array.isArray(body.keys))
24
+ throw new Error("Invalid signing keys");
25
+ return body.keys.filter(isKey);
26
+ });
27
+ const keys = previous ? fetched.catch(() => previous.keys) : fetched;
28
+ const entry = { keys, expires: Date.now() + lifetime, loaded: Date.now() };
29
+ cache.set(uri, entry);
30
+ keys.catch(() => {
31
+ if (cache.get(uri) === entry) cache.delete(uri);
32
+ });
33
+ return fetched;
34
+ }
35
+ function isKey(value) {
36
+ return isRecord(value) && typeof value.kid === "string";
37
+ }
38
+ export {
39
+ jwks
40
+ };