@lowdefy/api 5.6.0 → 6.0.0

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 (47) hide show
  1. package/dist/context/createAuthorize.js +4 -1
  2. package/dist/context/resolveStrategyCaller.js +56 -0
  3. package/dist/index.js +12 -2
  4. package/dist/routes/agent/callAgent.js +7 -184
  5. package/dist/routes/agent/prepareAgent.js +173 -0
  6. package/dist/routes/auth/createLogger.js +2 -5
  7. package/dist/routes/auth/createPrefixedCookies.js +52 -0
  8. package/dist/routes/auth/{getNextAuthConfig.js → getAuthConfig.js} +27 -16
  9. package/dist/routes/auth/resolveCookies.js +37 -0
  10. package/dist/routes/auth/strategies/createAuthStrategies.js +77 -0
  11. package/dist/routes/auth/strategies/getAuthStrategies.js +31 -0
  12. package/dist/routes/endpoints/authorizeApiEndpoint.js +8 -2
  13. package/dist/routes/endpoints/callEndpoint.js +26 -3
  14. package/dist/routes/endpoints/control/controlReject.js +3 -1
  15. package/dist/routes/endpoints/control/controlThrow.js +4 -1
  16. package/dist/routes/endpoints/findSchedule.js +35 -0
  17. package/dist/routes/endpoints/forwardScheduledEndpoint.js +106 -0
  18. package/dist/routes/endpoints/getEndpointConfig.js +9 -3
  19. package/dist/routes/endpoints/getEnvironmentSchedules.js +29 -0
  20. package/dist/routes/endpoints/handleAgentCall.js +83 -0
  21. package/dist/routes/endpoints/handleEndpointCall.js +49 -1
  22. package/dist/routes/endpoints/handleRenderNotification.js +189 -0
  23. package/dist/routes/endpoints/handleValidateSchema.js +3 -1
  24. package/dist/routes/endpoints/isUnauthenticatedHuman.js +31 -0
  25. package/dist/routes/endpoints/resolveCronEnvironment.js +37 -0
  26. package/dist/routes/endpoints/runDetachedEndpoint.js +66 -0
  27. package/dist/routes/endpoints/runRoutine.js +12 -0
  28. package/dist/routes/endpoints/runScheduledEndpoint.js +104 -0
  29. package/dist/routes/endpoints/runWebhookEndpoint.js +83 -0
  30. package/dist/routes/endpoints/scheduleBackground.js +48 -0
  31. package/dist/routes/mcp/createMcpServer.js +160 -0
  32. package/dist/routes/notifications/derivePreview.js +30 -0
  33. package/dist/routes/notifications/getNotificationConfig.js +32 -0
  34. package/dist/routes/notifications/resolveNotificationLinks.js +72 -0
  35. package/dist/routes/notifications/resolveThemeLogo.js +35 -0
  36. package/dist/routes/page/dynamic/resolveDynamicContent.js +172 -0
  37. package/dist/routes/page/dynamic/unescapeOperators.js +36 -0
  38. package/dist/routes/page/dynamic/validateFragment.js +104 -0
  39. package/dist/routes/page/getPageConfig.js +16 -6
  40. package/dist/routes/websocket/authorizeWebsocket.js +27 -0
  41. package/dist/routes/websocket/createChannelRegistry.js +269 -0
  42. package/dist/routes/websocket/createWebSocketConnection.js +131 -0
  43. package/dist/routes/websocket/getWebsocketConfig.js +30 -0
  44. package/dist/routes/websocket/getWebsocketResolver.js +33 -0
  45. package/dist/routes/websocket/prepareChannel.js +72 -0
  46. package/dist/test/testContext.js +4 -2
  47. package/package.json +12 -10
@@ -13,7 +13,7 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { ConfigError } from '@lowdefy/errors';
16
- function createAuthorize({ session }) {
16
+ function createAuthorize({ session, system = false }) {
17
17
  // Next-auth getSession provides a session object if the user is authenticated
18
18
  // else session will be null
19
19
  const authenticated = !!session;
@@ -24,6 +24,9 @@ function createAuthorize({ session }) {
24
24
  });
25
25
  }
26
26
  function authorize(config) {
27
+ // A system context (scheduled, webhook, detached runs) was authorized at the
28
+ // transport layer, so nested endpoint calls are never gated on a user session.
29
+ if (system === true) return true;
27
30
  const { auth } = config;
28
31
  if (auth.public === true) return true;
29
32
  if (auth.public === false) {
@@ -0,0 +1,56 @@
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
+ */ import { type } from '@lowdefy/helpers';
16
+ // Tries the configured API auth strategies in config order; the first
17
+ // verifier match wins. A strategy caller is a config-derived, org-less
18
+ // principal: roles are the strategy's static grant unioned with any
19
+ // claim-derived roles, and attributes are the static bag shallow-merged
20
+ // with claim-mapped values (claim wins), so _user.attributes reads the same
21
+ // for session and strategy callers.
22
+ async function resolveStrategyCaller({ headers, logger, strategies }) {
23
+ for (const strategy of strategies ?? []){
24
+ const match = await strategy.verify({
25
+ headers,
26
+ logger
27
+ });
28
+ if (type.isNone(match)) {
29
+ continue;
30
+ }
31
+ const roles = [
32
+ ...new Set([
33
+ ...strategy.roles,
34
+ ...match.roles ?? []
35
+ ])
36
+ ];
37
+ const attributes = {
38
+ ...strategy.attributes,
39
+ ...match.attributes ?? {}
40
+ };
41
+ logger.debug({
42
+ event: 'auth_strategy_authenticated',
43
+ authMethod: strategy.type,
44
+ strategyId: strategy.id
45
+ }, `Request authenticated by auth strategy "${strategy.id}" (${strategy.type}).`);
46
+ return {
47
+ ...match.user,
48
+ authMethod: strategy.type,
49
+ strategyId: strategy.id,
50
+ roles,
51
+ attributes
52
+ };
53
+ }
54
+ return null;
55
+ }
56
+ export default resolveStrategyCaller;
package/dist/index.js CHANGED
@@ -15,14 +15,24 @@
15
15
  */ import buildEndpointResult from './response/buildEndpointResult.js';
16
16
  import callAgent from './routes/agent/callAgent.js';
17
17
  import callEndpoint from './routes/endpoints/callEndpoint.js';
18
+ import getEndpointConfig from './routes/endpoints/getEndpointConfig.js';
18
19
  import callRequest from './routes/request/callRequest.js';
19
20
  import createApiContext from './context/createApiContext.js';
21
+ import createChannelRegistry from './routes/websocket/createChannelRegistry.js';
22
+ import createMcpServer from './routes/mcp/createMcpServer.js';
23
+ import createWebSocketConnection from './routes/websocket/createWebSocketConnection.js';
20
24
  import createSessionCallback from './routes/auth/callbacks/createSessionCallback.js';
25
+ import getAuthConfig from './routes/auth/getAuthConfig.js';
26
+ import getAuthStrategies from './routes/auth/strategies/getAuthStrategies.js';
21
27
  import getHomeAndMenus from './routes/rootConfig/getHomeAndMenus.js';
22
- import getNextAuthConfig from './routes/auth/getNextAuthConfig.js';
28
+ import resolveStrategyCaller from './context/resolveStrategyCaller.js';
23
29
  import getPageConfig from './routes/page/getPageConfig.js';
24
30
  import getRootConfig from './routes/rootConfig/getRootConfig.js';
25
31
  import logClientError from './routes/log/logClientError.js';
26
32
  import redactErrorResponse from './response/redactErrorResponse.js';
27
33
  import redactResponse from './response/redactResponse.js';
28
- export { buildEndpointResult, callAgent, callEndpoint, callRequest, createApiContext, createSessionCallback, getHomeAndMenus, getNextAuthConfig, getPageConfig, getRootConfig, logClientError, redactErrorResponse, redactResponse };
34
+ import forwardScheduledEndpoint from './routes/endpoints/forwardScheduledEndpoint.js';
35
+ import runDetachedEndpoint from './routes/endpoints/runDetachedEndpoint.js';
36
+ import runWebhookEndpoint from './routes/endpoints/runWebhookEndpoint.js';
37
+ import runScheduledEndpoint from './routes/endpoints/runScheduledEndpoint.js';
38
+ export { buildEndpointResult, callAgent, callEndpoint, getEndpointConfig, callRequest, createApiContext, createChannelRegistry, createMcpServer, createSessionCallback, createWebSocketConnection, getAuthConfig, getAuthStrategies, getHomeAndMenus, getPageConfig, getRootConfig, logClientError, redactErrorResponse, redactResponse, resolveStrategyCaller, forwardScheduledEndpoint, runDetachedEndpoint, runWebhookEndpoint, runScheduledEndpoint };
@@ -12,17 +12,8 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ import { type } from '@lowdefy/helpers';
16
- import buildEndpointResult from '../../response/buildEndpointResult.js';
17
- import createEvaluateOperators from '../../context/createEvaluateOperators.js';
18
- import authorizeAgent from './authorizeAgent.js';
19
- import authorizeApiEndpoint from '../endpoints/authorizeApiEndpoint.js';
20
- import getEndpointConfig from '../endpoints/getEndpointConfig.js';
21
- import runRoutine from '../endpoints/runRoutine.js';
22
- import getAgentConfig from './getAgentConfig.js';
23
- import getAgentResolver from './getAgentResolver.js';
24
- import getConnectionConfig from '../connections/getConnectionConfig.js';
25
- import getConnection from '../connections/getConnection.js';
15
+ */ import createEvaluateOperators from '../../context/createEvaluateOperators.js';
16
+ import prepareAgent from './prepareAgent.js';
26
17
  async function callAgent(context, { agentId, pageId, messages, conversationId, urlQuery, sharedState }) {
27
18
  const { logger } = context;
28
19
  context.pageId = pageId;
@@ -32,12 +23,6 @@ async function callAgent(context, { agentId, pageId, messages, conversationId, u
32
23
  agentId,
33
24
  pageId
34
25
  });
35
- const agentConfig = await getAgentConfig(context, {
36
- agentId
37
- });
38
- authorizeAgent(context, {
39
- agentConfig
40
- });
41
26
  const agentContext = {
42
27
  conversationId: conversationId ?? undefined,
43
28
  pageId,
@@ -45,174 +30,12 @@ async function callAgent(context, { agentId, pageId, messages, conversationId, u
45
30
  urlQuery: urlQuery ?? {},
46
31
  userId: context.user?.sub ?? context.user?.id ?? null
47
32
  };
48
- // Evaluate operators in agent properties (e.g. _user, _secret, _payload)
49
- agentConfig.properties = context.evaluateOperators({
50
- input: agentConfig.properties ?? {},
51
- location: agentConfig.agentId,
52
- payload: agentContext,
53
- state: {},
54
- steps: {}
55
- });
56
- // Load connection config from build artifacts using agent's connectionId
57
- const connectionConfig = await getConnectionConfig(context, {
58
- connectionId: agentConfig.connectionId,
59
- configKey: agentConfig['~k']
60
- });
61
- // Get connection plugin from registry
62
- const connection = getConnection(context, {
63
- connectionConfig
64
- });
65
- // Evaluate operators in connection properties
66
- const connectionProperties = context.evaluateOperators({
67
- input: connectionConfig.properties || {},
68
- location: connectionConfig.connectionId,
69
- payload: {},
70
- state: {},
71
- steps: {}
72
- });
73
- // Create connection instance (e.g., Anthropic provider)
74
- const connectionInstance = connection.create({
75
- connection: connectionProperties
76
- });
77
- // Get agent type from plugin registry
78
- const agentType = getAgentResolver(context, {
79
- agentConfig
80
- });
81
- // Build resolver context with callEndpoint that allows InternalApi endpoints
82
- const resolverContext = {
33
+ const { agentConfig, connectionInstance, agentType, resolverContext } = await prepareAgent(context, {
34
+ agentId,
83
35
  agentContext,
84
- i18n: context.i18n,
85
- evaluateOperators: (input)=>context.evaluateOperators({
86
- input,
87
- location: agentConfig.agentId,
88
- payload: agentContext,
89
- state: {},
90
- steps: {}
91
- }),
92
- callEndpoint: async (endpointId, { payload, abortSignal })=>{
93
- const endpointConfig = await getEndpointConfig(context, {
94
- endpointId
95
- });
96
- authorizeApiEndpoint(context, {
97
- endpointConfig
98
- });
99
- const routineContext = {
100
- steps: {},
101
- payload: payload ?? {},
102
- arrayIndices: [],
103
- items: {},
104
- state: {},
105
- endpointDepth: 0
106
- };
107
- const { error, response, status } = await runRoutine(context, routineContext, {
108
- routine: endpointConfig.routine
109
- });
110
- return buildEndpointResult(context, {
111
- error,
112
- response,
113
- status
114
- });
115
- },
116
- getEndpointConfig: async ({ endpointId })=>{
117
- return getEndpointConfig(context, {
118
- endpointId
119
- });
120
- },
121
- getAgentConfig: async ({ agentId })=>{
122
- const subAgentConfig = await getAgentConfig(context, {
123
- agentId
124
- });
125
- authorizeAgent(context, {
126
- agentConfig: subAgentConfig
127
- });
128
- return subAgentConfig;
129
- },
130
- getConnectionForAgent: async ({ agentConfig: subAgentConfig })=>{
131
- const subConnectionConfig = await getConnectionConfig(context, {
132
- connectionId: subAgentConfig.connectionId,
133
- configKey: subAgentConfig['~k']
134
- });
135
- const subConnection = getConnection(context, {
136
- connectionConfig: subConnectionConfig
137
- });
138
- const subConnectionProperties = context.evaluateOperators({
139
- input: subConnectionConfig.properties || {},
140
- location: subConnectionConfig.connectionId,
141
- payload: {},
142
- state: {},
143
- steps: {}
144
- });
145
- return subConnection.create({
146
- connection: subConnectionProperties
147
- });
148
- },
149
- resolveMcpSources: async ({ agentConfig: subAgentConfig })=>{
150
- const resolvedMcp = [];
151
- for (const mcpSource of subAgentConfig.mcp ?? []){
152
- if (!type.isNone(mcpSource.connectionId)) {
153
- const mcpConnConfig = await getConnectionConfig(context, {
154
- connectionId: mcpSource.connectionId,
155
- configKey: subAgentConfig['~k']
156
- });
157
- const mcpConnection = getConnection(context, {
158
- connectionConfig: mcpConnConfig
159
- });
160
- const mcpConnProps = context.evaluateOperators({
161
- input: mcpConnConfig.properties || {},
162
- location: mcpConnConfig.connectionId,
163
- payload: {},
164
- state: {},
165
- steps: {}
166
- });
167
- const mcpConfig = mcpConnection.create({
168
- connection: mcpConnProps
169
- });
170
- const { connectionId: _, ...overrides } = mcpSource;
171
- resolvedMcp.push({
172
- ...mcpConfig,
173
- ...overrides
174
- });
175
- } else {
176
- resolvedMcp.push(mcpSource);
177
- }
178
- }
179
- return resolvedMcp;
180
- }
181
- };
182
- // Resolve MCP connection references to inline config.
183
- // Agent-level overrides (like confirm) may still contain operators —
184
- // handleAgentChat evaluates those via its existing evaluateOperators call.
185
- const resolvedMcp = [];
186
- for (const mcpSource of agentConfig.mcp ?? []){
187
- if (!type.isNone(mcpSource.connectionId)) {
188
- const mcpConnConfig = await getConnectionConfig(context, {
189
- connectionId: mcpSource.connectionId,
190
- configKey: agentConfig['~k']
191
- });
192
- const mcpConnection = getConnection(context, {
193
- connectionConfig: mcpConnConfig
194
- });
195
- const mcpConnProps = context.evaluateOperators({
196
- input: mcpConnConfig.properties || {},
197
- location: mcpConnConfig.connectionId,
198
- payload: {},
199
- state: {},
200
- steps: {}
201
- });
202
- const mcpConfig = mcpConnection.create({
203
- connection: mcpConnProps
204
- });
205
- // Merge: connection properties as base, agent-level overrides on top
206
- const { connectionId: _, ...overrides } = mcpSource;
207
- resolvedMcp.push({
208
- ...mcpConfig,
209
- ...overrides
210
- });
211
- } else {
212
- resolvedMcp.push(mcpSource);
213
- }
214
- }
215
- agentConfig.mcp = resolvedMcp;
36
+ endpointDepth: 0,
37
+ mode: 'chat'
38
+ });
216
39
  // Call the agent resolver
217
40
  const { response } = await agentType.resolver({
218
41
  connection: connectionInstance,
@@ -0,0 +1,173 @@
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
+ */ import { type } from '@lowdefy/helpers';
16
+ import buildEndpointResult from '../../response/buildEndpointResult.js';
17
+ import getEndpointConfig from '../endpoints/getEndpointConfig.js';
18
+ import invokeEndpoint from '../endpoints/invokeEndpoint.js';
19
+ import authorizeAgent from './authorizeAgent.js';
20
+ import getAgentConfig from './getAgentConfig.js';
21
+ import getAgentResolver from './getAgentResolver.js';
22
+ import getConnectionConfig from '../connections/getConnectionConfig.js';
23
+ import getConnection from '../connections/getConnection.js';
24
+ // Shared agent invocation construction for the streaming chat route (callAgent)
25
+ // and the headless CallAgent routine step (handleAgentCall): loads the agent
26
+ // config, evaluates operators, creates the provider connection, and builds the
27
+ // resolver context. mode ('chat' | 'generate') selects the resolver's execution
28
+ // path; endpointDepth threads the endpoint call depth cap through agent tool
29
+ // and hook endpoint calls.
30
+ async function prepareAgent(context, { agentId, agentContext, endpointDepth = 0, mode = 'chat' }) {
31
+ const agentConfig = await getAgentConfig(context, {
32
+ agentId
33
+ });
34
+ authorizeAgent(context, {
35
+ agentConfig
36
+ });
37
+ // Evaluate operators in agent properties (e.g. _user, _secret, _payload)
38
+ agentConfig.properties = context.evaluateOperators({
39
+ input: agentConfig.properties ?? {},
40
+ location: agentConfig.agentId,
41
+ payload: agentContext,
42
+ state: {},
43
+ steps: {}
44
+ });
45
+ // Load connection config from build artifacts using agent's connectionId
46
+ const connectionConfig = await getConnectionConfig(context, {
47
+ connectionId: agentConfig.connectionId,
48
+ configKey: agentConfig['~k']
49
+ });
50
+ // Get connection plugin from registry
51
+ const connection = getConnection(context, {
52
+ connectionConfig
53
+ });
54
+ // Evaluate operators in connection properties
55
+ const connectionProperties = context.evaluateOperators({
56
+ input: connectionConfig.properties || {},
57
+ location: connectionConfig.connectionId,
58
+ payload: {},
59
+ state: {},
60
+ steps: {}
61
+ });
62
+ // Create connection instance (e.g., Anthropic provider)
63
+ const connectionInstance = connection.create({
64
+ connection: connectionProperties
65
+ });
66
+ // Get agent type from plugin registry
67
+ const agentType = getAgentResolver(context, {
68
+ agentConfig
69
+ });
70
+ // Build resolver context with callEndpoint that allows InternalApi endpoints
71
+ const resolverContext = {
72
+ agentContext,
73
+ i18n: context.i18n,
74
+ mode,
75
+ evaluateOperators: (input)=>context.evaluateOperators({
76
+ input,
77
+ location: agentConfig.agentId,
78
+ payload: agentContext,
79
+ state: {},
80
+ steps: {}
81
+ }),
82
+ callEndpoint: async (endpointId, { payload })=>{
83
+ const { error, response, status } = await invokeEndpoint(context, {
84
+ endpointId,
85
+ payload,
86
+ endpointDepth
87
+ });
88
+ return buildEndpointResult(context, {
89
+ error,
90
+ response,
91
+ status
92
+ });
93
+ },
94
+ getEndpointConfig: async ({ endpointId })=>{
95
+ return getEndpointConfig(context, {
96
+ endpointId
97
+ });
98
+ },
99
+ getAgentConfig: async ({ agentId: subAgentId })=>{
100
+ const subAgentConfig = await getAgentConfig(context, {
101
+ agentId: subAgentId
102
+ });
103
+ authorizeAgent(context, {
104
+ agentConfig: subAgentConfig
105
+ });
106
+ return subAgentConfig;
107
+ },
108
+ getConnectionForAgent: async ({ agentConfig: subAgentConfig })=>{
109
+ const subConnectionConfig = await getConnectionConfig(context, {
110
+ connectionId: subAgentConfig.connectionId,
111
+ configKey: subAgentConfig['~k']
112
+ });
113
+ const subConnection = getConnection(context, {
114
+ connectionConfig: subConnectionConfig
115
+ });
116
+ const subConnectionProperties = context.evaluateOperators({
117
+ input: subConnectionConfig.properties || {},
118
+ location: subConnectionConfig.connectionId,
119
+ payload: {},
120
+ state: {},
121
+ steps: {}
122
+ });
123
+ return subConnection.create({
124
+ connection: subConnectionProperties
125
+ });
126
+ },
127
+ resolveMcpSources: async ({ agentConfig: subAgentConfig })=>{
128
+ const resolvedMcp = [];
129
+ for (const mcpSource of subAgentConfig.mcp ?? []){
130
+ if (!type.isNone(mcpSource.connectionId)) {
131
+ const mcpConnConfig = await getConnectionConfig(context, {
132
+ connectionId: mcpSource.connectionId,
133
+ configKey: subAgentConfig['~k']
134
+ });
135
+ const mcpConnection = getConnection(context, {
136
+ connectionConfig: mcpConnConfig
137
+ });
138
+ const mcpConnProps = context.evaluateOperators({
139
+ input: mcpConnConfig.properties || {},
140
+ location: mcpConnConfig.connectionId,
141
+ payload: {},
142
+ state: {},
143
+ steps: {}
144
+ });
145
+ const mcpConfig = mcpConnection.create({
146
+ connection: mcpConnProps
147
+ });
148
+ const { connectionId: _, ...overrides } = mcpSource;
149
+ resolvedMcp.push({
150
+ ...mcpConfig,
151
+ ...overrides
152
+ });
153
+ } else {
154
+ resolvedMcp.push(mcpSource);
155
+ }
156
+ }
157
+ return resolvedMcp;
158
+ }
159
+ };
160
+ // Resolve MCP connection references to inline config.
161
+ // Agent-level overrides (like confirm) may still contain operators —
162
+ // handleAgentChat evaluates those via its existing evaluateOperators call.
163
+ agentConfig.mcp = await resolverContext.resolveMcpSources({
164
+ agentConfig
165
+ });
166
+ return {
167
+ agentConfig,
168
+ connectionInstance,
169
+ agentType,
170
+ resolverContext
171
+ };
172
+ }
173
+ export default prepareAgent;
@@ -20,11 +20,8 @@
20
20
  };
21
21
  function createLogger({ logger }) {
22
22
  return {
23
- error: (code, metadata)=>{
24
- const error = metadata instanceof Error ? metadata : metadata?.error;
25
- if (error) {
26
- error.code = code;
27
- }
23
+ // Auth.js v5 logger contract: error(error), warn(code), debug(message, metadata).
24
+ error: (error)=>{
28
25
  logger.error(error);
29
26
  },
30
27
  warn: (code)=>{
@@ -0,0 +1,52 @@
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
+ */ function slugifyPrefix(value) {
16
+ return String(value).trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
17
+ }
18
+ // Injects an app-specific prefix into each Auth.js cookie name so multiple apps
19
+ // on the same host do not share a cookie jar (browsers do not scope cookies by
20
+ // port). Only names are overridden — Auth.js v5 deep-merges `config.cookies`
21
+ // into its defaults, so cookie options (httpOnly, secure, maxAge) stay in sync
22
+ // with @auth/core's per-request defaults. Names mirror defaultCookies in
23
+ // @auth/core/lib/utils/cookie.js.
24
+ function createPrefixedCookies({ prefix, useSecureCookies }) {
25
+ const securePrefix = useSecureCookies ? '__Secure-' : '';
26
+ const hostPrefix = useSecureCookies ? '__Host-' : '';
27
+ return {
28
+ sessionToken: {
29
+ name: `${securePrefix}${prefix}.authjs.session-token`
30
+ },
31
+ callbackUrl: {
32
+ name: `${securePrefix}${prefix}.authjs.callback-url`
33
+ },
34
+ csrfToken: {
35
+ name: `${hostPrefix}${prefix}.authjs.csrf-token`
36
+ },
37
+ pkceCodeVerifier: {
38
+ name: `${securePrefix}${prefix}.authjs.pkce.code_verifier`
39
+ },
40
+ state: {
41
+ name: `${securePrefix}${prefix}.authjs.state`
42
+ },
43
+ nonce: {
44
+ name: `${securePrefix}${prefix}.authjs.nonce`
45
+ },
46
+ webauthnChallenge: {
47
+ name: `${securePrefix}${prefix}.authjs.challenge`
48
+ }
49
+ };
50
+ }
51
+ export { slugifyPrefix };
52
+ export default createPrefixedCookies;
@@ -19,10 +19,11 @@ import createCallbacks from './callbacks/createCallbacks.js';
19
19
  import createEvents from './events/createEvents.js';
20
20
  import createLogger from './createLogger.js';
21
21
  import createProviders from './createProviders.js';
22
- const nextAuthConfig = {};
22
+ import resolveCookies from './resolveCookies.js';
23
+ const authConfigCache = {};
23
24
  let initialized = false;
24
- function getNextAuthConfig({ appMeta, authJson, logger, plugins, secrets }) {
25
- if (initialized) return nextAuthConfig;
25
+ function getAuthConfig({ appMeta, authJson, dev, logger, plugins, secrets }) {
26
+ if (initialized) return authConfigCache;
26
27
  const operatorsParser = new ServerParser({
27
28
  lowdefyApp: appMeta,
28
29
  operators: {
@@ -40,36 +41,46 @@ function getNextAuthConfig({ appMeta, authJson, logger, plugins, secrets }) {
40
41
  if (operatorErrors.length > 0) {
41
42
  throw operatorErrors[0];
42
43
  }
43
- nextAuthConfig.adapter = createAdapter({
44
+ authConfigCache.adapter = createAdapter({
44
45
  authConfig,
45
46
  logger,
46
47
  plugins
47
48
  });
48
- nextAuthConfig.callbacks = createCallbacks({
49
+ authConfigCache.callbacks = createCallbacks({
49
50
  authConfig,
50
51
  logger,
51
52
  plugins
52
53
  });
53
- nextAuthConfig.events = createEvents({
54
+ authConfigCache.events = createEvents({
54
55
  authConfig,
55
56
  logger,
56
57
  plugins
57
58
  });
58
- nextAuthConfig.logger = createLogger({
59
+ authConfigCache.logger = createLogger({
59
60
  logger
60
61
  });
61
- nextAuthConfig.providers = createProviders({
62
+ authConfigCache.providers = createProviders({
62
63
  authConfig,
63
64
  logger,
64
65
  plugins
65
66
  });
66
- nextAuthConfig.debug = authConfig.debug ?? logger?.isLevelEnabled('debug') === true;
67
- nextAuthConfig.pages = authConfig.authPages;
68
- nextAuthConfig.session = authConfig.session;
69
- nextAuthConfig.theme = authConfig.theme;
70
- nextAuthConfig.cookies = authConfig?.advanced?.cookies;
71
- nextAuthConfig.originalRedirectCallback = nextAuthConfig.callbacks.redirect;
67
+ authConfigCache.debug = authConfig.debug ?? logger?.isLevelEnabled('debug') === true;
68
+ authConfigCache.pages = authConfig.authPages;
69
+ authConfigCache.session = authConfig.session;
70
+ authConfigCache.theme = authConfig.theme;
71
+ authConfigCache.cookies = resolveCookies({
72
+ appMeta,
73
+ authConfig,
74
+ dev
75
+ });
76
+ // Auth.js v5 reads AUTH_SECRET; the v4 NEXTAUTH_SECRET variable is not
77
+ // supported — deployments must rename it (see the v5-to-v6 migration doc).
78
+ authConfigCache.secret = process.env.AUTH_SECRET;
79
+ // Self-hosted servers run behind arbitrary proxies; derive URLs from request
80
+ // headers (v4 derived them from NEXTAUTH_URL, aliased to AUTH_URL at startup).
81
+ authConfigCache.trustHost = true;
82
+ authConfigCache.basePath = '/api/auth';
72
83
  initialized = true;
73
- return nextAuthConfig;
84
+ return authConfigCache;
74
85
  }
75
- export default getNextAuthConfig;
86
+ export default getAuthConfig;
@@ -0,0 +1,37 @@
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
+ */ import { type } from '@lowdefy/helpers';
16
+ import createPrefixedCookies, { slugifyPrefix } from './createPrefixedCookies.js';
17
+ // Cookie name resolution, in order of precedence:
18
+ // 1. An explicit `auth.advanced.cookies` object is used verbatim.
19
+ // 2. An explicit `auth.advanced.cookiePrefix` namespaces cookie names (dev and prod).
20
+ // 3. When running the dev server, derive a prefix from the app slug/name so multiple
21
+ // apps on localhost do not share a cookie jar (browsers do not scope cookies by
22
+ // port). Production is left untouched, returning undefined for Auth.js defaults.
23
+ function resolveCookies({ appMeta, authConfig, dev }) {
24
+ const explicitCookies = authConfig?.advanced?.cookies;
25
+ if (!type.isNone(explicitCookies)) return explicitCookies;
26
+ const devPrefix = dev ? slugifyPrefix(appMeta?.slug ?? appMeta?.name ?? '') : '';
27
+ const prefix = authConfig?.advanced?.cookiePrefix ?? (devPrefix === '' ? undefined : devPrefix);
28
+ if (type.isNone(prefix) || prefix === '') return undefined;
29
+ // Auth.js v5 reads AUTH_URL with NEXTAUTH_URL as a v4 fallback.
30
+ const authUrl = process.env.AUTH_URL ?? process.env.NEXTAUTH_URL;
31
+ const useSecureCookies = authUrl?.startsWith('https://') ?? false;
32
+ return createPrefixedCookies({
33
+ prefix,
34
+ useSecureCookies
35
+ });
36
+ }
37
+ export default resolveCookies;