carto-cli 0.1.0-rc.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/.nvmrc +1 -0
- package/ARCHITECTURE.md +497 -0
- package/CHANGELOG.md +28 -0
- package/LICENSE +15 -0
- package/MAP_JSON.md +516 -0
- package/README.md +1595 -0
- package/WORKFLOW_JSON.md +623 -0
- package/dist/api.js +489 -0
- package/dist/auth-oauth.js +485 -0
- package/dist/auth-server.js +432 -0
- package/dist/browser.js +30 -0
- package/dist/colors.js +45 -0
- package/dist/commands/activity.js +427 -0
- package/dist/commands/admin.js +177 -0
- package/dist/commands/ai.js +489 -0
- package/dist/commands/auth.js +652 -0
- package/dist/commands/connections.js +412 -0
- package/dist/commands/credentials.js +606 -0
- package/dist/commands/imports.js +234 -0
- package/dist/commands/maps.js +1022 -0
- package/dist/commands/org.js +195 -0
- package/dist/commands/sql.js +326 -0
- package/dist/commands/users.js +459 -0
- package/dist/commands/workflows.js +1025 -0
- package/dist/config.js +320 -0
- package/dist/download.js +108 -0
- package/dist/help.js +285 -0
- package/dist/http.js +139 -0
- package/dist/index.js +1133 -0
- package/dist/logo.js +11 -0
- package/dist/prompt.js +67 -0
- package/dist/schedule-parser.js +287 -0
- package/jest.config.ts +43 -0
- package/package.json +53 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildReauthCommand = buildReauthCommand;
|
|
4
|
+
exports.authLogin = authLogin;
|
|
5
|
+
exports.authLogout = authLogout;
|
|
6
|
+
exports.authUse = authUse;
|
|
7
|
+
exports.authStatus = authStatus;
|
|
8
|
+
exports.authWhoami = authWhoami;
|
|
9
|
+
exports.authLoginM2M = authLoginM2M;
|
|
10
|
+
const api_1 = require("../api");
|
|
11
|
+
const config_1 = require("../config");
|
|
12
|
+
const colors_1 = require("../colors");
|
|
13
|
+
const auth_server_1 = require("../auth-server");
|
|
14
|
+
const auth_oauth_1 = require("../auth-oauth");
|
|
15
|
+
const browser_1 = require("../browser");
|
|
16
|
+
/**
|
|
17
|
+
* Build the correct re-authentication command for the current profile and environment
|
|
18
|
+
* @param profileName Current profile name
|
|
19
|
+
* @param credentials Current profile credentials (null if not available)
|
|
20
|
+
* @returns Properly formatted carto auth login command
|
|
21
|
+
*/
|
|
22
|
+
function buildReauthCommand(profileName, credentials) {
|
|
23
|
+
let cmd = 'carto auth login';
|
|
24
|
+
// Add profile name if not default profile
|
|
25
|
+
// Quote profile names with spaces or special characters
|
|
26
|
+
if (profileName && profileName !== 'default') {
|
|
27
|
+
const needsQuotes = profileName.includes(' ') || profileName.includes('@') || profileName.includes('/');
|
|
28
|
+
cmd += needsQuotes ? ` "${profileName}"` : ` ${profileName}`;
|
|
29
|
+
}
|
|
30
|
+
// Add --env flag if not production environment
|
|
31
|
+
if (credentials?.auth_environment && credentials.auth_environment !== 'production') {
|
|
32
|
+
cmd += ` --env ${credentials.auth_environment}`;
|
|
33
|
+
}
|
|
34
|
+
// Add --organization-name flag if organization was used during login
|
|
35
|
+
if (credentials?.organization_name_used) {
|
|
36
|
+
const orgName = credentials.organization_name_used;
|
|
37
|
+
const needsQuotes = orgName.includes(' ') || orgName.includes('@') || orgName.includes('/');
|
|
38
|
+
cmd += needsQuotes ? ` --organization-name "${orgName}"` : ` --organization-name ${orgName}`;
|
|
39
|
+
}
|
|
40
|
+
return cmd;
|
|
41
|
+
}
|
|
42
|
+
async function authLogin(profileName, jsonOutput, env, organizationName, organizationId) {
|
|
43
|
+
try {
|
|
44
|
+
// Fixed port for OAuth callback (authorized in Auth0)
|
|
45
|
+
const port = 3003;
|
|
46
|
+
// Get OAuth configuration
|
|
47
|
+
const config = (0, auth_oauth_1.getAuthConfig)(env);
|
|
48
|
+
// Get accounts URL for organization lookup
|
|
49
|
+
const accountsUrl = (0, auth_oauth_1.getAccountsUrl)(env || auth_oauth_1.DEFAULT_AUTH_ENVIRONMENT);
|
|
50
|
+
// Resolve Auth0 Organization ID if organization name provided
|
|
51
|
+
let auth0OrganizationId = null;
|
|
52
|
+
if (organizationName) {
|
|
53
|
+
if (!jsonOutput) {
|
|
54
|
+
console.log((0, colors_1.info)(`š Looking up organization: ${organizationName}...`));
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
auth0OrganizationId = await (0, auth_oauth_1.getAuth0OrganizationIdByName)(accountsUrl, organizationName);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (jsonOutput) {
|
|
61
|
+
console.log(JSON.stringify({ success: false, error: `Failed to lookup organization: ${err.message}` }));
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log((0, colors_1.error)(`ā Failed to lookup organization: ${err.message}`));
|
|
65
|
+
console.log((0, colors_1.info)(' Tip: Verify the organization name is correct'));
|
|
66
|
+
}
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
if (!auth0OrganizationId) {
|
|
70
|
+
if (!jsonOutput) {
|
|
71
|
+
console.log((0, colors_1.warning)('ā SSO not configured for this organization'));
|
|
72
|
+
console.log((0, colors_1.info)(' Proceeding with standard login...'));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
if (!jsonOutput) {
|
|
77
|
+
console.log((0, colors_1.success)(`ā SSO enabled - redirecting to organization login`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (organizationId) {
|
|
82
|
+
if (jsonOutput) {
|
|
83
|
+
console.log(JSON.stringify({ success: false, error: 'Organization ID lookup not yet supported. Use --organization-name instead.' }));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log((0, colors_1.error)(`ā Organization ID lookup not yet supported`));
|
|
87
|
+
console.log((0, colors_1.info)(' Tip: Use --organization-name with the organization name'));
|
|
88
|
+
}
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
// Generate PKCE challenge and state
|
|
92
|
+
const { verifier, challenge } = (0, auth_oauth_1.generatePKCEChallenge)();
|
|
93
|
+
const state = (0, auth_oauth_1.generateState)();
|
|
94
|
+
// Build redirect URI
|
|
95
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
96
|
+
// Build authorization URL
|
|
97
|
+
const authUrl = (0, auth_oauth_1.buildAuthorizationUrl)(config, redirectUri, challenge, state, auth0OrganizationId || undefined);
|
|
98
|
+
if (!jsonOutput) {
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log((0, colors_1.info)(auth0OrganizationId ? 'š SSO Authentication' : 'š CARTO CLI Authentication'));
|
|
101
|
+
// Only show auth environment for non-production
|
|
102
|
+
const envName = env || auth_oauth_1.DEFAULT_AUTH_ENVIRONMENT;
|
|
103
|
+
if (envName !== 'production') {
|
|
104
|
+
console.log((0, colors_1.info)(` Auth Environment: ${config.domain}`));
|
|
105
|
+
}
|
|
106
|
+
console.log('');
|
|
107
|
+
// Try to open browser automatically
|
|
108
|
+
try {
|
|
109
|
+
await (0, browser_1.openBrowser)(authUrl);
|
|
110
|
+
console.log((0, colors_1.success)('ā Opening browser for authentication...'));
|
|
111
|
+
console.log('');
|
|
112
|
+
}
|
|
113
|
+
catch (browserError) {
|
|
114
|
+
// Fallback: show URL for manual opening
|
|
115
|
+
console.log((0, colors_1.warning)('ā Could not open browser automatically'));
|
|
116
|
+
console.log((0, colors_1.bold)('Please open this URL in your browser:'));
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log((0, colors_1.dim)(' ') + authUrl);
|
|
119
|
+
console.log('');
|
|
120
|
+
}
|
|
121
|
+
console.log((0, colors_1.info)(`ā³ Waiting for authentication (listening on http://localhost:${port}/callback)...`));
|
|
122
|
+
console.log('');
|
|
123
|
+
}
|
|
124
|
+
// Start local callback server
|
|
125
|
+
const serverPromise = (0, auth_server_1.startOAuthCallbackServer)(port, state);
|
|
126
|
+
// Wait for OAuth callback
|
|
127
|
+
const { code } = await serverPromise;
|
|
128
|
+
if (!jsonOutput) {
|
|
129
|
+
console.log((0, colors_1.info)('š Exchanging authorization code for access token...'));
|
|
130
|
+
}
|
|
131
|
+
// Exchange authorization code for access token
|
|
132
|
+
const tokenResponse = await (0, auth_oauth_1.exchangeCodeForToken)(config, code, verifier, redirectUri);
|
|
133
|
+
// Extract token
|
|
134
|
+
const accessToken = tokenResponse.access_token;
|
|
135
|
+
// Fetch full user info from CARTO accounts API (environment-specific)
|
|
136
|
+
// Use createMinimal because credentials don't exist yet during login
|
|
137
|
+
const tempClient = api_1.ApiClient.createMinimal(accessToken);
|
|
138
|
+
const userInfo = await tempClient.getFullUrl(`${accountsUrl}/users/me`, true);
|
|
139
|
+
// Build full credentials with org and user info
|
|
140
|
+
const fullCredentials = {
|
|
141
|
+
token: accessToken,
|
|
142
|
+
tenant_id: userInfo.user_metadata?.tenant_id || '',
|
|
143
|
+
tenant_domain: userInfo.user_metadata?.tenant_domain || '',
|
|
144
|
+
organization_id: userInfo.user_metadata?.account_id || '',
|
|
145
|
+
organization_name: userInfo.user_metadata?.account_name || '',
|
|
146
|
+
user_email: userInfo.email || '',
|
|
147
|
+
auth_environment: env || auth_oauth_1.DEFAULT_AUTH_ENVIRONMENT,
|
|
148
|
+
organization_name_used: organizationName || undefined,
|
|
149
|
+
};
|
|
150
|
+
// Determine profile name
|
|
151
|
+
let finalProfileName = profileName;
|
|
152
|
+
if (!finalProfileName) {
|
|
153
|
+
// Generate suggested name
|
|
154
|
+
finalProfileName = (0, config_1.suggestProfileName)(fullCredentials);
|
|
155
|
+
}
|
|
156
|
+
// Save credentials to file
|
|
157
|
+
(0, config_1.saveCredentials)(finalProfileName, fullCredentials);
|
|
158
|
+
if (jsonOutput) {
|
|
159
|
+
console.log(JSON.stringify({
|
|
160
|
+
success: true,
|
|
161
|
+
message: 'Authentication successful',
|
|
162
|
+
profile: finalProfileName,
|
|
163
|
+
tenant_id: fullCredentials.tenant_id,
|
|
164
|
+
tenant_domain: fullCredentials.tenant_domain,
|
|
165
|
+
organization_id: fullCredentials.organization_id,
|
|
166
|
+
organization_name: fullCredentials.organization_name,
|
|
167
|
+
user_email: fullCredentials.user_email,
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
console.log((0, colors_1.success)('\nā Authentication successful!'));
|
|
172
|
+
if (auth0OrganizationId) {
|
|
173
|
+
console.log((0, colors_1.success)(' Authenticated via SSO'));
|
|
174
|
+
}
|
|
175
|
+
console.log((0, colors_1.info)(` Tenant: ${fullCredentials.tenant_domain} (${fullCredentials.tenant_id})`));
|
|
176
|
+
console.log((0, colors_1.info)(` Organization: ${fullCredentials.organization_name} (${fullCredentials.organization_id})`));
|
|
177
|
+
console.log((0, colors_1.info)(` User: ${fullCredentials.user_email}`));
|
|
178
|
+
console.log('');
|
|
179
|
+
console.log((0, colors_1.success)(`ā Credentials saved as profile: ${(0, colors_1.bold)(finalProfileName)}`));
|
|
180
|
+
console.log((0, colors_1.success)(`ā Set as current profile`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
if (jsonOutput) {
|
|
185
|
+
console.log(JSON.stringify({ success: false, error: err.message }));
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
console.log((0, colors_1.error)('\nā Authentication failed: ' + err.message));
|
|
189
|
+
}
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function authLogout(profileName, jsonOutput) {
|
|
194
|
+
try {
|
|
195
|
+
const targetProfile = profileName || (0, config_1.getCurrentProfile)();
|
|
196
|
+
// Delete credentials
|
|
197
|
+
(0, config_1.deleteCredentials)(targetProfile);
|
|
198
|
+
// Also delete legacy config if removing default profile
|
|
199
|
+
if (targetProfile === 'default') {
|
|
200
|
+
try {
|
|
201
|
+
(0, config_1.deleteConfig)();
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Ignore if legacy config doesn't exist
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (jsonOutput) {
|
|
208
|
+
console.log(JSON.stringify({ success: true, message: 'Credentials removed successfully', profile: targetProfile }));
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
console.log((0, colors_1.success)(`ā Credentials removed successfully from profile: ${(0, colors_1.bold)(targetProfile)}`));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
if (jsonOutput) {
|
|
216
|
+
console.log(JSON.stringify({ success: false, error: err.message }));
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.log((0, colors_1.error)('ā Failed to remove credentials: ' + err.message));
|
|
220
|
+
}
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function authUse(profileName, jsonOutput) {
|
|
225
|
+
try {
|
|
226
|
+
const allCreds = (0, config_1.loadAllCredentials)();
|
|
227
|
+
if (!allCreds.profiles[profileName]) {
|
|
228
|
+
const available = Object.keys(allCreds.profiles);
|
|
229
|
+
if (jsonOutput) {
|
|
230
|
+
console.log(JSON.stringify({
|
|
231
|
+
success: false,
|
|
232
|
+
error: `Profile '${profileName}' not found`,
|
|
233
|
+
availableProfiles: available
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
console.log((0, colors_1.error)(`ā Profile '${profileName}' not found`));
|
|
238
|
+
if (available.length > 0) {
|
|
239
|
+
console.log((0, colors_1.info)('\nAvailable profiles:'));
|
|
240
|
+
available.forEach(p => console.log((0, colors_1.info)(` - ${p}`)));
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
console.log((0, colors_1.info)('\nNo profiles found. Run "carto auth login" to authenticate.'));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
(0, config_1.setCurrentProfile)(profileName);
|
|
249
|
+
const credentials = allCreds.profiles[profileName];
|
|
250
|
+
if (jsonOutput) {
|
|
251
|
+
console.log(JSON.stringify({
|
|
252
|
+
success: true,
|
|
253
|
+
message: 'Switched profile successfully',
|
|
254
|
+
profile: profileName,
|
|
255
|
+
tenant_id: credentials.tenant_id,
|
|
256
|
+
tenant_domain: credentials.tenant_domain,
|
|
257
|
+
organization_id: credentials.organization_id,
|
|
258
|
+
organization_name: credentials.organization_name,
|
|
259
|
+
user_email: credentials.user_email,
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
console.log((0, colors_1.success)(`ā Switched to profile: ${(0, colors_1.bold)(profileName)}`));
|
|
264
|
+
console.log((0, colors_1.info)(` Tenant: ${credentials.tenant_domain} (${credentials.tenant_id})`));
|
|
265
|
+
console.log((0, colors_1.info)(` Organization: ${credentials.organization_name} (${credentials.organization_id})`));
|
|
266
|
+
console.log((0, colors_1.info)(` User: ${credentials.user_email}`));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
if (jsonOutput) {
|
|
271
|
+
console.log(JSON.stringify({ success: false, error: err.message }));
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
console.log((0, colors_1.error)('ā Failed to switch profile: ' + err.message));
|
|
275
|
+
}
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function authStatus(profileName, jsonOutput) {
|
|
280
|
+
const currentProfile = profileName || (0, config_1.getCurrentProfile)();
|
|
281
|
+
const token = (0, config_1.getApiToken)(currentProfile);
|
|
282
|
+
const credentials = (0, config_1.loadCredentials)(currentProfile);
|
|
283
|
+
const config = (0, config_1.loadConfig)();
|
|
284
|
+
const allCredentials = (0, config_1.loadAllCredentials)();
|
|
285
|
+
const availableProfiles = Object.keys(allCredentials.profiles);
|
|
286
|
+
let tokenSource = 'none';
|
|
287
|
+
if (process.env.CARTO_API_TOKEN) {
|
|
288
|
+
tokenSource = 'environment';
|
|
289
|
+
}
|
|
290
|
+
else if (credentials) {
|
|
291
|
+
tokenSource = 'credentials';
|
|
292
|
+
}
|
|
293
|
+
else if (currentProfile === 'default' && config.apiToken) {
|
|
294
|
+
tokenSource = 'legacy_config';
|
|
295
|
+
}
|
|
296
|
+
// Get token expiration information
|
|
297
|
+
let tokenExpired = false;
|
|
298
|
+
let tokenExpiring = false;
|
|
299
|
+
let tokenExpirationTimestamp = null;
|
|
300
|
+
let tokenTimeRemaining = null;
|
|
301
|
+
let tokenExpirationFormatted = null;
|
|
302
|
+
if (token) {
|
|
303
|
+
tokenExpired = (0, auth_oauth_1.isTokenExpired)(token);
|
|
304
|
+
tokenExpiring = (0, auth_oauth_1.shouldWarnExpiration)(token);
|
|
305
|
+
tokenExpirationTimestamp = (0, auth_oauth_1.getTokenExpiration)(token);
|
|
306
|
+
tokenTimeRemaining = (0, auth_oauth_1.getTokenTimeRemaining)(token);
|
|
307
|
+
if (tokenExpirationTimestamp) {
|
|
308
|
+
const date = new Date(tokenExpirationTimestamp * 1000);
|
|
309
|
+
tokenExpirationFormatted = date.toLocaleString();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (jsonOutput) {
|
|
313
|
+
console.log(JSON.stringify({
|
|
314
|
+
authenticated: !!token,
|
|
315
|
+
tokenSource,
|
|
316
|
+
currentProfile: allCredentials.current_profile,
|
|
317
|
+
requestedProfile: currentProfile,
|
|
318
|
+
credentialsFile: '~/.carto_credentials.json',
|
|
319
|
+
tenant_id: credentials?.tenant_id,
|
|
320
|
+
tenant_domain: credentials?.tenant_domain,
|
|
321
|
+
organization_id: credentials?.organization_id,
|
|
322
|
+
organization_name: credentials?.organization_name,
|
|
323
|
+
user_email: credentials?.user_email,
|
|
324
|
+
availableProfiles: availableProfiles,
|
|
325
|
+
tokenExpired,
|
|
326
|
+
tokenExpiring,
|
|
327
|
+
tokenExpirationTimestamp,
|
|
328
|
+
tokenExpirationFormatted,
|
|
329
|
+
tokenTimeRemaining,
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
if (token) {
|
|
334
|
+
// Show authentication status with expiration awareness
|
|
335
|
+
if (tokenExpired) {
|
|
336
|
+
console.log((0, colors_1.error)('ā Token expired'));
|
|
337
|
+
}
|
|
338
|
+
else if (tokenExpiring) {
|
|
339
|
+
console.log((0, colors_1.warning)('ā Token expiring soon'));
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
console.log((0, colors_1.success)('ā Authenticated'));
|
|
343
|
+
}
|
|
344
|
+
console.log('');
|
|
345
|
+
console.log((0, colors_1.bold)(`Current profile: ${currentProfile}`) + (allCredentials.current_profile === currentProfile ? ' (default)' : ''));
|
|
346
|
+
console.log((0, colors_1.info)(` Token source: ${tokenSource}`));
|
|
347
|
+
console.log((0, colors_1.info)(` Token: ${token.substring(0, 20)}...`));
|
|
348
|
+
// Show expiration information if available
|
|
349
|
+
if (tokenExpirationTimestamp) {
|
|
350
|
+
console.log((0, colors_1.info)(` Expiration: ${tokenExpirationFormatted}`));
|
|
351
|
+
if (tokenTimeRemaining !== null) {
|
|
352
|
+
const timeStr = (0, auth_oauth_1.formatTimeRemaining)(tokenTimeRemaining);
|
|
353
|
+
const timeDisplay = tokenExpired ? (0, colors_1.error)(`(${timeStr})`) :
|
|
354
|
+
tokenExpiring ? (0, colors_1.warning)(`(in ${timeStr})`) :
|
|
355
|
+
(0, colors_1.info)(`(in ${timeStr})`);
|
|
356
|
+
console.log((0, colors_1.info)(` Time remaining: `) + timeDisplay);
|
|
357
|
+
}
|
|
358
|
+
// Status indicator
|
|
359
|
+
const statusEmoji = tokenExpired ? 'š“' : tokenExpiring ? 'š”' : 'š¢';
|
|
360
|
+
const statusText = tokenExpired ? 'Expired' : tokenExpiring ? 'Expiring soon' : 'Valid';
|
|
361
|
+
const statusColor = tokenExpired ? (0, colors_1.error)(statusText) : tokenExpiring ? (0, colors_1.warning)(statusText) : (0, colors_1.success)(statusText);
|
|
362
|
+
console.log((0, colors_1.info)(` Status: ${statusEmoji} `) + statusColor);
|
|
363
|
+
}
|
|
364
|
+
if (credentials) {
|
|
365
|
+
console.log('');
|
|
366
|
+
// Only show auth environment for non-production
|
|
367
|
+
if (credentials.auth_environment && credentials.auth_environment !== 'production') {
|
|
368
|
+
console.log((0, colors_1.info)(` Auth Environment: ${credentials.auth_environment}`));
|
|
369
|
+
}
|
|
370
|
+
console.log((0, colors_1.info)(` Tenant: ${credentials.tenant_domain} (${credentials.tenant_id})`));
|
|
371
|
+
if (credentials.organization_name) {
|
|
372
|
+
console.log((0, colors_1.info)(` Organization: ${credentials.organization_name} (${credentials.organization_id})`));
|
|
373
|
+
}
|
|
374
|
+
if (credentials.user_email) {
|
|
375
|
+
console.log((0, colors_1.info)(` User: ${credentials.user_email}`));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Show available profiles
|
|
379
|
+
if (availableProfiles.length > 0) {
|
|
380
|
+
console.log((0, colors_1.info)(`\nAvailable profiles (${availableProfiles.length}):`));
|
|
381
|
+
availableProfiles.forEach((profile) => {
|
|
382
|
+
const profileCreds = allCredentials.profiles[profile];
|
|
383
|
+
const isCurrent = profile === currentProfile;
|
|
384
|
+
const marker = isCurrent ? 'ā' : ' ';
|
|
385
|
+
// Only show auth environment label for non-production
|
|
386
|
+
const envLabel = (profileCreds.auth_environment && profileCreds.auth_environment !== 'production')
|
|
387
|
+
? ` [${profileCreds.auth_environment}]`
|
|
388
|
+
: '';
|
|
389
|
+
console.log((0, colors_1.info)(` ${marker} ${(0, colors_1.bold)(profile)}${envLabel}`));
|
|
390
|
+
console.log((0, colors_1.info)(` Tenant: ${profileCreds.tenant_domain} (${profileCreds.tenant_id})`));
|
|
391
|
+
if (profileCreds.organization_name) {
|
|
392
|
+
console.log((0, colors_1.info)(` Organization: ${profileCreds.organization_name} (${profileCreds.organization_id})`));
|
|
393
|
+
}
|
|
394
|
+
if (profileCreds.user_email) {
|
|
395
|
+
console.log((0, colors_1.info)(` User: ${profileCreds.user_email}`));
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
console.log('');
|
|
399
|
+
console.log((0, colors_1.dim)(`Use "carto auth use <profile>" to switch profiles`));
|
|
400
|
+
}
|
|
401
|
+
// Show warnings/errors for token expiration
|
|
402
|
+
const reauthCmd = buildReauthCommand(currentProfile, credentials);
|
|
403
|
+
if (tokenExpired) {
|
|
404
|
+
console.log('');
|
|
405
|
+
if (tokenSource === 'environment') {
|
|
406
|
+
console.log((0, colors_1.error)('š“ Token has EXPIRED (from CARTO_API_TOKEN environment variable)'));
|
|
407
|
+
console.log((0, colors_1.error)(' Please update your CARTO_API_TOKEN with a fresh token'));
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
console.log((0, colors_1.error)('š“ Token has EXPIRED'));
|
|
411
|
+
console.log((0, colors_1.error)(' Please re-authenticate to continue using the CLI:'));
|
|
412
|
+
console.log((0, colors_1.bold)(' ' + reauthCmd));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else if (tokenExpiring && tokenTimeRemaining !== null) {
|
|
416
|
+
console.log('');
|
|
417
|
+
console.log((0, colors_1.warning)('ā ļø Warning: Token is expiring soon'));
|
|
418
|
+
console.log((0, colors_1.warning)(` Less than 10% of token lifetime remaining`));
|
|
419
|
+
if (tokenSource === 'environment') {
|
|
420
|
+
console.log((0, colors_1.warning)(' Consider updating your CARTO_API_TOKEN with a fresh token'));
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
console.log((0, colors_1.warning)(' Consider re-authenticating to avoid interruptions:'));
|
|
424
|
+
console.log((0, colors_1.bold)(' ' + reauthCmd));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
console.log((0, colors_1.error)('ā Not authenticated'));
|
|
430
|
+
// Build suggested login command
|
|
431
|
+
// For not-authenticated case, we can't load credentials, so just use profile name
|
|
432
|
+
let loginCmd = 'carto auth login';
|
|
433
|
+
if (currentProfile && currentProfile !== 'default') {
|
|
434
|
+
const needsQuotes = currentProfile.includes(' ') || currentProfile.includes('@') || currentProfile.includes('/');
|
|
435
|
+
loginCmd += needsQuotes ? ` "${currentProfile}"` : ` ${currentProfile}`;
|
|
436
|
+
}
|
|
437
|
+
console.log((0, colors_1.info)(`Use "${loginCmd}" to authenticate`));
|
|
438
|
+
// Show available profiles even when not authenticated with current profile
|
|
439
|
+
if (availableProfiles.length > 0) {
|
|
440
|
+
console.log((0, colors_1.info)(`\nAvailable profiles (${availableProfiles.length}):`));
|
|
441
|
+
availableProfiles.forEach((profile) => {
|
|
442
|
+
const profileCreds = allCredentials.profiles[profile];
|
|
443
|
+
const isCurrent = profile === currentProfile;
|
|
444
|
+
const marker = isCurrent ? 'ā' : ' ';
|
|
445
|
+
console.log((0, colors_1.info)(` ${marker} ${(0, colors_1.bold)(profile)}: ${profileCreds.tenant_domain}`));
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async function authWhoami(token, baseUrl, jsonOutput, debug = false, profile) {
|
|
452
|
+
try {
|
|
453
|
+
const client = await api_1.ApiClient.create(token, baseUrl, debug, profile);
|
|
454
|
+
// Get accounts URL from profile credentials (environment-specific)
|
|
455
|
+
const profileName = profile || (0, config_1.getCurrentProfile)();
|
|
456
|
+
const credentials = (0, config_1.loadCredentials)(profileName);
|
|
457
|
+
const accountsUrl = (0, auth_oauth_1.getAccountsUrl)(credentials?.auth_environment || auth_oauth_1.DEFAULT_AUTH_ENVIRONMENT);
|
|
458
|
+
// Call the user info endpoint
|
|
459
|
+
const userInfo = await client.getFullUrl(`${accountsUrl}/users/me`, true);
|
|
460
|
+
if (jsonOutput) {
|
|
461
|
+
console.log(JSON.stringify(userInfo));
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
console.log((0, colors_1.bold)('\n=== Current User ===\n'));
|
|
465
|
+
console.log((0, colors_1.bold)('User ID: ') + userInfo.user_id);
|
|
466
|
+
console.log((0, colors_1.bold)('Name: ') + (userInfo.name || 'N/A'));
|
|
467
|
+
console.log((0, colors_1.bold)('Email: ') + userInfo.email);
|
|
468
|
+
console.log((0, colors_1.bold)('Nickname: ') + (userInfo.nickname || 'N/A'));
|
|
469
|
+
if (userInfo.user_metadata) {
|
|
470
|
+
console.log((0, colors_1.bold)('\nAccount Info:'));
|
|
471
|
+
console.log(' Account ID: ' + userInfo.user_metadata.account_id);
|
|
472
|
+
console.log(' Account Name: ' + userInfo.user_metadata.account_name);
|
|
473
|
+
console.log(' Tenant: ' + userInfo.user_metadata.tenant_domain + ' (' + userInfo.user_metadata.tenant_id + ')');
|
|
474
|
+
}
|
|
475
|
+
if (userInfo.app_metadata?.roles && userInfo.app_metadata.roles.length > 0) {
|
|
476
|
+
console.log((0, colors_1.bold)('\nRoles: ') + userInfo.app_metadata.roles.join(', '));
|
|
477
|
+
}
|
|
478
|
+
console.log((0, colors_1.bold)('\nEmail Verified: ') + (userInfo.email_verified ? 'Yes' : 'No'));
|
|
479
|
+
console.log((0, colors_1.bold)('Created: ') + new Date(userInfo.created_at).toLocaleString());
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
if (jsonOutput) {
|
|
484
|
+
console.log(JSON.stringify({ success: false, error: err.message }));
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
if (err.message.includes('401') || err.message.includes('Token not defined')) {
|
|
488
|
+
console.log((0, colors_1.error)('ā Authentication required'));
|
|
489
|
+
console.log('Please run: carto auth login');
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
console.log((0, colors_1.error)('ā Failed to get user info: ' + err.message));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* M2M (Machine-to-Machine) OAuth login for CI/CD environments
|
|
500
|
+
* Uses OAuth client_credentials grant - no browser interaction required
|
|
501
|
+
*/
|
|
502
|
+
async function authLoginM2M(profileName, jsonOutput, env, clientId, clientSecret, apiBaseUrl, force) {
|
|
503
|
+
try {
|
|
504
|
+
// Determine profile name early - for M2M, default to 'm2m' if not specified
|
|
505
|
+
const targetProfileName = profileName || 'm2m';
|
|
506
|
+
// Check for existing valid token (unless --force is used)
|
|
507
|
+
if (!force) {
|
|
508
|
+
const existingCredentials = (0, config_1.loadCredentials)(targetProfileName);
|
|
509
|
+
if (existingCredentials?.token) {
|
|
510
|
+
const tokenExpired = (0, auth_oauth_1.isTokenExpired)(existingCredentials.token);
|
|
511
|
+
const timeRemaining = (0, auth_oauth_1.getTokenTimeRemaining)(existingCredentials.token);
|
|
512
|
+
if (!tokenExpired && timeRemaining !== null && timeRemaining > 0) {
|
|
513
|
+
// Token is still valid - reuse it
|
|
514
|
+
if (jsonOutput) {
|
|
515
|
+
console.log(JSON.stringify({
|
|
516
|
+
success: true,
|
|
517
|
+
message: 'Using existing valid M2M token',
|
|
518
|
+
profile: targetProfileName,
|
|
519
|
+
organization_id: existingCredentials.organization_id || null,
|
|
520
|
+
api_base_url: existingCredentials.base_url,
|
|
521
|
+
type: 'm2m-service-account',
|
|
522
|
+
time_remaining_seconds: timeRemaining,
|
|
523
|
+
reused: true
|
|
524
|
+
}));
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
console.log('');
|
|
528
|
+
console.log((0, colors_1.success)(`ā Valid M2M token found for profile: ${(0, colors_1.bold)(targetProfileName)}`));
|
|
529
|
+
console.log((0, colors_1.info)(` Time remaining: ${(0, auth_oauth_1.formatTimeRemaining)(timeRemaining)}`));
|
|
530
|
+
if (existingCredentials.organization_id) {
|
|
531
|
+
console.log((0, colors_1.info)(` Organization: ${existingCredentials.organization_id}`));
|
|
532
|
+
}
|
|
533
|
+
console.log((0, colors_1.info)(` API Base URL: ${existingCredentials.base_url}`));
|
|
534
|
+
console.log('');
|
|
535
|
+
console.log((0, colors_1.dim)(' Use --force to request a new token'));
|
|
536
|
+
}
|
|
537
|
+
// Set as current profile
|
|
538
|
+
(0, config_1.setCurrentProfile)(targetProfileName);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// Get credentials from: flags > env vars
|
|
544
|
+
const finalClientId = clientId || process.env.CARTO_M2M_CLIENT_ID;
|
|
545
|
+
const finalClientSecret = clientSecret || process.env.CARTO_M2M_CLIENT_SECRET;
|
|
546
|
+
if (!finalClientId || !finalClientSecret) {
|
|
547
|
+
const missing = [];
|
|
548
|
+
if (!finalClientId)
|
|
549
|
+
missing.push('client_id');
|
|
550
|
+
if (!finalClientSecret)
|
|
551
|
+
missing.push('client_secret');
|
|
552
|
+
if (jsonOutput) {
|
|
553
|
+
console.log(JSON.stringify({
|
|
554
|
+
success: false,
|
|
555
|
+
error: `Missing M2M credentials: ${missing.join(', ')}`,
|
|
556
|
+
hint: 'Provide via --client-id/--client-secret flags or CARTO_M2M_CLIENT_ID/CARTO_M2M_CLIENT_SECRET environment variables'
|
|
557
|
+
}));
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
console.log((0, colors_1.error)(`ā Missing M2M credentials: ${missing.join(', ')}`));
|
|
561
|
+
console.log((0, colors_1.info)(' Provide via flags:'));
|
|
562
|
+
console.log((0, colors_1.info)(' carto auth login --m2m --client-id <id> --client-secret <secret>'));
|
|
563
|
+
console.log((0, colors_1.info)(' Or via environment variables:'));
|
|
564
|
+
console.log((0, colors_1.info)(' export CARTO_M2M_CLIENT_ID="your-client-id"'));
|
|
565
|
+
console.log((0, colors_1.info)(' export CARTO_M2M_CLIENT_SECRET="your-client-secret"'));
|
|
566
|
+
console.log((0, colors_1.info)(' carto auth login --m2m'));
|
|
567
|
+
}
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
// Get M2M auth config for the environment
|
|
571
|
+
const m2mConfig = (0, auth_oauth_1.getM2MAuthConfig)(env);
|
|
572
|
+
if (!jsonOutput) {
|
|
573
|
+
console.log('');
|
|
574
|
+
console.log((0, colors_1.info)('š M2M Authentication (CI/CD mode)'));
|
|
575
|
+
const envName = env || auth_oauth_1.DEFAULT_AUTH_ENVIRONMENT;
|
|
576
|
+
if (envName !== 'production') {
|
|
577
|
+
console.log((0, colors_1.info)(` Auth Environment: ${m2mConfig.domain}`));
|
|
578
|
+
}
|
|
579
|
+
console.log('');
|
|
580
|
+
console.log((0, colors_1.info)('š Exchanging M2M credentials for access token...'));
|
|
581
|
+
}
|
|
582
|
+
// Exchange M2M credentials for token
|
|
583
|
+
const tokenResponse = await (0, auth_oauth_1.exchangeM2MCredentialsForToken)(m2mConfig, finalClientId, finalClientSecret);
|
|
584
|
+
const accessToken = tokenResponse.access_token;
|
|
585
|
+
if (!jsonOutput) {
|
|
586
|
+
console.log((0, colors_1.success)('ā Token obtained successfully'));
|
|
587
|
+
}
|
|
588
|
+
// M2M tokens don't have a user - extract info from JWT claims
|
|
589
|
+
let organizationId = '';
|
|
590
|
+
try {
|
|
591
|
+
const tokenPayload = (0, auth_oauth_1.decodeJWT)(accessToken);
|
|
592
|
+
// CARTO M2M tokens include account info in http://app.carto.com/account_id claim
|
|
593
|
+
organizationId = tokenPayload['http://app.carto.com/account_id'] || '';
|
|
594
|
+
}
|
|
595
|
+
catch (decodeErr) {
|
|
596
|
+
// If JWT decode fails, continue with empty values
|
|
597
|
+
if (!jsonOutput) {
|
|
598
|
+
console.log((0, colors_1.warning)('ā Could not extract details from token'));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// For M2M, we need the API base URL - default to production gcp-us-east1
|
|
602
|
+
const finalBaseUrl = apiBaseUrl || process.env.CARTO_API_BASE_URL || 'https://gcp-us-east1.api.carto.com';
|
|
603
|
+
// Build credentials object - M2M stores base_url directly since there's no tenant lookup
|
|
604
|
+
const fullCredentials = {
|
|
605
|
+
token: accessToken,
|
|
606
|
+
tenant_id: '',
|
|
607
|
+
tenant_domain: '',
|
|
608
|
+
organization_id: organizationId,
|
|
609
|
+
organization_name: '',
|
|
610
|
+
user_email: 'm2m-service-account',
|
|
611
|
+
auth_environment: env || auth_oauth_1.DEFAULT_AUTH_ENVIRONMENT,
|
|
612
|
+
base_url: finalBaseUrl,
|
|
613
|
+
};
|
|
614
|
+
// Save credentials (targetProfileName was determined at the start of the function)
|
|
615
|
+
(0, config_1.saveCredentials)(targetProfileName, fullCredentials);
|
|
616
|
+
if (jsonOutput) {
|
|
617
|
+
console.log(JSON.stringify({
|
|
618
|
+
success: true,
|
|
619
|
+
message: 'M2M authentication successful',
|
|
620
|
+
profile: targetProfileName,
|
|
621
|
+
organization_id: fullCredentials.organization_id || null,
|
|
622
|
+
api_base_url: fullCredentials.base_url,
|
|
623
|
+
type: 'm2m-service-account',
|
|
624
|
+
expires_in: tokenResponse.expires_in,
|
|
625
|
+
}));
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
console.log((0, colors_1.success)('\nā M2M authentication successful!'));
|
|
629
|
+
if (fullCredentials.organization_id) {
|
|
630
|
+
console.log((0, colors_1.info)(` Organization: ${fullCredentials.organization_id}`));
|
|
631
|
+
}
|
|
632
|
+
console.log((0, colors_1.info)(` API Base URL: ${fullCredentials.base_url}`));
|
|
633
|
+
console.log((0, colors_1.info)(` Type: M2M Service Account`));
|
|
634
|
+
console.log((0, colors_1.info)(` Token expires in: ${Math.floor(tokenResponse.expires_in / 3600)} hours`));
|
|
635
|
+
console.log('');
|
|
636
|
+
console.log((0, colors_1.success)(`ā Credentials saved as profile: ${(0, colors_1.bold)(targetProfileName)}`));
|
|
637
|
+
console.log((0, colors_1.success)(`ā Set as current profile`));
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
catch (err) {
|
|
641
|
+
if (jsonOutput) {
|
|
642
|
+
console.log(JSON.stringify({ success: false, error: err.message }));
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
console.log((0, colors_1.error)('\nā M2M authentication failed: ' + err.message));
|
|
646
|
+
if (err.message.includes('Invalid M2M credentials')) {
|
|
647
|
+
console.log((0, colors_1.info)(' Check your CARTO_M2M_CLIENT_ID and CARTO_M2M_CLIENT_SECRET values'));
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
process.exit(1);
|
|
651
|
+
}
|
|
652
|
+
}
|