@robinmordasiewicz/f5xc-auth 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/dist/auth/credential-manager.d.ts +177 -0
  4. package/dist/auth/credential-manager.d.ts.map +1 -0
  5. package/dist/auth/credential-manager.js +417 -0
  6. package/dist/auth/credential-manager.js.map +1 -0
  7. package/dist/auth/http-client.d.ts +86 -0
  8. package/dist/auth/http-client.d.ts.map +1 -0
  9. package/dist/auth/http-client.js +255 -0
  10. package/dist/auth/http-client.js.map +1 -0
  11. package/dist/auth/index.d.ts +6 -0
  12. package/dist/auth/index.d.ts.map +1 -0
  13. package/dist/auth/index.js +6 -0
  14. package/dist/auth/index.js.map +1 -0
  15. package/dist/config/index.d.ts +5 -0
  16. package/dist/config/index.d.ts.map +1 -0
  17. package/dist/config/index.js +5 -0
  18. package/dist/config/index.js.map +1 -0
  19. package/dist/config/paths.d.ts +36 -0
  20. package/dist/config/paths.d.ts.map +1 -0
  21. package/dist/config/paths.js +68 -0
  22. package/dist/config/paths.js.map +1 -0
  23. package/dist/index.d.ts +30 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +34 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/profile/index.d.ts +6 -0
  28. package/dist/profile/index.d.ts.map +1 -0
  29. package/dist/profile/index.js +6 -0
  30. package/dist/profile/index.js.map +1 -0
  31. package/dist/profile/manager.d.ts +74 -0
  32. package/dist/profile/manager.d.ts.map +1 -0
  33. package/dist/profile/manager.js +326 -0
  34. package/dist/profile/manager.js.map +1 -0
  35. package/dist/profile/types.d.ts +53 -0
  36. package/dist/profile/types.d.ts.map +1 -0
  37. package/dist/profile/types.js +7 -0
  38. package/dist/profile/types.js.map +1 -0
  39. package/dist/utils/errors.d.ts +66 -0
  40. package/dist/utils/errors.d.ts.map +1 -0
  41. package/dist/utils/errors.js +179 -0
  42. package/dist/utils/errors.js.map +1 -0
  43. package/dist/utils/index.d.ts +6 -0
  44. package/dist/utils/index.d.ts.map +1 -0
  45. package/dist/utils/index.js +6 -0
  46. package/dist/utils/index.js.map +1 -0
  47. package/dist/utils/logging.d.ts +75 -0
  48. package/dist/utils/logging.d.ts.map +1 -0
  49. package/dist/utils/logging.js +131 -0
  50. package/dist/utils/logging.js.map +1 -0
  51. package/package.json +54 -0
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Authenticated HTTP Client for F5 Distributed Cloud API
3
+ *
4
+ * Provides a configured Axios client for making authenticated API requests.
5
+ * Supports both API token and P12 certificate (mTLS) authentication.
6
+ */
7
+ import axios from "axios";
8
+ import https from "https";
9
+ import { AuthMode } from "./credential-manager.js";
10
+ import { logger } from "../utils/logging.js";
11
+ import { F5XCApiError, AuthenticationError, wrapSSLError } from "../utils/errors.js";
12
+ /**
13
+ * Default HTTP client configuration
14
+ */
15
+ const DEFAULT_CONFIG = {
16
+ timeout: 30000, // 30 seconds
17
+ headers: {},
18
+ debug: false,
19
+ };
20
+ /**
21
+ * HTTP Client for F5XC API
22
+ *
23
+ * Creates and manages an authenticated Axios instance for making
24
+ * API requests to F5 Distributed Cloud.
25
+ */
26
+ export class HttpClient {
27
+ client = null;
28
+ credentialManager;
29
+ config;
30
+ constructor(credentialManager, config = {}) {
31
+ this.credentialManager = credentialManager;
32
+ this.config = { ...DEFAULT_CONFIG, ...config };
33
+ if (this.credentialManager.isAuthenticated()) {
34
+ this.client = this.createClient();
35
+ }
36
+ }
37
+ /**
38
+ * Build HTTPS agent options from credential manager TLS configuration
39
+ * Handles SSL/TLS configuration including insecure mode and custom CA
40
+ */
41
+ buildHttpsAgentOptions() {
42
+ const options = {
43
+ rejectUnauthorized: true, // Secure default
44
+ };
45
+ // Check for custom CA bundle
46
+ const caBundle = this.credentialManager.getCaBundle();
47
+ if (caBundle) {
48
+ options.ca = caBundle;
49
+ logger.info("Using custom CA bundle for TLS verification");
50
+ }
51
+ // Check for insecure mode (staging/development ONLY)
52
+ const tlsInsecure = this.credentialManager.getTlsInsecure();
53
+ if (tlsInsecure) {
54
+ options.rejectUnauthorized = false;
55
+ // Output to stderr for maximum visibility (always shown, even if logs are filtered)
56
+ const apiUrl = this.credentialManager.getApiUrl() ?? "unknown";
57
+ console.error("\n\x1b[33m⚠️ WARNING: TLS certificate verification is DISABLED\x1b[0m");
58
+ console.error(` URL: ${apiUrl}`);
59
+ console.error(" This should ONLY be used for staging/development environments!");
60
+ console.error(" Consider using F5XC_CA_BUNDLE for a more secure solution.\n");
61
+ logger.warn("TLS certificate verification DISABLED - this is insecure and should only be used for staging/development");
62
+ }
63
+ return options;
64
+ }
65
+ /**
66
+ * Create configured Axios client
67
+ */
68
+ createClient() {
69
+ const authMode = this.credentialManager.getAuthMode();
70
+ const baseURL = this.credentialManager.getApiUrl();
71
+ if (!baseURL) {
72
+ throw new AuthenticationError("API URL not configured");
73
+ }
74
+ const axiosConfig = {
75
+ baseURL,
76
+ timeout: this.config.timeout,
77
+ headers: {
78
+ "Content-Type": "application/json",
79
+ Accept: "application/json",
80
+ ...this.config.headers,
81
+ },
82
+ };
83
+ // Get TLS configuration options
84
+ const tlsOptions = this.buildHttpsAgentOptions();
85
+ // Configure authentication
86
+ if (authMode === AuthMode.TOKEN) {
87
+ const token = this.credentialManager.getToken();
88
+ if (!token) {
89
+ throw new AuthenticationError("API token not configured");
90
+ }
91
+ axiosConfig.headers = {
92
+ ...axiosConfig.headers,
93
+ Authorization: `APIToken ${token}`,
94
+ };
95
+ // Apply TLS configuration for token auth (needed for custom CA or insecure mode)
96
+ if (tlsOptions.ca || !tlsOptions.rejectUnauthorized) {
97
+ axiosConfig.httpsAgent = new https.Agent(tlsOptions);
98
+ }
99
+ }
100
+ else if (authMode === AuthMode.CERTIFICATE) {
101
+ const p12Buffer = this.credentialManager.getP12Certificate();
102
+ const cert = this.credentialManager.getCert();
103
+ const key = this.credentialManager.getKey();
104
+ if (p12Buffer) {
105
+ // Create HTTPS agent with P12 certificate for mTLS
106
+ // F5XC P12 certificates typically don't require a password
107
+ axiosConfig.httpsAgent = new https.Agent({
108
+ ...tlsOptions,
109
+ pfx: p12Buffer,
110
+ });
111
+ }
112
+ else if (cert && key) {
113
+ // Create HTTPS agent with separate cert/key for mTLS
114
+ axiosConfig.httpsAgent = new https.Agent({
115
+ ...tlsOptions,
116
+ cert,
117
+ key,
118
+ });
119
+ }
120
+ else {
121
+ throw new AuthenticationError("Certificate not loaded - provide P12 bundle or cert/key pair");
122
+ }
123
+ }
124
+ const client = axios.create(axiosConfig);
125
+ // Add request interceptor for logging
126
+ client.interceptors.request.use((config) => {
127
+ // Add request timestamp for duration calculation
128
+ config.metadata = {
129
+ startTime: Date.now(),
130
+ };
131
+ if (this.config.debug) {
132
+ logger.debug("API Request", {
133
+ method: config.method?.toUpperCase(),
134
+ url: config.url,
135
+ baseURL: config.baseURL,
136
+ });
137
+ }
138
+ return config;
139
+ }, (error) => {
140
+ logger.error("Request interceptor error", { error });
141
+ return Promise.reject(error);
142
+ });
143
+ // Add response interceptor for logging and error handling
144
+ client.interceptors.response.use((response) => {
145
+ const config = response.config;
146
+ const duration = config.metadata ? Date.now() - config.metadata.startTime : 0;
147
+ if (this.config.debug) {
148
+ logger.debug("API Response", {
149
+ status: response.status,
150
+ url: response.config.url,
151
+ duration: `${duration}ms`,
152
+ });
153
+ }
154
+ return response;
155
+ }, (error) => {
156
+ if (axios.isAxiosError(error)) {
157
+ // Check for SSL/TLS certificate errors first
158
+ const errorCode = error.code?.toLowerCase() ?? "";
159
+ const errorMessage = error.message.toLowerCase();
160
+ if (errorCode === "err_tls_cert_altname_invalid" ||
161
+ errorCode === "unable_to_verify_leaf_signature" ||
162
+ errorCode === "depth_zero_self_signed_cert" ||
163
+ errorCode === "cert_has_expired" ||
164
+ errorMessage.includes("certificate") ||
165
+ errorMessage.includes("altnames") ||
166
+ errorMessage.includes("self signed")) {
167
+ // Wrap SSL error with actionable guidance
168
+ throw wrapSSLError(error, baseURL);
169
+ }
170
+ const status = error.response?.status;
171
+ const message = error.response?.data?.message ?? error.message;
172
+ logger.error("API Error", {
173
+ status,
174
+ message,
175
+ url: error.config?.url,
176
+ });
177
+ // Transform to F5XC API error
178
+ throw new F5XCApiError(message, status, error.response?.data);
179
+ }
180
+ throw error;
181
+ });
182
+ logger.info("HTTP client created", {
183
+ baseURL,
184
+ authMode,
185
+ timeout: this.config.timeout,
186
+ });
187
+ return client;
188
+ }
189
+ /**
190
+ * Check if the client is available (authenticated mode)
191
+ */
192
+ isAvailable() {
193
+ return this.client !== null;
194
+ }
195
+ /**
196
+ * Make a GET request
197
+ */
198
+ async get(path, config) {
199
+ return this.request("GET", path, undefined, config);
200
+ }
201
+ /**
202
+ * Make a POST request
203
+ */
204
+ async post(path, data, config) {
205
+ return this.request("POST", path, data, config);
206
+ }
207
+ /**
208
+ * Make a PUT request
209
+ */
210
+ async put(path, data, config) {
211
+ return this.request("PUT", path, data, config);
212
+ }
213
+ /**
214
+ * Make a DELETE request
215
+ */
216
+ async delete(path, config) {
217
+ return this.request("DELETE", path, undefined, config);
218
+ }
219
+ /**
220
+ * Make a generic request
221
+ */
222
+ async request(method, path, data, config) {
223
+ if (!this.client) {
224
+ throw new AuthenticationError("HTTP client not available - server is in documentation mode. " +
225
+ "Set F5XC_API_URL and F5XC_API_TOKEN (or F5XC_P12_BUNDLE for certificate auth) to enable API execution.");
226
+ }
227
+ const startTime = Date.now();
228
+ const response = await this.client.request({
229
+ method,
230
+ url: path,
231
+ data,
232
+ ...config,
233
+ });
234
+ const duration = Date.now() - startTime;
235
+ return {
236
+ data: response.data,
237
+ status: response.status,
238
+ headers: response.headers,
239
+ duration,
240
+ };
241
+ }
242
+ /**
243
+ * Get the underlying Axios instance
244
+ */
245
+ getAxiosInstance() {
246
+ return this.client;
247
+ }
248
+ }
249
+ /**
250
+ * Create HTTP client from credential manager
251
+ */
252
+ export function createHttpClient(credentialManager, config) {
253
+ return new HttpClient(credentialManager, config);
254
+ }
255
+ //# sourceMappingURL=http-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../src/auth/http-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Auth module exports
3
+ */
4
+ export * from "./credential-manager.js";
5
+ export * from "./http-client.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Auth module exports
3
+ */
4
+ export * from "./credential-manager.js";
5
+ export * from "./http-client.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Config module exports
3
+ */
4
+ export * from "./paths.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,YAAY,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Config module exports
3
+ */
4
+ export * from "./paths.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,YAAY,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * XDG Base Directory compliant paths for F5 XC configuration
3
+ * See: https://specifications.freedesktop.org/basedir/latest/
4
+ *
5
+ * This is the single source of truth for all application paths.
6
+ * All modules should import from here instead of constructing paths directly.
7
+ *
8
+ * Default paths:
9
+ * - Config: ~/.config/f5xc/
10
+ * - State: ~/.local/state/f5xc/
11
+ */
12
+ /**
13
+ * Get XDG-compliant config directory
14
+ * Config files: settings, profiles, preferences
15
+ * Default: ~/.config/f5xc
16
+ */
17
+ export declare function getConfigDir(): string;
18
+ /**
19
+ * Get XDG-compliant state directory
20
+ * State files: history, logs, undo history, session state
21
+ * Default: ~/.local/state/f5xc
22
+ */
23
+ export declare function getStateDir(): string;
24
+ /**
25
+ * Centralized path definitions
26
+ * Use these getters for all file path access throughout the application
27
+ */
28
+ export declare const paths: {
29
+ readonly configDir: string;
30
+ readonly profilesDir: string;
31
+ readonly activeProfile: string;
32
+ readonly settings: string;
33
+ readonly stateDir: string;
34
+ readonly history: string;
35
+ };
36
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/config/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAMrC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAMpC;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK;wBAEC,MAAM;0BAGJ,MAAM;4BAGJ,MAAM;uBAGX,MAAM;uBAKN,MAAM;sBAGP,MAAM;CAGtB,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * XDG Base Directory compliant paths for F5 XC configuration
3
+ * See: https://specifications.freedesktop.org/basedir/latest/
4
+ *
5
+ * This is the single source of truth for all application paths.
6
+ * All modules should import from here instead of constructing paths directly.
7
+ *
8
+ * Default paths:
9
+ * - Config: ~/.config/f5xc/
10
+ * - State: ~/.local/state/f5xc/
11
+ */
12
+ import { homedir } from "os";
13
+ import { join } from "path";
14
+ /**
15
+ * Application name for XDG-compliant directory structure
16
+ */
17
+ const APP_NAME = "f5xc";
18
+ /**
19
+ * Get XDG-compliant config directory
20
+ * Config files: settings, profiles, preferences
21
+ * Default: ~/.config/f5xc
22
+ */
23
+ export function getConfigDir() {
24
+ const xdgConfig = process.env.XDG_CONFIG_HOME;
25
+ if (xdgConfig) {
26
+ return join(xdgConfig, APP_NAME);
27
+ }
28
+ return join(homedir(), ".config", APP_NAME);
29
+ }
30
+ /**
31
+ * Get XDG-compliant state directory
32
+ * State files: history, logs, undo history, session state
33
+ * Default: ~/.local/state/f5xc
34
+ */
35
+ export function getStateDir() {
36
+ const xdgState = process.env.XDG_STATE_HOME;
37
+ if (xdgState) {
38
+ return join(xdgState, APP_NAME);
39
+ }
40
+ return join(homedir(), ".local", "state", APP_NAME);
41
+ }
42
+ /**
43
+ * Centralized path definitions
44
+ * Use these getters for all file path access throughout the application
45
+ */
46
+ export const paths = {
47
+ // Config files (XDG_CONFIG_HOME)
48
+ get configDir() {
49
+ return getConfigDir();
50
+ },
51
+ get profilesDir() {
52
+ return join(getConfigDir(), "profiles");
53
+ },
54
+ get activeProfile() {
55
+ return join(getConfigDir(), "active_profile");
56
+ },
57
+ get settings() {
58
+ return join(getConfigDir(), "config.yaml");
59
+ },
60
+ // State files (XDG_STATE_HOME)
61
+ get stateDir() {
62
+ return getStateDir();
63
+ },
64
+ get history() {
65
+ return join(getStateDir(), "history");
66
+ },
67
+ };
68
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/config/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;GAEG;AACH,MAAM,QAAQ,GAAG,MAAM,CAAC;AAExB;;;;GAIG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC9C,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC5C,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,iCAAiC;IACjC,IAAI,SAAS;QACX,OAAO,YAAY,EAAE,CAAC;IACxB,CAAC;IACD,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,YAAY,EAAE,EAAE,UAAU,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,YAAY,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,YAAY,EAAE,EAAE,aAAa,CAAC,CAAC;IAC7C,CAAC;IAED,+BAA+B;IAC/B,IAAI,QAAQ;QACV,OAAO,WAAW,EAAE,CAAC;IACvB,CAAC;IACD,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,WAAW,EAAE,EAAE,SAAS,CAAC,CAAC;IACxC,CAAC;CACF,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @robinmordasiewicz/f5xc-auth
3
+ *
4
+ * Shared authentication library for F5 Distributed Cloud MCP servers.
5
+ * Provides XDG-compliant profile management and credential handling.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { CredentialManager, ProfileManager, getProfileManager } from '@robinmordasiewicz/f5xc-auth';
10
+ *
11
+ * // Initialize credentials
12
+ * const credentialManager = new CredentialManager();
13
+ * await credentialManager.initialize();
14
+ *
15
+ * if (credentialManager.isAuthenticated()) {
16
+ * console.log(`Authenticated as: ${credentialManager.getTenant()}`);
17
+ * }
18
+ *
19
+ * // Manage profiles
20
+ * const profileManager = getProfileManager();
21
+ * const profiles = await profileManager.list();
22
+ * ```
23
+ */
24
+ export { CredentialManager, AuthMode, AUTH_ENV_VARS, normalizeApiUrl, extractTenantFromUrl, type Credentials, } from "./auth/credential-manager.js";
25
+ export { HttpClient, createHttpClient, type HttpClientConfig, type ApiResponse, } from "./auth/http-client.js";
26
+ export { ProfileManager, getProfileManager, type Profile, type ProfileConfig, type ProfileResult, type ProfileValidationError, } from "./profile/index.js";
27
+ export { paths, getConfigDir, getStateDir } from "./config/paths.js";
28
+ export { logger, createLogger, LogLevel, type LoggerConfig, } from "./utils/logging.js";
29
+ export { F5XCError, AuthenticationError, F5XCApiError, ConfigurationError, SSLCertificateError, wrapSSLError, } from "./utils/errors.js";
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EACL,iBAAiB,EACjB,QAAQ,EACR,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,KAAK,WAAW,GACjB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,sBAAsB,GAC5B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrE,OAAO,EACL,MAAM,EACN,YAAY,EACZ,QAAQ,EACR,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,GACb,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @robinmordasiewicz/f5xc-auth
3
+ *
4
+ * Shared authentication library for F5 Distributed Cloud MCP servers.
5
+ * Provides XDG-compliant profile management and credential handling.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { CredentialManager, ProfileManager, getProfileManager } from '@robinmordasiewicz/f5xc-auth';
10
+ *
11
+ * // Initialize credentials
12
+ * const credentialManager = new CredentialManager();
13
+ * await credentialManager.initialize();
14
+ *
15
+ * if (credentialManager.isAuthenticated()) {
16
+ * console.log(`Authenticated as: ${credentialManager.getTenant()}`);
17
+ * }
18
+ *
19
+ * // Manage profiles
20
+ * const profileManager = getProfileManager();
21
+ * const profiles = await profileManager.list();
22
+ * ```
23
+ */
24
+ // Auth module
25
+ export { CredentialManager, AuthMode, AUTH_ENV_VARS, normalizeApiUrl, extractTenantFromUrl, } from "./auth/credential-manager.js";
26
+ export { HttpClient, createHttpClient, } from "./auth/http-client.js";
27
+ // Profile module
28
+ export { ProfileManager, getProfileManager, } from "./profile/index.js";
29
+ // Config module
30
+ export { paths, getConfigDir, getStateDir } from "./config/paths.js";
31
+ // Utils module
32
+ export { logger, createLogger, LogLevel, } from "./utils/logging.js";
33
+ export { F5XCError, AuthenticationError, F5XCApiError, ConfigurationError, SSLCertificateError, wrapSSLError, } from "./utils/errors.js";
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,cAAc;AACd,OAAO,EACL,iBAAiB,EACjB,QAAQ,EACR,aAAa,EACb,eAAe,EACf,oBAAoB,GAErB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,UAAU,EACV,gBAAgB,GAGjB,MAAM,uBAAuB,CAAC;AAE/B,iBAAiB;AACjB,OAAO,EACL,cAAc,EACd,iBAAiB,GAKlB,MAAM,oBAAoB,CAAC;AAE5B,gBAAgB;AAChB,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErE,eAAe;AACf,OAAO,EACL,MAAM,EACN,YAAY,EACZ,QAAQ,GAET,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,GACb,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Profile module exports
3
+ */
4
+ export * from "./types.js";
5
+ export * from "./manager.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/profile/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Profile module exports
3
+ */
4
+ export * from "./types.js";
5
+ export * from "./manager.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/profile/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * ProfileManager - XDG-compliant profile storage and management
3
+ *
4
+ * Profiles are stored in ~/.config/f5xc/profiles/ (XDG Base Directory compliant)
5
+ */
6
+ import type { Profile, ProfileResult } from "./types.js";
7
+ /**
8
+ * ProfileManager handles profile CRUD operations with secure file storage
9
+ */
10
+ export declare class ProfileManager {
11
+ private config;
12
+ constructor();
13
+ /**
14
+ * Ensure config directories exist
15
+ */
16
+ ensureDirectories(): Promise<void>;
17
+ /**
18
+ * Get path to profile JSON file
19
+ */
20
+ private getProfilePath;
21
+ /**
22
+ * Validate profile name (alphanumeric, dash, underscore only)
23
+ */
24
+ private isValidName;
25
+ /**
26
+ * Validate API URL format
27
+ */
28
+ private isValidUrl;
29
+ /**
30
+ * List all saved profiles
31
+ */
32
+ list(): Promise<Profile[]>;
33
+ /**
34
+ * Get a profile by name
35
+ */
36
+ get(name: string): Promise<Profile | null>;
37
+ /**
38
+ * Save a profile (create or update)
39
+ */
40
+ save(profile: Profile): Promise<ProfileResult>;
41
+ /**
42
+ * Delete a profile by name
43
+ */
44
+ delete(name: string): Promise<ProfileResult>;
45
+ /**
46
+ * Get the name of the active profile
47
+ */
48
+ getActive(): Promise<string | null>;
49
+ /**
50
+ * Clear the active profile (used for force delete)
51
+ */
52
+ clearActive(): Promise<void>;
53
+ /**
54
+ * Set the active profile
55
+ */
56
+ setActive(name: string): Promise<ProfileResult>;
57
+ /**
58
+ * Get the active profile (full profile data)
59
+ */
60
+ getActiveProfile(): Promise<Profile | null>;
61
+ /**
62
+ * Check if a profile exists
63
+ */
64
+ exists(name: string): Promise<boolean>;
65
+ /**
66
+ * Mask sensitive fields for display
67
+ */
68
+ maskProfile(profile: Profile): Record<string, string>;
69
+ }
70
+ /**
71
+ * Get the global ProfileManager instance
72
+ */
73
+ export declare function getProfileManager(): ProfileManager;
74
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/profile/manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAiB,aAAa,EAAE,MAAM,YAAY,CAAC;AAoBxE;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAgB;;IAU9B;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAOxC;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACH,OAAO,CAAC,UAAU;IASlB;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAuChC;;OAEG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IA8BhD;;OAEG;IACG,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC;IA+CpD;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAqClD;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASzC;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAQlC;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA8BrD;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAQjD;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5C;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CA8BtD;AAKD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAKlD"}