@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
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { createAuthorizationContext } from '@naylence/core';
|
|
2
|
+
import { getLogger } from '../../util/logging.js';
|
|
3
|
+
const logger = getLogger('naylence.fame.security.auth.default_policy_authorizer');
|
|
4
|
+
function decodeCredentials(credentials) {
|
|
5
|
+
if (typeof TextDecoder !== 'undefined') {
|
|
6
|
+
return new TextDecoder().decode(credentials);
|
|
7
|
+
}
|
|
8
|
+
if (typeof Buffer !== 'undefined') {
|
|
9
|
+
return Buffer.from(credentials).toString('utf-8');
|
|
10
|
+
}
|
|
11
|
+
throw new Error('Unable to decode credential bytes without TextDecoder support');
|
|
12
|
+
}
|
|
13
|
+
function normalizeToken(credentials) {
|
|
14
|
+
const raw = typeof credentials === 'string'
|
|
15
|
+
? credentials
|
|
16
|
+
: decodeCredentials(credentials);
|
|
17
|
+
const trimmed = raw.trim();
|
|
18
|
+
if (trimmed.length === 0) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (trimmed.toLowerCase().startsWith('bearer ')) {
|
|
22
|
+
const candidate = trimmed.slice(7).trim();
|
|
23
|
+
return candidate.length > 0 ? candidate : undefined;
|
|
24
|
+
}
|
|
25
|
+
return trimmed;
|
|
26
|
+
}
|
|
27
|
+
function normalizeOptions(options) {
|
|
28
|
+
if (options === undefined || options === null) {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
if (typeof options !== 'object') {
|
|
32
|
+
throw new TypeError('DefaultPolicyAuthorizer options must be an object');
|
|
33
|
+
}
|
|
34
|
+
const candidate = options;
|
|
35
|
+
return {
|
|
36
|
+
tokenVerifier: candidate.tokenVerifier ?? candidate.token_verifier,
|
|
37
|
+
policy: candidate.policy,
|
|
38
|
+
policySource: candidate.policySource ?? candidate.policy_source,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* An authorizer that delegates authorization decisions to a pluggable policy.
|
|
43
|
+
*
|
|
44
|
+
* This authorizer combines token-based authentication with policy-based
|
|
45
|
+
* authorization. The token verifier handles authentication (validating
|
|
46
|
+
* credentials), while the authorization policy handles authorization
|
|
47
|
+
* decisions (allow/deny based on the request context).
|
|
48
|
+
*/
|
|
49
|
+
export class DefaultPolicyAuthorizer {
|
|
50
|
+
constructor(options = {}) {
|
|
51
|
+
this.policyLoaded = false;
|
|
52
|
+
const normalized = normalizeOptions(options);
|
|
53
|
+
if (normalized.tokenVerifier) {
|
|
54
|
+
this.tokenVerifierImpl = normalized.tokenVerifier;
|
|
55
|
+
}
|
|
56
|
+
if (normalized.policy) {
|
|
57
|
+
this.policyImpl = normalized.policy;
|
|
58
|
+
this.policyLoaded = true;
|
|
59
|
+
}
|
|
60
|
+
if (normalized.policySource) {
|
|
61
|
+
this.policySource = normalized.policySource;
|
|
62
|
+
}
|
|
63
|
+
// Validate that we have either a policy or a policy source
|
|
64
|
+
if (!normalized.policy && !normalized.policySource) {
|
|
65
|
+
throw new Error('DefaultPolicyAuthorizer requires either a policy or a policySource');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The currently active authorization policy.
|
|
70
|
+
*/
|
|
71
|
+
get policy() {
|
|
72
|
+
if (!this.policyImpl) {
|
|
73
|
+
throw new Error('Authorization policy not loaded. Call ensurePolicyLoaded() first.');
|
|
74
|
+
}
|
|
75
|
+
return this.policyImpl;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The token verifier used for authentication.
|
|
79
|
+
*/
|
|
80
|
+
get tokenVerifier() {
|
|
81
|
+
if (!this.tokenVerifierImpl) {
|
|
82
|
+
throw new Error('DefaultPolicyAuthorizer is not initialized properly, missing tokenVerifier');
|
|
83
|
+
}
|
|
84
|
+
return this.tokenVerifierImpl;
|
|
85
|
+
}
|
|
86
|
+
set tokenVerifier(verifier) {
|
|
87
|
+
this.tokenVerifierImpl = verifier;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Ensures the authorization policy is loaded.
|
|
91
|
+
* If using a policy source, loads the policy from it.
|
|
92
|
+
*/
|
|
93
|
+
async ensurePolicyLoaded() {
|
|
94
|
+
if (this.policyLoaded && this.policyImpl) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (!this.policySource) {
|
|
98
|
+
throw new Error('No policy source configured and no policy provided');
|
|
99
|
+
}
|
|
100
|
+
logger.debug('loading_policy_from_source');
|
|
101
|
+
this.policyImpl = await this.policySource.loadPolicy();
|
|
102
|
+
this.policyLoaded = true;
|
|
103
|
+
logger.info('policy_loaded_from_source');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Reloads the authorization policy from the policy source.
|
|
107
|
+
* Only works if a policy source was configured.
|
|
108
|
+
*/
|
|
109
|
+
async reloadPolicy() {
|
|
110
|
+
if (!this.policySource) {
|
|
111
|
+
throw new Error('Cannot reload policy: no policy source configured');
|
|
112
|
+
}
|
|
113
|
+
logger.debug('reloading_policy_from_source');
|
|
114
|
+
this.policyImpl = await this.policySource.loadPolicy();
|
|
115
|
+
this.policyLoaded = true;
|
|
116
|
+
logger.info('policy_reloaded_from_source');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Authenticates credentials and returns an authorization context.
|
|
120
|
+
*
|
|
121
|
+
* @param credentials - The credentials to authenticate (token string or bytes)
|
|
122
|
+
* @returns The authorization context if authentication succeeds, undefined otherwise
|
|
123
|
+
*/
|
|
124
|
+
async authenticate(credentials) {
|
|
125
|
+
const token = normalizeToken(credentials);
|
|
126
|
+
if (!token) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const verifier = this.tokenVerifier;
|
|
131
|
+
const context = await verifier.verify(token);
|
|
132
|
+
return createAuthorizationContext({
|
|
133
|
+
...context,
|
|
134
|
+
authenticated: true,
|
|
135
|
+
authorized: false, // Authorization happens in authorize()
|
|
136
|
+
authMethod: context.authMethod ?? 'jwt',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
logger.warning('token_verification_failed', {
|
|
141
|
+
error: error instanceof Error ? error.message : String(error),
|
|
142
|
+
});
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Authorizes a request using the configured authorization policy.
|
|
148
|
+
*
|
|
149
|
+
* For NodeAttach frames, evaluates policy with action='Connect'.
|
|
150
|
+
* For other frames, this method performs basic authentication validation
|
|
151
|
+
* but does NOT infer send/receive actions. Route-level authorization
|
|
152
|
+
* is handled separately via authorizeRoute().
|
|
153
|
+
*
|
|
154
|
+
* @param node - The node handling the request
|
|
155
|
+
* @param envelope - The FAME envelope being authorized
|
|
156
|
+
* @param context - Optional delivery context
|
|
157
|
+
* @returns The authorization context if authorized, undefined if denied
|
|
158
|
+
*/
|
|
159
|
+
async authorize(node, envelope, context) {
|
|
160
|
+
const authorization = context?.security?.authorization;
|
|
161
|
+
// Must be authenticated first
|
|
162
|
+
if (!authorization || !authorization.authenticated) {
|
|
163
|
+
logger.debug('authorization_denied_not_authenticated');
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
// Ensure policy is loaded
|
|
167
|
+
await this.ensurePolicyLoaded();
|
|
168
|
+
// For NodeAttach frames, evaluate policy with 'Connect' action
|
|
169
|
+
const frameType = envelope.frame?.type;
|
|
170
|
+
if (frameType === 'NodeAttach') {
|
|
171
|
+
let decision;
|
|
172
|
+
try {
|
|
173
|
+
decision = await this.policy.evaluateRequest(node, envelope, context, 'Connect');
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
logger.error('policy_evaluation_failed', {
|
|
177
|
+
error: error instanceof Error ? error.message : String(error),
|
|
178
|
+
action: 'Connect',
|
|
179
|
+
});
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
if (decision.effect === 'allow') {
|
|
183
|
+
logger.debug('authorization_allowed', {
|
|
184
|
+
matchedRule: decision.matchedRule,
|
|
185
|
+
reason: decision.reason,
|
|
186
|
+
action: 'Connect',
|
|
187
|
+
});
|
|
188
|
+
return createAuthorizationContext({
|
|
189
|
+
...authorization,
|
|
190
|
+
authorized: true,
|
|
191
|
+
authMethod: authorization.authMethod ?? 'policy',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
logger.debug('authorization_denied', {
|
|
196
|
+
matchedRule: decision.matchedRule,
|
|
197
|
+
reason: decision.reason,
|
|
198
|
+
action: 'Connect',
|
|
199
|
+
});
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// For non-NodeAttach frames, authentication is sufficient at this stage.
|
|
204
|
+
// Route-level authorization is performed via authorizeRoute() after
|
|
205
|
+
// the routing decision is made.
|
|
206
|
+
logger.debug('authorization_passed_authentication_only', {
|
|
207
|
+
envp_id: envelope.id,
|
|
208
|
+
frame_type: frameType,
|
|
209
|
+
});
|
|
210
|
+
return createAuthorizationContext({
|
|
211
|
+
...authorization,
|
|
212
|
+
authorized: true,
|
|
213
|
+
authMethod: authorization.authMethod ?? 'policy',
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Authorizes a routing action after the routing decision has been made.
|
|
218
|
+
*
|
|
219
|
+
* This method evaluates the authorization policy with the explicitly
|
|
220
|
+
* provided action token (ForwardUpstream, ForwardDownstream, ForwardPeer,
|
|
221
|
+
* DeliverLocal).
|
|
222
|
+
*
|
|
223
|
+
* @param node - The node handling the request
|
|
224
|
+
* @param envelope - The FAME envelope being routed
|
|
225
|
+
* @param action - The authorization action token from the routing decision
|
|
226
|
+
* @param context - Optional delivery context
|
|
227
|
+
* @returns RouteAuthorizationResult with authorization decision
|
|
228
|
+
*/
|
|
229
|
+
async authorizeRoute(node, envelope, action, context) {
|
|
230
|
+
const authorization = context?.security?.authorization;
|
|
231
|
+
// If not authenticated, deny route authorization
|
|
232
|
+
if (!authorization || !authorization.authenticated) {
|
|
233
|
+
logger.debug('route_authorization_denied_not_authenticated', {
|
|
234
|
+
action,
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
authorized: false,
|
|
238
|
+
denialReason: 'not_authenticated',
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// Ensure policy is loaded
|
|
242
|
+
await this.ensurePolicyLoaded();
|
|
243
|
+
// Evaluate the policy with the provided action
|
|
244
|
+
let decision;
|
|
245
|
+
try {
|
|
246
|
+
decision = await this.policy.evaluateRequest(node, envelope, context, action);
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
logger.error('route_policy_evaluation_failed', {
|
|
250
|
+
error: error instanceof Error ? error.message : String(error),
|
|
251
|
+
action,
|
|
252
|
+
});
|
|
253
|
+
return {
|
|
254
|
+
authorized: false,
|
|
255
|
+
denialReason: 'policy_evaluation_error',
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
if (decision.effect === 'allow') {
|
|
259
|
+
logger.debug('route_authorization_allowed', {
|
|
260
|
+
matchedRule: decision.matchedRule,
|
|
261
|
+
reason: decision.reason,
|
|
262
|
+
action,
|
|
263
|
+
});
|
|
264
|
+
return {
|
|
265
|
+
authorized: true,
|
|
266
|
+
authContext: createAuthorizationContext({
|
|
267
|
+
...authorization,
|
|
268
|
+
authorized: true,
|
|
269
|
+
authMethod: authorization.authMethod ?? 'policy',
|
|
270
|
+
}),
|
|
271
|
+
matchedRule: decision.matchedRule,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
logger.debug('route_authorization_denied', {
|
|
276
|
+
matchedRule: decision.matchedRule,
|
|
277
|
+
reason: decision.reason,
|
|
278
|
+
action,
|
|
279
|
+
});
|
|
280
|
+
return {
|
|
281
|
+
authorized: false,
|
|
282
|
+
denialReason: decision.reason ?? 'policy_denied',
|
|
283
|
+
matchedRule: decision.matchedRule,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -51,6 +51,7 @@ export class OAuth2AuthorizerFactory extends AuthorizerFactory {
|
|
|
51
51
|
maxTtlSec: normalized.maxTtlSec,
|
|
52
52
|
reverseAuthTtlSec: normalized.reverseAuthTtlSec,
|
|
53
53
|
enforceTokenSubjectNodeIdentity: normalized.enforceTokenSubjectNodeIdentity,
|
|
54
|
+
trustedClientScope: normalized.trustedClientScope,
|
|
54
55
|
};
|
|
55
56
|
if (tokenIssuer) {
|
|
56
57
|
authorizerOptions.tokenIssuer = tokenIssuer;
|
|
@@ -120,6 +121,11 @@ function normalizeConfig(config) {
|
|
|
120
121
|
: DEFAULT_REVERSE_AUTH_TTL_SEC;
|
|
121
122
|
const enforceTokenSubjectNodeIdentity = normalizeBooleanOption(source.enforceTokenSubjectNodeIdentity ??
|
|
122
123
|
source.enforce_token_subject_node_identity, false);
|
|
124
|
+
const trustedClientScope = typeof source.trustedClientScope === 'string'
|
|
125
|
+
? source.trustedClientScope
|
|
126
|
+
: typeof source.trusted_client_scope === 'string'
|
|
127
|
+
? source.trusted_client_scope
|
|
128
|
+
: undefined;
|
|
123
129
|
const tokenVerifierConfigInput = source.tokenVerifierConfig ?? source.token_verifier_config ?? null;
|
|
124
130
|
const tokenVerifierConfig = normalizeTokenVerifierConfig({
|
|
125
131
|
config: tokenVerifierConfigInput,
|
|
@@ -140,6 +146,7 @@ function normalizeConfig(config) {
|
|
|
140
146
|
reverseAuthTtlSec: reverseAuthCandidate,
|
|
141
147
|
enforceTokenSubjectNodeIdentity,
|
|
142
148
|
...(audience ? { audience } : {}),
|
|
149
|
+
...(trustedClientScope ? { trustedClientScope } : {}),
|
|
143
150
|
};
|
|
144
151
|
if (tokenIssuerConfig) {
|
|
145
152
|
normalized.tokenIssuerConfig = tokenIssuerConfig;
|
|
@@ -38,6 +38,10 @@ function normalizeOptions(raw) {
|
|
|
38
38
|
(typeof snake.enforce_token_subject_node_identity === 'boolean'
|
|
39
39
|
? snake.enforce_token_subject_node_identity
|
|
40
40
|
: undefined);
|
|
41
|
+
const trustedClientScope = camel.trustedClientScope ??
|
|
42
|
+
(typeof snake.trusted_client_scope === 'string'
|
|
43
|
+
? snake.trusted_client_scope
|
|
44
|
+
: undefined);
|
|
41
45
|
return {
|
|
42
46
|
tokenVerifier,
|
|
43
47
|
tokenIssuer,
|
|
@@ -48,6 +52,7 @@ function normalizeOptions(raw) {
|
|
|
48
52
|
maxTtlSec,
|
|
49
53
|
reverseAuthTtlSec,
|
|
50
54
|
enforceTokenSubjectNodeIdentity,
|
|
55
|
+
trustedClientScope,
|
|
51
56
|
};
|
|
52
57
|
}
|
|
53
58
|
export class OAuth2Authorizer {
|
|
@@ -63,6 +68,7 @@ export class OAuth2Authorizer {
|
|
|
63
68
|
options.reverseAuthTtlSec ?? DEFAULT_REVERSE_AUTH_TTL_SEC;
|
|
64
69
|
this.enforceTokenSubjectNodeIdentity =
|
|
65
70
|
options.enforceTokenSubjectNodeIdentity ?? false;
|
|
71
|
+
this.trustedClientScope = options.trustedClientScope ?? 'node.trusted';
|
|
66
72
|
}
|
|
67
73
|
get tokenVerifier() {
|
|
68
74
|
return this.tokenVerifierImpl;
|
|
@@ -192,11 +198,20 @@ export class OAuth2Authorizer {
|
|
|
192
198
|
});
|
|
193
199
|
return undefined;
|
|
194
200
|
}
|
|
195
|
-
// Enforce token subject node identity if enabled
|
|
201
|
+
// Enforce token subject node identity if enabled and not a trusted client
|
|
196
202
|
if (this.enforceTokenSubjectNodeIdentity) {
|
|
197
|
-
const
|
|
198
|
-
if (
|
|
199
|
-
|
|
203
|
+
const isTrustedClient = scopes.has(this.trustedClientScope);
|
|
204
|
+
if (isTrustedClient) {
|
|
205
|
+
logger.debug('oauth2_attach_trusted_client_bypass', {
|
|
206
|
+
system_id: frame.systemId,
|
|
207
|
+
trusted_scope: this.trustedClientScope,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
const validationResult = await this.validateTokenSubjectNodeIdentity(frame.systemId, claims);
|
|
212
|
+
if (!validationResult) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
200
215
|
}
|
|
201
216
|
}
|
|
202
217
|
claims.instance_id = claims.instance_id ?? frame.instanceId;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization policy definition types.
|
|
3
|
+
*
|
|
4
|
+
* This module defines the schema for authorization policies that can be
|
|
5
|
+
* loaded from YAML/JSON files and evaluated at runtime.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Maximum nesting depth for scope requirements.
|
|
9
|
+
*/
|
|
10
|
+
export const MAX_SCOPE_NESTING_DEPTH = 5;
|
|
11
|
+
/**
|
|
12
|
+
* Known fields in AuthorizationPolicyDefinition.
|
|
13
|
+
*/
|
|
14
|
+
export const KNOWN_POLICY_FIELDS = new Set([
|
|
15
|
+
'version',
|
|
16
|
+
'default_effect',
|
|
17
|
+
'rules',
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* Known fields in AuthorizationRuleDefinition.
|
|
21
|
+
* Fields not in this set trigger a warning.
|
|
22
|
+
*/
|
|
23
|
+
export const KNOWN_RULE_FIELDS = new Set([
|
|
24
|
+
'id',
|
|
25
|
+
'description',
|
|
26
|
+
'effect',
|
|
27
|
+
'action',
|
|
28
|
+
'address',
|
|
29
|
+
'frame_type',
|
|
30
|
+
'origin_type',
|
|
31
|
+
'scope',
|
|
32
|
+
'when', // Reserved for advanced-security
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* Valid action values.
|
|
36
|
+
*/
|
|
37
|
+
export const VALID_ACTIONS = [
|
|
38
|
+
'Connect',
|
|
39
|
+
'ForwardUpstream',
|
|
40
|
+
'ForwardDownstream',
|
|
41
|
+
'ForwardPeer',
|
|
42
|
+
'DeliverLocal',
|
|
43
|
+
'*',
|
|
44
|
+
];
|
|
45
|
+
/**
|
|
46
|
+
* Valid origin type values (lowercase, matching DeliveryOriginType string values).
|
|
47
|
+
*/
|
|
48
|
+
export const VALID_ORIGIN_TYPES = [
|
|
49
|
+
'downstream',
|
|
50
|
+
'upstream',
|
|
51
|
+
'peer',
|
|
52
|
+
'local',
|
|
53
|
+
];
|
|
54
|
+
/**
|
|
55
|
+
* Valid effect values.
|
|
56
|
+
*/
|
|
57
|
+
export const VALID_EFFECTS = ['allow', 'deny'];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AbstractResourceFactory, createDefaultResource, createResource, } from '@naylence/factory';
|
|
2
|
+
/**
|
|
3
|
+
* Base type identifier for authorization policy factories.
|
|
4
|
+
*/
|
|
5
|
+
export const AUTHORIZATION_POLICY_FACTORY_BASE_TYPE = 'AuthorizationPolicyFactory';
|
|
6
|
+
/**
|
|
7
|
+
* Abstract factory base class for creating authorization policies.
|
|
8
|
+
*
|
|
9
|
+
* Implementations of this factory create specific types of authorization
|
|
10
|
+
* policies (e.g., expression-based, rule-based, etc.).
|
|
11
|
+
*/
|
|
12
|
+
export class AuthorizationPolicyFactory extends AbstractResourceFactory {
|
|
13
|
+
/**
|
|
14
|
+
* Static helper to create an authorization policy using the factory registry.
|
|
15
|
+
*
|
|
16
|
+
* @param config - Configuration for the policy
|
|
17
|
+
* @param options - Resource creation options
|
|
18
|
+
* @returns The created policy, or undefined if no factory matched
|
|
19
|
+
*/
|
|
20
|
+
static async createAuthorizationPolicy(config, options = {}) {
|
|
21
|
+
if (config) {
|
|
22
|
+
const policy = await createResource(AUTHORIZATION_POLICY_FACTORY_BASE_TYPE, config, options);
|
|
23
|
+
if (!policy) {
|
|
24
|
+
throw new Error('Failed to create authorization policy from configuration');
|
|
25
|
+
}
|
|
26
|
+
return policy;
|
|
27
|
+
}
|
|
28
|
+
const policy = await createDefaultResource(AUTHORIZATION_POLICY_FACTORY_BASE_TYPE, null, options);
|
|
29
|
+
return policy ?? undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AbstractResourceFactory, createDefaultResource, createResource, } from '@naylence/factory';
|
|
2
|
+
/**
|
|
3
|
+
* Base type identifier for authorization policy source factories.
|
|
4
|
+
*/
|
|
5
|
+
export const AUTHORIZATION_POLICY_SOURCE_FACTORY_BASE_TYPE = 'AuthorizationPolicySourceFactory';
|
|
6
|
+
/**
|
|
7
|
+
* Abstract factory base class for creating authorization policy sources.
|
|
8
|
+
*
|
|
9
|
+
* Implementations of this factory create specific types of policy sources
|
|
10
|
+
* (e.g., local file, remote store, in-memory, etc.).
|
|
11
|
+
*/
|
|
12
|
+
export class AuthorizationPolicySourceFactory extends AbstractResourceFactory {
|
|
13
|
+
/**
|
|
14
|
+
* Static helper to create an authorization policy source using the factory registry.
|
|
15
|
+
*
|
|
16
|
+
* @param config - Configuration for the policy source
|
|
17
|
+
* @param options - Resource creation options
|
|
18
|
+
* @returns The created policy source, or undefined if no factory matched
|
|
19
|
+
*/
|
|
20
|
+
static async createAuthorizationPolicySource(config, options = {}) {
|
|
21
|
+
if (config) {
|
|
22
|
+
const source = await createResource(AUTHORIZATION_POLICY_SOURCE_FACTORY_BASE_TYPE, config, options);
|
|
23
|
+
if (!source) {
|
|
24
|
+
throw new Error('Failed to create authorization policy source from configuration');
|
|
25
|
+
}
|
|
26
|
+
return source;
|
|
27
|
+
}
|
|
28
|
+
const source = await createDefaultResource(AUTHORIZATION_POLICY_SOURCE_FACTORY_BASE_TYPE, null, options);
|
|
29
|
+
return source ?? undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for creating BasicAuthorizationPolicy instances.
|
|
3
|
+
*/
|
|
4
|
+
import { AUTHORIZATION_POLICY_FACTORY_BASE_TYPE, AuthorizationPolicyFactory, } from './authorization-policy-factory.js';
|
|
5
|
+
/**
|
|
6
|
+
* Lazy import for tree-shaking.
|
|
7
|
+
*/
|
|
8
|
+
async function safeImportModule() {
|
|
9
|
+
return await import('./basic-authorization-policy.js');
|
|
10
|
+
}
|
|
11
|
+
function normalizeConfig(config) {
|
|
12
|
+
if (!config) {
|
|
13
|
+
throw new Error('BasicAuthorizationPolicyFactory requires a configuration with a policyDefinition');
|
|
14
|
+
}
|
|
15
|
+
const candidate = config;
|
|
16
|
+
// Support both camelCase and snake_case for policyDefinition
|
|
17
|
+
const policyDefinition = (candidate.policyDefinition ??
|
|
18
|
+
candidate.policy_definition);
|
|
19
|
+
if (!policyDefinition || typeof policyDefinition !== 'object') {
|
|
20
|
+
throw new Error('BasicAuthorizationPolicyConfig requires a policyDefinition object');
|
|
21
|
+
}
|
|
22
|
+
// Support both camelCase and snake_case for warnOnUnknownFields
|
|
23
|
+
const warnOnUnknownFields = candidate.warnOnUnknownFields ?? candidate.warn_on_unknown_fields;
|
|
24
|
+
if (warnOnUnknownFields !== undefined && typeof warnOnUnknownFields !== 'boolean') {
|
|
25
|
+
throw new Error('warnOnUnknownFields must be a boolean');
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
policyDefinition,
|
|
29
|
+
warnOnUnknownFields: warnOnUnknownFields ?? true,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Factory metadata for registration.
|
|
34
|
+
*/
|
|
35
|
+
export const FACTORY_META = {
|
|
36
|
+
base: AUTHORIZATION_POLICY_FACTORY_BASE_TYPE,
|
|
37
|
+
key: 'BasicAuthorizationPolicy',
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Factory for creating BasicAuthorizationPolicy instances.
|
|
41
|
+
*/
|
|
42
|
+
export class BasicAuthorizationPolicyFactory extends AuthorizationPolicyFactory {
|
|
43
|
+
constructor() {
|
|
44
|
+
super(...arguments);
|
|
45
|
+
this.type = 'BasicAuthorizationPolicy';
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a BasicAuthorizationPolicy from the given configuration.
|
|
49
|
+
*
|
|
50
|
+
* @param config - Configuration with policyDefinition
|
|
51
|
+
* @returns The created authorization policy
|
|
52
|
+
*/
|
|
53
|
+
async create(config) {
|
|
54
|
+
const normalized = normalizeConfig(config);
|
|
55
|
+
const { BasicAuthorizationPolicy } = await safeImportModule();
|
|
56
|
+
return new BasicAuthorizationPolicy({
|
|
57
|
+
policyDefinition: normalized.policyDefinition,
|
|
58
|
+
warnOnUnknownFields: normalized.warnOnUnknownFields,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export default BasicAuthorizationPolicyFactory;
|