@lowdefy/server-e2e 0.0.0-experimental-20260909201552 → 0.0.0-experimental-20260910082708

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/client/App.jsx CHANGED
@@ -39,7 +39,7 @@ function ThemeTokenResolver({ lowdefyRef, children }) {
39
39
  }
40
40
 
41
41
  function App({ config }) {
42
- const { rootConfig, user } = config;
42
+ const { rootConfig, session } = config;
43
43
 
44
44
  const usageDataRef = useRef({});
45
45
  const lowdefyRef = useRef({ eventCallback: createLogUsage({ usageDataRef }) });
@@ -111,9 +111,9 @@ function App({ config }) {
111
111
  <AntdApp>
112
112
  <ThemeTokenResolver lowdefyRef={lowdefyRef}>
113
113
  <ErrorBoundary fullPage onError={handleError}>
114
- <Auth user={user}>
114
+ <Auth session={session}>
115
115
  {(auth) => {
116
- usageDataRef.current.user = auth.user?.id;
116
+ usageDataRef.current.user = auth.session?.hashed_id;
117
117
  return <Page auth={auth} config={config} lowdefy={lowdefyRef.current} />;
118
118
  }}
119
119
  </Auth>
@@ -24,46 +24,19 @@ function e2eNotSupported() {
24
24
  throw new Error('Sign-in and sign-out are not supported in e2e testing.');
25
25
  }
26
26
 
27
- function Auth({ children, user }) {
27
+ function Auth({ children, session }) {
28
28
  const auth = {
29
29
  authConfig,
30
- user,
31
- // Matches the BetterAuth client contract: { data, error }.
30
+ session,
32
31
  getSession: async () => {
33
32
  const res = await fetch('/api/auth/session');
34
- if (res.ok) {
35
- return { data: await res.json(), error: null };
36
- }
37
- return { data: null, error: null };
38
- },
39
- getResolvedUser: async () => {
40
- const res = await fetch('/api/user');
41
33
  if (res.ok) {
42
34
  return res.json();
43
35
  }
44
- return { user: null };
36
+ return null;
45
37
  },
46
- acceptInvitation: e2eNotSupported,
47
- addPasskey: e2eNotSupported,
48
- changePassword: e2eNotSupported,
49
- deletePasskey: e2eNotSupported,
50
- updatePasskey: e2eNotSupported,
51
- requestPasswordReset: e2eNotSupported,
52
- resetPassword: e2eNotSupported,
53
- revokeOtherSessions: e2eNotSupported,
54
- sendVerificationEmail: e2eNotSupported,
55
- setActiveOrganization: e2eNotSupported,
56
- signInEmail: e2eNotSupported,
57
- signInMagicLink: e2eNotSupported,
58
- signInOauth2: e2eNotSupported,
59
- signInSocial: e2eNotSupported,
38
+ signIn: e2eNotSupported,
60
39
  signOut: e2eNotSupported,
61
- signUpEmail: e2eNotSupported,
62
- twoFactorDisable: e2eNotSupported,
63
- twoFactorEnable: e2eNotSupported,
64
- twoFactorGenerateBackupCodes: e2eNotSupported,
65
- twoFactorVerifyBackupCode: e2eNotSupported,
66
- twoFactorVerifyTotp: e2eNotSupported,
67
40
  };
68
41
  return children(auth);
69
42
  }
@@ -14,23 +14,22 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- // E2E test-user injection: the caller is read from the lowdefy_e2e_user
17
+ // E2E test-user injection: the session is read from the lowdefy_e2e_user
18
18
  // cookie (base64-encoded JSON user object) instead of a real auth engine.
19
- // The cookie user is pre-resolved - it substitutes for resolveAuthentication
20
- // and its roles are authoritative.
21
- function getUser(c) {
19
+ function getSession(c) {
22
20
  const cookieHeader = c.req.header('cookie') ?? '';
23
21
  const match = cookieHeader.match(/lowdefy_e2e_user=([^;]+)/);
24
22
  if (!match) {
25
- return null;
23
+ return undefined;
26
24
  }
27
25
 
28
26
  try {
29
27
  const decoded = Buffer.from(decodeURIComponent(match[1]), 'base64').toString();
30
- return JSON.parse(decoded);
28
+ const user = JSON.parse(decoded);
29
+ return { user };
31
30
  } catch {
32
- return null;
31
+ return undefined;
33
32
  }
34
33
  }
35
34
 
36
- export default getUser;
35
+ export default getSession;
@@ -15,11 +15,13 @@
15
15
  */
16
16
 
17
17
  function logRequest({ context }) {
18
- const user = context.user ?? {};
18
+ const { user = {} } = context.session ?? {};
19
19
  context.logger.debug({
20
20
  user: {
21
21
  id: user.id,
22
22
  roles: user.roles,
23
+ sub: user.sub,
24
+ session_id: user.session_id,
23
25
  },
24
26
  url: context.req.url,
25
27
  method: context.req.method,
@@ -42,11 +42,11 @@ async function createCustomPluginTypesMap({ directories }) {
42
42
  agents: {},
43
43
  auth: {
44
44
  adapters: {},
45
+ callbacks: {},
46
+ events: {},
45
47
  providers: {},
46
- strategies: {},
47
48
  },
48
49
  blockMetas: {},
49
- connectionMetas: {},
50
50
  blocks: {},
51
51
  connections: {},
52
52
  icons: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/server-e2e",
3
- "version": "0.0.0-experimental-20260909201552",
3
+ "version": "0.0.0-experimental-20260910082708",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Lowdefy e2e testing server with cookie-based user injection",
6
6
  "homepage": "https://lowdefy.com",
@@ -43,30 +43,29 @@
43
43
  "@ant-design/cssinjs": "2.1.2",
44
44
  "@ant-design/x": "2.7.0",
45
45
  "@hono/node-server": "2.0.4",
46
- "@lowdefy/actions-core": "0.0.0-experimental-20260909201552",
47
- "@lowdefy/api": "0.0.0-experimental-20260909201552",
48
- "@lowdefy/block-utils": "0.0.0-experimental-20260909201552",
49
- "@lowdefy/blocks-aggrid": "0.0.0-experimental-20260909201552",
50
- "@lowdefy/blocks-antd": "0.0.0-experimental-20260909201552",
51
- "@lowdefy/blocks-antd-x": "0.0.0-experimental-20260909201552",
52
- "@lowdefy/blocks-basic": "0.0.0-experimental-20260909201552",
53
- "@lowdefy/blocks-captcha": "0.0.0-experimental-20260909201552",
54
- "@lowdefy/blocks-echarts": "0.0.0-experimental-20260909201552",
55
- "@lowdefy/blocks-loaders": "0.0.0-experimental-20260909201552",
56
- "@lowdefy/blocks-markdown": "0.0.0-experimental-20260909201552",
57
- "@lowdefy/blocks-tiptap": "0.0.0-experimental-20260909201552",
58
- "@lowdefy/client": "0.0.0-experimental-20260909201552",
59
- "@lowdefy/connection-axios-http": "0.0.0-experimental-20260909201552",
60
- "@lowdefy/connection-mongodb": "0.0.0-experimental-20260909201552",
61
- "@lowdefy/errors": "0.0.0-experimental-20260909201552",
62
- "@lowdefy/helpers": "0.0.0-experimental-20260909201552",
63
- "@lowdefy/layout": "0.0.0-experimental-20260909201552",
64
- "@lowdefy/logger": "0.0.0-experimental-20260909201552",
65
- "@lowdefy/node-utils": "0.0.0-experimental-20260909201552",
66
- "@lowdefy/operators-js": "0.0.0-experimental-20260909201552",
67
- "@lowdefy/operators-nunjucks": "0.0.0-experimental-20260909201552",
68
- "@lowdefy/operators-uuid": "0.0.0-experimental-20260909201552",
69
- "@lowdefy/websockets-core": "0.0.0-experimental-20260909201552",
46
+ "@lowdefy/actions-core": "0.0.0-experimental-20260910082708",
47
+ "@lowdefy/api": "0.0.0-experimental-20260910082708",
48
+ "@lowdefy/block-utils": "0.0.0-experimental-20260910082708",
49
+ "@lowdefy/blocks-aggrid": "0.0.0-experimental-20260910082708",
50
+ "@lowdefy/blocks-antd": "0.0.0-experimental-20260910082708",
51
+ "@lowdefy/blocks-antd-x": "0.0.0-experimental-20260910082708",
52
+ "@lowdefy/blocks-basic": "0.0.0-experimental-20260910082708",
53
+ "@lowdefy/blocks-echarts": "0.0.0-experimental-20260910082708",
54
+ "@lowdefy/blocks-loaders": "0.0.0-experimental-20260910082708",
55
+ "@lowdefy/blocks-markdown": "0.0.0-experimental-20260910082708",
56
+ "@lowdefy/blocks-tiptap": "0.0.0-experimental-20260910082708",
57
+ "@lowdefy/client": "0.0.0-experimental-20260910082708",
58
+ "@lowdefy/connection-axios-http": "0.0.0-experimental-20260910082708",
59
+ "@lowdefy/connection-mongodb": "0.0.0-experimental-20260910082708",
60
+ "@lowdefy/errors": "0.0.0-experimental-20260910082708",
61
+ "@lowdefy/helpers": "0.0.0-experimental-20260910082708",
62
+ "@lowdefy/layout": "0.0.0-experimental-20260910082708",
63
+ "@lowdefy/logger": "0.0.0-experimental-20260910082708",
64
+ "@lowdefy/node-utils": "0.0.0-experimental-20260910082708",
65
+ "@lowdefy/operators-js": "0.0.0-experimental-20260910082708",
66
+ "@lowdefy/operators-nunjucks": "0.0.0-experimental-20260910082708",
67
+ "@lowdefy/operators-uuid": "0.0.0-experimental-20260910082708",
68
+ "@lowdefy/websockets-core": "0.0.0-experimental-20260910082708",
70
69
  "@tailwindcss/postcss": "4.3.1",
71
70
  "@vitejs/plugin-react": "6.0.2",
72
71
  "antd": "6.3.1",
@@ -83,7 +82,7 @@
83
82
  },
84
83
  "devDependencies": {
85
84
  "@jest/globals": "28.1.3",
86
- "@lowdefy/build": "0.0.0-experimental-20260909201552",
85
+ "@lowdefy/build": "0.0.0-experimental-20260910082708",
87
86
  "@next/eslint-plugin-next": "16.1.6",
88
87
  "@tailwindcss/postcss": "4.3.1",
89
88
  "jest": "28.1.3",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/server-e2e",
3
- "version": "0.0.0-experimental-20260909201552",
3
+ "version": "0.0.0-experimental-20260910082708",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Lowdefy e2e testing server with cookie-based user injection",
6
6
  "homepage": "https://lowdefy.com",
@@ -51,30 +51,29 @@
51
51
  "@ant-design/cssinjs": "2.1.2",
52
52
  "@ant-design/x": "2.7.0",
53
53
  "@hono/node-server": "2.0.4",
54
- "@lowdefy/actions-core": "0.0.0-experimental-20260909201552",
55
- "@lowdefy/api": "0.0.0-experimental-20260909201552",
56
- "@lowdefy/block-utils": "0.0.0-experimental-20260909201552",
57
- "@lowdefy/blocks-aggrid": "0.0.0-experimental-20260909201552",
58
- "@lowdefy/blocks-antd": "0.0.0-experimental-20260909201552",
59
- "@lowdefy/blocks-antd-x": "0.0.0-experimental-20260909201552",
60
- "@lowdefy/blocks-basic": "0.0.0-experimental-20260909201552",
61
- "@lowdefy/blocks-captcha": "0.0.0-experimental-20260909201552",
62
- "@lowdefy/blocks-echarts": "0.0.0-experimental-20260909201552",
63
- "@lowdefy/blocks-loaders": "0.0.0-experimental-20260909201552",
64
- "@lowdefy/blocks-markdown": "0.0.0-experimental-20260909201552",
65
- "@lowdefy/blocks-tiptap": "0.0.0-experimental-20260909201552",
66
- "@lowdefy/client": "0.0.0-experimental-20260909201552",
67
- "@lowdefy/connection-axios-http": "0.0.0-experimental-20260909201552",
68
- "@lowdefy/connection-mongodb": "0.0.0-experimental-20260909201552",
69
- "@lowdefy/errors": "0.0.0-experimental-20260909201552",
70
- "@lowdefy/helpers": "0.0.0-experimental-20260909201552",
71
- "@lowdefy/layout": "0.0.0-experimental-20260909201552",
72
- "@lowdefy/logger": "0.0.0-experimental-20260909201552",
73
- "@lowdefy/node-utils": "0.0.0-experimental-20260909201552",
74
- "@lowdefy/operators-js": "0.0.0-experimental-20260909201552",
75
- "@lowdefy/operators-nunjucks": "0.0.0-experimental-20260909201552",
76
- "@lowdefy/operators-uuid": "0.0.0-experimental-20260909201552",
77
- "@lowdefy/websockets-core": "0.0.0-experimental-20260909201552",
54
+ "@lowdefy/actions-core": "0.0.0-experimental-20260910082708",
55
+ "@lowdefy/api": "0.0.0-experimental-20260910082708",
56
+ "@lowdefy/block-utils": "0.0.0-experimental-20260910082708",
57
+ "@lowdefy/blocks-aggrid": "0.0.0-experimental-20260910082708",
58
+ "@lowdefy/blocks-antd": "0.0.0-experimental-20260910082708",
59
+ "@lowdefy/blocks-antd-x": "0.0.0-experimental-20260910082708",
60
+ "@lowdefy/blocks-basic": "0.0.0-experimental-20260910082708",
61
+ "@lowdefy/blocks-echarts": "0.0.0-experimental-20260910082708",
62
+ "@lowdefy/blocks-loaders": "0.0.0-experimental-20260910082708",
63
+ "@lowdefy/blocks-markdown": "0.0.0-experimental-20260910082708",
64
+ "@lowdefy/blocks-tiptap": "0.0.0-experimental-20260910082708",
65
+ "@lowdefy/client": "0.0.0-experimental-20260910082708",
66
+ "@lowdefy/connection-axios-http": "0.0.0-experimental-20260910082708",
67
+ "@lowdefy/connection-mongodb": "0.0.0-experimental-20260910082708",
68
+ "@lowdefy/errors": "0.0.0-experimental-20260910082708",
69
+ "@lowdefy/helpers": "0.0.0-experimental-20260910082708",
70
+ "@lowdefy/layout": "0.0.0-experimental-20260910082708",
71
+ "@lowdefy/logger": "0.0.0-experimental-20260910082708",
72
+ "@lowdefy/node-utils": "0.0.0-experimental-20260910082708",
73
+ "@lowdefy/operators-js": "0.0.0-experimental-20260910082708",
74
+ "@lowdefy/operators-nunjucks": "0.0.0-experimental-20260910082708",
75
+ "@lowdefy/operators-uuid": "0.0.0-experimental-20260910082708",
76
+ "@lowdefy/websockets-core": "0.0.0-experimental-20260910082708",
78
77
  "@tailwindcss/postcss": "4.3.1",
79
78
  "@vitejs/plugin-react": "6.0.2",
80
79
  "antd": "6.3.1",
@@ -91,7 +90,7 @@
91
90
  },
92
91
  "devDependencies": {
93
92
  "@jest/globals": "28.1.3",
94
- "@lowdefy/build": "0.0.0-experimental-20260909201552",
93
+ "@lowdefy/build": "0.0.0-experimental-20260910082708",
95
94
  "@next/eslint-plugin-next": "16.1.6",
96
95
  "@tailwindcss/postcss": "4.3.1",
97
96
  "jest": "28.1.3",
package/src/app.js CHANGED
@@ -29,7 +29,6 @@ import renderPage from './html/renderPage.js';
29
29
  import requestHandler from './routes/request.js';
30
30
  import sessionMockHandler from './routes/sessionMock.js';
31
31
  import usageHandler from './routes/usage.js';
32
- import userHandler from './routes/user.js';
33
32
  import websocketHandler from './routes/websocket.js';
34
33
 
35
34
  const basePath = lowdefyConfig.basePath ?? '';
@@ -55,7 +54,6 @@ function createApp() {
55
54
  app.all('/api/usage', usageHandler);
56
55
  app.get('/api/websocket', websocketHandler);
57
56
  app.get('/api/page/*', apiPageHandler);
58
- app.get('/api/user', userHandler);
59
57
 
60
58
  // Vite build output (includes public/ via Vite's publicDir copy). Falls
61
59
  // through to the page routes when no file matches.
@@ -28,7 +28,7 @@ const basePath = lowdefyConfig.basePath ?? '';
28
28
  // getStaticProps. The home redirect logic lives here, not at the route level.
29
29
  async function renderPage(c, { pageId, status = 200 }) {
30
30
  const context = c.get('lowdefyContext');
31
- const { logger, user } = context;
31
+ const { logger, session } = context;
32
32
 
33
33
  let resolvedPageId = pageId;
34
34
  const rootConfig = await getRootConfig(context);
@@ -42,12 +42,12 @@ async function renderPage(c, { pageId, status = 200 }) {
42
42
  resolvedPageId = home.pageId;
43
43
  }
44
44
 
45
- const result = await getPageConfig(context, {
45
+ const pageConfig = await getPageConfig(context, {
46
46
  pageId: resolvedPageId,
47
47
  urlQuery: c.req.query(),
48
48
  });
49
49
 
50
- if (result.status !== 'ok') {
50
+ if (!pageConfig) {
51
51
  if (resolvedPageId === '404') {
52
52
  // No 404 page in the build — return a plain 404 rather than redirecting in a loop.
53
53
  return c.text('Page not found.', 404);
@@ -56,8 +56,6 @@ async function renderPage(c, { pageId, status = 200 }) {
56
56
  return c.redirect(`${basePath}/404`, 302);
57
57
  }
58
58
 
59
- const { pageConfig } = result;
60
-
61
59
  logger.info({ event: 'page_view', pageId: resolvedPageId });
62
60
 
63
61
  const html = template({
@@ -69,7 +67,7 @@ async function renderPage(c, { pageId, status = 200 }) {
69
67
  basePath,
70
68
  pageConfig,
71
69
  rootConfig,
72
- user: user ?? null,
70
+ session: session ?? null,
73
71
  },
74
72
  themeConfig,
75
73
  title: pageConfig.properties?.title ?? resolvedPageId,
@@ -15,24 +15,22 @@
15
15
  */
16
16
 
17
17
  import path from 'node:path';
18
- import { createApiContext, normalizeInjectedCaller } from '@lowdefy/api';
18
+ import { createApiContext } from '@lowdefy/api';
19
19
  import { v4 as uuid } from 'uuid';
20
20
 
21
21
  import agents from '../../build/plugins/agents.js';
22
22
  import appMeta from '../../lib/build/appMeta.js';
23
- import authJson from '../../lib/build/auth.js';
24
23
  import config from '../../lib/build/config.js';
25
24
  import connections from '../../build/plugins/connections.js';
26
25
  import createHandleError from '../../lib/server/log/createHandleError.js';
27
26
  import createLogger from '../../lib/server/log/createLogger.js';
28
27
  import fileCache from '../../lib/server/fileCache.js';
29
28
  import getE2eSecrets from '../../lib/server/getE2eSecrets.js';
30
- import getUser from '../../lib/server/auth/getUser.js';
29
+ import getSession from '../../lib/server/auth/session.js';
31
30
  import i18nConfig from '../../lib/build/i18n.js';
32
31
  import jsMap from '../../build/plugins/operators/serverJsMap.js';
33
32
  import logRequest from '../../lib/server/log/logRequest.js';
34
33
  import operators from '../../build/plugins/operators/server.js';
35
- import steps from '../../build/plugins/steps.js';
36
34
  import websockets from '../../build/plugins/websockets.js';
37
35
 
38
36
  const secrets = getE2eSecrets();
@@ -69,23 +67,11 @@ function apiContext() {
69
67
  hostname: c.req.header('host'),
70
68
  },
71
69
  secrets,
72
- steps,
73
70
  websockets,
74
71
  };
75
72
  context.handleError = createHandleError({ context });
76
- // The cookie user is a pre-resolved caller substituting for
77
- // resolveAuthentication - normalizeInjectedCaller floors it to the
78
- // resolved-caller shape. An absent cookie stays a logged-out (null) caller.
79
- const user = getUser(c);
80
- context.user = user ? normalizeInjectedCaller(user) : null;
73
+ context.session = getSession(c);
81
74
  createApiContext(context);
82
- // No auth engine runs in the e2e server, so createApiContext retains no
83
- // organization binding. Derive the policy from the built auth config so
84
- // the tenant wall's policy gate still engages for a tenant app; pinned
85
- // stays null (no engine means no seeded organization row).
86
- if (authJson.organizations) {
87
- context.organization = { policy: authJson.organizations.policy, pinned: null };
88
- }
89
75
  logRequest({ context });
90
76
  c.set('lowdefyContext', context);
91
77
  return next();
@@ -22,6 +22,17 @@ import { HTTPException } from 'hono/http-exception';
22
22
  // JSON the old apiWrapper returned; page routes get a plain 500.
23
23
  function createErrorHandler({ basePath = '', logger }) {
24
24
  return async function errorHandler(error, c) {
25
+ // A route or transport that has already decided its own HTTP answer - hono's
26
+ // HTTPException carries the response to send. The MCP transport refuses an
27
+ // unsupported protocol-version header this way (a 404 with a JSON-RPC body
28
+ // that the client SDK falls back on to negotiate a version both sides
29
+ // know). Not a fault: send its response and log one warning line, no
30
+ // structured error log and no Sentry capture. Turning it into a 500 would
31
+ // hide the status the client acts on.
32
+ if (error instanceof HTTPException) {
33
+ logger.warn(`${error.status} answered by the route: ${c.req.method} ${c.req.path}`);
34
+ return error.getResponse();
35
+ }
25
36
  const path = basePath ? c.req.path.replace(basePath, '') : c.req.path;
26
37
  // Unauthenticated requests to protected endpoints are expected traffic -
27
38
  // one warning line and a 401, skipping the structured error log so
@@ -44,29 +55,6 @@ function createErrorHandler({ basePath = '', logger }) {
44
55
  }
45
56
  return c.text('Forbidden', 403);
46
57
  }
47
- // An authorized caller who has not enrolled a second factor under
48
- // auth.twoFactor.required. 403, not 401 - a 401 reads to the client as a dead
49
- // session and bounces the user to sign-in, which is the loop the gate exists to
50
- // avoid. Expected traffic under required: true, so one warning line and no
51
- // Sentry capture, exactly as for AuthenticationError.
52
- if (error.name === 'TwoFactorEnrolmentRequiredError') {
53
- logger.warn(`Two-factor enrolment required: ${c.req.method} ${c.req.path}`);
54
- if (path.startsWith('/api/')) {
55
- return c.json({ name: error.name, message: error.message }, 403);
56
- }
57
- return c.text('Two-factor enrolment required', 403);
58
- }
59
- // A route or transport that has already decided its own HTTP answer - hono's
60
- // HTTPException carries the response to send. The MCP transport refuses an
61
- // unsupported protocol-version header this way (a 404 with a JSON-RPC body
62
- // that the client SDK falls back on to negotiate a version both sides
63
- // know). Not a fault: send its response and log one warning line, no
64
- // structured error log and no Sentry capture. Turning it into a 500 would
65
- // hide the status the client acts on.
66
- if (error instanceof HTTPException) {
67
- logger.warn(`${error.status} answered by the route: ${c.req.method} ${c.req.path}`);
68
- return error.getResponse();
69
- }
70
58
  const context = c.get('lowdefyContext');
71
59
  if (context) {
72
60
  await context.handleError(error);
@@ -15,7 +15,6 @@
15
15
  */
16
16
 
17
17
  import { Hono } from 'hono';
18
- import { HTTPException } from 'hono/http-exception';
19
18
  import { jest } from '@jest/globals';
20
19
 
21
20
  import createErrorHandler from './errorHandler.js';
@@ -63,15 +62,6 @@ function createAuthorizationError(message) {
63
62
  return error;
64
63
  }
65
64
 
66
- function createTwoFactorEnrolmentRequiredError(message) {
67
- const error = new Error(message);
68
- error.name = 'TwoFactorEnrolmentRequiredError';
69
- error.received = SECRET;
70
- return error;
71
- }
72
-
73
- // Two Error levels, each with a stack (every Error has one) and a recognisable
74
- // `received`, plus a configKey the policy keeps.
75
65
  function createErrorWithCause() {
76
66
  const cause = new Error('Cause message.');
77
67
  cause.received = `${SECRET}-cause`;
@@ -161,69 +151,6 @@ test('errorHandler returns text Forbidden at 403 for an AuthorizationError on a
161
151
  expect(logger.error).not.toHaveBeenCalled();
162
152
  });
163
153
 
164
- test('errorHandler returns 403 with only name and message for a TwoFactorEnrolmentRequiredError on an api path', async () => {
165
- const logger = createLogger();
166
- const context = { handleError: jest.fn() };
167
- const res = await createApp({
168
- context,
169
- error: createTwoFactorEnrolmentRequiredError('Two-factor enrolment required.'),
170
- logger,
171
- }).request('/api/request/getUsers');
172
-
173
- expect(res.status).toEqual(403);
174
- expect(await res.json()).toEqual({
175
- name: 'TwoFactorEnrolmentRequiredError',
176
- message: 'Two-factor enrolment required.',
177
- });
178
- expect(logger.warn).toHaveBeenCalledTimes(1);
179
- expect(logger.warn.mock.calls[0][0]).toMatch(/Two-factor enrolment required/);
180
- expect(logger.error).not.toHaveBeenCalled();
181
- expect(context.handleError).not.toHaveBeenCalled();
182
- });
183
-
184
- test('errorHandler does not send a TwoFactorEnrolmentRequiredError through the redactor', async () => {
185
- const res = await createApp({
186
- error: createTwoFactorEnrolmentRequiredError('Two-factor enrolment required.'),
187
- logger: createLogger(),
188
- }).request('/api/request/getUsers');
189
-
190
- const body = await res.json();
191
- expect(body['~e']).toBeUndefined();
192
- });
193
-
194
- test('errorHandler returns text at 403 for a TwoFactorEnrolmentRequiredError on a page path', async () => {
195
- const logger = createLogger();
196
- const context = { handleError: jest.fn() };
197
- const res = await createApp({
198
- context,
199
- error: createTwoFactorEnrolmentRequiredError('Two-factor enrolment required.'),
200
- logger,
201
- }).request('/home');
202
-
203
- expect(res.status).toEqual(403);
204
- expect(await res.text()).toEqual('Two-factor enrolment required');
205
- expect(logger.warn).toHaveBeenCalledTimes(1);
206
- expect(context.handleError).not.toHaveBeenCalled();
207
- });
208
-
209
- test('errorHandler sends an HTTPException response as-is with one warning and no error log', async () => {
210
- const logger = createLogger();
211
- const error = new HTTPException(404, {
212
- res: Response.json(
213
- { jsonrpc: '2.0', error: { code: -32000, message: 'Unsupported protocol version' } },
214
- { status: 404 }
215
- ),
216
- });
217
- const res = await createApp({ error, logger }).request('/api/mcp', { method: 'POST' });
218
- expect(res.status).toEqual(404);
219
- expect(await res.json()).toEqual({
220
- jsonrpc: '2.0',
221
- error: { code: -32000, message: 'Unsupported protocol version' },
222
- });
223
- expect(logger.warn).toHaveBeenCalledWith('404 answered by the route: POST /api/mcp');
224
- expect(logger.error).not.toHaveBeenCalled();
225
- });
226
-
227
154
  test('errorHandler returns 500 with the serialized error envelope on an api path', async () => {
228
155
  const res = await createApp({
229
156
  error: createErrorWithCause(),
@@ -25,13 +25,13 @@ async function apiPageHandler(c) {
25
25
  const pageId = getPathSegments(c, '/api/page/').join('/');
26
26
  // The client forwards its current query string on the fetch so Dynamic block
27
27
  // resolution sees the same urlQuery as an initial HTML load.
28
- const result = await getPageConfig(context, { pageId, urlQuery: c.req.query() });
29
- if (result.status !== 'ok') {
28
+ const pageConfig = await getPageConfig(context, { pageId, urlQuery: c.req.query() });
29
+ if (!pageConfig) {
30
30
  context.logger.info({ event: 'api_page_not_found', pageId });
31
31
  return c.json({ pageConfig: null }, 404);
32
32
  }
33
33
  context.logger.info({ event: 'api_page_view', pageId });
34
- return c.json({ pageConfig: result.pageConfig });
34
+ return c.json({ pageConfig });
35
35
  }
36
36
 
37
37
  export default apiPageHandler;
@@ -16,10 +16,7 @@
16
16
 
17
17
  async function sessionMockHandler(c) {
18
18
  const context = c.get('lowdefyContext');
19
- if (!context.user) {
20
- return c.json(null);
21
- }
22
- return c.json({ session: { id: 'e2e' }, user: context.user });
19
+ return c.json(context.session ?? {});
23
20
  }
24
21
 
25
22
  export default sessionMockHandler;
@@ -1,27 +0,0 @@
1
- /*
2
- Copyright 2020-2026 Lowdefy, Inc
3
-
4
- Licensed under the Apache License, Version 2.0 (the "License");
5
- you may not use this file except in compliance with the License.
6
- You may obtain a copy of the License at
7
-
8
- http://www.apache.org/licenses/LICENSE-2.0
9
-
10
- Unless required by applicable law or agreed to in writing, software
11
- distributed under the License is distributed on an "AS IS" BASIS,
12
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- See the License for the specific language governing permissions and
14
- limitations under the License.
15
- */
16
-
17
- // The resolved caller for the client - the same resolveAuthentication output
18
- // the first page load embeds. The base session carries neither roles (read
19
- // live from the active member row) nor the merged attributes bag, so the
20
- // client re-syncs from here (UpdateSession) after a session change such as
21
- // SetActiveOrganization.
22
- async function userHandler(c) {
23
- const context = c.get('lowdefyContext');
24
- return c.json({ user: context.user ?? null });
25
- }
26
-
27
- export default userHandler;