@oas-tools/oas-telemetry 0.7.0-alpha.4 → 0.7.0-alpha.5
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/.env.example +4 -2
- package/dist/cjs/config/config.cjs +7 -5
- package/dist/cjs/routesManager.cjs +36 -48
- package/dist/cjs/tlm-auth/authController.cjs +91 -26
- package/dist/cjs/tlm-auth/authMiddleware.cjs +20 -7
- package/dist/cjs/tlm-auth/authRoutes.cjs +3 -2
- package/dist/esm/config/config.js +4 -2
- package/dist/esm/routesManager.js +37 -49
- package/dist/esm/tlm-auth/authController.js +62 -20
- package/dist/esm/tlm-auth/authMiddleware.js +18 -9
- package/dist/esm/tlm-auth/authRoutes.js +4 -3
- package/dist/types/config/config.d.ts +6 -3
- package/dist/types/tlm-auth/authController.d.ts +2 -1
- package/dist/ui/assets/{index-BzIdRox6.js → index-Bgd7fFFH.js} +201 -191
- package/dist/ui/assets/index-Cz3N1n1Q.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +2 -2
- package/dist/ui/assets/index-CkoHzrrt.css +0 -1
package/.env.example
CHANGED
|
@@ -24,12 +24,14 @@ OASTLM_CONFIG_GENERAL_SPEC_FILE_NAME=
|
|
|
24
24
|
|
|
25
25
|
# Enable authentication | Possible values: "true", "false" | Default: "false"
|
|
26
26
|
OASTLM_CONFIG_AUTH_ENABLED=
|
|
27
|
-
# Maximum age for API keys (ms) | Default: 3600000 (1 hour)
|
|
28
|
-
OASTLM_CONFIG_AUTH_API_KEY_MAX_AGE=
|
|
29
27
|
# Password for authentication | Default: "oas-telemetry-password"
|
|
30
28
|
OASTLM_CONFIG_AUTH_PASSWORD=
|
|
31
29
|
# JWT secret for authentication | Default: "oas-telemetry-secret"
|
|
32
30
|
OASTLM_CONFIG_AUTH_JWT_SECRET=
|
|
31
|
+
# Access token max age (ms) | Default: 300000 (5 minutes)
|
|
32
|
+
OASTLM_CONFIG_AUTH_ACCESS_TOKEN_MAX_AGE=
|
|
33
|
+
# Refresh token max age (ms) | Default: 604800000 (7 days)
|
|
34
|
+
OASTLM_CONFIG_AUTH_REFRESH_TOKEN_MAX_AGE=
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
# Enable memory exporter for traces | Possible values: "true", "false" | Default: "true"
|
|
@@ -18,9 +18,10 @@ const loadEnv = () => {
|
|
|
18
18
|
},
|
|
19
19
|
auth: {
|
|
20
20
|
enabled: getParsedEnvVar("OASTLM_CONFIG_AUTH_ENABLED", v => v === "true"),
|
|
21
|
-
apiKeyMaxAge: getParsedEnvVar("OASTLM_CONFIG_AUTH_API_KEY_MAX_AGE", v => parseInt(v, 10)),
|
|
22
21
|
password: getParsedEnvVar("OASTLM_CONFIG_AUTH_PASSWORD"),
|
|
23
|
-
jwtSecret: getParsedEnvVar("OASTLM_CONFIG_AUTH_JWT_SECRET")
|
|
22
|
+
jwtSecret: getParsedEnvVar("OASTLM_CONFIG_AUTH_JWT_SECRET"),
|
|
23
|
+
accessTokenMaxAge: getParsedEnvVar("OASTLM_CONFIG_AUTH_ACCESS_TOKEN_MAX_AGE", v => parseInt(v, 10)),
|
|
24
|
+
refreshTokenMaxAge: getParsedEnvVar("OASTLM_CONFIG_AUTH_REFRESH_TOKEN_MAX_AGE", v => parseInt(v, 10))
|
|
24
25
|
},
|
|
25
26
|
traces: {
|
|
26
27
|
memoryExporter: {
|
|
@@ -70,10 +71,11 @@ const defaultConfig = exports.defaultConfig = {
|
|
|
70
71
|
},
|
|
71
72
|
auth: {
|
|
72
73
|
enabled: false,
|
|
73
|
-
apiKeyMaxAge: 1000 * 60 * 60,
|
|
74
|
-
// 1 hour
|
|
75
74
|
password: "oas-telemetry-password",
|
|
76
|
-
jwtSecret: "oas-telemetry-secret"
|
|
75
|
+
jwtSecret: "oas-telemetry-secret",
|
|
76
|
+
accessTokenMaxAge: 1000 * 60 * 5,
|
|
77
|
+
// 5 minutes
|
|
78
|
+
refreshTokenMaxAge: 1000 * 60 * 60 * 24 * 7 // 7 days
|
|
77
79
|
},
|
|
78
80
|
ai: {
|
|
79
81
|
openAIKey: null,
|
|
@@ -23,13 +23,21 @@ const configureRoutes = (router, oasTlmConfig) => {
|
|
|
23
23
|
if (_bootConfig.bootEnvVariables.OASTLM_BOOT_ENV === 'development') {
|
|
24
24
|
_logger.default.info("Running in development mode, enabling CORS for all origins");
|
|
25
25
|
router.use((0, _cors.default)({
|
|
26
|
-
origin:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
origin: (origin, callback) => {
|
|
27
|
+
if (!origin || /^http:\/\/localhost:\d+$/.test(origin)) {
|
|
28
|
+
callback(null, true);
|
|
29
|
+
} else {
|
|
30
|
+
callback(new Error('Not allowed by CORS'));
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
credentials: true
|
|
30
34
|
}));
|
|
31
35
|
}
|
|
32
|
-
|
|
36
|
+
const telemetryBaseUrl = oasTlmConfig.general.baseUrl;
|
|
37
|
+
// Sub-router for all telemetry endpoints
|
|
38
|
+
const telemetryRouter = (0, _express.Router)();
|
|
39
|
+
// Body parser for JSON requests
|
|
40
|
+
telemetryRouter.use((req, res, next) => {
|
|
33
41
|
if (req.body !== undefined) {
|
|
34
42
|
return next(); // Already parsed, no need to parse again.
|
|
35
43
|
}
|
|
@@ -37,52 +45,32 @@ const configureRoutes = (router, oasTlmConfig) => {
|
|
|
37
45
|
limit: '10mb'
|
|
38
46
|
})(req, res, next);
|
|
39
47
|
});
|
|
40
|
-
|
|
41
|
-
const baseUrl = oasTlmConfig.general.baseUrl;
|
|
42
|
-
router.use(baseUrl, allAuthMiddlewares);
|
|
43
|
-
router.use(baseUrl + "/traces", (0, _traceRoutes.getTraceRoutes)());
|
|
44
|
-
router.use(baseUrl + "/metrics", (0, _metricsRoutes.getMetricsRoutes)());
|
|
45
|
-
router.use(baseUrl + "/logs", (0, _logRoutes.getLogRoutes)());
|
|
46
|
-
router.use(baseUrl + "/ai", getWrappedMiddlewares(() => oasTlmConfig.ai.openAIKey !== null, [(0, _aiRoutes.getAIRoutes)(oasTlmConfig)]));
|
|
47
|
-
// WARNING: This path must be the same as the one used in the UI package App.tsx "oas-telemetry-ui"
|
|
48
|
-
router.use(baseUrl + "/oas-telemetry-ui", (0, _uiRoutes.getUIRoutes)());
|
|
49
|
-
router.use(baseUrl + "/utils", (0, _utilRoutes.getUtilsRoutes)(oasTlmConfig));
|
|
50
|
-
router.use(baseUrl + "/plugins", (0, _pluginRoutes.getPluginRoutes)());
|
|
51
|
-
router.get(baseUrl + '/health', (_req, res) => {
|
|
48
|
+
telemetryRouter.get('/health', (_req, res) => {
|
|
52
49
|
res.status(200).send({
|
|
53
50
|
status: 'OK'
|
|
54
51
|
});
|
|
55
52
|
});
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
res.redirect(
|
|
53
|
+
// Redirect to the UI when accessing the base URL
|
|
54
|
+
telemetryRouter.get('/', (req, res) => {
|
|
55
|
+
res.redirect(`${telemetryBaseUrl}/oas-telemetry-ui/`);
|
|
59
56
|
});
|
|
57
|
+
// WARNING: This path must be the same as the one used in the UI package App.tsx "oas-telemetry-ui"
|
|
58
|
+
telemetryRouter.use("/oas-telemetry-ui", (0, _uiRoutes.getUIRoutes)());
|
|
59
|
+
// Auth routes must be registered. If authentication is not enabled, all requests will be allowed.
|
|
60
|
+
// Frontend will use these endpoints;
|
|
61
|
+
telemetryRouter.use((0, _cookieParser.default)());
|
|
62
|
+
// Refresh token uses /auth/refresh path, careful if you change it
|
|
63
|
+
telemetryRouter.use('/auth', (0, _authRoutes.getAuthRoutes)(oasTlmConfig));
|
|
64
|
+
telemetryRouter.use((0, _authMiddleware.getAuthMiddleware)(oasTlmConfig));
|
|
65
|
+
telemetryRouter.use("/traces", (0, _traceRoutes.getTraceRoutes)());
|
|
66
|
+
telemetryRouter.use("/metrics", (0, _metricsRoutes.getMetricsRoutes)());
|
|
67
|
+
telemetryRouter.use("/logs", (0, _logRoutes.getLogRoutes)());
|
|
68
|
+
if (oasTlmConfig.ai.openAIKey) {
|
|
69
|
+
telemetryRouter.use("/ai", (0, _aiRoutes.getAIRoutes)(oasTlmConfig));
|
|
70
|
+
}
|
|
71
|
+
telemetryRouter.use("/utils", (0, _utilRoutes.getUtilsRoutes)(oasTlmConfig));
|
|
72
|
+
telemetryRouter.use("/plugins", (0, _pluginRoutes.getPluginRoutes)());
|
|
73
|
+
// Mount the telemetryRouter under telemetryBaseUrl
|
|
74
|
+
router.use(telemetryBaseUrl, telemetryRouter);
|
|
60
75
|
};
|
|
61
|
-
|
|
62
|
-
* This function wraps the provided middleware functions with a condition callback.
|
|
63
|
-
* If the condition callback returns true, the middleware/router will be executed.
|
|
64
|
-
* If the condition callback returns false, the middleware/router will be skipped.
|
|
65
|
-
*
|
|
66
|
-
* @callback {function} conditionCallback A callback function that returns a boolean to determine if the middleware should be used.
|
|
67
|
-
* @param {Array} middlewares An array of middleware or routers to be wrapped.
|
|
68
|
-
* @returns {Array} An array of wrapped middleware functions.
|
|
69
|
-
*/
|
|
70
|
-
exports.configureRoutes = configureRoutes;
|
|
71
|
-
function getWrappedMiddlewares(conditionCallback, middlewares) {
|
|
72
|
-
return middlewares.map(middleware => {
|
|
73
|
-
return function (req, res, next) {
|
|
74
|
-
if (conditionCallback()) {
|
|
75
|
-
if (typeof middleware === 'function') {
|
|
76
|
-
// look for handle property, if it exists, it's a router. If not call middleware
|
|
77
|
-
if (middleware.handle) {
|
|
78
|
-
middleware.handle(req, res, next);
|
|
79
|
-
} else {
|
|
80
|
-
middleware(req, res, next);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
} else {
|
|
84
|
-
next();
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
});
|
|
88
|
-
}
|
|
76
|
+
exports.configureRoutes = configureRoutes;
|
|
@@ -3,69 +3,134 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.getLogout = exports.getLogin = exports.
|
|
6
|
+
exports.getRefresh = exports.getLogout = exports.getLogin = exports.getAuthEnabled = void 0;
|
|
7
7
|
var _jsonwebtoken = _interopRequireDefault(require("jsonwebtoken"));
|
|
8
8
|
var _logger = _interopRequireDefault(require("../utils/logger.cjs"));
|
|
9
9
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
function generateAccessToken(secret, expiresIn) {
|
|
11
|
+
return _jsonwebtoken.default.sign({
|
|
12
|
+
type: "access"
|
|
13
|
+
}, secret, {
|
|
14
|
+
expiresIn: Math.floor(expiresIn / 1000)
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function generateRefreshToken(secret, expiresIn) {
|
|
18
|
+
return _jsonwebtoken.default.sign({
|
|
19
|
+
type: "refresh"
|
|
20
|
+
}, secret, {
|
|
21
|
+
expiresIn: Math.floor(expiresIn / 1000)
|
|
22
|
+
});
|
|
23
|
+
}
|
|
10
24
|
const getLogin = oasTlmConfig => (req, res) => {
|
|
25
|
+
if (!oasTlmConfig.auth.enabled) {
|
|
26
|
+
res.status(200).json({
|
|
27
|
+
valid: true,
|
|
28
|
+
message: "Auth disabled"
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
11
32
|
try {
|
|
12
33
|
const {
|
|
13
34
|
password
|
|
14
35
|
} = req.body;
|
|
15
36
|
if (password === oasTlmConfig.auth.password) {
|
|
16
|
-
const
|
|
17
|
-
|
|
37
|
+
const accessToken = generateAccessToken(oasTlmConfig.auth.jwtSecret, oasTlmConfig.auth.accessTokenMaxAge);
|
|
38
|
+
const refreshToken = generateRefreshToken(oasTlmConfig.auth.jwtSecret, oasTlmConfig.auth.refreshTokenMaxAge);
|
|
39
|
+
res.cookie("accessToken", accessToken, {
|
|
40
|
+
maxAge: oasTlmConfig.auth.accessTokenMaxAge,
|
|
18
41
|
httpOnly: true,
|
|
19
|
-
secure:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
42
|
+
secure: process.env.NODE_ENV === "production",
|
|
43
|
+
sameSite: "lax",
|
|
44
|
+
path: "/"
|
|
45
|
+
});
|
|
46
|
+
res.cookie("refreshToken", refreshToken, {
|
|
47
|
+
maxAge: oasTlmConfig.auth.refreshTokenMaxAge,
|
|
48
|
+
httpOnly: true,
|
|
49
|
+
secure: process.env.NODE_ENV === "production",
|
|
50
|
+
sameSite: "lax",
|
|
51
|
+
path: oasTlmConfig.general.baseUrl + "/auth/refresh" // <-- cambiado de "/auth/refresh" a oasTlmConfig.general.baseUrl + "/auth/refresh"
|
|
52
|
+
});
|
|
26
53
|
res.status(200).json({
|
|
27
54
|
valid: true,
|
|
28
|
-
message:
|
|
55
|
+
message: "Login successful"
|
|
29
56
|
});
|
|
30
57
|
return;
|
|
31
58
|
}
|
|
32
59
|
res.status(400).json({
|
|
33
60
|
valid: false,
|
|
34
|
-
message:
|
|
61
|
+
message: "Invalid password"
|
|
35
62
|
});
|
|
36
63
|
} catch (error) {
|
|
37
|
-
_logger.default.
|
|
64
|
+
_logger.default.error("Login error: ", error);
|
|
38
65
|
res.status(500).json({
|
|
39
66
|
valid: false,
|
|
40
|
-
message:
|
|
67
|
+
message: "Internal server error"
|
|
41
68
|
});
|
|
42
69
|
}
|
|
43
70
|
};
|
|
44
71
|
exports.getLogin = getLogin;
|
|
45
72
|
const getLogout = oasTlmConfig => (req, res) => {
|
|
46
|
-
|
|
47
|
-
|
|
73
|
+
if (!oasTlmConfig.auth.enabled) {
|
|
74
|
+
res.status(200).json({
|
|
75
|
+
valid: true,
|
|
76
|
+
message: "Auth disabled"
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
res.clearCookie('accessToken', {
|
|
81
|
+
path: '/'
|
|
82
|
+
});
|
|
83
|
+
res.clearCookie('refreshToken', {
|
|
84
|
+
path: oasTlmConfig.general.baseUrl + '/auth/refresh'
|
|
85
|
+
}); // <-- cambiado de "/auth/refresh" a oasTlmConfig.general.baseUrl + "/auth/refresh"
|
|
86
|
+
res.status(200).json({
|
|
87
|
+
valid: true,
|
|
88
|
+
message: "Logged out"
|
|
89
|
+
});
|
|
48
90
|
};
|
|
49
91
|
exports.getLogout = getLogout;
|
|
50
|
-
const
|
|
51
|
-
if (!
|
|
92
|
+
const getRefresh = oasTlmConfig => (req, res) => {
|
|
93
|
+
if (!oasTlmConfig.auth.enabled) {
|
|
52
94
|
res.status(200).json({
|
|
95
|
+
valid: true,
|
|
96
|
+
message: "Auth disabled"
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const refreshToken = req.cookies.refreshToken;
|
|
101
|
+
if (!refreshToken) {
|
|
102
|
+
res.status(401).json({
|
|
53
103
|
valid: false,
|
|
54
|
-
message:
|
|
104
|
+
message: "No refresh token"
|
|
55
105
|
});
|
|
56
106
|
return;
|
|
57
107
|
}
|
|
58
|
-
|
|
59
|
-
|
|
108
|
+
try {
|
|
109
|
+
const payload = _jsonwebtoken.default.verify(refreshToken, oasTlmConfig.auth.jwtSecret);
|
|
110
|
+
if (payload.type !== "refresh") throw new Error("Invalid token type");
|
|
111
|
+
const accessToken = generateAccessToken(oasTlmConfig.auth.jwtSecret, oasTlmConfig.auth.accessTokenMaxAge);
|
|
112
|
+
res.cookie("accessToken", accessToken, {
|
|
113
|
+
maxAge: oasTlmConfig.auth.accessTokenMaxAge,
|
|
114
|
+
httpOnly: true,
|
|
115
|
+
secure: process.env.NODE_ENV === "production",
|
|
116
|
+
sameSite: "lax",
|
|
117
|
+
path: "/"
|
|
118
|
+
});
|
|
60
119
|
res.status(200).json({
|
|
61
120
|
valid: true,
|
|
62
|
-
message:
|
|
121
|
+
message: "Refreshed"
|
|
122
|
+
});
|
|
123
|
+
} catch (err) {
|
|
124
|
+
res.status(401).json({
|
|
125
|
+
valid: false,
|
|
126
|
+
message: "Invalid refresh token"
|
|
63
127
|
});
|
|
64
|
-
return;
|
|
65
128
|
}
|
|
129
|
+
};
|
|
130
|
+
exports.getRefresh = getRefresh;
|
|
131
|
+
const getAuthEnabled = oasTlmConfig => (req, res) => {
|
|
66
132
|
res.status(200).json({
|
|
67
|
-
|
|
68
|
-
message: 'Invalid API Key'
|
|
133
|
+
enabled: !!oasTlmConfig.auth.enabled
|
|
69
134
|
});
|
|
70
135
|
};
|
|
71
|
-
exports.
|
|
136
|
+
exports.getAuthEnabled = getAuthEnabled;
|
|
@@ -8,13 +8,26 @@ var _jsonwebtoken = _interopRequireDefault(require("jsonwebtoken"));
|
|
|
8
8
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
9
|
function getAuthMiddleware(oasTlmConfig) {
|
|
10
10
|
return function authMiddleware(req, res, next) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
if (!oasTlmConfig.auth.enabled) {
|
|
12
|
+
return next();
|
|
13
|
+
}
|
|
14
|
+
const token = req.cookies.accessToken;
|
|
15
|
+
if (!token) {
|
|
16
|
+
res.status(401).json({
|
|
17
|
+
valid: false,
|
|
18
|
+
message: "No access token"
|
|
19
|
+
});
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const payload = _jsonwebtoken.default.verify(token, oasTlmConfig.auth.jwtSecret);
|
|
24
|
+
if (payload.type !== "access") throw new Error("Invalid token type");
|
|
25
|
+
return next();
|
|
26
|
+
} catch {
|
|
27
|
+
res.status(401).json({
|
|
28
|
+
valid: false,
|
|
29
|
+
message: "Invalid access token"
|
|
30
|
+
});
|
|
17
31
|
}
|
|
18
|
-
res.status(401).redirect(oasTlmConfig.general.baseUrl + oasTlmConfig.general.uiPath + '/login');
|
|
19
32
|
};
|
|
20
33
|
}
|
|
@@ -9,8 +9,9 @@ var _authController = require("./authController.cjs");
|
|
|
9
9
|
const getAuthRoutes = oasTlmConfig => {
|
|
10
10
|
const router = (0, _express.Router)();
|
|
11
11
|
router.post('/login', (0, _authController.getLogin)(oasTlmConfig));
|
|
12
|
-
router.
|
|
13
|
-
router.
|
|
12
|
+
router.post('/refresh', (0, _authController.getRefresh)(oasTlmConfig));
|
|
13
|
+
router.post('/logout', (0, _authController.getLogout)(oasTlmConfig));
|
|
14
|
+
router.get('/enabled', (0, _authController.getAuthEnabled)(oasTlmConfig));
|
|
14
15
|
return router;
|
|
15
16
|
};
|
|
16
17
|
exports.getAuthRoutes = getAuthRoutes;
|
|
@@ -11,9 +11,10 @@ const loadEnv = () => {
|
|
|
11
11
|
},
|
|
12
12
|
auth: {
|
|
13
13
|
enabled: getParsedEnvVar("OASTLM_CONFIG_AUTH_ENABLED", (v) => v === "true"),
|
|
14
|
-
apiKeyMaxAge: getParsedEnvVar("OASTLM_CONFIG_AUTH_API_KEY_MAX_AGE", (v) => parseInt(v, 10)),
|
|
15
14
|
password: getParsedEnvVar("OASTLM_CONFIG_AUTH_PASSWORD"),
|
|
16
15
|
jwtSecret: getParsedEnvVar("OASTLM_CONFIG_AUTH_JWT_SECRET"),
|
|
16
|
+
accessTokenMaxAge: getParsedEnvVar("OASTLM_CONFIG_AUTH_ACCESS_TOKEN_MAX_AGE", (v) => parseInt(v, 10)),
|
|
17
|
+
refreshTokenMaxAge: getParsedEnvVar("OASTLM_CONFIG_AUTH_REFRESH_TOKEN_MAX_AGE", (v) => parseInt(v, 10)),
|
|
17
18
|
},
|
|
18
19
|
traces: {
|
|
19
20
|
memoryExporter: {
|
|
@@ -61,9 +62,10 @@ export const defaultConfig = {
|
|
|
61
62
|
},
|
|
62
63
|
auth: {
|
|
63
64
|
enabled: false,
|
|
64
|
-
apiKeyMaxAge: 1000 * 60 * 60, // 1 hour
|
|
65
65
|
password: "oas-telemetry-password",
|
|
66
66
|
jwtSecret: "oas-telemetry-secret",
|
|
67
|
+
accessTokenMaxAge: 1000 * 60 * 5, // 5 minutes
|
|
68
|
+
refreshTokenMaxAge: 1000 * 60 * 60 * 24 * 7, // 7 days
|
|
67
69
|
},
|
|
68
70
|
ai: {
|
|
69
71
|
openAIKey: null,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { json } from "express";
|
|
1
|
+
import { Router, json } from "express";
|
|
2
2
|
import logger from "./utils/logger.js";
|
|
3
3
|
import cors from 'cors';
|
|
4
4
|
import { getTraceRoutes } from "./tlm-trace/traceRoutes.js";
|
|
@@ -16,62 +16,50 @@ export const configureRoutes = (router, oasTlmConfig) => {
|
|
|
16
16
|
if (bootEnvVariables.OASTLM_BOOT_ENV === 'development') {
|
|
17
17
|
logger.info("Running in development mode, enabling CORS for all origins");
|
|
18
18
|
router.use(cors({
|
|
19
|
-
origin:
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
origin: (origin, callback) => {
|
|
20
|
+
if (!origin || /^http:\/\/localhost:\d+$/.test(origin)) {
|
|
21
|
+
callback(null, true);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
callback(new Error('Not allowed by CORS'));
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
credentials: true
|
|
22
28
|
}));
|
|
23
29
|
}
|
|
24
|
-
|
|
30
|
+
const telemetryBaseUrl = oasTlmConfig.general.baseUrl;
|
|
31
|
+
// Sub-router for all telemetry endpoints
|
|
32
|
+
const telemetryRouter = Router();
|
|
33
|
+
// Body parser for JSON requests
|
|
34
|
+
telemetryRouter.use((req, res, next) => {
|
|
25
35
|
if (req.body !== undefined) {
|
|
26
36
|
return next(); // Already parsed, no need to parse again.
|
|
27
37
|
}
|
|
28
38
|
return json({ limit: '10mb' })(req, res, next);
|
|
29
39
|
});
|
|
30
|
-
|
|
31
|
-
const baseUrl = oasTlmConfig.general.baseUrl;
|
|
32
|
-
router.use(baseUrl, allAuthMiddlewares);
|
|
33
|
-
router.use(baseUrl + "/traces", getTraceRoutes());
|
|
34
|
-
router.use(baseUrl + "/metrics", getMetricsRoutes());
|
|
35
|
-
router.use(baseUrl + "/logs", getLogRoutes());
|
|
36
|
-
router.use(baseUrl + "/ai", getWrappedMiddlewares(() => oasTlmConfig.ai.openAIKey !== null, [getAIRoutes(oasTlmConfig)]));
|
|
37
|
-
// WARNING: This path must be the same as the one used in the UI package App.tsx "oas-telemetry-ui"
|
|
38
|
-
router.use(baseUrl + "/oas-telemetry-ui", getUIRoutes());
|
|
39
|
-
router.use(baseUrl + "/utils", getUtilsRoutes(oasTlmConfig));
|
|
40
|
-
router.use(baseUrl + "/plugins", getPluginRoutes());
|
|
41
|
-
router.get(baseUrl + '/health', (_req, res) => {
|
|
40
|
+
telemetryRouter.get('/health', (_req, res) => {
|
|
42
41
|
res.status(200).send({ status: 'OK' });
|
|
43
42
|
});
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
res.redirect(
|
|
43
|
+
// Redirect to the UI when accessing the base URL
|
|
44
|
+
telemetryRouter.get('/', (req, res) => {
|
|
45
|
+
res.redirect(`${telemetryBaseUrl}/oas-telemetry-ui/`);
|
|
47
46
|
});
|
|
47
|
+
// WARNING: This path must be the same as the one used in the UI package App.tsx "oas-telemetry-ui"
|
|
48
|
+
telemetryRouter.use("/oas-telemetry-ui", getUIRoutes());
|
|
49
|
+
// Auth routes must be registered. If authentication is not enabled, all requests will be allowed.
|
|
50
|
+
// Frontend will use these endpoints;
|
|
51
|
+
telemetryRouter.use(cookieParser());
|
|
52
|
+
// Refresh token uses /auth/refresh path, careful if you change it
|
|
53
|
+
telemetryRouter.use('/auth', getAuthRoutes(oasTlmConfig));
|
|
54
|
+
telemetryRouter.use(getAuthMiddleware(oasTlmConfig));
|
|
55
|
+
telemetryRouter.use("/traces", getTraceRoutes());
|
|
56
|
+
telemetryRouter.use("/metrics", getMetricsRoutes());
|
|
57
|
+
telemetryRouter.use("/logs", getLogRoutes());
|
|
58
|
+
if (oasTlmConfig.ai.openAIKey) {
|
|
59
|
+
telemetryRouter.use("/ai", getAIRoutes(oasTlmConfig));
|
|
60
|
+
}
|
|
61
|
+
telemetryRouter.use("/utils", getUtilsRoutes(oasTlmConfig));
|
|
62
|
+
telemetryRouter.use("/plugins", getPluginRoutes());
|
|
63
|
+
// Mount the telemetryRouter under telemetryBaseUrl
|
|
64
|
+
router.use(telemetryBaseUrl, telemetryRouter);
|
|
48
65
|
};
|
|
49
|
-
/**
|
|
50
|
-
* This function wraps the provided middleware functions with a condition callback.
|
|
51
|
-
* If the condition callback returns true, the middleware/router will be executed.
|
|
52
|
-
* If the condition callback returns false, the middleware/router will be skipped.
|
|
53
|
-
*
|
|
54
|
-
* @callback {function} conditionCallback A callback function that returns a boolean to determine if the middleware should be used.
|
|
55
|
-
* @param {Array} middlewares An array of middleware or routers to be wrapped.
|
|
56
|
-
* @returns {Array} An array of wrapped middleware functions.
|
|
57
|
-
*/
|
|
58
|
-
function getWrappedMiddlewares(conditionCallback, middlewares) {
|
|
59
|
-
return middlewares.map(middleware => {
|
|
60
|
-
return function (req, res, next) {
|
|
61
|
-
if (conditionCallback()) {
|
|
62
|
-
if (typeof middleware === 'function') {
|
|
63
|
-
// look for handle property, if it exists, it's a router. If not call middleware
|
|
64
|
-
if (middleware.handle) {
|
|
65
|
-
middleware.handle(req, res, next);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
middleware(req, res, next);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
next();
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
});
|
|
77
|
-
}
|
|
@@ -1,40 +1,82 @@
|
|
|
1
1
|
import jwt from 'jsonwebtoken';
|
|
2
2
|
import logger from '../utils/logger.js';
|
|
3
|
+
function generateAccessToken(secret, expiresIn) {
|
|
4
|
+
return jwt.sign({ type: "access" }, secret, { expiresIn: Math.floor(expiresIn / 1000) });
|
|
5
|
+
}
|
|
6
|
+
function generateRefreshToken(secret, expiresIn) {
|
|
7
|
+
return jwt.sign({ type: "refresh" }, secret, { expiresIn: Math.floor(expiresIn / 1000) });
|
|
8
|
+
}
|
|
3
9
|
export const getLogin = (oasTlmConfig) => (req, res) => {
|
|
10
|
+
if (!oasTlmConfig.auth.enabled) {
|
|
11
|
+
res.status(200).json({ valid: true, message: "Auth disabled" });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
4
14
|
try {
|
|
5
15
|
const { password } = req.body;
|
|
6
16
|
if (password === oasTlmConfig.auth.password) {
|
|
7
|
-
const
|
|
8
|
-
|
|
17
|
+
const accessToken = generateAccessToken(oasTlmConfig.auth.jwtSecret, oasTlmConfig.auth.accessTokenMaxAge);
|
|
18
|
+
const refreshToken = generateRefreshToken(oasTlmConfig.auth.jwtSecret, oasTlmConfig.auth.refreshTokenMaxAge);
|
|
19
|
+
res.cookie("accessToken", accessToken, {
|
|
20
|
+
maxAge: oasTlmConfig.auth.accessTokenMaxAge,
|
|
21
|
+
httpOnly: true,
|
|
22
|
+
secure: process.env.NODE_ENV === "production",
|
|
23
|
+
sameSite: "lax",
|
|
24
|
+
path: "/"
|
|
25
|
+
});
|
|
26
|
+
res.cookie("refreshToken", refreshToken, {
|
|
27
|
+
maxAge: oasTlmConfig.auth.refreshTokenMaxAge,
|
|
9
28
|
httpOnly: true,
|
|
10
|
-
secure:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
res.
|
|
15
|
-
res.status(200).json({ valid: true, message: 'API Key is valid' });
|
|
29
|
+
secure: process.env.NODE_ENV === "production",
|
|
30
|
+
sameSite: "lax",
|
|
31
|
+
path: oasTlmConfig.general.baseUrl + "/auth/refresh" // <-- cambiado de "/auth/refresh" a oasTlmConfig.general.baseUrl + "/auth/refresh"
|
|
32
|
+
});
|
|
33
|
+
res.status(200).json({ valid: true, message: "Login successful" });
|
|
16
34
|
return;
|
|
17
35
|
}
|
|
18
|
-
res.status(400).json({ valid: false, message:
|
|
36
|
+
res.status(400).json({ valid: false, message: "Invalid password" });
|
|
19
37
|
}
|
|
20
38
|
catch (error) {
|
|
21
|
-
logger.
|
|
22
|
-
res.status(500).json({ valid: false, message:
|
|
39
|
+
logger.error("Login error: ", error);
|
|
40
|
+
res.status(500).json({ valid: false, message: "Internal server error" });
|
|
23
41
|
}
|
|
24
42
|
};
|
|
25
43
|
export const getLogout = (oasTlmConfig) => (req, res) => {
|
|
26
|
-
|
|
27
|
-
|
|
44
|
+
if (!oasTlmConfig.auth.enabled) {
|
|
45
|
+
res.status(200).json({ valid: true, message: "Auth disabled" });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
res.clearCookie('accessToken', { path: '/' });
|
|
49
|
+
res.clearCookie('refreshToken', { path: oasTlmConfig.general.baseUrl + '/auth/refresh' }); // <-- cambiado de "/auth/refresh" a oasTlmConfig.general.baseUrl + "/auth/refresh"
|
|
50
|
+
res.status(200).json({ valid: true, message: "Logged out" });
|
|
28
51
|
};
|
|
29
|
-
export const
|
|
30
|
-
if (!
|
|
31
|
-
res.status(200).json({ valid:
|
|
52
|
+
export const getRefresh = (oasTlmConfig) => (req, res) => {
|
|
53
|
+
if (!oasTlmConfig.auth.enabled) {
|
|
54
|
+
res.status(200).json({ valid: true, message: "Auth disabled" });
|
|
32
55
|
return;
|
|
33
56
|
}
|
|
34
|
-
const
|
|
35
|
-
if (
|
|
36
|
-
res.status(
|
|
57
|
+
const refreshToken = req.cookies.refreshToken;
|
|
58
|
+
if (!refreshToken) {
|
|
59
|
+
res.status(401).json({ valid: false, message: "No refresh token" });
|
|
37
60
|
return;
|
|
38
61
|
}
|
|
39
|
-
|
|
62
|
+
try {
|
|
63
|
+
const payload = jwt.verify(refreshToken, oasTlmConfig.auth.jwtSecret);
|
|
64
|
+
if (payload.type !== "refresh")
|
|
65
|
+
throw new Error("Invalid token type");
|
|
66
|
+
const accessToken = generateAccessToken(oasTlmConfig.auth.jwtSecret, oasTlmConfig.auth.accessTokenMaxAge);
|
|
67
|
+
res.cookie("accessToken", accessToken, {
|
|
68
|
+
maxAge: oasTlmConfig.auth.accessTokenMaxAge,
|
|
69
|
+
httpOnly: true,
|
|
70
|
+
secure: process.env.NODE_ENV === "production",
|
|
71
|
+
sameSite: "lax",
|
|
72
|
+
path: "/"
|
|
73
|
+
});
|
|
74
|
+
res.status(200).json({ valid: true, message: "Refreshed" });
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
res.status(401).json({ valid: false, message: "Invalid refresh token" });
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
export const getAuthEnabled = (oasTlmConfig) => (req, res) => {
|
|
81
|
+
res.status(200).json({ enabled: !!oasTlmConfig.auth.enabled });
|
|
40
82
|
};
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import jwt from
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
2
|
export function getAuthMiddleware(oasTlmConfig) {
|
|
3
3
|
return function authMiddleware(req, res, next) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
if (!oasTlmConfig.auth.enabled) {
|
|
5
|
+
return next();
|
|
6
|
+
}
|
|
7
|
+
const token = req.cookies.accessToken;
|
|
8
|
+
if (!token) {
|
|
9
|
+
res.status(401).json({ valid: false, message: "No access token" });
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const payload = jwt.verify(token, oasTlmConfig.auth.jwtSecret);
|
|
14
|
+
if (payload.type !== "access")
|
|
15
|
+
throw new Error("Invalid token type");
|
|
16
|
+
return next();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
res.status(401).json({ valid: false, message: "Invalid access token" });
|
|
20
|
+
}
|
|
12
21
|
};
|
|
13
22
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import { getLogin, getLogout,
|
|
2
|
+
import { getLogin, getLogout, getRefresh, getAuthEnabled } from './authController.js';
|
|
3
3
|
export const getAuthRoutes = (oasTlmConfig) => {
|
|
4
4
|
const router = Router();
|
|
5
5
|
router.post('/login', getLogin(oasTlmConfig));
|
|
6
|
-
router.
|
|
7
|
-
router.
|
|
6
|
+
router.post('/refresh', getRefresh(oasTlmConfig));
|
|
7
|
+
router.post('/logout', getLogout(oasTlmConfig));
|
|
8
|
+
router.get('/enabled', getAuthEnabled(oasTlmConfig));
|
|
8
9
|
return router;
|
|
9
10
|
};
|