@lowdefy/server-dev 0.0.0-experimental-20260714092003 → 0.0.0-experimental-20260714122106

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.
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import React, { useEffect, useReducer, useRef, useState } from 'react';
18
+ import { createPortal } from 'react-dom';
18
19
 
19
20
  import { blockAncestorChain, describeElement, nearestBlock } from './elementInspect.js';
20
21
  import {
@@ -663,7 +664,16 @@ function FeedbackOverlay({ basePath, pageId, onClose }) {
663
664
  } catch {
664
665
  content = null;
665
666
  }
666
- return content;
667
+ // Portal to document.body so the overlay is a top-level sibling of the app
668
+ // root. Rendered inline it inherits the app root's stacking context, and an
669
+ // open antd modal (portalled to body, zIndex ~1000) then paints above the
670
+ // overlay regardless of its own zIndex — making the comment textarea
671
+ // unclickable. As a body sibling its zIndex competes in the root stacking
672
+ // context and wins.
673
+ if (typeof document === 'undefined' || !document.body) {
674
+ return content;
675
+ }
676
+ return createPortal(content, document.body);
667
677
  }
668
678
 
669
679
  // Pure render function kept outside the hook-bearing component body so a
@@ -776,7 +786,11 @@ function renderOverlayContent({
776
786
  <div style={descriptorRow}>{state.draftShapes.length} shape(s) drawn</div>
777
787
  )}
778
788
  <div style={buttonRow}>
779
- <button type="button" style={secondaryButton} onClick={() => dispatch({ type: 'DISCARD_DRAFT' })}>
789
+ <button
790
+ type="button"
791
+ style={secondaryButton}
792
+ onClick={() => dispatch({ type: 'DISCARD_DRAFT' })}
793
+ >
780
794
  Cancel
781
795
  </button>
782
796
  <button type="button" style={primaryButton} onClick={handleSave}>
@@ -823,10 +837,18 @@ function renderOverlayContent({
823
837
  </label>
824
838
  {state.sendError && <div style={errorRow}>{state.sendError}</div>}
825
839
  <div style={buttonRow}>
826
- <button type="button" style={dangerButton} onClick={() => dispatch({ type: 'DISCARD_ALL' })}>
840
+ <button
841
+ type="button"
842
+ style={dangerButton}
843
+ onClick={() => dispatch({ type: 'DISCARD_ALL' })}
844
+ >
827
845
  Discard all
828
846
  </button>
829
- <button type="button" style={secondaryButton} onClick={() => dispatch({ type: 'ADD_ANOTHER' })}>
847
+ <button
848
+ type="button"
849
+ style={secondaryButton}
850
+ onClick={() => dispatch({ type: 'ADD_ANOTHER' })}
851
+ >
830
852
  Add another
831
853
  </button>
832
854
  <button
@@ -14,8 +14,6 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- /* eslint-disable react/jsx-props-no-spreading */
18
-
19
17
  import React from 'react';
20
18
  import AuthConfigured from './AuthConfigured.jsx';
21
19
  import AuthNotConfigured from './AuthNotConfigured.js';
@@ -25,15 +23,15 @@ import { serializer } from '@lowdefy/helpers';
25
23
  // Client code imports the build JSON directly — Vite handles JSON imports;
26
24
  // the lib/build/*.js wrappers are server-only (they read from disk).
27
25
  // Deserialize to restore arrays from their ~arr build markers (providers
28
- // must be a real array for single-provider inference in createAuthMethods).
26
+ // must be a real array for provider lookups in createAuthMethods).
29
27
  import rawAuthConfig from '../../../build/auth.json';
30
28
 
31
29
  const authConfig = serializer.deserialize(rawAuthConfig);
32
30
 
33
- function Auth({ children, session }) {
31
+ function Auth({ children, user }) {
34
32
  if (authConfig.configured === true) {
35
33
  return (
36
- <AuthConfigured serverSession={session} authConfig={authConfig}>
34
+ <AuthConfigured serverUser={user} authConfig={authConfig}>
37
35
  {(auth) => children(auth)}
38
36
  </AuthConfigured>
39
37
  );
@@ -14,17 +14,17 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- /* eslint-disable react/jsx-props-no-spreading */
18
-
19
17
  import React, { useEffect, useRef } from 'react';
18
+ import { createAuthClient } from 'better-auth/react';
20
19
  import {
21
- authConfigManager,
22
- getSession,
23
- SessionProvider,
24
- signIn,
25
- signOut,
26
- useSession,
27
- } from '@hono/auth-js/react';
20
+ adminClient,
21
+ genericOAuthClient,
22
+ magicLinkClient,
23
+ organizationClient,
24
+ phoneNumberClient,
25
+ twoFactorClient,
26
+ } from 'better-auth/client/plugins';
27
+ import { passkeyClient } from '@better-auth/passkey/client';
28
28
 
29
29
  import { serializer } from '@lowdefy/helpers';
30
30
 
@@ -32,42 +32,122 @@ import rawLowdefyConfig from '../../../build/config.json';
32
32
 
33
33
  const lowdefyConfig = serializer.deserialize(rawLowdefyConfig);
34
34
 
35
- // @hono/auth-js/react configures its fetch paths through a module-level
36
- // manager instead of SessionProvider props.
37
- if (lowdefyConfig.basePath) {
38
- authConfigManager.setConfig({ basePath: `${lowdefyConfig.basePath}/api/auth` });
39
- }
35
+ const authClient = createAuthClient({
36
+ baseURL: `${window.location.origin}${lowdefyConfig.basePath ?? ''}/api/auth`,
37
+ plugins: [
38
+ adminClient(),
39
+ genericOAuthClient(),
40
+ magicLinkClient(),
41
+ organizationClient(),
42
+ passkeyClient(),
43
+ phoneNumberClient(),
44
+ twoFactorClient(),
45
+ ],
46
+ });
40
47
 
41
- function Session({ children }) {
42
- const wasAuthenticated = useRef(false);
43
- const { data: session, status } = useSession();
44
- wasAuthenticated.current = wasAuthenticated.current || status === 'authenticated';
48
+ // The server resolves the caller per request and embeds it in the page
49
+ // config, so the first render never flashes unauthenticated. The BetterAuth
50
+ // client store takes over once its session fetch settles.
51
+ //
52
+ // Roles and merged attributes resolve server-side from the active member row
53
+ // - the base session carries neither. The last server-resolved caller is
54
+ // kept in a ref: while the session user is unchanged its roles and
55
+ // attributes stay authoritative, and UpdateSession refreshes the ref from
56
+ // /api/user after a change (e.g. SetActiveOrganization).
57
+ function Session({ children, reloadSuppressedRef, serverUser }) {
58
+ const { data: session, isPending } = authClient.useSession();
59
+ const resolvedUserRef = useRef(serverUser);
60
+ const wasAuthenticated = useRef(Boolean(serverUser));
45
61
 
46
62
  useEffect(() => {
47
- if (wasAuthenticated.current && status === 'unauthenticated') {
63
+ if (session) {
64
+ wasAuthenticated.current = true;
65
+ }
66
+ // Reload after sign-out (or session revocation) so the server can apply
67
+ // the page auth fork - a protected page redirects to the login page.
68
+ // A logout with a callbackUrl suppresses the reload - its own navigation
69
+ // would otherwise race this one.
70
+ if (wasAuthenticated.current && !isPending && !session && !reloadSuppressedRef.current) {
48
71
  window.location.reload();
49
72
  }
50
- }, [status]);
73
+ }, [session, isPending]);
51
74
 
52
- // If session is passed to SessionProvider from the server-rendered config
53
- // we won't have a loading state here, but unauthenticated first loads do.
54
- if (status === 'loading') {
55
- return '';
75
+ if (isPending) {
76
+ return children(resolvedUserRef.current, resolvedUserRef);
77
+ }
78
+ if (!session?.user) {
79
+ return children(null, resolvedUserRef);
56
80
  }
57
- return children(session);
81
+ const resolved = resolvedUserRef.current;
82
+ const user =
83
+ resolved && resolved.id === session.user.id
84
+ ? { ...session.user, roles: resolved.roles, attributes: resolved.attributes }
85
+ : { roles: [], ...session.user };
86
+ return children(user, resolvedUserRef);
58
87
  }
59
88
 
60
- function AuthConfigured({ authConfig, children, serverSession }) {
61
- const auth = { authConfig, getSession, signIn, signOut };
89
+ function AuthConfigured({ authConfig, children, serverUser }) {
90
+ const reloadSuppressedRef = useRef(false);
91
+ const auth = {
92
+ authConfig,
93
+ getSession: ({ disableCookieCache } = {}) =>
94
+ authClient.getSession(disableCookieCache ? { query: { disableCookieCache: true } } : {}),
95
+ // The server-resolved caller - roles from the active member row and the
96
+ // merged attributes bag - for re-syncing after session changes.
97
+ getResolvedUser: async () => {
98
+ const response = await fetch(`${lowdefyConfig.basePath ?? ''}/api/user`, {
99
+ credentials: 'same-origin',
100
+ });
101
+ if (!response.ok) {
102
+ // Throw instead of parsing an error page - a silent { user: undefined }
103
+ // would read as a client-side logout in updateSession.
104
+ throw new Error(`Failed to fetch the resolved user (HTTP ${response.status}).`);
105
+ }
106
+ return response.json();
107
+ },
108
+ suppressSignOutReload: () => {
109
+ reloadSuppressedRef.current = true;
110
+ },
111
+ acceptInvitation: (params) => authClient.organization.acceptInvitation(params),
112
+ // addPasskey runs the WebAuthn browser ceremony itself - options fetch,
113
+ // authenticator prompt, verification.
114
+ addPasskey: (params) => authClient.passkey.addPasskey(params),
115
+ changePassword: (params) => authClient.changePassword(params),
116
+ deletePasskey: (params) => authClient.passkey.deletePasskey(params),
117
+ impersonateUser: (params) => authClient.admin.impersonateUser(params),
118
+ phoneNumberRequestPasswordReset: (params) =>
119
+ authClient.phoneNumber.requestPasswordReset(params),
120
+ phoneNumberResetPassword: (params) => authClient.phoneNumber.resetPassword(params),
121
+ phoneNumberSendOtp: (params) => authClient.phoneNumber.sendOtp(params),
122
+ phoneNumberVerify: (params) => authClient.phoneNumber.verify(params),
123
+ requestPasswordReset: (params) => authClient.requestPasswordReset(params),
124
+ resetPassword: (params) => authClient.resetPassword(params),
125
+ revokeOtherSessions: () => authClient.revokeOtherSessions(),
126
+ sendVerificationEmail: (params) => authClient.sendVerificationEmail(params),
127
+ setActiveOrganization: (params) => authClient.organization.setActive(params),
128
+ signInEmail: (params) => authClient.signIn.email(params),
129
+ signInMagicLink: (params) => authClient.signIn.magicLink(params),
130
+ signInOauth2: (params) => authClient.signIn.oauth2(params),
131
+ signInPhoneNumber: (params) => authClient.signIn.phoneNumber(params),
132
+ signInSocial: (params) => authClient.signIn.social(params),
133
+ signOut: () => authClient.signOut(),
134
+ signUpEmail: (params) => authClient.signUp.email(params),
135
+ stopImpersonating: () => authClient.admin.stopImpersonating(),
136
+ twoFactorDisable: (params) => authClient.twoFactor.disable(params),
137
+ twoFactorEnable: (params) => authClient.twoFactor.enable(params),
138
+ twoFactorVerifyBackupCode: (params) => authClient.twoFactor.verifyBackupCode(params),
139
+ twoFactorVerifyTotp: (params) => authClient.twoFactor.verifyTotp(params),
140
+ };
62
141
  return (
63
- <SessionProvider session={serverSession}>
64
- <Session>
65
- {(session) => {
66
- auth.session = session;
67
- return children(auth);
68
- }}
69
- </Session>
70
- </SessionProvider>
142
+ <Session reloadSuppressedRef={reloadSuppressedRef} serverUser={serverUser}>
143
+ {(user, resolvedUserRef) => {
144
+ auth.user = user;
145
+ auth.updateResolvedUser = (resolved) => {
146
+ resolvedUserRef.current = resolved;
147
+ };
148
+ return children(auth);
149
+ }}
150
+ </Session>
71
151
  );
72
152
  }
73
153
 
@@ -13,7 +13,6 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
- /* eslint-disable react/jsx-props-no-spreading */
17
16
 
18
17
  function authNotConfigured() {
19
18
  throw new Error('Auth not configured.');
@@ -22,9 +21,30 @@ function authNotConfigured() {
22
21
  function AuthNotConfigured({ authConfig, children }) {
23
22
  const auth = {
24
23
  authConfig,
24
+ user: null,
25
+ acceptInvitation: authNotConfigured,
26
+ addPasskey: authNotConfigured,
27
+ changePassword: authNotConfigured,
28
+ deletePasskey: authNotConfigured,
29
+ getResolvedUser: authNotConfigured,
25
30
  getSession: authNotConfigured,
26
- signIn: authNotConfigured,
31
+ impersonateUser: authNotConfigured,
32
+ requestPasswordReset: authNotConfigured,
33
+ resetPassword: authNotConfigured,
34
+ revokeOtherSessions: authNotConfigured,
35
+ sendVerificationEmail: authNotConfigured,
36
+ setActiveOrganization: authNotConfigured,
37
+ signInEmail: authNotConfigured,
38
+ signInMagicLink: authNotConfigured,
39
+ signInOauth2: authNotConfigured,
40
+ signInSocial: authNotConfigured,
27
41
  signOut: authNotConfigured,
42
+ signUpEmail: authNotConfigured,
43
+ stopImpersonating: authNotConfigured,
44
+ twoFactorDisable: authNotConfigured,
45
+ twoFactorEnable: authNotConfigured,
46
+ twoFactorVerifyBackupCode: authNotConfigured,
47
+ twoFactorVerifyTotp: authNotConfigured,
28
48
  };
29
49
 
30
50
  return children(auth);
@@ -54,6 +54,13 @@ async function fetchPageConfig(url) {
54
54
  if (res.status === 404) {
55
55
  return null;
56
56
  }
57
+ if (res.status === 401) {
58
+ // Logged-out navigation to a protected page - full load to the login
59
+ // page so it can return here after sign-in.
60
+ const { redirect } = await res.json();
61
+ window.location.assign(redirect ?? '/404');
62
+ return new Promise(() => {});
63
+ }
57
64
  const data = await res.json();
58
65
  if (data?.buildError) {
59
66
  return data;
@@ -18,6 +18,7 @@ import { chromium } from 'playwright-core';
18
18
  import { type } from '@lowdefy/helpers';
19
19
 
20
20
  import lowdefyConfig from '../build/config.js';
21
+ import { HEADLESS_USER_COOKIE, headlessUser } from '../server/auth/headlessUser.js';
21
22
 
22
23
  // playwright-core does not bundle a browser (unlike @playwright/test) — it
23
24
  // only drives one that is already installed. `channel: 'chrome'` picks up a
@@ -73,6 +74,16 @@ function buildPageUrl({ origin, pageId }) {
73
74
  async function openPage({ browser, origin, pageId, width = 1280, height = 800, timeout = 15000 }) {
74
75
  const url = buildPageUrl({ origin, pageId });
75
76
  const context = await browser.newContext({ viewport: { width, height } });
77
+ // Inject an authenticated user so auth-protected pages don't 404 for the
78
+ // cookieless headless context. Mirrors the e2e user-cookie pattern; scoped to
79
+ // `origin` so it rides along on the same-origin /api/* fetches.
80
+ await context.addCookies([
81
+ {
82
+ name: HEADLESS_USER_COOKIE,
83
+ value: Buffer.from(JSON.stringify(headlessUser)).toString('base64'),
84
+ url: origin,
85
+ },
86
+ ]);
76
87
  const page = await context.newPage();
77
88
  try {
78
89
  await page.goto(url, { waitUntil: 'networkidle', timeout });
@@ -0,0 +1,68 @@
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
+ import path from 'node:path';
18
+ import { createSystemContext as buildSystemContext } from '@lowdefy/api';
19
+ import { getSecretsFromEnv } from '@lowdefy/node-utils';
20
+ import { v4 as uuid } from 'uuid';
21
+
22
+ import agents from '../../../build/plugins/agents.js';
23
+ import appMeta from '../../build/appMeta.js';
24
+ import config from '../../build/config.js';
25
+ import connections from '../../../build/plugins/connections.js';
26
+ import createHandleError from '../log/createHandleError.js';
27
+ import createLogger from '../log/createLogger.js';
28
+ import fileCache from '../fileCache.js';
29
+ import i18nConfig from '../../build/i18n.js';
30
+ import loadDynamicJsMap from '../loadDynamicJsMap.js';
31
+ import operators from '../../../build/plugins/operators/server.js';
32
+ import steps from '../../../build/plugins/steps.js';
33
+ import websockets from '../../../build/plugins/websockets.js';
34
+
35
+ const secrets = getSecretsFromEnv();
36
+
37
+ // Builds a fresh system context per auth hook fire - the trusted internal
38
+ // caller the BetterAuth engine uses to invoke hook endpoints. All fields are
39
+ // startup singletons except the per-fire rid, logger, handleError, the
40
+ // dynamic jsMap re-read (JIT rebuilds rewrite serverJsMap.js), and auth -
41
+ // auth is threaded in by the caller (createHookDispatch) rather than
42
+ // imported directly, since it comes from the same BetterAuth construction
43
+ // that this factory is a dependency of (see createHookDispatch.js).
44
+ function createSystemContext({ auth } = {}) {
45
+ const rid = uuid();
46
+ const buildDirectory = path.join(process.cwd(), 'build');
47
+ return buildSystemContext({
48
+ rid,
49
+ agents,
50
+ appMeta,
51
+ auth,
52
+ buildDirectory,
53
+ config,
54
+ configDirectory: process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd(),
55
+ connections,
56
+ createHandleError,
57
+ fileCache,
58
+ i18n: i18nConfig,
59
+ jsMap: loadDynamicJsMap(buildDirectory),
60
+ logger: createLogger({ rid }),
61
+ operators,
62
+ secrets,
63
+ steps,
64
+ websockets,
65
+ });
66
+ }
67
+
68
+ export default createSystemContext;
@@ -0,0 +1,46 @@
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
+ import { getBetterAuth } from '@lowdefy/api';
18
+ import { getSecretsFromEnv } from '@lowdefy/node-utils';
19
+
20
+ import adapters from '../../../build/plugins/auth/adapters.js';
21
+ import appMeta from '../../build/appMeta.js';
22
+ import authJson from '../../build/auth.js';
23
+ import createSystemContext from './createSystemContext.js';
24
+ import lowdefyConfig from '../../build/config.js';
25
+ import providers from '../../../build/plugins/auth/providers.js';
26
+
27
+ // Returns the BetterAuth instance (memoized in @lowdefy/api), or null when
28
+ // auth is not configured. dev: true relaxes cookies for plain-http localhost
29
+ // and derives a per-app cookie prefix so parallel dev apps do not collide.
30
+ function getAuth({ logger }) {
31
+ if (authJson.configured !== true) {
32
+ return null;
33
+ }
34
+ return getBetterAuth({
35
+ appMeta,
36
+ authJson,
37
+ config: lowdefyConfig,
38
+ createSystemContext,
39
+ dev: true,
40
+ logger,
41
+ plugins: { adapters, providers },
42
+ secrets: getSecretsFromEnv(),
43
+ });
44
+ }
45
+
46
+ export default getAuth;
@@ -0,0 +1,39 @@
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
+ import { HEADLESS_USER_COOKIE } from './headlessUser.js';
18
+
19
+ // Mirrors the e2e user-injection pattern (server-e2e/lib/server/auth/session.js):
20
+ // the headless renderer sets a base64-JSON user cookie on its own browser
21
+ // context, so its /api/* fetches carry a session while the developer's real
22
+ // browser (no cookie) is unaffected.
23
+ function getHeadlessSession(c) {
24
+ const cookieHeader = c.req.header('cookie') ?? '';
25
+ const match = cookieHeader.match(new RegExp(`${HEADLESS_USER_COOKIE}=([^;]+)`));
26
+ if (!match) {
27
+ return undefined;
28
+ }
29
+
30
+ try {
31
+ const decoded = Buffer.from(decodeURIComponent(match[1]), 'base64').toString();
32
+ const user = JSON.parse(decoded);
33
+ return { user };
34
+ } catch {
35
+ return undefined;
36
+ }
37
+ }
38
+
39
+ export default getHeadlessSession;
@@ -14,13 +14,15 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { createSessionCallback } from '@lowdefy/api';
18
17
  import { serializer } from '@lowdefy/helpers';
19
18
 
20
19
  import authJson from '../../build/auth.js';
21
- import callbacks from '../../../build/plugins/auth/callbacks.js';
22
20
 
23
- async function getMockSession() {
21
+ // dev.mockUser is a pre-resolved caller that substitutes for the whole
22
+ // resolveAuthentication step - no auth engine, no session lookup, no
23
+ // membership wall. Its roles are authoritative. LOWDEFY_DEV_USER takes
24
+ // priority over auth.dev.mockUser from lowdefy.yaml.
25
+ function getMockUser() {
24
26
  const mockUserJson = process.env.LOWDEFY_DEV_USER;
25
27
  let mockUser;
26
28
 
@@ -35,7 +37,7 @@ async function getMockSession() {
35
37
  }
36
38
 
37
39
  if (!mockUser) {
38
- return undefined;
40
+ return null;
39
41
  }
40
42
 
41
43
  // Deserialize to restore arrays from ~arr markers and remove other build markers
@@ -48,20 +50,10 @@ async function getMockSession() {
48
50
  );
49
51
  }
50
52
 
51
- // Create session callback to transform mock user
52
- const sessionCallback = createSessionCallback({
53
- authConfig: authJson,
54
- plugins: { callbacks },
55
- });
56
-
57
- // Transform mock user through session callback (mock user acts as token)
58
- const session = await sessionCallback({
59
- session: { user: {} },
60
- token: mockUser,
61
- user: mockUser,
62
- });
63
-
64
- return session;
53
+ if (!mockUser.roles) {
54
+ mockUser.roles = [];
55
+ }
56
+ return mockUser;
65
57
  }
66
58
 
67
- export default getMockSession;
59
+ export default getMockUser;
@@ -14,25 +14,27 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { getAuthConfig as getApiAuthConfig } from '@lowdefy/api';
17
+ import { getAuthStrategies } from '@lowdefy/api';
18
18
  import { getSecretsFromEnv } from '@lowdefy/node-utils';
19
19
 
20
- import adapters from '../../../build/plugins/auth/adapters.js';
21
20
  import appMeta from '../../build/appMeta.js';
22
21
  import authJson from '../../build/auth.js';
23
- import callbacks from '../../../build/plugins/auth/callbacks.js';
24
- import events from '../../../build/plugins/auth/events.js';
25
- import providers from '../../../build/plugins/auth/providers.js';
22
+ import strategyTypes from '../../../build/plugins/auth/strategies.js';
26
23
 
27
- function getAuthConfig({ logger }) {
28
- return getApiAuthConfig({
24
+ // Returns the API auth strategy verifiers (memoized in @lowdefy/api), or an
25
+ // empty list when auth is not configured - resolveAuthentication tries them
26
+ // in config order when no session resolves.
27
+ function getStrategies({ logger }) {
28
+ if (authJson.configured !== true) {
29
+ return [];
30
+ }
31
+ return getAuthStrategies({
29
32
  appMeta,
30
33
  authJson,
31
- dev: true,
32
34
  logger,
33
- plugins: { adapters, callbacks, events, providers },
35
+ plugins: { strategies: strategyTypes },
34
36
  secrets: getSecretsFromEnv(),
35
37
  });
36
38
  }
37
39
 
38
- export default getAuthConfig;
40
+ export default getStrategies;
@@ -0,0 +1,29 @@
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
+ // Shared between the headless renderer (getBrowser.js, which sets the cookie on
18
+ // its browser context) and the dev getSession (getHeadlessSession.js, which
19
+ // decodes it) so the cookie name and injected user shape live in one place.
20
+ const HEADLESS_USER_COOKIE = 'lowdefy_headless_user';
21
+
22
+ const headlessUser = {
23
+ sub: 'lowdefy-headless',
24
+ id: 'lowdefy-headless',
25
+ name: 'Lowdefy Headless',
26
+ roles: [],
27
+ };
28
+
29
+ export { HEADLESS_USER_COOKIE, headlessUser };