@lowdefy/api 0.0.0-experimental-20260827092002 → 0.0.0-experimental-20260827092153
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/dist/context/applySystemTrust.js +33 -0
- package/dist/context/createApiContext.js +18 -3
- package/dist/context/createAuthorizeOutcome.js +91 -0
- package/dist/context/createEvaluateOperators.js +2 -1
- package/dist/context/createSystemContext.js +78 -0
- package/dist/context/normalizeInjectedCaller.js +50 -0
- package/dist/context/resolveAuthentication.js +468 -0
- package/dist/context/resolveStrategyCaller.js +60 -0
- package/dist/email/renderAuthEmail.js +98 -0
- package/dist/index.js +17 -4
- package/dist/response/buildEndpointResult.js +33 -0
- package/dist/response/normalizeErrorSources.js +64 -0
- package/dist/response/omitErrorProps.js +51 -0
- package/dist/response/redactErrorResponse.js +30 -0
- package/dist/response/redactResponse.js +31 -0
- package/dist/routes/agent/prepareAgent.js +7 -11
- package/dist/routes/auth/buildAdminPlugin.js +33 -0
- package/dist/routes/auth/buildCaptchaPlugin.js +61 -0
- package/dist/routes/auth/buildPhoneNumberPlugin.js +57 -0
- package/dist/routes/auth/buildProviders.js +51 -0
- package/dist/routes/auth/createAsMetadataHandler.js +27 -0
- package/dist/routes/auth/createAuthLogger.js +31 -0
- package/dist/routes/auth/createSendEmail.js +49 -0
- package/dist/routes/auth/getAuthEnforcement.js +41 -0
- package/dist/routes/auth/getBetterAuth.js +87 -0
- package/dist/routes/auth/getBetterAuthConfig.js +600 -0
- package/dist/routes/auth/hooks/authHookPoints.js +204 -0
- package/dist/routes/auth/hooks/buildEngineHooks.js +68 -0
- package/dist/routes/auth/hooks/buildHooks.js +112 -0
- package/dist/routes/auth/{createAdapter.js → hooks/composeAfterSlot.js} +11 -9
- package/dist/routes/auth/hooks/composeBeforeSlot.js +41 -0
- package/dist/routes/auth/hooks/createHookDispatch.js +59 -0
- package/dist/routes/auth/hooks/createUserAfterHook.js +33 -0
- package/dist/routes/auth/hooks/createUserBeforeHook.js +47 -0
- package/dist/routes/auth/hooks/findHookUser.js +27 -0
- package/dist/routes/auth/{callbacks/createCallbackPlugins.js → modelNames.js} +15 -7
- package/dist/routes/auth/organizations/OrganizationKeyError.js +28 -0
- package/dist/routes/auth/organizations/authorizeRole.js +27 -0
- package/dist/routes/auth/organizations/buildOrganizationPlugin.js +181 -0
- package/dist/routes/auth/organizations/createAcceptActiveOrgGuardHook.js +57 -0
- package/dist/routes/auth/organizations/createActiveOrgPolicyHook.js +249 -0
- package/dist/routes/auth/organizations/createAdmissionGateHook.js +50 -0
- package/dist/routes/auth/organizations/createAfterAcceptInvitationHook.js +90 -0
- package/dist/routes/auth/organizations/createAutoJoinHook.js +68 -0
- package/dist/routes/auth/organizations/createMagicLinkSendGate.js +58 -0
- package/dist/routes/auth/organizations/ensureOrganization.js +141 -0
- package/dist/routes/auth/organizations/findPendingInvitation.js +43 -0
- package/dist/routes/auth/{callbacks/addUserFieldsToSession.js → organizations/getHookRequestHeaders.js} +9 -13
- package/dist/routes/auth/organizations/getOrganizationBinding.js +65 -0
- package/dist/routes/auth/organizations/isEmailAdmitted.js +89 -0
- package/dist/routes/auth/organizations/organizationAccessControl.js +94 -0
- package/dist/routes/auth/organizations/resolvePinnedOrganization.js +62 -0
- package/dist/routes/auth/requestHooks/beginTwoFactorChallenge.js +145 -0
- package/dist/routes/auth/requestHooks/buildRequestHooks.js +179 -0
- package/dist/routes/auth/requestHooks/createTwoFactorChallengeHook.js +59 -0
- package/dist/routes/auth/requestHooks/dispatchRequestHooks.js +45 -0
- package/dist/routes/auth/requestHooks/matchOAuthCallback.js +47 -0
- package/dist/routes/auth/requestHooks/redirectToChallenge.js +79 -0
- package/dist/routes/auth/requestHooks/resolveTwoFactorPageUrl.js +31 -0
- package/dist/routes/auth/resolveCookiePrefix.js +30 -0
- package/dist/routes/auth/sanitizeSessionResponse.js +27 -0
- package/dist/routes/auth/strategies/createAuthStrategies.js +77 -0
- package/dist/routes/auth/{events/createEventPlugins.js → strategies/getAuthStrategies.js} +16 -6
- package/dist/routes/connections/resolveTenantPreflight.js +172 -0
- package/dist/routes/endpoints/addStepResult.js +12 -2
- package/dist/routes/endpoints/authorizeApiEndpoint.js +18 -3
- package/dist/routes/endpoints/callEndpoint.js +13 -14
- package/dist/routes/endpoints/control/controlSetState.js +13 -2
- package/dist/routes/endpoints/getEndpointConfig.js +19 -3
- package/dist/routes/endpoints/handleAuthStep.js +204 -0
- package/dist/routes/endpoints/handleEndpointCall.js +14 -1
- package/dist/routes/endpoints/handleRenderNotification.js +1 -1
- package/dist/routes/endpoints/handleRequest.js +8 -1
- package/dist/routes/endpoints/resolveStepOrganizationId.js +64 -0
- package/dist/routes/endpoints/runDetachedEndpoint.js +37 -25
- package/dist/routes/endpoints/runRoutine.js +9 -1
- package/dist/routes/endpoints/runScheduledEndpoint.js +14 -20
- package/dist/routes/endpoints/runWebhookEndpoint.js +53 -26
- package/dist/routes/endpoints/runWebhookVerify.js +111 -0
- package/dist/routes/endpoints/test/runTest.js +3 -4
- package/dist/routes/mcp/createMcpServer.js +199 -0
- package/dist/routes/mcp/getMcpJwks.js +93 -0
- package/dist/routes/mcp/getMcpResourceBinding.js +41 -0
- package/dist/routes/mcp/getMcpUri.js +75 -0
- package/dist/routes/mcp/oauthResourceLifecycle.js +208 -0
- package/dist/routes/page/getPageConfig.js +55 -5
- package/dist/routes/request/authorizeRequest.js +20 -3
- package/dist/routes/request/callRequest.js +10 -2
- package/dist/routes/request/callRequestResolver.js +6 -2
- package/dist/routes/request/getRequestConfig.js +19 -3
- package/dist/routes/request/resolveTenant.js +132 -0
- package/dist/routes/rootConfig/menus/filterMenuList.js +5 -2
- package/dist/routes/websocket/authorizeWebsocket.js +12 -3
- package/dist/routes/websocket/createChannelRegistry.js +14 -5
- package/dist/routes/websocket/prepareChannel.js +13 -1
- package/dist/test/testContext.js +18 -8
- package/package.json +18 -9
- package/dist/context/createAuthorize.js +0 -45
- package/dist/routes/auth/callbacks/addUserFieldsToToken.js +0 -30
- package/dist/routes/auth/callbacks/createCallbacks.js +0 -46
- package/dist/routes/auth/callbacks/createJWTCallback.js +0 -77
- package/dist/routes/auth/callbacks/createRedirectCallback.js +0 -41
- package/dist/routes/auth/callbacks/createSessionCallback.js +0 -81
- package/dist/routes/auth/callbacks/createSignInCallback.js +0 -40
- package/dist/routes/auth/callbacks/validateSessionRoles.js +0 -33
- package/dist/routes/auth/createLogger.js +0 -35
- package/dist/routes/auth/createPrefixedCookies.js +0 -52
- package/dist/routes/auth/createProviders.js +0 -25
- package/dist/routes/auth/events/createCreateUserEvent.js +0 -41
- package/dist/routes/auth/events/createEvents.js +0 -56
- package/dist/routes/auth/events/createLinkAccountEvent.js +0 -43
- package/dist/routes/auth/events/createSessionEvent.js +0 -34
- package/dist/routes/auth/events/createSignInEvent.js +0 -46
- package/dist/routes/auth/events/createSignOutEvent.js +0 -43
- package/dist/routes/auth/events/createUpdateUserEvent.js +0 -41
- package/dist/routes/auth/getAuthConfig.js +0 -86
- package/dist/routes/auth/resolveCookies.js +0 -37
- /package/dist/{routes/notifications → email}/resolveThemeLogo.js +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
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 createAuthorizeOutcome from './createAuthorizeOutcome.js';
|
|
16
|
+
// The single writer of the trusted, caller-less system-context invariants
|
|
17
|
+
// (Decision 1). It sets all three together on an existing context so no caller
|
|
18
|
+
// can set one without the others:
|
|
19
|
+
// - user: null — caller-less (detectable via type.isNone)
|
|
20
|
+
// - system: true — the run-level trust marker every authorization layer reads
|
|
21
|
+
// - authorizeOutcome — derived from createAuthorizeOutcome so it reads context.system
|
|
22
|
+
// createSystemContext builds a FRESH context for the off-request hook path;
|
|
23
|
+
// this applies the same state to a request context a runner already holds
|
|
24
|
+
// (cron at construction, webhook once its verify gate passes, a detached run
|
|
25
|
+
// whose dispatcher was itself a system context). One greppable place means a
|
|
26
|
+
// future runner cannot reintroduce the silent-no-op bug by forgetting a line.
|
|
27
|
+
function applySystemTrust(context) {
|
|
28
|
+
context.user = null;
|
|
29
|
+
context.system = true;
|
|
30
|
+
context.authorizeOutcome = createAuthorizeOutcome(context);
|
|
31
|
+
return context;
|
|
32
|
+
}
|
|
33
|
+
export default applySystemTrust;
|
|
@@ -12,11 +12,14 @@
|
|
|
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
|
|
15
|
+
*/ import createAuthorizeOutcome from './createAuthorizeOutcome.js';
|
|
16
16
|
import createReadConfigFile from './createReadConfigFile.js';
|
|
17
|
+
import getAuthEnforcement from '../routes/auth/getAuthEnforcement.js';
|
|
18
|
+
import getOrganizationBinding from '../routes/auth/organizations/getOrganizationBinding.js';
|
|
17
19
|
import resolveLocale from './resolveLocale.js';
|
|
18
20
|
function createApiContext(context) {
|
|
19
|
-
context.user
|
|
21
|
+
// context.user is written by resolveAuthentication (or a pre-resolved
|
|
22
|
+
// dev/e2e caller) before this runs - never assigned here.
|
|
20
23
|
if (context.i18n?.defaultLocale) {
|
|
21
24
|
const active = resolveLocale({
|
|
22
25
|
i18n: context.i18n,
|
|
@@ -29,7 +32,19 @@ function createApiContext(context) {
|
|
|
29
32
|
} else {
|
|
30
33
|
context.i18n = undefined;
|
|
31
34
|
}
|
|
32
|
-
|
|
35
|
+
// The retained organizations state ({ policy, pinned }) - read by the
|
|
36
|
+
// _organization operator and the org-scoped admin steps; null when auth
|
|
37
|
+
// (or organizations) is not configured.
|
|
38
|
+
context.organization = getOrganizationBinding({
|
|
39
|
+
auth: context.auth ?? null
|
|
40
|
+
});
|
|
41
|
+
// The retained build-time authorization facts - the enrolment floor, the exempt
|
|
42
|
+
// enrolment page, and the unlisted-page-id default. null when auth is not
|
|
43
|
+
// configured, in which case nothing is protected and nothing is required.
|
|
44
|
+
context.authEnforcement = getAuthEnforcement({
|
|
45
|
+
auth: context.auth ?? null
|
|
46
|
+
});
|
|
47
|
+
context.authorizeOutcome = createAuthorizeOutcome(context);
|
|
33
48
|
context.readConfigFile = createReadConfigFile(context);
|
|
34
49
|
}
|
|
35
50
|
export default createApiContext;
|
|
@@ -0,0 +1,91 @@
|
|
|
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 { ConfigError } from '@lowdefy/errors';
|
|
17
|
+
// createAuthorizeOutcome is called with the context object on every path
|
|
18
|
+
// (createApiContext, applySystemTrust, and the runners), so `system` is read
|
|
19
|
+
// straight off context.system - the single run-level trust marker every
|
|
20
|
+
// authorization layer reads.
|
|
21
|
+
//
|
|
22
|
+
// It returns an OUTCOME, not a boolean: 'allow', 'deny', or 'enrol_required'.
|
|
23
|
+
// The rename is load-bearing. Every previous caller was a truthiness test, and
|
|
24
|
+
// every non-empty string is truthy, so a caller left on `authorize` would read
|
|
25
|
+
// 'deny' as authorized and stop enforcing auth entirely on that surface. There
|
|
26
|
+
// is deliberately NO boolean authorize left beside this - a missed call site
|
|
27
|
+
// must throw TypeError, which also catches callers outside this repo.
|
|
28
|
+
//
|
|
29
|
+
// The ordering is security-critical and written here exactly once. Enrolment is
|
|
30
|
+
// checked LAST, after roles: checking it first hands an unenrolled caller an
|
|
31
|
+
// enrol redirect for every protected page that EXISTS and /404 for those that do
|
|
32
|
+
// not, which is a working page-enumeration oracle over pages they have no access
|
|
33
|
+
// to. Checked last, they see the enrol outcome only for pages they would have
|
|
34
|
+
// been let into - which tells them exactly what an enrolled caller learns by
|
|
35
|
+
// getting the page.
|
|
36
|
+
function createAuthorizeOutcome({ authEnforcement, system, user }) {
|
|
37
|
+
const authenticated = !type.isNone(user);
|
|
38
|
+
// A caller awaiting an organization is signed in and holds no membership -
|
|
39
|
+
// under tenant, the invited user before they accept (resolveAuthentication).
|
|
40
|
+
// They are known so the always-public accept page can address them, and
|
|
41
|
+
// refused everywhere auth.public is false, roles or not: empty roles is not a
|
|
42
|
+
// wall (user-model Decision 2), so authenticated alone must not admit them to
|
|
43
|
+
// a role-less protected page. Strategy callers (apiKey, jwt) also hold no
|
|
44
|
+
// organization but sit deliberately outside the membership boundary, so the
|
|
45
|
+
// test is this explicit marker, never the absence of an organization. The
|
|
46
|
+
// caller is the normalizeCaller-snaked record, so the key is snake_case here.
|
|
47
|
+
const awaitingOrganization = user?.awaiting_organization === true;
|
|
48
|
+
const roles = user?.roles ?? [];
|
|
49
|
+
if (!Array.isArray(roles) || roles.some((role)=>!type.isString(role))) {
|
|
50
|
+
throw new ConfigError('user.roles must be an array of strings.', {
|
|
51
|
+
received: roles
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const enrolmentRequired = authEnforcement?.twoFactorRequired === true;
|
|
55
|
+
const enrolPageId = authEnforcement?.twoFactorEnrolPageId ?? null;
|
|
56
|
+
// pageId is passed only by the page route. Requests, endpoints, websockets and
|
|
57
|
+
// menu items pass none, so the enrolment-page exemption cannot be reached by a
|
|
58
|
+
// request id that happens to collide with the enrol page's id.
|
|
59
|
+
function authorizeOutcome(config, { pageId } = {}) {
|
|
60
|
+
// A system context has no session and no caller, so "does this caller's
|
|
61
|
+
// session carry the required roles?" is undefined, not denied - and it holds
|
|
62
|
+
// no factor either, so the enrolment floor is undefined for it too.
|
|
63
|
+
if (system === true) return 'allow';
|
|
64
|
+
const { auth } = config;
|
|
65
|
+
if (auth.public === true) return 'allow';
|
|
66
|
+
if (auth.public === false) {
|
|
67
|
+
if (!authenticated) return 'deny';
|
|
68
|
+
if (awaitingOrganization) return 'deny';
|
|
69
|
+
if (auth.roles && !auth.roles.some((role)=>roles.includes(role))) return 'deny';
|
|
70
|
+
// Enrolment last, after roles. Strictly === false: the key is present only
|
|
71
|
+
// on a caller resolved from a session, so API strategy callers and the
|
|
72
|
+
// dev/e2e injected callers pass untouched by inheritance rather than by a
|
|
73
|
+
// carve-out (Decision 10). `!user.two_factor_enrolled` would redirect every
|
|
74
|
+
// one of them.
|
|
75
|
+
if (enrolmentRequired && user.two_factor_enrolled === false) {
|
|
76
|
+
// The enrolment page itself is exempt from the enrolment check alone -
|
|
77
|
+
// it is a protected page and stays one; this is not a public-ness
|
|
78
|
+
// exemption (Decision 8).
|
|
79
|
+
if (!type.isNone(pageId) && pageId === enrolPageId) return 'allow';
|
|
80
|
+
return 'enrol_required';
|
|
81
|
+
}
|
|
82
|
+
return 'allow';
|
|
83
|
+
}
|
|
84
|
+
throw new ConfigError('auth.public must be true or false.', {
|
|
85
|
+
received: auth.public,
|
|
86
|
+
configKey: config['~k']
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return authorizeOutcome;
|
|
90
|
+
}
|
|
91
|
+
export default createAuthorizeOutcome;
|
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { ServerParser } from '@lowdefy/operators';
|
|
16
16
|
function createEvaluateOperators(context) {
|
|
17
|
-
const { appMeta, i18n, jsMap, operators, secrets, user } = context;
|
|
17
|
+
const { appMeta, i18n, jsMap, operators, organization, secrets, user } = context;
|
|
18
18
|
const operatorsParser = new ServerParser({
|
|
19
19
|
i18n,
|
|
20
20
|
jsMap,
|
|
21
21
|
lowdefyApp: appMeta,
|
|
22
22
|
operators,
|
|
23
|
+
organization,
|
|
23
24
|
secrets,
|
|
24
25
|
user
|
|
25
26
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
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 applySystemTrust from './applySystemTrust.js';
|
|
16
|
+
import createEvaluateOperators from './createEvaluateOperators.js';
|
|
17
|
+
import createReadConfigFile from './createReadConfigFile.js';
|
|
18
|
+
import getAuthEnforcement from '../routes/auth/getAuthEnforcement.js';
|
|
19
|
+
import getOrganizationBinding from '../routes/auth/organizations/getOrganizationBinding.js';
|
|
20
|
+
// Builds a fresh off-request context for a trusted, caller-less system context -
|
|
21
|
+
// an auth hook fire. The caller is the auth engine, not a user: `user` is null
|
|
22
|
+
// so the run is detectable as caller-less (`type.isNone(context.user)`) and
|
|
23
|
+
// `_user` resolves to nothing (the hook's subject is in `_payload`). The run is
|
|
24
|
+
// trusted at construction (`context.system = true`), the single trust marker
|
|
25
|
+
// every authorization layer reads (Decision 1); its `authorizeOutcome` is derived
|
|
26
|
+
// from createAuthorizeOutcome like every other layer, so endpoint auth is not
|
|
27
|
+
// re-checked against a session (Decision 2).
|
|
28
|
+
function createSystemContext({ agents, appMeta, auth, buildDirectory, config, configDirectory, connections, createHandleError, fileCache, i18n, jsMap, logger, notifications, operators, rid, secrets, steps, websockets }) {
|
|
29
|
+
const context = {
|
|
30
|
+
rid,
|
|
31
|
+
agents,
|
|
32
|
+
appMeta,
|
|
33
|
+
auth,
|
|
34
|
+
buildDirectory,
|
|
35
|
+
config,
|
|
36
|
+
configDirectory,
|
|
37
|
+
connections,
|
|
38
|
+
fileCache,
|
|
39
|
+
headers: {},
|
|
40
|
+
i18n: i18n?.defaultLocale ? {
|
|
41
|
+
...i18n,
|
|
42
|
+
active: i18n.defaultLocale
|
|
43
|
+
} : undefined,
|
|
44
|
+
jsMap,
|
|
45
|
+
logger,
|
|
46
|
+
notifications,
|
|
47
|
+
operators,
|
|
48
|
+
req: {
|
|
49
|
+
url: 'system:auth-hook',
|
|
50
|
+
method: null,
|
|
51
|
+
hostname: null
|
|
52
|
+
},
|
|
53
|
+
secrets,
|
|
54
|
+
steps,
|
|
55
|
+
websockets
|
|
56
|
+
};
|
|
57
|
+
// Hook routines read the retained organizations state too - a step or
|
|
58
|
+
// _organization inside a hook-bound endpoint resolves the same pinned org.
|
|
59
|
+
context.organization = getOrganizationBinding({
|
|
60
|
+
auth: auth ?? null
|
|
61
|
+
});
|
|
62
|
+
// A system context bypasses the enrolment check outright (system === true
|
|
63
|
+
// returns early), so this exists for shape consistency, not a live read.
|
|
64
|
+
context.authEnforcement = getAuthEnforcement({
|
|
65
|
+
auth: auth ?? null
|
|
66
|
+
});
|
|
67
|
+
// Trusted, caller-less system context (Decisions 1, 2): user: null,
|
|
68
|
+
// system: true, and authorizeOutcome derived from createAuthorizeOutcome - the
|
|
69
|
+
// same invariant bundle the caller-less runners apply, set in one place.
|
|
70
|
+
applySystemTrust(context);
|
|
71
|
+
context.handleError = createHandleError({
|
|
72
|
+
context
|
|
73
|
+
});
|
|
74
|
+
context.readConfigFile = createReadConfigFile(context);
|
|
75
|
+
context.evaluateOperators = createEvaluateOperators(context);
|
|
76
|
+
return context;
|
|
77
|
+
}
|
|
78
|
+
export default createSystemContext;
|
|
@@ -0,0 +1,50 @@
|
|
|
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 { normalizeCaller, type } from '@lowdefy/helpers';
|
|
16
|
+
// The single writer of the pre-resolved-caller shape. A dev mock user, the dev
|
|
17
|
+
// headless renderer cookie, and the e2e test cookie each substitute for the
|
|
18
|
+
// whole resolveAuthentication step, and must match the shape it produces -
|
|
19
|
+
// including its snake_case keys, so an injected caller may be authored either
|
|
20
|
+
// way and still reads like a resolved one. That output always carries roles as
|
|
21
|
+
// an array and attributes as an object (a whole-bag { _user: 'attributes' }
|
|
22
|
+
// read gives {} on a real caller), so both are floored here. profile,
|
|
23
|
+
// contact_id, active_organization_id and two_factor_enrolled are only ever
|
|
24
|
+
// produced by a real user-row/member read, so they are left exactly as the
|
|
25
|
+
// caller carries them - absent stays absent, and a bare _user.profile read is
|
|
26
|
+
// null-safe.
|
|
27
|
+
// organization_id (the tenant wall's org value) mirrors resolveAuthentication,
|
|
28
|
+
// where it always equals active_organization_id - an injected caller declaring
|
|
29
|
+
// either key gets both, so mock/e2e callers pass walled connections without
|
|
30
|
+
// duplicating the org under two names. A caller with neither stays org-less
|
|
31
|
+
// and fails closed on walled connections, same as a strategy caller.
|
|
32
|
+
//
|
|
33
|
+
// The floors read the raw user: roles, attributes and profile are single words
|
|
34
|
+
// that snake to themselves, so an authored key and its normalized name are the
|
|
35
|
+
// same key. The org floor reads the normalized object, where either authored
|
|
36
|
+
// spelling has already collapsed onto one name.
|
|
37
|
+
function normalizeInjectedCaller(user) {
|
|
38
|
+
const normalized = {
|
|
39
|
+
...normalizeCaller(user),
|
|
40
|
+
roles: user.roles ?? [],
|
|
41
|
+
attributes: user.attributes ?? {}
|
|
42
|
+
};
|
|
43
|
+
const organizationId = normalized.organization_id ?? normalized.active_organization_id;
|
|
44
|
+
if (!type.isNone(organizationId)) {
|
|
45
|
+
normalized.organization_id = organizationId;
|
|
46
|
+
normalized.active_organization_id = normalized.active_organization_id ?? organizationId;
|
|
47
|
+
}
|
|
48
|
+
return normalized;
|
|
49
|
+
}
|
|
50
|
+
export default normalizeInjectedCaller;
|