@aifabrix/miso-client 3.1.2 → 3.2.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/README.md +57 -2
- package/dist/express/client-token-endpoint.d.ts +76 -0
- package/dist/express/client-token-endpoint.d.ts.map +1 -0
- package/dist/express/client-token-endpoint.js +231 -0
- package/dist/express/client-token-endpoint.js.map +1 -0
- package/dist/express/index.d.ts +2 -1
- package/dist/express/index.d.ts.map +1 -1
- package/dist/express/index.js +8 -3
- package/dist/express/index.js.map +1 -1
- package/dist/express/response-middleware.d.ts.map +1 -1
- package/dist/express/response-middleware.js.map +1 -1
- package/dist/index.d.ts +19 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -4
- package/dist/index.js.map +1 -1
- package/dist/services/auth.service.d.ts +26 -4
- package/dist/services/auth.service.d.ts.map +1 -1
- package/dist/services/auth.service.js +137 -6
- package/dist/services/auth.service.js.map +1 -1
- package/dist/services/browser-permission.service.d.ts +60 -0
- package/dist/services/browser-permission.service.d.ts.map +1 -0
- package/dist/services/browser-permission.service.js +159 -0
- package/dist/services/browser-permission.service.js.map +1 -0
- package/dist/services/browser-role.service.d.ts +60 -0
- package/dist/services/browser-role.service.d.ts.map +1 -0
- package/dist/services/browser-role.service.js +159 -0
- package/dist/services/browser-role.service.js.map +1 -0
- package/dist/services/cache.service.d.ts.map +1 -1
- package/dist/services/cache.service.js +4 -0
- package/dist/services/cache.service.js.map +1 -1
- package/dist/services/logger.service.d.ts +99 -24
- package/dist/services/logger.service.d.ts.map +1 -1
- package/dist/services/logger.service.js +174 -44
- package/dist/services/logger.service.js.map +1 -1
- package/dist/services/redis.service.d.ts.map +1 -1
- package/dist/services/redis.service.js +3 -6
- package/dist/services/redis.service.js.map +1 -1
- package/dist/types/config.types.d.ts +22 -0
- package/dist/types/config.types.d.ts.map +1 -1
- package/dist/types/config.types.js.map +1 -1
- package/dist/types/data-client.types.d.ts +10 -0
- package/dist/types/data-client.types.d.ts.map +1 -1
- package/dist/types/data-client.types.js.map +1 -1
- package/dist/utils/audit-log-queue.d.ts +4 -0
- package/dist/utils/audit-log-queue.d.ts.map +1 -1
- package/dist/utils/audit-log-queue.js +22 -2
- package/dist/utils/audit-log-queue.js.map +1 -1
- package/dist/utils/browser-jwt-decoder.d.ts +20 -0
- package/dist/utils/browser-jwt-decoder.d.ts.map +1 -0
- package/dist/utils/browser-jwt-decoder.js +75 -0
- package/dist/utils/browser-jwt-decoder.js.map +1 -0
- package/dist/utils/controller-url-resolver.d.ts +16 -0
- package/dist/utils/controller-url-resolver.d.ts.map +1 -1
- package/dist/utils/controller-url-resolver.js +12 -0
- package/dist/utils/controller-url-resolver.js.map +1 -1
- package/dist/utils/data-client-audit.d.ts.map +1 -1
- package/dist/utils/data-client-audit.js +19 -8
- package/dist/utils/data-client-audit.js.map +1 -1
- package/dist/utils/data-client-auth.d.ts +19 -7
- package/dist/utils/data-client-auth.d.ts.map +1 -1
- package/dist/utils/data-client-auth.js +269 -144
- package/dist/utils/data-client-auth.js.map +1 -1
- package/dist/utils/data-client-auto-init.d.ts +66 -0
- package/dist/utils/data-client-auto-init.d.ts.map +1 -0
- package/dist/utils/data-client-auto-init.js +259 -0
- package/dist/utils/data-client-auto-init.js.map +1 -0
- package/dist/utils/data-client-redirect.d.ts +52 -0
- package/dist/utils/data-client-redirect.d.ts.map +1 -0
- package/dist/utils/data-client-redirect.js +233 -0
- package/dist/utils/data-client-redirect.js.map +1 -0
- package/dist/utils/data-client-request.d.ts +8 -1
- package/dist/utils/data-client-request.d.ts.map +1 -1
- package/dist/utils/data-client-request.js +30 -5
- package/dist/utils/data-client-request.js.map +1 -1
- package/dist/utils/data-client.d.ts +116 -0
- package/dist/utils/data-client.d.ts.map +1 -1
- package/dist/utils/data-client.js +349 -4
- package/dist/utils/data-client.js.map +1 -1
- package/dist/utils/logging-helpers.d.ts +51 -0
- package/dist/utils/logging-helpers.d.ts.map +1 -0
- package/dist/utils/logging-helpers.js +57 -0
- package/dist/utils/logging-helpers.js.map +1 -0
- package/dist/utils/request-context.d.ts +32 -0
- package/dist/utils/request-context.d.ts.map +1 -0
- package/dist/utils/request-context.js +81 -0
- package/dist/utils/request-context.js.map +1 -0
- package/package.json +9 -2
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DataClient Auto-Initialization Helper
|
|
4
|
+
* Automatically fetches configuration from server and initializes DataClient
|
|
5
|
+
*
|
|
6
|
+
* Provides zero-config client-side setup for DataClient initialization
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getCachedDataClientConfig = getCachedDataClientConfig;
|
|
10
|
+
exports.autoInitializeDataClient = autoInitializeDataClient;
|
|
11
|
+
const data_client_1 = require("./data-client");
|
|
12
|
+
const data_client_utils_1 = require("./data-client-utils");
|
|
13
|
+
const client_token_endpoint_1 = require("../express/client-token-endpoint");
|
|
14
|
+
/**
|
|
15
|
+
* Get cached config from localStorage
|
|
16
|
+
*
|
|
17
|
+
* @returns Cached config or null if not found or expired
|
|
18
|
+
*/
|
|
19
|
+
function getCachedConfig() {
|
|
20
|
+
if (!(0, data_client_utils_1.isBrowser)()) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const cachedStr = (0, data_client_utils_1.getLocalStorage)("miso:dataclient-config");
|
|
25
|
+
if (!cachedStr) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const cached = JSON.parse(cachedStr);
|
|
29
|
+
// Check if expired
|
|
30
|
+
if (cached.expiresAt && cached.expiresAt < Date.now()) {
|
|
31
|
+
(0, data_client_utils_1.removeLocalStorage)("miso:dataclient-config");
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return cached;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Invalid cache, remove it
|
|
38
|
+
(0, data_client_utils_1.removeLocalStorage)("miso:dataclient-config");
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get cached DataClient configuration from localStorage
|
|
44
|
+
*
|
|
45
|
+
* Returns the cached configuration that was stored by autoInitializeDataClient.
|
|
46
|
+
* Useful for reading configuration without re-initializing DataClient.
|
|
47
|
+
*
|
|
48
|
+
* @returns Cached config or null if not found or expired
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { getCachedDataClientConfig } from '@aifabrix/miso-client';
|
|
53
|
+
*
|
|
54
|
+
* const cachedConfig = getCachedDataClientConfig();
|
|
55
|
+
* if (cachedConfig) {
|
|
56
|
+
* console.log('Base URL:', cachedConfig.baseUrl);
|
|
57
|
+
* console.log('Controller URL:', cachedConfig.controllerUrl);
|
|
58
|
+
* console.log('Client ID:', cachedConfig.clientId);
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
function getCachedDataClientConfig() {
|
|
63
|
+
const cached = getCachedConfig();
|
|
64
|
+
return cached?.config || null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Cache config in localStorage
|
|
68
|
+
*
|
|
69
|
+
* @param config - Config to cache
|
|
70
|
+
* @param expiresIn - Expiration time in seconds
|
|
71
|
+
*/
|
|
72
|
+
function cacheConfig(config, expiresIn) {
|
|
73
|
+
if (!(0, data_client_utils_1.isBrowser)()) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const cached = {
|
|
78
|
+
config,
|
|
79
|
+
expiresAt: Date.now() + expiresIn * 1000,
|
|
80
|
+
};
|
|
81
|
+
(0, data_client_utils_1.setLocalStorage)("miso:dataclient-config", JSON.stringify(cached));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Ignore localStorage errors (SSR, private browsing, etc.)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Fetch config from server endpoint
|
|
89
|
+
*
|
|
90
|
+
* @param baseUrl - Base URL for the API
|
|
91
|
+
* @param clientTokenUri - Client token endpoint URI
|
|
92
|
+
* @returns Config response
|
|
93
|
+
*/
|
|
94
|
+
async function fetchConfig(baseUrl, clientTokenUri) {
|
|
95
|
+
// Build full URL
|
|
96
|
+
const fullUrl = /^https?:\/\//i.test(clientTokenUri)
|
|
97
|
+
? clientTokenUri
|
|
98
|
+
: `${baseUrl}${clientTokenUri}`;
|
|
99
|
+
// Add timeout to prevent hanging (30 seconds)
|
|
100
|
+
const timeout = 30000;
|
|
101
|
+
const controller = new AbortController();
|
|
102
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
103
|
+
try {
|
|
104
|
+
// Try POST first (standard), fallback to GET
|
|
105
|
+
let response;
|
|
106
|
+
try {
|
|
107
|
+
response = await fetch(fullUrl, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
},
|
|
112
|
+
credentials: "include",
|
|
113
|
+
signal: controller.signal,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (fetchError) {
|
|
117
|
+
clearTimeout(timeoutId);
|
|
118
|
+
// Handle timeout and network errors
|
|
119
|
+
const errorMessage = fetchError instanceof Error ? fetchError.message : String(fetchError);
|
|
120
|
+
const errorName = fetchError instanceof Error ? fetchError.name : "Unknown";
|
|
121
|
+
if (errorName === "AbortError" || errorMessage.includes("aborted")) {
|
|
122
|
+
throw new Error(`Request timeout: The client token endpoint did not respond within ${timeout}ms. ` +
|
|
123
|
+
`Please check if the server is running and accessible at ${fullUrl}`);
|
|
124
|
+
}
|
|
125
|
+
if (errorMessage.includes("ERR_EMPTY_RESPONSE") || errorMessage.includes("empty response")) {
|
|
126
|
+
throw new Error(`Connection error: The server closed the connection without sending a response. ` +
|
|
127
|
+
`Please check if the server is running and accessible at ${fullUrl}`);
|
|
128
|
+
}
|
|
129
|
+
if (errorMessage.includes("Failed to fetch") || errorMessage.includes("network")) {
|
|
130
|
+
throw new Error(`Network error: Cannot connect to ${fullUrl}. ` +
|
|
131
|
+
`Please check your network connection and ensure the server is running.`);
|
|
132
|
+
}
|
|
133
|
+
throw new Error(`Failed to fetch config: ${errorMessage}`);
|
|
134
|
+
}
|
|
135
|
+
clearTimeout(timeoutId);
|
|
136
|
+
// If POST fails with 405, try GET
|
|
137
|
+
if (response.status === 405) {
|
|
138
|
+
const getController = new AbortController();
|
|
139
|
+
const getTimeoutId = setTimeout(() => getController.abort(), timeout);
|
|
140
|
+
try {
|
|
141
|
+
response = await fetch(fullUrl, {
|
|
142
|
+
method: "GET",
|
|
143
|
+
credentials: "include",
|
|
144
|
+
signal: getController.signal,
|
|
145
|
+
});
|
|
146
|
+
clearTimeout(getTimeoutId);
|
|
147
|
+
}
|
|
148
|
+
catch (fetchError) {
|
|
149
|
+
clearTimeout(getTimeoutId);
|
|
150
|
+
const errorMessage = fetchError instanceof Error ? fetchError.message : String(fetchError);
|
|
151
|
+
const errorName = fetchError instanceof Error ? fetchError.name : "Unknown";
|
|
152
|
+
if (errorName === "AbortError" || errorMessage.includes("aborted")) {
|
|
153
|
+
throw new Error(`Request timeout: The client token endpoint did not respond within ${timeout}ms. ` +
|
|
154
|
+
`Please check if the server is running and accessible at ${fullUrl}`);
|
|
155
|
+
}
|
|
156
|
+
throw new Error(`Failed to fetch config: ${errorMessage}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
const errorText = await response.text().catch(() => "Unable to read error response");
|
|
161
|
+
throw new Error(`Failed to fetch config: ${response.status} ${response.statusText}. ${errorText}`);
|
|
162
|
+
}
|
|
163
|
+
const data = (await response.json());
|
|
164
|
+
// Check if response has config
|
|
165
|
+
if (!(0, client_token_endpoint_1.hasConfig)(data)) {
|
|
166
|
+
throw new Error("Invalid response format: config not found in response. " +
|
|
167
|
+
"Make sure your server endpoint uses createClientTokenEndpoint() helper.");
|
|
168
|
+
}
|
|
169
|
+
return data.config;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (error instanceof Error) {
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
throw new Error(`Network error: ${String(error)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Auto-initialize DataClient with server-provided configuration
|
|
180
|
+
*
|
|
181
|
+
* Automatically:
|
|
182
|
+
* 1. Detects if running in browser
|
|
183
|
+
* 2. Checks localStorage cache first
|
|
184
|
+
* 3. Fetches config from server endpoint if needed
|
|
185
|
+
* 4. Initializes DataClient with server-provided config
|
|
186
|
+
* 5. Caches config for future use
|
|
187
|
+
*
|
|
188
|
+
* @param options - Optional configuration
|
|
189
|
+
* @returns Initialized DataClient instance
|
|
190
|
+
* @throws Error if initialization fails
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```typescript
|
|
194
|
+
* import { autoInitializeDataClient } from '@aifabrix/miso-client';
|
|
195
|
+
*
|
|
196
|
+
* // One line - everything is automatic
|
|
197
|
+
* const dataClient = await autoInitializeDataClient();
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
async function autoInitializeDataClient(options) {
|
|
201
|
+
// Check if running in browser
|
|
202
|
+
if (!(0, data_client_utils_1.isBrowser)()) {
|
|
203
|
+
throw new Error("autoInitializeDataClient() is only available in browser environment");
|
|
204
|
+
}
|
|
205
|
+
const opts = {
|
|
206
|
+
clientTokenUri: options?.clientTokenUri || "/api/v1/auth/client-token",
|
|
207
|
+
cacheConfig: options?.cacheConfig ?? true,
|
|
208
|
+
baseUrl: options?.baseUrl,
|
|
209
|
+
onError: options?.onError,
|
|
210
|
+
};
|
|
211
|
+
try {
|
|
212
|
+
// Auto-detect baseUrl from window.location if not provided
|
|
213
|
+
const baseUrl = opts.baseUrl ||
|
|
214
|
+
((0, data_client_utils_1.isBrowser)()
|
|
215
|
+
? globalThis.window.location.origin
|
|
216
|
+
: "");
|
|
217
|
+
if (!baseUrl) {
|
|
218
|
+
throw new Error("Unable to detect baseUrl. Please provide baseUrl option.");
|
|
219
|
+
}
|
|
220
|
+
let config = null;
|
|
221
|
+
// Check cache first if enabled
|
|
222
|
+
if (opts.cacheConfig) {
|
|
223
|
+
const cached = getCachedConfig();
|
|
224
|
+
if (cached) {
|
|
225
|
+
config = cached.config;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Fetch from server if not cached
|
|
229
|
+
if (!config) {
|
|
230
|
+
config = await fetchConfig(baseUrl, opts.clientTokenUri);
|
|
231
|
+
// Cache config if enabled (use expiresIn from token response if available)
|
|
232
|
+
// Default to 30 minutes (1800 seconds) if not available
|
|
233
|
+
if (opts.cacheConfig) {
|
|
234
|
+
cacheConfig(config, 1800);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Build DataClient config
|
|
238
|
+
const dataClientConfig = {
|
|
239
|
+
baseUrl: config.baseUrl,
|
|
240
|
+
misoConfig: {
|
|
241
|
+
clientId: config.clientId,
|
|
242
|
+
controllerUrl: config.controllerUrl,
|
|
243
|
+
controllerPublicUrl: config.controllerPublicUrl,
|
|
244
|
+
clientTokenUri: config.clientTokenUri,
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
// Initialize and return DataClient
|
|
248
|
+
return new data_client_1.DataClient(dataClientConfig);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
252
|
+
// Call error callback if provided
|
|
253
|
+
if (opts.onError) {
|
|
254
|
+
opts.onError(err);
|
|
255
|
+
}
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=data-client-auto-init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-client-auto-init.js","sourceRoot":"","sources":["../../src/utils/data-client-auto-init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataClient redirect utilities
|
|
3
|
+
* Handles redirect to login/logout flows
|
|
4
|
+
*/
|
|
5
|
+
import { MisoClient } from "../index";
|
|
6
|
+
import { DataClientConfig } from "../types/data-client.types";
|
|
7
|
+
/**
|
|
8
|
+
* Synchronously get and validate redirect URL
|
|
9
|
+
* Validates the URL before returning it
|
|
10
|
+
* @param url - URL to validate and return
|
|
11
|
+
* @param fallbackUrl - Optional fallback URL if primary is invalid
|
|
12
|
+
* @returns Validated URL string or null if both are invalid
|
|
13
|
+
*/
|
|
14
|
+
export declare function getValidatedRedirectUrl(url: string | null, fallbackUrl?: string | null): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Redirect to controller login page
|
|
17
|
+
* Redirects user browser directly to controller's login URL with client token
|
|
18
|
+
* NO API CALLS - just browser redirect
|
|
19
|
+
*
|
|
20
|
+
* Flow:
|
|
21
|
+
* 1. User visits: http://localhost:4111/dataplane/
|
|
22
|
+
* 2. redirectToLogin() redirects to: {controllerPublicUrl}{loginUrl}?redirect={redirectUrl}&x-client-token={token}
|
|
23
|
+
* 3. Controller handles OAuth flow and redirects back after authentication
|
|
24
|
+
* 4. Controller redirects to: {redirectUrl}#token={userToken}
|
|
25
|
+
* 5. DataClient.handleOAuthCallback() extracts token from hash and stores securely
|
|
26
|
+
*
|
|
27
|
+
* Security:
|
|
28
|
+
* - Token is passed in hash fragment (#token=...) not query parameter
|
|
29
|
+
* - Hash fragments are NOT sent to server (not in logs)
|
|
30
|
+
* - Hash fragments are NOT stored in browser history
|
|
31
|
+
* - Token is immediately removed from URL after extraction
|
|
32
|
+
*
|
|
33
|
+
* @param config - DataClient configuration
|
|
34
|
+
* @param getClientTokenFn - Function to get client token
|
|
35
|
+
* @param redirectUrl - Optional redirect URL to return to after login (defaults to current page URL)
|
|
36
|
+
*/
|
|
37
|
+
export declare function redirectToLogin(config: DataClientConfig, getClientTokenFn: () => Promise<string | null>, redirectUrl?: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Logout user and redirect to controller logout page
|
|
40
|
+
* Clears tokens from localStorage, clears cache, and redirects to controller logout URL
|
|
41
|
+
* NO API CALLS - just browser redirect
|
|
42
|
+
* Controller handles the logout process
|
|
43
|
+
*
|
|
44
|
+
* @param config - DataClient configuration
|
|
45
|
+
* @param getTokenFn - Function to get user token
|
|
46
|
+
* @param getClientTokenFn - Function to get client token
|
|
47
|
+
* @param clearCacheFn - Function to clear HTTP cache
|
|
48
|
+
* @param redirectUrl - Optional redirect URL after logout (defaults to logoutUrl or loginUrl)
|
|
49
|
+
* @param _misoClient - MisoClient instance (not used, kept for API compatibility)
|
|
50
|
+
*/
|
|
51
|
+
export declare function logout(config: DataClientConfig, getTokenFn: () => string | null, getClientTokenFn: () => Promise<string | null>, clearCacheFn: () => void, redirectUrl?: string, _misoClient?: MisoClient | null): Promise<void>;
|
|
52
|
+
//# sourceMappingURL=data-client-redirect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-client-redirect.d.ts","sourceRoot":"","sources":["../../src/utils/data-client-redirect.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAkD9D;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAiBtG;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,gBAAgB,EACxB,gBAAgB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAC9C,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CA2Df;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,MAAM,CAC1B,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,MAAM,MAAM,GAAG,IAAI,EAC/B,gBAAgB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAC9C,YAAY,EAAE,MAAM,IAAI,EACxB,WAAW,CAAC,EAAE,MAAM,EACpB,WAAW,CAAC,EAAE,UAAU,GAAG,IAAI,GAC9B,OAAO,CAAC,IAAI,CAAC,CAiFf"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DataClient redirect utilities
|
|
4
|
+
* Handles redirect to login/logout flows
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.getValidatedRedirectUrl = getValidatedRedirectUrl;
|
|
8
|
+
exports.redirectToLogin = redirectToLogin;
|
|
9
|
+
exports.logout = logout;
|
|
10
|
+
const data_client_auth_1 = require("./data-client-auth");
|
|
11
|
+
const data_client_utils_1 = require("./data-client-utils");
|
|
12
|
+
/**
|
|
13
|
+
* Validate redirect URL for safety and format
|
|
14
|
+
* Checks for valid URL format and safe protocols (http, https)
|
|
15
|
+
* @param url - URL to validate
|
|
16
|
+
* @returns Validated URL string or null if invalid
|
|
17
|
+
*/
|
|
18
|
+
function validateRedirectUrl(url) {
|
|
19
|
+
if (!url || typeof url !== 'string' || url.trim() === '') {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const trimmedUrl = url.trim();
|
|
23
|
+
// Check for dangerous protocols (javascript:, data:, etc.)
|
|
24
|
+
const dangerousProtocols = ['javascript:', 'data:', 'vbscript:', 'file:', 'about:'];
|
|
25
|
+
const lowerUrl = trimmedUrl.toLowerCase();
|
|
26
|
+
for (const protocol of dangerousProtocols) {
|
|
27
|
+
if (lowerUrl.startsWith(protocol)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Try to parse as URL
|
|
32
|
+
let parsedUrl;
|
|
33
|
+
try {
|
|
34
|
+
// If it's a relative URL, create a full URL using current origin
|
|
35
|
+
if (trimmedUrl.startsWith('/') || !trimmedUrl.includes('://')) {
|
|
36
|
+
const origin = globalThis.window.location.origin;
|
|
37
|
+
parsedUrl = new URL(trimmedUrl, origin);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
parsedUrl = new URL(trimmedUrl);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
// Only allow http and https protocols
|
|
47
|
+
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// Return the validated URL (use href to get full URL)
|
|
51
|
+
const validatedUrl = parsedUrl.href;
|
|
52
|
+
return validatedUrl;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Synchronously get and validate redirect URL
|
|
56
|
+
* Validates the URL before returning it
|
|
57
|
+
* @param url - URL to validate and return
|
|
58
|
+
* @param fallbackUrl - Optional fallback URL if primary is invalid
|
|
59
|
+
* @returns Validated URL string or null if both are invalid
|
|
60
|
+
*/
|
|
61
|
+
function getValidatedRedirectUrl(url, fallbackUrl) {
|
|
62
|
+
// Try primary URL first
|
|
63
|
+
const validatedUrl = validateRedirectUrl(url);
|
|
64
|
+
if (validatedUrl) {
|
|
65
|
+
return validatedUrl;
|
|
66
|
+
}
|
|
67
|
+
// Try fallback URL if provided
|
|
68
|
+
if (fallbackUrl) {
|
|
69
|
+
const validatedFallback = validateRedirectUrl(fallbackUrl);
|
|
70
|
+
if (validatedFallback) {
|
|
71
|
+
return validatedFallback;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Both URLs invalid
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Redirect to controller login page
|
|
79
|
+
* Redirects user browser directly to controller's login URL with client token
|
|
80
|
+
* NO API CALLS - just browser redirect
|
|
81
|
+
*
|
|
82
|
+
* Flow:
|
|
83
|
+
* 1. User visits: http://localhost:4111/dataplane/
|
|
84
|
+
* 2. redirectToLogin() redirects to: {controllerPublicUrl}{loginUrl}?redirect={redirectUrl}&x-client-token={token}
|
|
85
|
+
* 3. Controller handles OAuth flow and redirects back after authentication
|
|
86
|
+
* 4. Controller redirects to: {redirectUrl}#token={userToken}
|
|
87
|
+
* 5. DataClient.handleOAuthCallback() extracts token from hash and stores securely
|
|
88
|
+
*
|
|
89
|
+
* Security:
|
|
90
|
+
* - Token is passed in hash fragment (#token=...) not query parameter
|
|
91
|
+
* - Hash fragments are NOT sent to server (not in logs)
|
|
92
|
+
* - Hash fragments are NOT stored in browser history
|
|
93
|
+
* - Token is immediately removed from URL after extraction
|
|
94
|
+
*
|
|
95
|
+
* @param config - DataClient configuration
|
|
96
|
+
* @param getClientTokenFn - Function to get client token
|
|
97
|
+
* @param redirectUrl - Optional redirect URL to return to after login (defaults to current page URL)
|
|
98
|
+
*/
|
|
99
|
+
async function redirectToLogin(config, getClientTokenFn, redirectUrl) {
|
|
100
|
+
if (!(0, data_client_utils_1.isBrowser)()) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const currentUrl = globalThis.window.location.href;
|
|
104
|
+
const finalRedirectUrl = redirectUrl || currentUrl;
|
|
105
|
+
// Get controller public URL (for browser environments)
|
|
106
|
+
const controllerUrl = (0, data_client_auth_1.getControllerUrl)(config.misoConfig);
|
|
107
|
+
if (!controllerUrl) {
|
|
108
|
+
const error = new Error("Controller URL is not configured. Please configure controllerUrl or controllerPublicUrl in your DataClient configuration.");
|
|
109
|
+
error.details = {
|
|
110
|
+
hasMisoConfig: !!config.misoConfig,
|
|
111
|
+
controllerUrl: config.misoConfig?.controllerUrl,
|
|
112
|
+
controllerPublicUrl: config.misoConfig?.controllerPublicUrl,
|
|
113
|
+
controllerPrivateUrl: config.misoConfig?.controllerPrivateUrl,
|
|
114
|
+
};
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
// Get client token
|
|
118
|
+
const clientToken = await getClientTokenFn();
|
|
119
|
+
// Get login URL from config (defaults to '/login')
|
|
120
|
+
// Validate that loginUrl is not an API endpoint (should be a page, not /api/...)
|
|
121
|
+
let loginPath = config.loginUrl || '/login';
|
|
122
|
+
// Warn if loginUrl looks like an API endpoint (common mistake)
|
|
123
|
+
if (loginPath.startsWith('/api/')) {
|
|
124
|
+
console.warn(`⚠️ Warning: loginUrl is set to an API endpoint (${loginPath}). ` +
|
|
125
|
+
`redirectToLogin() should redirect to a login PAGE (e.g., '/login'), not an API endpoint. ` +
|
|
126
|
+
`Using default '/login' instead.`);
|
|
127
|
+
loginPath = '/login';
|
|
128
|
+
}
|
|
129
|
+
// Build full controller login URL
|
|
130
|
+
// If loginPath is already a full URL, use it; otherwise prepend controllerUrl
|
|
131
|
+
let loginUrl;
|
|
132
|
+
if (/^https?:\/\//i.test(loginPath)) {
|
|
133
|
+
loginUrl = new URL(loginPath);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
loginUrl = new URL(loginPath, controllerUrl);
|
|
137
|
+
}
|
|
138
|
+
// Add redirect parameter
|
|
139
|
+
loginUrl.searchParams.set('redirect', finalRedirectUrl);
|
|
140
|
+
// Add client token as query parameter (controller will read it)
|
|
141
|
+
if (clientToken) {
|
|
142
|
+
loginUrl.searchParams.set('x-client-token', clientToken);
|
|
143
|
+
}
|
|
144
|
+
// Redirect user directly to controller login page
|
|
145
|
+
// Controller handles OAuth flow and redirects back after authentication
|
|
146
|
+
globalThis.window.location.href = loginUrl.toString();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Logout user and redirect to controller logout page
|
|
150
|
+
* Clears tokens from localStorage, clears cache, and redirects to controller logout URL
|
|
151
|
+
* NO API CALLS - just browser redirect
|
|
152
|
+
* Controller handles the logout process
|
|
153
|
+
*
|
|
154
|
+
* @param config - DataClient configuration
|
|
155
|
+
* @param getTokenFn - Function to get user token
|
|
156
|
+
* @param getClientTokenFn - Function to get client token
|
|
157
|
+
* @param clearCacheFn - Function to clear HTTP cache
|
|
158
|
+
* @param redirectUrl - Optional redirect URL after logout (defaults to logoutUrl or loginUrl)
|
|
159
|
+
* @param _misoClient - MisoClient instance (not used, kept for API compatibility)
|
|
160
|
+
*/
|
|
161
|
+
async function logout(config, getTokenFn, getClientTokenFn, clearCacheFn, redirectUrl, _misoClient) {
|
|
162
|
+
if (!(0, data_client_utils_1.isBrowser)())
|
|
163
|
+
return;
|
|
164
|
+
// Get tokens BEFORE clearing (needed for passing to controller)
|
|
165
|
+
const token = getTokenFn();
|
|
166
|
+
const clientToken = await getClientTokenFn();
|
|
167
|
+
// Clear tokens from localStorage
|
|
168
|
+
const keys = config.tokenKeys || ["token", "accessToken", "authToken"];
|
|
169
|
+
keys.forEach(key => {
|
|
170
|
+
try {
|
|
171
|
+
const storage = globalThis.localStorage;
|
|
172
|
+
if (storage) {
|
|
173
|
+
storage.removeItem(key);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
// Ignore localStorage errors (SSR, private browsing, etc.)
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
// Clear HTTP cache
|
|
181
|
+
clearCacheFn();
|
|
182
|
+
// Get controller public URL (for browser environments)
|
|
183
|
+
const controllerUrl = (0, data_client_auth_1.getControllerUrl)(config.misoConfig);
|
|
184
|
+
if (!controllerUrl) {
|
|
185
|
+
// Fallback to local redirect if controller URL not configured
|
|
186
|
+
const finalRedirectUrl = redirectUrl || config.logoutUrl || config.loginUrl || "/login";
|
|
187
|
+
const origin = globalThis.window.location.origin;
|
|
188
|
+
const fullUrl = /^https?:\/\//i.test(finalRedirectUrl)
|
|
189
|
+
? finalRedirectUrl
|
|
190
|
+
: `${origin}${finalRedirectUrl.startsWith("/") ? finalRedirectUrl : `/${finalRedirectUrl}`}`;
|
|
191
|
+
const validatedUrl = getValidatedRedirectUrl(fullUrl);
|
|
192
|
+
if (validatedUrl) {
|
|
193
|
+
globalThis.window.location.href = validatedUrl;
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Get logout URL from config (defaults to '/logout' or falls back to '/login')
|
|
198
|
+
const logoutPath = config.logoutUrl || config.loginUrl || '/logout';
|
|
199
|
+
// Build full controller logout URL
|
|
200
|
+
// If logoutPath is already a full URL, use it; otherwise prepend controllerUrl
|
|
201
|
+
let logoutUrl;
|
|
202
|
+
if (/^https?:\/\//i.test(logoutPath)) {
|
|
203
|
+
logoutUrl = new URL(logoutPath);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
logoutUrl = new URL(logoutPath, controllerUrl);
|
|
207
|
+
}
|
|
208
|
+
// Determine redirect URL: redirectUrl param > logoutUrl config > loginUrl config > '/login'
|
|
209
|
+
const finalRedirectUrl = redirectUrl || config.logoutUrl || config.loginUrl || "/login";
|
|
210
|
+
// Construct full redirect URL (if relative, make absolute)
|
|
211
|
+
let fullRedirectUrl;
|
|
212
|
+
if (/^https?:\/\//i.test(finalRedirectUrl)) {
|
|
213
|
+
fullRedirectUrl = finalRedirectUrl;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
const origin = globalThis.window.location.origin;
|
|
217
|
+
fullRedirectUrl = `${origin}${finalRedirectUrl.startsWith("/") ? finalRedirectUrl : `/${finalRedirectUrl}`}`;
|
|
218
|
+
}
|
|
219
|
+
logoutUrl.searchParams.set('redirect', fullRedirectUrl);
|
|
220
|
+
// Add client token as query parameter (controller will read it)
|
|
221
|
+
if (clientToken) {
|
|
222
|
+
logoutUrl.searchParams.set('x-client-token', clientToken);
|
|
223
|
+
}
|
|
224
|
+
// Add user token as query parameter if available (controller will read it)
|
|
225
|
+
// Token was retrieved before clearing localStorage
|
|
226
|
+
if (token) {
|
|
227
|
+
logoutUrl.searchParams.set('token', token);
|
|
228
|
+
}
|
|
229
|
+
// Redirect user directly to controller logout page
|
|
230
|
+
// Controller handles logout process and redirects back
|
|
231
|
+
globalThis.window.location.href = logoutUrl.toString();
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=data-client-redirect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-client-redirect.js","sourceRoot":"","sources":["../../src/utils/data-client-redirect.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
import { ApiRequestOptions, DataClientConfig, InterceptorConfig, CacheEntry } from "../types/data-client.types";
|
|
6
6
|
import { HasAnyTokenFn, GetTokenFn } from "./data-client-audit";
|
|
7
7
|
import { MisoClient } from "../index";
|
|
8
|
+
/**
|
|
9
|
+
* Token refresh callback function type
|
|
10
|
+
*/
|
|
11
|
+
export type RefreshUserTokenFn = () => Promise<{
|
|
12
|
+
token: string;
|
|
13
|
+
expiresIn: number;
|
|
14
|
+
} | null>;
|
|
8
15
|
/**
|
|
9
16
|
* Extract headers from Headers object or Record
|
|
10
17
|
*/
|
|
@@ -24,7 +31,7 @@ export declare function makeFetchRequest(method: string, url: string, config: Da
|
|
|
24
31
|
/**
|
|
25
32
|
* Execute HTTP request with retry logic
|
|
26
33
|
*/
|
|
27
|
-
export declare function executeHttpRequest<T>(method: string, fullUrl: string, endpoint: string, config: DataClientConfig, cache: Map<string, CacheEntry>, cacheKey: string, cacheEnabled: boolean, startTime: number, misoClient: MisoClient | null, hasAnyToken: HasAnyTokenFn, getToken: GetTokenFn, handleAuthError: () => void, interceptors: InterceptorConfig, metrics: {
|
|
34
|
+
export declare function executeHttpRequest<T>(method: string, fullUrl: string, endpoint: string, config: DataClientConfig, cache: Map<string, CacheEntry>, cacheKey: string, cacheEnabled: boolean, startTime: number, misoClient: MisoClient | null, hasAnyToken: HasAnyTokenFn, getToken: GetTokenFn, handleAuthError: () => void, refreshUserToken: RefreshUserTokenFn, interceptors: InterceptorConfig, metrics: {
|
|
28
35
|
totalRequests: number;
|
|
29
36
|
totalFailures: number;
|
|
30
37
|
responseTimes: number[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-client-request.d.ts","sourceRoot":"","sources":["../../src/utils/data-client-request.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAKhB,iBAAiB,EACjB,UAAU,EACX,MAAM,4BAA4B,CAAC;AAOpC,OAAO,EAAsB,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,GACnG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAsBpC;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CASrE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,WAAW,GACnB,WAAW,CAkBb;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,UAAU,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"data-client-request.d.ts","sourceRoot":"","sources":["../../src/utils/data-client-request.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAKhB,iBAAiB,EACjB,UAAU,EACX,MAAM,4BAA4B,CAAC;AAOpC,OAAO,EAAsB,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAAC;AAE5F;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,GACnG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAsBpC;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CASrE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,WAAW,GACnB,WAAW,CAkBb;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,UAAU,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,QAAQ,CAAC,CAgEnB;AA0LD;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EACxC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EAC9B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,OAAO,EACrB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,UAAU,GAAG,IAAI,EAC7B,WAAW,EAAE,aAAa,EAC1B,QAAQ,EAAE,UAAU,EACpB,eAAe,EAAE,MAAM,IAAI,EAC3B,gBAAgB,EAAE,kBAAkB,EACpC,YAAY,EAAE,iBAAiB,EAC/B,OAAO,EAAE;IACP,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB,EACD,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,CAAC,CAAC,CAqMZ"}
|