@zetra/citrineos-util 1.8.3-fork.1
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/authorization/ApiAuthPlugin.d.ts +52 -0
- package/dist/authorization/ApiAuthPlugin.js +122 -0
- package/dist/authorization/ApiAuthPlugin.js.map +1 -0
- package/dist/authorization/OidcTokenProvider.d.ts +15 -0
- package/dist/authorization/OidcTokenProvider.js +47 -0
- package/dist/authorization/OidcTokenProvider.js.map +1 -0
- package/dist/authorization/index.d.ts +4 -0
- package/dist/authorization/index.js +8 -0
- package/dist/authorization/index.js.map +1 -0
- package/dist/authorization/provider/LocalByPassAuthProvider.d.ts +34 -0
- package/dist/authorization/provider/LocalByPassAuthProvider.js +62 -0
- package/dist/authorization/provider/LocalByPassAuthProvider.js.map +1 -0
- package/dist/authorization/provider/OIDCAuthProvider.d.ts +62 -0
- package/dist/authorization/provider/OIDCAuthProvider.js +173 -0
- package/dist/authorization/provider/OIDCAuthProvider.js.map +1 -0
- package/dist/authorization/rbac/RbacRulesLoader.d.ts +32 -0
- package/dist/authorization/rbac/RbacRulesLoader.js +105 -0
- package/dist/authorization/rbac/RbacRulesLoader.js.map +1 -0
- package/dist/authorization/rbac/UrlMatcher.d.ts +14 -0
- package/dist/authorization/rbac/UrlMatcher.js +44 -0
- package/dist/authorization/rbac/UrlMatcher.js.map +1 -0
- package/dist/authorizer/RealTimeAuthorizer.d.ts +28 -0
- package/dist/authorizer/RealTimeAuthorizer.js +152 -0
- package/dist/authorizer/RealTimeAuthorizer.js.map +1 -0
- package/dist/authorizer/index.d.ts +1 -0
- package/dist/authorizer/index.js +5 -0
- package/dist/authorizer/index.js.map +1 -0
- package/dist/cache/memory.d.ts +19 -0
- package/dist/cache/memory.js +147 -0
- package/dist/cache/memory.js.map +1 -0
- package/dist/cache/redis.d.ts +16 -0
- package/dist/cache/redis.js +120 -0
- package/dist/cache/redis.js.map +1 -0
- package/dist/certificate/CertificateAuthority.d.ts +38 -0
- package/dist/certificate/CertificateAuthority.js +233 -0
- package/dist/certificate/CertificateAuthority.js.map +1 -0
- package/dist/certificate/CertificateUtil.d.ts +60 -0
- package/dist/certificate/CertificateUtil.js +317 -0
- package/dist/certificate/CertificateUtil.js.map +1 -0
- package/dist/certificate/client/acme.d.ts +37 -0
- package/dist/certificate/client/acme.js +138 -0
- package/dist/certificate/client/acme.js.map +1 -0
- package/dist/certificate/client/hubject.d.ts +41 -0
- package/dist/certificate/client/hubject.js +221 -0
- package/dist/certificate/client/hubject.js.map +1 -0
- package/dist/certificate/client/interface.d.ts +12 -0
- package/dist/certificate/client/interface.js +5 -0
- package/dist/certificate/client/interface.js.map +1 -0
- package/dist/certificate/index.d.ts +2 -0
- package/dist/certificate/index.js +6 -0
- package/dist/certificate/index.js.map +1 -0
- package/dist/files/ftpServer.d.ts +4 -0
- package/dist/files/ftpServer.js +9 -0
- package/dist/files/ftpServer.js.map +1 -0
- package/dist/files/gcpCloudStorage.d.ts +39 -0
- package/dist/files/gcpCloudStorage.js +130 -0
- package/dist/files/gcpCloudStorage.js.map +1 -0
- package/dist/files/localStorage.d.ts +14 -0
- package/dist/files/localStorage.js +57 -0
- package/dist/files/localStorage.js.map +1 -0
- package/dist/files/s3Storage.d.ts +17 -0
- package/dist/files/s3Storage.js +118 -0
- package/dist/files/s3Storage.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/networkconnection/WebsocketNetworkConnection.d.ts +135 -0
- package/dist/networkconnection/WebsocketNetworkConnection.js +474 -0
- package/dist/networkconnection/WebsocketNetworkConnection.js.map +1 -0
- package/dist/networkconnection/authenticator/Authenticator.d.ts +20 -0
- package/dist/networkconnection/authenticator/Authenticator.js +39 -0
- package/dist/networkconnection/authenticator/Authenticator.js.map +1 -0
- package/dist/networkconnection/authenticator/AuthenticatorFilter.d.ts +11 -0
- package/dist/networkconnection/authenticator/AuthenticatorFilter.js +30 -0
- package/dist/networkconnection/authenticator/AuthenticatorFilter.js.map +1 -0
- package/dist/networkconnection/authenticator/BasicAuthenticationFilter.d.ts +17 -0
- package/dist/networkconnection/authenticator/BasicAuthenticationFilter.js +51 -0
- package/dist/networkconnection/authenticator/BasicAuthenticationFilter.js.map +1 -0
- package/dist/networkconnection/authenticator/ConnectedStationFilter.d.ts +14 -0
- package/dist/networkconnection/authenticator/ConnectedStationFilter.js +25 -0
- package/dist/networkconnection/authenticator/ConnectedStationFilter.js.map +1 -0
- package/dist/networkconnection/authenticator/NetworkProfileFilter.d.ts +16 -0
- package/dist/networkconnection/authenticator/NetworkProfileFilter.js +84 -0
- package/dist/networkconnection/authenticator/NetworkProfileFilter.js.map +1 -0
- package/dist/networkconnection/authenticator/UnknownStationFilter.d.ts +16 -0
- package/dist/networkconnection/authenticator/UnknownStationFilter.js +25 -0
- package/dist/networkconnection/authenticator/UnknownStationFilter.js.map +1 -0
- package/dist/networkconnection/authenticator/errors/AuthenticationError.d.ts +6 -0
- package/dist/networkconnection/authenticator/errors/AuthenticationError.js +25 -0
- package/dist/networkconnection/authenticator/errors/AuthenticationError.js.map +1 -0
- package/dist/networkconnection/authenticator/errors/IUpgradeError.d.ts +9 -0
- package/dist/networkconnection/authenticator/errors/IUpgradeError.js +5 -0
- package/dist/networkconnection/authenticator/errors/IUpgradeError.js.map +1 -0
- package/dist/networkconnection/authenticator/errors/UnknownError.d.ts +6 -0
- package/dist/networkconnection/authenticator/errors/UnknownError.js +24 -0
- package/dist/networkconnection/authenticator/errors/UnknownError.js.map +1 -0
- package/dist/networkconnection/index.d.ts +5 -0
- package/dist/networkconnection/index.js +9 -0
- package/dist/networkconnection/index.js.map +1 -0
- package/dist/queue/index.d.ts +4 -0
- package/dist/queue/index.js +8 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/kafka/receiver.d.ts +35 -0
- package/dist/queue/kafka/receiver.js +179 -0
- package/dist/queue/kafka/receiver.js.map +1 -0
- package/dist/queue/kafka/sender.d.ts +53 -0
- package/dist/queue/kafka/sender.js +189 -0
- package/dist/queue/kafka/sender.js.map +1 -0
- package/dist/queue/rabbit-mq/receiver.d.ts +89 -0
- package/dist/queue/rabbit-mq/receiver.js +472 -0
- package/dist/queue/rabbit-mq/receiver.js.map +1 -0
- package/dist/queue/rabbit-mq/sender.d.ts +90 -0
- package/dist/queue/rabbit-mq/sender.js +251 -0
- package/dist/queue/rabbit-mq/sender.js.map +1 -0
- package/dist/security/SignedMeterValuesUtil.d.ts +44 -0
- package/dist/security/SignedMeterValuesUtil.js +135 -0
- package/dist/security/SignedMeterValuesUtil.js.map +1 -0
- package/dist/security/authentication.d.ts +2 -0
- package/dist/security/authentication.js +26 -0
- package/dist/security/authentication.js.map +1 -0
- package/dist/util/RequestOperations.d.ts +14 -0
- package/dist/util/RequestOperations.js +25 -0
- package/dist/util/RequestOperations.js.map +1 -0
- package/dist/util/StringOperations.d.ts +1 -0
- package/dist/util/StringOperations.js +8 -0
- package/dist/util/StringOperations.js.map +1 -0
- package/dist/util/emaidCheckDigitCalculator.d.ts +15 -0
- package/dist/util/emaidCheckDigitCalculator.js +179 -0
- package/dist/util/emaidCheckDigitCalculator.js.map +1 -0
- package/dist/util/idGenerator.d.ts +7 -0
- package/dist/util/idGenerator.js +10 -0
- package/dist/util/idGenerator.js.map +1 -0
- package/dist/util/parser.d.ts +31 -0
- package/dist/util/parser.js +60 -0
- package/dist/util/parser.js.map +1 -0
- package/dist/util/swagger.d.ts +5 -0
- package/dist/util/swagger.js +154 -0
- package/dist/util/swagger.js.map +1 -0
- package/dist/util/validator.d.ts +110 -0
- package/dist/util/validator.js +534 -0
- package/dist/util/validator.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { FastifyPluginAsync, FastifyReply } from 'fastify';
|
|
2
|
+
import type { ILogObj } from 'tslog';
|
|
3
|
+
import { Logger } from 'tslog';
|
|
4
|
+
import type { IApiAuthProvider, UserInfo } from '@citrineos/base';
|
|
5
|
+
/**
|
|
6
|
+
* Options for the authentication plugin
|
|
7
|
+
*/
|
|
8
|
+
export interface AuthPluginOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Routes that don't require authentication
|
|
11
|
+
*/
|
|
12
|
+
excludedRoutes?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Enable verbose debug logging
|
|
15
|
+
*/
|
|
16
|
+
debug?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Extend the FastifyRequest interface to include user information
|
|
20
|
+
*/
|
|
21
|
+
declare module 'fastify' {
|
|
22
|
+
interface FastifyRequest {
|
|
23
|
+
/**
|
|
24
|
+
* Authenticated user information
|
|
25
|
+
*/
|
|
26
|
+
user?: UserInfo;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Extend FastifyInstance to include our auth functions and provider
|
|
31
|
+
*/
|
|
32
|
+
declare module 'fastify' {
|
|
33
|
+
interface FastifyInstance {
|
|
34
|
+
/**
|
|
35
|
+
* The authentication provider instance
|
|
36
|
+
*/
|
|
37
|
+
authProvider: IApiAuthProvider;
|
|
38
|
+
/**
|
|
39
|
+
* Authenticates a request by validating the token in Authorization header
|
|
40
|
+
*/
|
|
41
|
+
authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Authorizes a request for a specific resource
|
|
44
|
+
*/
|
|
45
|
+
authorize: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export declare const apiAuthPluginFp: FastifyPluginAsync<{
|
|
49
|
+
provider: IApiAuthProvider;
|
|
50
|
+
options?: AuthPluginOptions;
|
|
51
|
+
logger?: Logger<ILogObj>;
|
|
52
|
+
}>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { Logger } from 'tslog';
|
|
5
|
+
import fp from 'fastify-plugin';
|
|
6
|
+
import { HttpStatus } from '@citrineos/base';
|
|
7
|
+
/**
|
|
8
|
+
* Authentication plugin for Fastify
|
|
9
|
+
* This plugin adds authentication and authorization capabilities to Fastify
|
|
10
|
+
* using the provided auth provider
|
|
11
|
+
*
|
|
12
|
+
* @param fastify Fastify instance
|
|
13
|
+
* @param provider Auth provider instance
|
|
14
|
+
* @param options Plugin options
|
|
15
|
+
*/
|
|
16
|
+
const apiAuthPlugin = async (fastify, { provider, options = {}, logger }) => {
|
|
17
|
+
//TODO add logger instance ?
|
|
18
|
+
const _logger = logger
|
|
19
|
+
? logger.getSubLogger({ name: 'AuthPlugin' })
|
|
20
|
+
: new Logger({ name: 'AuthPlugin' });
|
|
21
|
+
// Register the auth provider
|
|
22
|
+
fastify.decorate('authProvider', provider);
|
|
23
|
+
// Helper to check if a route is excluded from authentication
|
|
24
|
+
function isExcludedRoute(url) {
|
|
25
|
+
// Always exclude health check
|
|
26
|
+
if (url === '/health') {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const isExcluded = !!options.excludedRoutes?.some((route) => url === route || url.startsWith(`${route}/`));
|
|
30
|
+
if (isExcluded && options.debug) {
|
|
31
|
+
_logger.debug(`Skipping authentication for excluded route: ${url}`);
|
|
32
|
+
}
|
|
33
|
+
return isExcluded;
|
|
34
|
+
}
|
|
35
|
+
// Authentication decorator - validates token from Authorization header
|
|
36
|
+
fastify.decorate('authenticate', async function (request, reply) {
|
|
37
|
+
try {
|
|
38
|
+
// Extract token
|
|
39
|
+
const token = await provider.extractToken(request);
|
|
40
|
+
if (!token) {
|
|
41
|
+
reply.code(HttpStatus.UNAUTHORIZED).send({
|
|
42
|
+
error: 'Unauthorized',
|
|
43
|
+
message: 'Missing or invalid authorization header',
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Authenticate token
|
|
48
|
+
const authResult = await provider.authenticateToken(token);
|
|
49
|
+
if (!authResult.isAuthenticated || !authResult.user) {
|
|
50
|
+
reply.code(HttpStatus.UNAUTHORIZED).send({
|
|
51
|
+
error: 'Unauthorized',
|
|
52
|
+
message: authResult.error || 'Invalid token',
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Store user info in request
|
|
57
|
+
request.user = authResult.user;
|
|
58
|
+
if (options.debug) {
|
|
59
|
+
_logger.debug(`Authenticated user: ${authResult.user.id} (${authResult.user.name})`);
|
|
60
|
+
_logger.debug(`Roles: ${authResult.user.roles.join(', ')}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
_logger.error('Authentication error:', error);
|
|
65
|
+
reply.code(HttpStatus.UNAUTHORIZED).send({
|
|
66
|
+
error: 'Unauthorized',
|
|
67
|
+
message: 'Authentication failed',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// Authorization decorator - authorizes user for the requested resource
|
|
72
|
+
fastify.decorate('authorize', async function (request, reply) {
|
|
73
|
+
try {
|
|
74
|
+
// Check if user is authenticated
|
|
75
|
+
if (!request.user) {
|
|
76
|
+
reply.code(HttpStatus.UNAUTHORIZED).send({
|
|
77
|
+
error: 'Unauthorized',
|
|
78
|
+
message: 'Authentication required',
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Authorize user for this request
|
|
83
|
+
const authzResult = await provider.authorizeUser(request.user, request);
|
|
84
|
+
if (!authzResult.isAuthorized) {
|
|
85
|
+
reply.code(HttpStatus.FORBIDDEN).send({
|
|
86
|
+
error: 'Forbidden',
|
|
87
|
+
message: authzResult.error || 'Insufficient permissions',
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (options.debug) {
|
|
92
|
+
_logger.debug(`Authorized user ${request.user.id} for ${request.method} ${request.url}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
_logger.error('Authorization error:', error);
|
|
97
|
+
reply.code(HttpStatus.FORBIDDEN).send({
|
|
98
|
+
error: 'Forbidden',
|
|
99
|
+
message: 'Authorization failed',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
// Add global authentication hook for all routes
|
|
104
|
+
fastify.addHook('onRequest', async (request, reply) => {
|
|
105
|
+
// Skip authentication for excluded routes
|
|
106
|
+
if (isExcludedRoute(request.url)) {
|
|
107
|
+
if (options.debug) {
|
|
108
|
+
_logger.trace(`Skipping authentication for excluded route: ${request.url}`);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Authenticate and authorize the request
|
|
113
|
+
await fastify.authenticate(request, reply);
|
|
114
|
+
await fastify.authorize(request, reply);
|
|
115
|
+
});
|
|
116
|
+
_logger.info('Authentication plugin registered');
|
|
117
|
+
};
|
|
118
|
+
export const apiAuthPluginFp = fp(apiAuthPlugin, {
|
|
119
|
+
name: 'apiAuth',
|
|
120
|
+
fastify: '5.x',
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=ApiAuthPlugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiAuthPlugin.js","sourceRoot":"","sources":["../../src/authorization/ApiAuthPlugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ILogObj } from 'tslog';
|
|
2
|
+
import { Logger } from 'tslog';
|
|
3
|
+
export interface OidcTokenProviderConfig {
|
|
4
|
+
tokenUrl: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
clientSecret: string;
|
|
7
|
+
audience: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class OidcTokenProvider {
|
|
10
|
+
private config;
|
|
11
|
+
private oidcToken?;
|
|
12
|
+
private readonly _logger;
|
|
13
|
+
constructor(config: OidcTokenProviderConfig, logger?: Logger<ILogObj>);
|
|
14
|
+
getToken(): Promise<string>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { Logger } from 'tslog';
|
|
5
|
+
export class OidcTokenProvider {
|
|
6
|
+
config;
|
|
7
|
+
oidcToken;
|
|
8
|
+
_logger;
|
|
9
|
+
constructor(config, logger) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
this._logger = logger
|
|
12
|
+
? logger.getSubLogger({ name: this.constructor.name })
|
|
13
|
+
: new Logger({ name: this.constructor.name });
|
|
14
|
+
}
|
|
15
|
+
async getToken() {
|
|
16
|
+
if (this.oidcToken && this.oidcToken.expiresAt > Date.now()) {
|
|
17
|
+
this._logger.debug('Returning cached OIDC token');
|
|
18
|
+
return this.oidcToken.accessToken;
|
|
19
|
+
}
|
|
20
|
+
this._logger.debug('Fetching new OIDC token');
|
|
21
|
+
const response = await fetch(this.config.tokenUrl, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
25
|
+
},
|
|
26
|
+
body: new URLSearchParams({
|
|
27
|
+
grant_type: 'client_credentials',
|
|
28
|
+
client_id: this.config.clientId,
|
|
29
|
+
client_secret: this.config.clientSecret,
|
|
30
|
+
audience: this.config.audience,
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const errorText = await response.text();
|
|
35
|
+
this._logger.error('Failed to fetch OIDC token:', errorText);
|
|
36
|
+
throw new Error(`Failed to fetch OIDC token: ${response.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
const tokenData = await response.json();
|
|
39
|
+
this.oidcToken = {
|
|
40
|
+
accessToken: tokenData.access_token,
|
|
41
|
+
// Set expiry to 1 minute before actual expiration to be safe
|
|
42
|
+
expiresAt: Date.now() + (tokenData.expires_in - 60) * 1000,
|
|
43
|
+
};
|
|
44
|
+
return this.oidcToken.accessToken;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=OidcTokenProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OidcTokenProvider.js","sourceRoot":"","sources":["../../src/authorization/OidcTokenProvider.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AAGtC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAc/B,MAAM,OAAO,iBAAiB;IAKlB;IAJF,SAAS,CAAa;IACb,OAAO,CAAkB;IAE1C,YACU,MAA+B,EACvC,MAAwB;QADhB,WAAM,GAAN,MAAM,CAAyB;QAGvC,IAAI,CAAC,OAAO,GAAG,MAAM;YACnB,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;YACtD,CAAC,CAAC,IAAI,MAAM,CAAU,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IAEM,KAAK,CAAC,QAAQ;QACnB,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAC5D,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;YAClD,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;QACpC,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC9C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;YACjD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,mCAAmC;aACpD;YACD,IAAI,EAAE,IAAI,eAAe,CAAC;gBACxB,UAAU,EAAE,oBAAoB;gBAChC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAC/B,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;gBACvC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;aAC/B,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,SAAS,CAAC,CAAC;YAC7D,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACxE,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,SAAS,GAAG;YACf,WAAW,EAAE,SAAS,CAAC,YAAY;YACnC,6DAA6D;YAC7D,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,SAAS,CAAC,UAAU,GAAG,EAAE,CAAC,GAAG,IAAI;SAC3D,CAAC;QAEF,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;IACpC,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
export * from './ApiAuthPlugin.js';
|
|
5
|
+
export { LocalBypassAuthProvider } from './provider/LocalByPassAuthProvider.js';
|
|
6
|
+
export { OIDCAuthProvider } from './provider/OIDCAuthProvider.js';
|
|
7
|
+
export { OidcTokenProvider } from './OidcTokenProvider.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/authorization/index.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AACtC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { FastifyRequest } from 'fastify';
|
|
2
|
+
import type { ILogObj } from 'tslog';
|
|
3
|
+
import { Logger } from 'tslog';
|
|
4
|
+
import type { IApiAuthProvider, UserInfo } from '@citrineos/base';
|
|
5
|
+
import { ApiAuthenticationResult, ApiAuthorizationResult } from '@citrineos/base';
|
|
6
|
+
/**
|
|
7
|
+
* A local bypass authentication provider that doesn't perform actual authentication
|
|
8
|
+
* Only for development and testing environments
|
|
9
|
+
*/
|
|
10
|
+
export declare class LocalBypassAuthProvider implements IApiAuthProvider {
|
|
11
|
+
private readonly _logger;
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new local bypass authentication provider
|
|
14
|
+
*
|
|
15
|
+
* @param logger Optional logger instance
|
|
16
|
+
*/
|
|
17
|
+
constructor(logger?: Logger<ILogObj>);
|
|
18
|
+
extractToken(_request: FastifyRequest): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Always returns a successful authentication with admin user
|
|
21
|
+
*
|
|
22
|
+
* @param token Ignored, can be any string
|
|
23
|
+
* @returns Authentication result with admin user info
|
|
24
|
+
*/
|
|
25
|
+
authenticateToken(_token: string): Promise<ApiAuthenticationResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Always returns a successful authorization
|
|
28
|
+
*
|
|
29
|
+
* @param user Ignored, can be any user
|
|
30
|
+
* @param request Ignored, can be any request
|
|
31
|
+
* @returns Always successful authorization
|
|
32
|
+
*/
|
|
33
|
+
authorizeUser(user: UserInfo, request: FastifyRequest): Promise<ApiAuthorizationResult>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { Logger } from 'tslog';
|
|
5
|
+
import { ApiAuthenticationResult, ApiAuthorizationResult } from '@citrineos/base';
|
|
6
|
+
/**
|
|
7
|
+
* A local bypass authentication provider that doesn't perform actual authentication
|
|
8
|
+
* Only for development and testing environments
|
|
9
|
+
*/
|
|
10
|
+
export class LocalBypassAuthProvider {
|
|
11
|
+
_logger;
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new local bypass authentication provider
|
|
14
|
+
*
|
|
15
|
+
* @param logger Optional logger instance
|
|
16
|
+
*/
|
|
17
|
+
constructor(logger) {
|
|
18
|
+
this._logger = logger
|
|
19
|
+
? logger.getSubLogger({ name: this.constructor.name })
|
|
20
|
+
: new Logger({ name: this.constructor.name });
|
|
21
|
+
this._logger.warn('⚠️ WARNING: Using LocalBypassAuthProvider - This should only be used in development environments');
|
|
22
|
+
}
|
|
23
|
+
async extractToken(_request) {
|
|
24
|
+
// Always return a dummy token for local bypass
|
|
25
|
+
this._logger.debug('LocalBypassAuthProvider.authenticateToken: Returning dummy token');
|
|
26
|
+
return 'local-bypass-token';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Always returns a successful authentication with admin user
|
|
30
|
+
*
|
|
31
|
+
* @param token Ignored, can be any string
|
|
32
|
+
* @returns Authentication result with admin user info
|
|
33
|
+
*/
|
|
34
|
+
async authenticateToken(_token) {
|
|
35
|
+
this._logger.debug('LocalBypassAuthProvider.authenticateToken: Bypassing authentication, using dummy user');
|
|
36
|
+
// Create a default admin user
|
|
37
|
+
const user = {
|
|
38
|
+
id: 'local-admin',
|
|
39
|
+
name: 'Local Admin',
|
|
40
|
+
email: 'admin@local',
|
|
41
|
+
roles: ['admin', 'user'],
|
|
42
|
+
groups: ['administrators'],
|
|
43
|
+
tenantId: '1',
|
|
44
|
+
metadata: {
|
|
45
|
+
isLocalBypass: true,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
return ApiAuthenticationResult.success(user);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Always returns a successful authorization
|
|
52
|
+
*
|
|
53
|
+
* @param user Ignored, can be any user
|
|
54
|
+
* @param request Ignored, can be any request
|
|
55
|
+
* @returns Always successful authorization
|
|
56
|
+
*/
|
|
57
|
+
async authorizeUser(user, request) {
|
|
58
|
+
this._logger.debug(`LocalBypassAuthProvider.authorizeUser: Bypassing authorization for ${request.method} ${request.url}`);
|
|
59
|
+
return ApiAuthorizationResult.success();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=LocalByPassAuthProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalByPassAuthProvider.js","sourceRoot":"","sources":["../../../src/authorization/provider/LocalByPassAuthProvider.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AAItC,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAElF;;;GAGG;AACH,MAAM,OAAO,uBAAuB;IACjB,OAAO,CAAkB;IAE1C;;;;OAIG;IACH,YAAY,MAAwB;QAClC,IAAI,CAAC,OAAO,GAAG,MAAM;YACnB,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;YACtD,CAAC,CAAC,IAAI,MAAM,CAAU,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;QAEzD,IAAI,CAAC,OAAO,CAAC,IAAI,CACf,kGAAkG,CACnG,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,QAAwB;QACzC,+CAA+C;QAC/C,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAC;QACvF,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,iBAAiB,CAAC,MAAc;QACpC,IAAI,CAAC,OAAO,CAAC,KAAK,CAChB,uFAAuF,CACxF,CAAC;QAEF,8BAA8B;QAC9B,MAAM,IAAI,GAAa;YACrB,EAAE,EAAE,aAAa;YACjB,IAAI,EAAE,aAAa;YACnB,KAAK,EAAE,aAAa;YACpB,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;YACxB,MAAM,EAAE,CAAC,gBAAgB,CAAC;YAC1B,QAAQ,EAAE,GAAG;YACb,QAAQ,EAAE;gBACR,aAAa,EAAE,IAAI;aACpB;SACF,CAAC;QAEF,OAAO,uBAAuB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,aAAa,CAAC,IAAc,EAAE,OAAuB;QACzD,IAAI,CAAC,OAAO,CAAC,KAAK,CAChB,sEAAsE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,EAAE,CACtG,CAAC;QAEF,OAAO,sBAAsB,CAAC,OAAO,EAAE,CAAC;IAC1C,CAAC;CACF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { FastifyRequest } from 'fastify';
|
|
2
|
+
import type { ILogObj } from 'tslog';
|
|
3
|
+
import { Logger } from 'tslog';
|
|
4
|
+
import type { IApiAuthProvider, UserInfo } from '@citrineos/base';
|
|
5
|
+
import { ApiAuthenticationResult, ApiAuthorizationResult } from '@citrineos/base';
|
|
6
|
+
export interface OIDCConfig {
|
|
7
|
+
jwksUri: string;
|
|
8
|
+
issuer: string;
|
|
9
|
+
audience?: string;
|
|
10
|
+
cacheTime?: number;
|
|
11
|
+
rateLimit?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* OIDC authentication provider implementation
|
|
15
|
+
*/
|
|
16
|
+
export declare class OIDCAuthProvider implements IApiAuthProvider {
|
|
17
|
+
private readonly _config;
|
|
18
|
+
private readonly _logger;
|
|
19
|
+
private readonly _jkwsClient;
|
|
20
|
+
private readonly _rulesLoader;
|
|
21
|
+
private readonly _defaultTenantId;
|
|
22
|
+
/**
|
|
23
|
+
* Creates a new Keycloak authentication provider
|
|
24
|
+
*
|
|
25
|
+
* @param config OIDC configuration
|
|
26
|
+
* @param logger Optional logger instance
|
|
27
|
+
*/
|
|
28
|
+
constructor(config: OIDCConfig, logger?: Logger<ILogObj>);
|
|
29
|
+
extractToken(request: FastifyRequest): Promise<string | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Authenticates a JWT token from and OIDC provider
|
|
32
|
+
*
|
|
33
|
+
* @param token JWT token to authenticate
|
|
34
|
+
* @returns Authentication result with user info if successful
|
|
35
|
+
*/
|
|
36
|
+
authenticateToken(token: string): Promise<ApiAuthenticationResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Authorizes a user for a specific request
|
|
39
|
+
* This implementation checks if the user has the required permissions
|
|
40
|
+
* for the requested URL and method
|
|
41
|
+
*
|
|
42
|
+
* @param user User information
|
|
43
|
+
* @param request Fastify request
|
|
44
|
+
* @returns Authorization result
|
|
45
|
+
*/
|
|
46
|
+
authorizeUser(user: UserInfo, request: FastifyRequest): Promise<ApiAuthorizationResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Fetches the public key from OIDC provider
|
|
49
|
+
* @param {string} kid Key ID from the JWT header
|
|
50
|
+
* @returns {Promise<string>} Public key as a string
|
|
51
|
+
* @private
|
|
52
|
+
*/
|
|
53
|
+
private fetchPublicKey;
|
|
54
|
+
/**
|
|
55
|
+
* Check if a user has any of the required roles for a specific tenant
|
|
56
|
+
*
|
|
57
|
+
* @param user User with roles
|
|
58
|
+
* @param requiredRoles Array of role names (without tenant prefix)
|
|
59
|
+
* @returns True if user has any of the required roles
|
|
60
|
+
*/
|
|
61
|
+
private userHasRequiredRole;
|
|
62
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { Logger } from 'tslog';
|
|
5
|
+
import jwt from 'jsonwebtoken';
|
|
6
|
+
import jwksClient from 'jwks-rsa';
|
|
7
|
+
import JwksRsa from 'jwks-rsa';
|
|
8
|
+
import { ApiAuthenticationResult, ApiAuthorizationResult } from '@citrineos/base';
|
|
9
|
+
import { createPublicKey } from 'crypto';
|
|
10
|
+
import { RbacRulesLoader } from '../rbac/RbacRulesLoader.js';
|
|
11
|
+
/**
|
|
12
|
+
* OIDC authentication provider implementation
|
|
13
|
+
*/
|
|
14
|
+
export class OIDCAuthProvider {
|
|
15
|
+
_config;
|
|
16
|
+
_logger;
|
|
17
|
+
_jkwsClient;
|
|
18
|
+
_rulesLoader;
|
|
19
|
+
_defaultTenantId = '1'; //TODO get default from config
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new Keycloak authentication provider
|
|
22
|
+
*
|
|
23
|
+
* @param config OIDC configuration
|
|
24
|
+
* @param logger Optional logger instance
|
|
25
|
+
*/
|
|
26
|
+
constructor(config, logger) {
|
|
27
|
+
this._config = {
|
|
28
|
+
cacheTime: 60 * 60 * 1000, // Default 1 hour cache
|
|
29
|
+
rateLimit: true,
|
|
30
|
+
...config,
|
|
31
|
+
};
|
|
32
|
+
this._logger = logger
|
|
33
|
+
? logger.getSubLogger({ name: this.constructor.name })
|
|
34
|
+
: new Logger({ name: this.constructor.name });
|
|
35
|
+
this._logger.info('OIDC auth provider config', this._config);
|
|
36
|
+
// Create the JWKS client
|
|
37
|
+
this._jkwsClient = jwksClient({
|
|
38
|
+
jwksUri: this._config.jwksUri,
|
|
39
|
+
cache: true,
|
|
40
|
+
cacheMaxAge: this._config.cacheTime,
|
|
41
|
+
rateLimit: this._config.rateLimit,
|
|
42
|
+
jwksRequestsPerMinute: 5, // Limit requests to JWKS endpoint
|
|
43
|
+
});
|
|
44
|
+
this._rulesLoader = new RbacRulesLoader('rbac-rules.json', this._logger);
|
|
45
|
+
this._logger.info(`OIDC auth provider setup with jwksUri: ${this._config.jwksUri}`);
|
|
46
|
+
}
|
|
47
|
+
async extractToken(request) {
|
|
48
|
+
// Extract the Authorization header
|
|
49
|
+
const authHeader = request.headers.authorization;
|
|
50
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
51
|
+
this._logger.warn('No Bearer token found in request headers');
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
// Return the token without the "Bearer " prefix
|
|
55
|
+
const token = authHeader.slice(7).trim();
|
|
56
|
+
this._logger.debug('Extracted token from request:', token);
|
|
57
|
+
return token;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Authenticates a JWT token from and OIDC provider
|
|
61
|
+
*
|
|
62
|
+
* @param token JWT token to authenticate
|
|
63
|
+
* @returns Authentication result with user info if successful
|
|
64
|
+
*/
|
|
65
|
+
async authenticateToken(token) {
|
|
66
|
+
try {
|
|
67
|
+
const decoded = jwt.decode(token, { complete: true });
|
|
68
|
+
if (!decoded || typeof decoded !== 'object' || !decoded.header || !decoded.header.kid) {
|
|
69
|
+
throw new Error('Invalid token format');
|
|
70
|
+
}
|
|
71
|
+
const publicKey = await this.fetchPublicKey(decoded.header.kid);
|
|
72
|
+
// Verify the token with the public key
|
|
73
|
+
const payload = jwt.verify(token, createPublicKey(publicKey));
|
|
74
|
+
// Extract user info from the decoded token
|
|
75
|
+
const user = {
|
|
76
|
+
id: payload.sub,
|
|
77
|
+
name: payload.preferred_username || payload.name || payload.sub,
|
|
78
|
+
email: payload.email || '',
|
|
79
|
+
roles: payload.resource_access.citrineos?.roles,
|
|
80
|
+
tenantId: payload.tenant_id || this._defaultTenantId,
|
|
81
|
+
metadata: {
|
|
82
|
+
firstName: payload.given_name,
|
|
83
|
+
lastName: payload.family_name,
|
|
84
|
+
fullName: payload.name,
|
|
85
|
+
emailVerified: payload.email_verified,
|
|
86
|
+
locale: payload.locale || 'en-US',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
return ApiAuthenticationResult.success(user);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
this._logger.error('Token authentication failed:', error);
|
|
93
|
+
return ApiAuthenticationResult.failure(error instanceof Error ? error.message : 'Invalid token');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Authorizes a user for a specific request
|
|
98
|
+
* This implementation checks if the user has the required permissions
|
|
99
|
+
* for the requested URL and method
|
|
100
|
+
*
|
|
101
|
+
* @param user User information
|
|
102
|
+
* @param request Fastify request
|
|
103
|
+
* @returns Authorization result
|
|
104
|
+
*/
|
|
105
|
+
async authorizeUser(user, request) {
|
|
106
|
+
try {
|
|
107
|
+
// Get the requested resource and method
|
|
108
|
+
const url = request.url;
|
|
109
|
+
const method = request.method;
|
|
110
|
+
const tenantId = request.query.tenantId || this._defaultTenantId;
|
|
111
|
+
const requiredRoles = this._rulesLoader.getRequiredRoles(tenantId, url, method);
|
|
112
|
+
//If no role is found for the requested resource and tenant, decline access
|
|
113
|
+
if (!requiredRoles || requiredRoles.length === 0) {
|
|
114
|
+
return ApiAuthorizationResult.failure(`Tenant does not have access to this resource ${url}`);
|
|
115
|
+
}
|
|
116
|
+
if (this.userHasRequiredRole(user, requiredRoles)) {
|
|
117
|
+
return ApiAuthorizationResult.success();
|
|
118
|
+
}
|
|
119
|
+
return ApiAuthorizationResult.failure(`Missing required roles. Need one of: ${requiredRoles.join(', ')} for tenant ${tenantId}`);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
this._logger.error('Authorization error:', error);
|
|
123
|
+
return ApiAuthorizationResult.failure(`Authorization error: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Fetches the public key from OIDC provider
|
|
128
|
+
* @param {string} kid Key ID from the JWT header
|
|
129
|
+
* @returns {Promise<string>} Public key as a string
|
|
130
|
+
* @private
|
|
131
|
+
*/
|
|
132
|
+
async fetchPublicKey(kid) {
|
|
133
|
+
try {
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
this._jkwsClient.getSigningKey(kid, (err, key) => {
|
|
136
|
+
if (err) {
|
|
137
|
+
this._logger.error(`Error fetching signing key for kid: ${kid}`, err);
|
|
138
|
+
return reject(err);
|
|
139
|
+
}
|
|
140
|
+
if (!key) {
|
|
141
|
+
const error = new Error(`No signing key found for kid: ${kid}`);
|
|
142
|
+
this._logger.error(error.message);
|
|
143
|
+
return reject(error);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
// Get the public key
|
|
147
|
+
const signingKey = key.getPublicKey();
|
|
148
|
+
resolve(signingKey);
|
|
149
|
+
}
|
|
150
|
+
catch (keyError) {
|
|
151
|
+
this._logger.error('Error extracting public key:', keyError);
|
|
152
|
+
reject(keyError);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
this._logger.error('Failed to fetch public key:', error);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Check if a user has any of the required roles for a specific tenant
|
|
164
|
+
*
|
|
165
|
+
* @param user User with roles
|
|
166
|
+
* @param requiredRoles Array of role names (without tenant prefix)
|
|
167
|
+
* @returns True if user has any of the required roles
|
|
168
|
+
*/
|
|
169
|
+
userHasRequiredRole(user, requiredRoles) {
|
|
170
|
+
return user.roles.some((userRole) => requiredRoles.includes(userRole));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=OIDCAuthProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OIDCAuthProvider.js","sourceRoot":"","sources":["../../../src/authorization/provider/OIDCAuthProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ILogObj } from 'tslog';
|
|
2
|
+
import { Logger } from 'tslog';
|
|
3
|
+
/**
|
|
4
|
+
* Class to load and validate RBAC rules
|
|
5
|
+
*/
|
|
6
|
+
export declare class RbacRulesLoader {
|
|
7
|
+
private _rules;
|
|
8
|
+
private readonly _logger;
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new RBAC rules loader
|
|
11
|
+
*
|
|
12
|
+
* @param rulesFilePath Path to the JSON rules file
|
|
13
|
+
* @param logger Logger instance
|
|
14
|
+
*/
|
|
15
|
+
constructor(rulesFilePath: string, logger: Logger<ILogObj>);
|
|
16
|
+
/**
|
|
17
|
+
* Load and validate rules from a JSON file
|
|
18
|
+
*
|
|
19
|
+
* @param filePath Path to the JSON rules file
|
|
20
|
+
*/
|
|
21
|
+
private loadRules;
|
|
22
|
+
/**
|
|
23
|
+
* Get the required roles for a specific tenant, URL, and HTTP method
|
|
24
|
+
*
|
|
25
|
+
* @param tenantId Tenant identifier
|
|
26
|
+
* @param url URL path
|
|
27
|
+
* @param method HTTP method
|
|
28
|
+
* @returns Array of required roles or null if no matching rule
|
|
29
|
+
*/
|
|
30
|
+
getRequiredRoles(tenantId: string, url: string, method: string): string[] | null;
|
|
31
|
+
private normalizeUrl;
|
|
32
|
+
}
|