@naylence/runtime 0.3.21 → 0.4.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.
- package/dist/browser/index.cjs +3144 -1307
- package/dist/browser/index.mjs +3116 -1301
- package/dist/cjs/naylence/fame/factory-manifest.js +6 -0
- package/dist/cjs/naylence/fame/node/node-event-listener.js +4 -0
- package/dist/cjs/naylence/fame/security/auth/default-policy-authorizer-factory.js +147 -0
- package/dist/cjs/naylence/fame/security/auth/default-policy-authorizer.js +291 -0
- package/dist/cjs/naylence/fame/security/auth/oauth2-authorizer-factory.js +7 -0
- package/dist/cjs/naylence/fame/security/auth/oauth2-authorizer.js +19 -4
- package/dist/cjs/naylence/fame/security/auth/policy/authorization-policy-definition.js +60 -0
- package/dist/cjs/naylence/fame/security/auth/policy/authorization-policy-factory.js +35 -0
- package/dist/cjs/naylence/fame/security/auth/policy/authorization-policy-source-factory.js +35 -0
- package/dist/cjs/naylence/fame/security/auth/policy/authorization-policy-source.js +2 -0
- package/dist/cjs/naylence/fame/security/auth/policy/authorization-policy.js +2 -0
- package/dist/cjs/naylence/fame/security/auth/policy/basic-authorization-policy-factory.js +99 -0
- package/dist/cjs/naylence/fame/security/auth/policy/basic-authorization-policy.js +449 -0
- package/dist/cjs/naylence/fame/security/auth/policy/index.js +40 -0
- package/dist/cjs/naylence/fame/security/auth/policy/local-file-authorization-policy-source-factory.js +101 -0
- package/dist/cjs/naylence/fame/security/auth/policy/local-file-authorization-policy-source.js +164 -0
- package/dist/cjs/naylence/fame/security/auth/policy/pattern-matcher.js +195 -0
- package/dist/cjs/naylence/fame/security/auth/policy/scope-matcher.js +169 -0
- package/dist/cjs/naylence/fame/security/auth/policy-authorizer.js +2 -0
- package/dist/cjs/naylence/fame/security/default-security-manager.js +94 -0
- package/dist/cjs/naylence/fame/security/index.js +3 -0
- package/dist/cjs/naylence/fame/security/node-security-profile-factory.js +3 -1
- package/dist/cjs/naylence/fame/sentinel/router.js +67 -1
- package/dist/cjs/naylence/fame/sentinel/sentinel.js +46 -2
- package/dist/cjs/naylence/fame/util/register-runtime-factories.js +2 -0
- package/dist/cjs/version.js +2 -2
- package/dist/esm/naylence/fame/factory-manifest.js +6 -0
- package/dist/esm/naylence/fame/node/node-event-listener.js +4 -0
- package/dist/esm/naylence/fame/security/auth/default-policy-authorizer-factory.js +110 -0
- package/dist/esm/naylence/fame/security/auth/default-policy-authorizer.js +287 -0
- package/dist/esm/naylence/fame/security/auth/oauth2-authorizer-factory.js +7 -0
- package/dist/esm/naylence/fame/security/auth/oauth2-authorizer.js +19 -4
- package/dist/esm/naylence/fame/security/auth/policy/authorization-policy-definition.js +57 -0
- package/dist/esm/naylence/fame/security/auth/policy/authorization-policy-factory.js +31 -0
- package/dist/esm/naylence/fame/security/auth/policy/authorization-policy-source-factory.js +31 -0
- package/dist/esm/naylence/fame/security/auth/policy/authorization-policy-source.js +1 -0
- package/dist/esm/naylence/fame/security/auth/policy/authorization-policy.js +1 -0
- package/dist/esm/naylence/fame/security/auth/policy/basic-authorization-policy-factory.js +62 -0
- package/dist/esm/naylence/fame/security/auth/policy/basic-authorization-policy.js +445 -0
- package/dist/esm/naylence/fame/security/auth/policy/index.js +20 -0
- package/dist/esm/naylence/fame/security/auth/policy/local-file-authorization-policy-source-factory.js +64 -0
- package/dist/esm/naylence/fame/security/auth/policy/local-file-authorization-policy-source.js +127 -0
- package/dist/esm/naylence/fame/security/auth/policy/pattern-matcher.js +185 -0
- package/dist/esm/naylence/fame/security/auth/policy/scope-matcher.js +162 -0
- package/dist/esm/naylence/fame/security/auth/policy-authorizer.js +1 -0
- package/dist/esm/naylence/fame/security/default-security-manager.js +94 -0
- package/dist/esm/naylence/fame/security/index.js +3 -0
- package/dist/esm/naylence/fame/security/node-security-profile-factory.js +2 -0
- package/dist/esm/naylence/fame/sentinel/router.js +64 -0
- package/dist/esm/naylence/fame/sentinel/sentinel.js +47 -3
- package/dist/esm/naylence/fame/util/register-runtime-factories.js +2 -0
- package/dist/esm/version.js +2 -2
- package/dist/node/index.cjs +3140 -1303
- package/dist/node/index.mjs +3116 -1301
- package/dist/node/node.cjs +3191 -1338
- package/dist/node/node.mjs +3167 -1336
- package/dist/types/naylence/fame/factory-manifest.d.ts +1 -1
- package/dist/types/naylence/fame/node/node-event-listener.d.ts +31 -0
- package/dist/types/naylence/fame/security/auth/authorizer.d.ts +37 -0
- package/dist/types/naylence/fame/security/auth/default-policy-authorizer-factory.d.ts +55 -0
- package/dist/types/naylence/fame/security/auth/default-policy-authorizer.d.ts +99 -0
- package/dist/types/naylence/fame/security/auth/oauth2-authorizer-factory.d.ts +2 -0
- package/dist/types/naylence/fame/security/auth/oauth2-authorizer.d.ts +2 -0
- package/dist/types/naylence/fame/security/auth/policy/authorization-policy-definition.d.ts +166 -0
- package/dist/types/naylence/fame/security/auth/policy/authorization-policy-factory.d.ts +38 -0
- package/dist/types/naylence/fame/security/auth/policy/authorization-policy-source-factory.d.ts +38 -0
- package/dist/types/naylence/fame/security/auth/policy/authorization-policy-source.d.ts +20 -0
- package/dist/types/naylence/fame/security/auth/policy/authorization-policy.d.ts +55 -0
- package/dist/types/naylence/fame/security/auth/policy/basic-authorization-policy-factory.d.ts +42 -0
- package/dist/types/naylence/fame/security/auth/policy/basic-authorization-policy.d.ts +78 -0
- package/dist/types/naylence/fame/security/auth/policy/index.d.ts +19 -0
- package/dist/types/naylence/fame/security/auth/policy/local-file-authorization-policy-source-factory.d.ts +51 -0
- package/dist/types/naylence/fame/security/auth/policy/local-file-authorization-policy-source.d.ts +67 -0
- package/dist/types/naylence/fame/security/auth/policy/pattern-matcher.d.ts +84 -0
- package/dist/types/naylence/fame/security/auth/policy/scope-matcher.d.ts +61 -0
- package/dist/types/naylence/fame/security/auth/policy-authorizer.d.ts +12 -0
- package/dist/types/naylence/fame/security/default-security-manager.d.ts +22 -0
- package/dist/types/naylence/fame/security/index.d.ts +2 -0
- package/dist/types/naylence/fame/security/node-security-profile-factory.d.ts +1 -0
- package/dist/types/naylence/fame/sentinel/router.d.ts +68 -0
- package/dist/types/naylence/fame/sentinel/sentinel.d.ts +16 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ const security_policy_js_1 = require("./policy/security-policy.js");
|
|
|
7
7
|
const envelope_security_handler_js_1 = require("../node/envelope-security-handler.js");
|
|
8
8
|
const secure_channel_frame_handler_js_1 = require("../node/secure-channel-frame-handler.js");
|
|
9
9
|
const key_frame_handler_js_1 = require("../sentinel/key-frame-handler.js");
|
|
10
|
+
const router_js_1 = require("../sentinel/router.js");
|
|
10
11
|
const logging_js_1 = require("../util/logging.js");
|
|
11
12
|
const util_js_1 = require("../util/util.js");
|
|
12
13
|
const eddsa_signer_verifier_js_1 = require("../security/signing/eddsa-signer-verifier.js");
|
|
@@ -792,6 +793,99 @@ class DefaultSecurityManager {
|
|
|
792
793
|
});
|
|
793
794
|
return envelope;
|
|
794
795
|
}
|
|
796
|
+
/**
|
|
797
|
+
* Route authorization hook - invoked after routing policy selects an action.
|
|
798
|
+
*
|
|
799
|
+
* This method provides centralized route authorization by:
|
|
800
|
+
* 1. Mapping the RoutingAction to an authorization action token
|
|
801
|
+
* 2. Calling authorizer.authorizeRoute() if available
|
|
802
|
+
* 3. Returning a Deny action on authorization failure (opaque on wire)
|
|
803
|
+
*
|
|
804
|
+
* @param node - The node performing the routing
|
|
805
|
+
* @param envelope - The envelope being routed
|
|
806
|
+
* @param selected - The RoutingAction selected by routing policy
|
|
807
|
+
* @param state - The current router state
|
|
808
|
+
* @param context - Optional delivery context
|
|
809
|
+
* @returns The action to execute (selected if authorized, Deny if denied)
|
|
810
|
+
*/
|
|
811
|
+
async onRoutingActionSelected(node, envelope, selected, _state, context) {
|
|
812
|
+
// If no authorizer or authorizer doesn't implement authorizeRoute, allow
|
|
813
|
+
if (!this._authorizer) {
|
|
814
|
+
return selected;
|
|
815
|
+
}
|
|
816
|
+
if (typeof this._authorizer.authorizeRoute !== 'function') {
|
|
817
|
+
return selected;
|
|
818
|
+
}
|
|
819
|
+
// Map RoutingAction to authorization action token
|
|
820
|
+
const actionToken = (0, router_js_1.mapRoutingActionToAuthorizationAction)(selected);
|
|
821
|
+
// Terminal actions (Drop, Deny) don't need authorization
|
|
822
|
+
if (actionToken === null) {
|
|
823
|
+
return selected;
|
|
824
|
+
}
|
|
825
|
+
try {
|
|
826
|
+
const authResult = await this._authorizer.authorizeRoute(node, envelope, actionToken, context ?? undefined);
|
|
827
|
+
// undefined means allow (authorizer has no opinion)
|
|
828
|
+
if (authResult === undefined) {
|
|
829
|
+
return selected;
|
|
830
|
+
}
|
|
831
|
+
// Check authorization result
|
|
832
|
+
if (authResult.authorized) {
|
|
833
|
+
logger.debug('route_authorization_allowed', {
|
|
834
|
+
envp_id: envelope.id,
|
|
835
|
+
action: actionToken,
|
|
836
|
+
frame_type: envelope.frame?.type ?? null,
|
|
837
|
+
matched_rule: authResult.matchedRule ?? null,
|
|
838
|
+
});
|
|
839
|
+
return selected;
|
|
840
|
+
}
|
|
841
|
+
// Authorization denied - return Deny action with opaque NACK
|
|
842
|
+
logger.warning('route_authorization_denied_by_policy', {
|
|
843
|
+
envp_id: envelope.id,
|
|
844
|
+
action: actionToken,
|
|
845
|
+
frame_type: envelope.frame?.type ?? null,
|
|
846
|
+
origin_type: context?.originType ?? null,
|
|
847
|
+
to: envelope.to?.toString() ?? null,
|
|
848
|
+
denial_reason: authResult.denialReason ?? 'policy_denied',
|
|
849
|
+
matched_rule: authResult.matchedRule ?? null,
|
|
850
|
+
});
|
|
851
|
+
// Determine disclosure mode from configuration
|
|
852
|
+
const disclosure = this.getNackDisclosureMode();
|
|
853
|
+
return new router_js_1.Deny({
|
|
854
|
+
internalReason: authResult.denialReason ?? 'unauthorized_route',
|
|
855
|
+
deniedAction: actionToken,
|
|
856
|
+
matchedRule: authResult.matchedRule,
|
|
857
|
+
disclosure,
|
|
858
|
+
context: {
|
|
859
|
+
frame_type: envelope.frame?.type ?? null,
|
|
860
|
+
origin_type: context?.originType ?? null,
|
|
861
|
+
},
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
catch (error) {
|
|
865
|
+
logger.error('route_authorization_error', {
|
|
866
|
+
envp_id: envelope.id,
|
|
867
|
+
action: actionToken,
|
|
868
|
+
error: error instanceof Error ? error.message : String(error),
|
|
869
|
+
});
|
|
870
|
+
// On error, deny by default (fail-safe)
|
|
871
|
+
return new router_js_1.Deny({
|
|
872
|
+
internalReason: 'authorization_error',
|
|
873
|
+
deniedAction: actionToken,
|
|
874
|
+
disclosure: 'opaque',
|
|
875
|
+
context: {
|
|
876
|
+
error: error instanceof Error ? error.message : String(error),
|
|
877
|
+
},
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Gets the NACK disclosure mode from configuration.
|
|
883
|
+
* Default is 'opaque' to avoid leaking route existence.
|
|
884
|
+
*/
|
|
885
|
+
getNackDisclosureMode() {
|
|
886
|
+
// Future: Could be made configurable via _policy or constructor options
|
|
887
|
+
return 'opaque';
|
|
888
|
+
}
|
|
795
889
|
async onForwardToRoute(node, nextSegment, envelope, context) {
|
|
796
890
|
logger.debug('on_forward_to_route_start', {
|
|
797
891
|
envp_id: envelope.id,
|
|
@@ -4,10 +4,13 @@ exports.PROFILE_NAME_OPEN = exports.PROFILE_NAME_GATED_CALLBACK = exports.PROFIL
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
tslib_1.__exportStar(require("./auth/authorizer.js"), exports);
|
|
6
6
|
tslib_1.__exportStar(require("./auth/auth-identity.js"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./auth/policy-authorizer.js"), exports);
|
|
7
8
|
var authorizer_factory_js_1 = require("./auth/authorizer-factory.js");
|
|
8
9
|
Object.defineProperty(exports, "AUTHORIZER_FACTORY_BASE_TYPE", { enumerable: true, get: function () { return authorizer_factory_js_1.AUTHORIZER_FACTORY_BASE_TYPE; } });
|
|
9
10
|
Object.defineProperty(exports, "AuthorizerFactory", { enumerable: true, get: function () { return authorizer_factory_js_1.AuthorizerFactory; } });
|
|
10
11
|
tslib_1.__exportStar(require("./auth/auth-injection-strategy.js"), exports);
|
|
12
|
+
// Authorization policy exports
|
|
13
|
+
tslib_1.__exportStar(require("./auth/policy/index.js"), exports);
|
|
11
14
|
var auth_injection_strategy_factory_js_1 = require("./auth/auth-injection-strategy-factory.js");
|
|
12
15
|
Object.defineProperty(exports, "AUTH_INJECTION_STRATEGY_FACTORY_BASE_TYPE", { enumerable: true, get: function () { return auth_injection_strategy_factory_js_1.AUTH_INJECTION_STRATEGY_FACTORY_BASE_TYPE; } });
|
|
13
16
|
Object.defineProperty(exports, "AuthInjectionStrategyFactory", { enumerable: true, get: function () { return auth_injection_strategy_factory_js_1.AuthInjectionStrategyFactory; } });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NodeSecurityProfileFactory = exports.FACTORY_META = exports.PROFILE_NAME_OPEN = exports.PROFILE_NAME_GATED_CALLBACK = exports.PROFILE_NAME_GATED = exports.PROFILE_NAME_OVERLAY_CALLBACK = exports.PROFILE_NAME_OVERLAY = exports.PROFILE_NAME_STRICT_OVERLAY = exports.ENV_VAR_ENFORCE_TOKEN_SUBJECT_NODE_IDENTITY = exports.ENV_VAR_JWT_REVERSE_AUTH_AUDIENCE = exports.ENV_VAR_JWT_REVERSE_AUTH_TRUSTED_ISSUER = exports.ENV_VAR_HMAC_SECRET = exports.ENV_VAR_DEFAULT_ENCRYPTION_LEVEL = exports.ENV_VAR_JWKS_URL = exports.ENV_VAR_JWT_AUDIENCE = exports.ENV_VAR_JWT_ALGORITHM = exports.ENV_VAR_JWT_TRUSTED_ISSUER = void 0;
|
|
3
|
+
exports.NodeSecurityProfileFactory = exports.FACTORY_META = exports.PROFILE_NAME_OPEN = exports.PROFILE_NAME_GATED_CALLBACK = exports.PROFILE_NAME_GATED = exports.PROFILE_NAME_OVERLAY_CALLBACK = exports.PROFILE_NAME_OVERLAY = exports.PROFILE_NAME_STRICT_OVERLAY = exports.ENV_VAR_TRUSTED_CLIENT_SCOPE = exports.ENV_VAR_ENFORCE_TOKEN_SUBJECT_NODE_IDENTITY = exports.ENV_VAR_JWT_REVERSE_AUTH_AUDIENCE = exports.ENV_VAR_JWT_REVERSE_AUTH_TRUSTED_ISSUER = exports.ENV_VAR_HMAC_SECRET = exports.ENV_VAR_DEFAULT_ENCRYPTION_LEVEL = exports.ENV_VAR_JWKS_URL = exports.ENV_VAR_JWT_AUDIENCE = exports.ENV_VAR_JWT_ALGORITHM = exports.ENV_VAR_JWT_TRUSTED_ISSUER = void 0;
|
|
4
4
|
const factory_1 = require("@naylence/factory");
|
|
5
5
|
const security_manager_factory_js_1 = require("./security-manager-factory.js");
|
|
6
6
|
const logging_js_1 = require("../util/logging.js");
|
|
@@ -14,6 +14,7 @@ exports.ENV_VAR_HMAC_SECRET = 'FAME_HMAC_SECRET';
|
|
|
14
14
|
exports.ENV_VAR_JWT_REVERSE_AUTH_TRUSTED_ISSUER = 'FAME_JWT_REVERSE_AUTH_TRUSTED_ISSUER';
|
|
15
15
|
exports.ENV_VAR_JWT_REVERSE_AUTH_AUDIENCE = 'FAME_JWT_REVERSE_AUTH_AUDIENCE';
|
|
16
16
|
exports.ENV_VAR_ENFORCE_TOKEN_SUBJECT_NODE_IDENTITY = 'FAME_ENFORCE_TOKEN_SUBJECT_NODE_IDENTITY';
|
|
17
|
+
exports.ENV_VAR_TRUSTED_CLIENT_SCOPE = 'FAME_TRUSTED_CLIENT_SCOPE';
|
|
17
18
|
exports.PROFILE_NAME_STRICT_OVERLAY = 'strict-overlay';
|
|
18
19
|
exports.PROFILE_NAME_OVERLAY = 'overlay';
|
|
19
20
|
exports.PROFILE_NAME_OVERLAY_CALLBACK = 'overlay-callback';
|
|
@@ -250,6 +251,7 @@ const GATED_PROFILE = {
|
|
|
250
251
|
algorithm: factory_1.Expressions.env(exports.ENV_VAR_JWT_ALGORITHM, 'RS256'),
|
|
251
252
|
audience: factory_1.Expressions.env(exports.ENV_VAR_JWT_AUDIENCE),
|
|
252
253
|
enforce_token_subject_node_identity: factory_1.Expressions.env(exports.ENV_VAR_ENFORCE_TOKEN_SUBJECT_NODE_IDENTITY, 'false'),
|
|
254
|
+
trusted_client_scope: factory_1.Expressions.env(exports.ENV_VAR_TRUSTED_CLIENT_SCOPE, 'node.trusted'),
|
|
253
255
|
},
|
|
254
256
|
};
|
|
255
257
|
const GATED_CALLBACK_PROFILE = {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RouterState = exports.ForwardPeer = exports.ForwardChild = exports.DeliverLocal = exports.ForwardUp = exports.Drop = void 0;
|
|
3
|
+
exports.RouterState = exports.Deny = exports.ForwardPeer = exports.ForwardChild = exports.DeliverLocal = exports.ForwardUp = exports.Drop = void 0;
|
|
4
|
+
exports.mapRoutingActionToAuthorizationAction = mapRoutingActionToAuthorizationAction;
|
|
4
5
|
exports.emitDeliveryNack = emitDeliveryNack;
|
|
5
6
|
const core_1 = require("@naylence/core");
|
|
6
7
|
const errors_js_1 = require("../errors/errors.js");
|
|
@@ -99,6 +100,71 @@ class ForwardPeer {
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
exports.ForwardPeer = ForwardPeer;
|
|
103
|
+
/**
|
|
104
|
+
* RoutingAction that denies an envelope due to authorization failure.
|
|
105
|
+
*
|
|
106
|
+
* Emits an opaque NO_ROUTE NACK on wire (by default) to avoid leaking
|
|
107
|
+
* route existence, while logging the true denial reason internally.
|
|
108
|
+
*/
|
|
109
|
+
class Deny {
|
|
110
|
+
constructor(options) {
|
|
111
|
+
this.options = options;
|
|
112
|
+
}
|
|
113
|
+
async execute(envelope, router, state, context) {
|
|
114
|
+
const { internalReason, deniedAction, matchedRule, context: extraContext, disclosure = 'opaque', } = this.options;
|
|
115
|
+
// Log detailed denial internally
|
|
116
|
+
logger.warning('route_authorization_denied', {
|
|
117
|
+
envp_id: envelope.id,
|
|
118
|
+
frame_type: envelope.frame?.type ?? null,
|
|
119
|
+
to: envelope.to?.toString() ?? null,
|
|
120
|
+
internal_reason: internalReason,
|
|
121
|
+
denied_action: deniedAction ?? null,
|
|
122
|
+
matched_rule: matchedRule ?? null,
|
|
123
|
+
origin_type: context?.originType ?? null,
|
|
124
|
+
...extraContext,
|
|
125
|
+
});
|
|
126
|
+
// Emit opaque NACK on wire (or verbose if configured)
|
|
127
|
+
const wireCode = disclosure === 'verbose' ? 'UNAUTHORIZED_ROUTE' : 'NO_ROUTE';
|
|
128
|
+
await emitDeliveryNack(envelope, router, state, wireCode, context ?? undefined);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.Deny = Deny;
|
|
132
|
+
/**
|
|
133
|
+
* Maps a RoutingAction instance to an authorization action token.
|
|
134
|
+
*
|
|
135
|
+
* This function uses instanceof checks to determine the action type,
|
|
136
|
+
* avoiding the need to expose action objects to the authorizer.
|
|
137
|
+
*
|
|
138
|
+
* For unknown/custom RoutingAction types, returns null. Callers should
|
|
139
|
+
* treat null as "deny by default" for security (unknown actions are not
|
|
140
|
+
* authorized).
|
|
141
|
+
*
|
|
142
|
+
* @param action - The RoutingAction instance to map
|
|
143
|
+
* @returns The authorization action token, or null for terminal/unknown actions
|
|
144
|
+
*/
|
|
145
|
+
function mapRoutingActionToAuthorizationAction(action) {
|
|
146
|
+
if (action instanceof ForwardUp) {
|
|
147
|
+
return 'ForwardUpstream';
|
|
148
|
+
}
|
|
149
|
+
if (action instanceof ForwardChild) {
|
|
150
|
+
return 'ForwardDownstream';
|
|
151
|
+
}
|
|
152
|
+
if (action instanceof ForwardPeer) {
|
|
153
|
+
return 'ForwardPeer';
|
|
154
|
+
}
|
|
155
|
+
if (action instanceof DeliverLocal) {
|
|
156
|
+
return 'DeliverLocal';
|
|
157
|
+
}
|
|
158
|
+
// Drop and Deny are terminal actions that don't need authorization
|
|
159
|
+
if (action instanceof Drop || action instanceof Deny) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
// Unknown RoutingAction: return null, caller should deny by default
|
|
163
|
+
logger.warning('unknown_routing_action_for_authorization', {
|
|
164
|
+
action_type: action?.constructor?.name ?? 'unknown',
|
|
165
|
+
});
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
102
168
|
class RouterState {
|
|
103
169
|
constructor(options) {
|
|
104
170
|
const normalized = normalizeRouterStateOptions(options);
|
|
@@ -281,8 +281,11 @@ class Sentinel extends node_js_1.FameNode {
|
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
283
|
const state = this.buildRouterState();
|
|
284
|
-
|
|
285
|
-
|
|
284
|
+
let action = await this.routingPolicy.decide(processedEnvelope, state, context);
|
|
285
|
+
// Dispatch onRoutingActionSelected hook to allow authorization/replacement
|
|
286
|
+
// The hook must return the action to execute; null/undefined/throw => Drop
|
|
287
|
+
const actionToExecute = await this.dispatchRoutingActionSelected(processedEnvelope, action, state, context);
|
|
288
|
+
await actionToExecute.execute(processedEnvelope, this, state, context);
|
|
286
289
|
}
|
|
287
290
|
async forwardToRoute(nextSegment, envelope, context) {
|
|
288
291
|
if (this.originMatches(context, nextSegment, core_1.DeliveryOriginType.DOWNSTREAM)) {
|
|
@@ -828,6 +831,47 @@ class Sentinel extends node_js_1.FameNode {
|
|
|
828
831
|
});
|
|
829
832
|
}
|
|
830
833
|
}
|
|
834
|
+
/**
|
|
835
|
+
* Dispatches the onRoutingActionSelected event to all event listeners.
|
|
836
|
+
*
|
|
837
|
+
* This allows listeners (like DefaultSecurityManager) to authorize
|
|
838
|
+
* routing actions and optionally replace them with Deny actions.
|
|
839
|
+
*
|
|
840
|
+
* The hook must return the RoutingAction to execute. If a listener returns
|
|
841
|
+
* null, undefined, or throws, the router will execute a Drop action.
|
|
842
|
+
*
|
|
843
|
+
* @param envelope - The envelope being routed
|
|
844
|
+
* @param selected - The RoutingAction selected by the routing policy
|
|
845
|
+
* @param state - The current router state
|
|
846
|
+
* @param context - Optional delivery context
|
|
847
|
+
* @returns The RoutingAction to execute (never null/undefined)
|
|
848
|
+
*/
|
|
849
|
+
async dispatchRoutingActionSelected(envelope, selected, state, context) {
|
|
850
|
+
let currentAction = selected;
|
|
851
|
+
for (const listener of this.eventListeners) {
|
|
852
|
+
if (typeof listener.onRoutingActionSelected !== 'function') {
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
try {
|
|
856
|
+
const result = await listener.onRoutingActionSelected(this, envelope, currentAction, state, context);
|
|
857
|
+
// null/undefined => treat as denial, execute Drop
|
|
858
|
+
if (result == null) {
|
|
859
|
+
return new router_js_1.Drop();
|
|
860
|
+
}
|
|
861
|
+
// Update current action for next listener in chain
|
|
862
|
+
currentAction = result;
|
|
863
|
+
}
|
|
864
|
+
catch (error) {
|
|
865
|
+
// Hook threw => treat as denial, execute Drop
|
|
866
|
+
logger.warning('routing_action_hook_error', {
|
|
867
|
+
envp_id: envelope.id,
|
|
868
|
+
error: error instanceof Error ? error.message : String(error),
|
|
869
|
+
});
|
|
870
|
+
return new router_js_1.Drop();
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return currentAction;
|
|
874
|
+
}
|
|
831
875
|
static async aserve(options = {}) {
|
|
832
876
|
const { logLevel, rootConfig, config, node = null, fabric: providedFabric = null, signals = ['SIGINT', 'SIGTERM'], signal, ...fabricOptions } = options;
|
|
833
877
|
const resolvedLevel = normalizeServeLogLevel(logLevel) ?? logging_js_1.LogLevel.INFO;
|
|
@@ -45,6 +45,8 @@ const NODE_ONLY_FACTORY_MODULES = new Set([
|
|
|
45
45
|
'./connector/websocket-listener-factory.js',
|
|
46
46
|
'./telemetry/open-telemetry-trace-emitter-factory.js',
|
|
47
47
|
'./security/credential/prompt-credential-provider-factory.js',
|
|
48
|
+
'./security/auth/default-policy-authorizer-factory.js',
|
|
49
|
+
'./security/auth/policy/local-file-authorization-policy-source-factory.js',
|
|
48
50
|
]);
|
|
49
51
|
const BROWSER_ONLY_FACTORY_MODULES = new Set([
|
|
50
52
|
'./security/auth/oauth2-pkce-token-provider-factory.js',
|
package/dist/cjs/version.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// This file is auto-generated during build - do not edit manually
|
|
3
|
-
// Generated from package.json version: 0.
|
|
3
|
+
// Generated from package.json version: 0.4.0
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.VERSION = void 0;
|
|
6
6
|
/**
|
|
7
7
|
* The package version, injected at build time.
|
|
8
8
|
* @internal
|
|
9
9
|
*/
|
|
10
|
-
exports.VERSION = '0.
|
|
10
|
+
exports.VERSION = '0.4.0';
|
|
@@ -29,6 +29,7 @@ export const MODULES = [
|
|
|
29
29
|
"./placement/static-node-placement-strategy-factory.js",
|
|
30
30
|
"./security/auth/bearer-token-header-auth-injection-strategy-factory.js",
|
|
31
31
|
"./security/auth/default-authorizer-factory.js",
|
|
32
|
+
"./security/auth/default-policy-authorizer-factory.js",
|
|
32
33
|
"./security/auth/jwks-jwt-token-verifier-factory.js",
|
|
33
34
|
"./security/auth/jwt-token-issuer-factory.js",
|
|
34
35
|
"./security/auth/jwt-token-verifier-factory.js",
|
|
@@ -40,6 +41,8 @@ export const MODULES = [
|
|
|
40
41
|
"./security/auth/oauth2-authorizer-factory.js",
|
|
41
42
|
"./security/auth/oauth2-client-credentials-token-provider-factory.js",
|
|
42
43
|
"./security/auth/oauth2-pkce-token-provider-factory.js",
|
|
44
|
+
"./security/auth/policy/basic-authorization-policy-factory.js",
|
|
45
|
+
"./security/auth/policy/local-file-authorization-policy-source-factory.js",
|
|
43
46
|
"./security/auth/query-param-auth-injection-strategy-factory.js",
|
|
44
47
|
"./security/auth/shared-secret-authorizer-factory.js",
|
|
45
48
|
"./security/auth/shared-secret-token-provider-factory.js",
|
|
@@ -108,6 +111,7 @@ export const MODULE_LOADERS = {
|
|
|
108
111
|
"./placement/static-node-placement-strategy-factory.js": () => import("./placement/static-node-placement-strategy-factory.js"),
|
|
109
112
|
"./security/auth/bearer-token-header-auth-injection-strategy-factory.js": () => import("./security/auth/bearer-token-header-auth-injection-strategy-factory.js"),
|
|
110
113
|
"./security/auth/default-authorizer-factory.js": () => import("./security/auth/default-authorizer-factory.js"),
|
|
114
|
+
"./security/auth/default-policy-authorizer-factory.js": () => import(/* webpackIgnore: true */ /* @vite-ignore */ "./security/auth/default-policy-authorizer-factory.js"),
|
|
111
115
|
"./security/auth/jwks-jwt-token-verifier-factory.js": () => import("./security/auth/jwks-jwt-token-verifier-factory.js"),
|
|
112
116
|
"./security/auth/jwt-token-issuer-factory.js": () => import("./security/auth/jwt-token-issuer-factory.js"),
|
|
113
117
|
"./security/auth/jwt-token-verifier-factory.js": () => import("./security/auth/jwt-token-verifier-factory.js"),
|
|
@@ -119,6 +123,8 @@ export const MODULE_LOADERS = {
|
|
|
119
123
|
"./security/auth/oauth2-authorizer-factory.js": () => import("./security/auth/oauth2-authorizer-factory.js"),
|
|
120
124
|
"./security/auth/oauth2-client-credentials-token-provider-factory.js": () => import("./security/auth/oauth2-client-credentials-token-provider-factory.js"),
|
|
121
125
|
"./security/auth/oauth2-pkce-token-provider-factory.js": () => import("./security/auth/oauth2-pkce-token-provider-factory.js"),
|
|
126
|
+
"./security/auth/policy/basic-authorization-policy-factory.js": () => import("./security/auth/policy/basic-authorization-policy-factory.js"),
|
|
127
|
+
"./security/auth/policy/local-file-authorization-policy-source-factory.js": () => import(/* webpackIgnore: true */ /* @vite-ignore */ "./security/auth/policy/local-file-authorization-policy-source-factory.js"),
|
|
122
128
|
"./security/auth/query-param-auth-injection-strategy-factory.js": () => import("./security/auth/query-param-auth-injection-strategy-factory.js"),
|
|
123
129
|
"./security/auth/shared-secret-authorizer-factory.js": () => import("./security/auth/shared-secret-authorizer-factory.js"),
|
|
124
130
|
"./security/auth/shared-secret-token-provider-factory.js": () => import("./security/auth/shared-secret-token-provider-factory.js"),
|
|
@@ -45,6 +45,10 @@ export class BaseNodeEventListener {
|
|
|
45
45
|
// Default implementation passes envelope through unchanged
|
|
46
46
|
return envelope;
|
|
47
47
|
}
|
|
48
|
+
async onRoutingActionSelected(_node, _envelope, selected, _state, _context) {
|
|
49
|
+
// Default implementation returns the selected action unchanged
|
|
50
|
+
return selected;
|
|
51
|
+
}
|
|
48
52
|
async onForwardUpstream(_node, envelope, _context) {
|
|
49
53
|
// Default implementation passes envelope through unchanged
|
|
50
54
|
return envelope;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { safeImport } from '../../util/lazy-import.js';
|
|
2
|
+
import { AUTHORIZER_FACTORY_BASE_TYPE, AuthorizerFactory, } from './authorizer-factory.js';
|
|
3
|
+
import { TokenVerifierFactory, } from './token-verifier-factory.js';
|
|
4
|
+
import { AuthorizationPolicySourceFactory, } from './policy/authorization-policy-source-factory.js';
|
|
5
|
+
import { AuthorizationPolicyFactory, } from './policy/authorization-policy-factory.js';
|
|
6
|
+
let defaultPolicyAuthorizerModulePromise = null;
|
|
7
|
+
async function getDefaultPolicyAuthorizerModule() {
|
|
8
|
+
if (!defaultPolicyAuthorizerModulePromise) {
|
|
9
|
+
defaultPolicyAuthorizerModulePromise = safeImport(() => import('./default-policy-authorizer.js'), 'default-policy-authorizer');
|
|
10
|
+
}
|
|
11
|
+
return defaultPolicyAuthorizerModulePromise;
|
|
12
|
+
}
|
|
13
|
+
function normalizeConfig(config) {
|
|
14
|
+
if (!config) {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
const candidate = config;
|
|
18
|
+
const verifierConfig = candidate.verifier ?? null;
|
|
19
|
+
if (verifierConfig && typeof verifierConfig !== 'object') {
|
|
20
|
+
throw new Error('PolicyAuthorizer verifier configuration must be an object');
|
|
21
|
+
}
|
|
22
|
+
const policyConfig = candidate.policy ?? null;
|
|
23
|
+
if (policyConfig && typeof policyConfig !== 'object') {
|
|
24
|
+
throw new Error('PolicyAuthorizer policy configuration must be an object');
|
|
25
|
+
}
|
|
26
|
+
const policySourceConfig = candidate.policySource ?? candidate.policy_source ?? null;
|
|
27
|
+
if (policySourceConfig && typeof policySourceConfig !== 'object') {
|
|
28
|
+
throw new Error('PolicyAuthorizer policySource configuration must be an object');
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
verifier: verifierConfig,
|
|
32
|
+
policy: policyConfig,
|
|
33
|
+
policySource: policySourceConfig,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function isTokenVerifier(candidate) {
|
|
37
|
+
return Boolean(candidate && typeof candidate.verify === 'function');
|
|
38
|
+
}
|
|
39
|
+
function isAuthorizationPolicy(candidate) {
|
|
40
|
+
return Boolean(candidate &&
|
|
41
|
+
typeof candidate.evaluateRequest === 'function');
|
|
42
|
+
}
|
|
43
|
+
function isAuthorizationPolicySource(candidate) {
|
|
44
|
+
return Boolean(candidate &&
|
|
45
|
+
typeof candidate.loadPolicy === 'function');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Factory metadata for registration.
|
|
49
|
+
*/
|
|
50
|
+
export const FACTORY_META = {
|
|
51
|
+
base: AUTHORIZER_FACTORY_BASE_TYPE,
|
|
52
|
+
key: 'PolicyAuthorizer',
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Factory for creating DefaultPolicyAuthorizer instances.
|
|
56
|
+
*
|
|
57
|
+
* This factory uses lazy loading to avoid pulling in Node.js-specific
|
|
58
|
+
* code in browser environments.
|
|
59
|
+
*/
|
|
60
|
+
export class DefaultPolicyAuthorizerFactory extends AuthorizerFactory {
|
|
61
|
+
constructor() {
|
|
62
|
+
super(...arguments);
|
|
63
|
+
this.type = 'PolicyAuthorizer';
|
|
64
|
+
this.isDefault = true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Creates a DefaultPolicyAuthorizer from the given configuration.
|
|
68
|
+
*
|
|
69
|
+
* @param config - Configuration for the authorizer
|
|
70
|
+
* @param factoryArgs - Additional factory arguments:
|
|
71
|
+
* - TokenVerifier instance
|
|
72
|
+
* - AuthorizationPolicy instance
|
|
73
|
+
* - AuthorizationPolicySource instance
|
|
74
|
+
* @returns The created authorizer
|
|
75
|
+
*/
|
|
76
|
+
async create(config, ...factoryArgs) {
|
|
77
|
+
const normalized = normalizeConfig(config);
|
|
78
|
+
// Resolve token verifier
|
|
79
|
+
let tokenVerifier = factoryArgs.find(isTokenVerifier);
|
|
80
|
+
if (!tokenVerifier && normalized.verifier) {
|
|
81
|
+
tokenVerifier = await TokenVerifierFactory.createTokenVerifier(normalized.verifier);
|
|
82
|
+
}
|
|
83
|
+
if (!tokenVerifier) {
|
|
84
|
+
throw new Error('PolicyAuthorizer requires a verifier configuration or instance');
|
|
85
|
+
}
|
|
86
|
+
// Resolve policy or policy source
|
|
87
|
+
let policy = factoryArgs.find(isAuthorizationPolicy);
|
|
88
|
+
let policySource = factoryArgs.find(isAuthorizationPolicySource);
|
|
89
|
+
// Create policy from config if not provided as argument
|
|
90
|
+
if (!policy && normalized.policy) {
|
|
91
|
+
policy = await AuthorizationPolicyFactory.createAuthorizationPolicy(normalized.policy);
|
|
92
|
+
}
|
|
93
|
+
// Create policy source from config if not provided as argument
|
|
94
|
+
if (!policySource && normalized.policySource) {
|
|
95
|
+
policySource =
|
|
96
|
+
await AuthorizationPolicySourceFactory.createAuthorizationPolicySource(normalized.policySource);
|
|
97
|
+
}
|
|
98
|
+
// Validate that we have either policy or policy source
|
|
99
|
+
if (!policy && !policySource) {
|
|
100
|
+
throw new Error('PolicyAuthorizer requires either a policy or policySource configuration');
|
|
101
|
+
}
|
|
102
|
+
const { DefaultPolicyAuthorizer } = await getDefaultPolicyAuthorizerModule();
|
|
103
|
+
return new DefaultPolicyAuthorizer({
|
|
104
|
+
tokenVerifier,
|
|
105
|
+
policy,
|
|
106
|
+
policySource,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export default DefaultPolicyAuthorizerFactory;
|