@raster-app/cli 0.2.0 → 0.3.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/README.md +45 -46
- package/dist/index.js +883 -84
- package/package.json +2 -6
package/dist/index.js
CHANGED
|
@@ -16337,14 +16337,11 @@ config(en_default());
|
|
|
16337
16337
|
// package.json
|
|
16338
16338
|
var package_default = {
|
|
16339
16339
|
name: "@raster-app/cli",
|
|
16340
|
-
version: "0.
|
|
16340
|
+
version: "0.3.1",
|
|
16341
16341
|
description: "Command-line client for the Raster API",
|
|
16342
16342
|
license: "Apache-2.0",
|
|
16343
16343
|
type: "module",
|
|
16344
|
-
|
|
16345
|
-
type: "git",
|
|
16346
|
-
url: "https://github.com/raster-app/raster-cli"
|
|
16347
|
-
},
|
|
16344
|
+
homepage: "https://docs.raster.app/api/cli",
|
|
16348
16345
|
engines: {
|
|
16349
16346
|
node: ">=20"
|
|
16350
16347
|
},
|
|
@@ -16357,7 +16354,6 @@ var package_default = {
|
|
|
16357
16354
|
],
|
|
16358
16355
|
scripts: {
|
|
16359
16356
|
build: "bun build src/index.ts --outdir dist --target node --banner '#!/usr/bin/env node'",
|
|
16360
|
-
"build:binary": "bun build src/index.ts --compile --target=bun-darwin-arm64 --outfile raster",
|
|
16361
16357
|
"gen:types": "bun scripts/gen-types.ts",
|
|
16362
16358
|
"check:types": "tsc --noEmit",
|
|
16363
16359
|
"check:tarball": "bun scripts/check-tarball.ts",
|
|
@@ -16891,18 +16887,27 @@ class ApiError extends CliError {
|
|
|
16891
16887
|
// src/api/client.ts
|
|
16892
16888
|
var VERSION_HEADER = { "Api-Version": API_VERSION };
|
|
16893
16889
|
var LOCAL_HOSTNAMES = new Set(["localhost", "127.0.0.1"]);
|
|
16894
|
-
function
|
|
16890
|
+
function isRasterHost(hostname3) {
|
|
16891
|
+
return hostname3 === "raster.app" || hostname3.endsWith(".raster.app");
|
|
16892
|
+
}
|
|
16893
|
+
function assertRasterOrLocalHost(rawUrl, label) {
|
|
16895
16894
|
let parsed;
|
|
16896
16895
|
try {
|
|
16897
16896
|
parsed = new URL(rawUrl);
|
|
16898
16897
|
} catch {
|
|
16899
|
-
throw new UsageError(`Invalid
|
|
16898
|
+
throw new UsageError(`Invalid ${label}: ${rawUrl}`);
|
|
16899
|
+
}
|
|
16900
|
+
if (LOCAL_HOSTNAMES.has(parsed.hostname)) {
|
|
16901
|
+
return rawUrl.replace(/\/+$/, "");
|
|
16900
16902
|
}
|
|
16901
|
-
if (parsed.protocol !== "https:"
|
|
16902
|
-
throw new UsageError(
|
|
16903
|
+
if (parsed.protocol !== "https:" || !isRasterHost(parsed.hostname)) {
|
|
16904
|
+
throw new UsageError(`${label} must be https on a raster.app host (got ${rawUrl}); localhost is allowed for development.`);
|
|
16903
16905
|
}
|
|
16904
16906
|
return rawUrl.replace(/\/+$/, "");
|
|
16905
16907
|
}
|
|
16908
|
+
function validateBaseUrl(rawUrl) {
|
|
16909
|
+
return assertRasterOrLocalHost(rawUrl, "API base URL");
|
|
16910
|
+
}
|
|
16906
16911
|
function buildRequestHeaders(apiKey) {
|
|
16907
16912
|
const headers = { "Api-Version": API_VERSION };
|
|
16908
16913
|
if (apiKey)
|
|
@@ -17031,10 +17036,23 @@ function formatBytes(size) {
|
|
|
17031
17036
|
var CONFIG_FILE_NAME = "config.json";
|
|
17032
17037
|
var CONFIG_FILE_MODE = 384;
|
|
17033
17038
|
var CONFIG_DIR_MODE = 448;
|
|
17039
|
+
var oauthTokensSchema = exports_external.object({
|
|
17040
|
+
accessToken: exports_external.string().min(1),
|
|
17041
|
+
refreshToken: exports_external.string().min(1),
|
|
17042
|
+
expiresAt: exports_external.number(),
|
|
17043
|
+
issuer: exports_external.url(),
|
|
17044
|
+
scope: exports_external.string(),
|
|
17045
|
+
resource: exports_external.url()
|
|
17046
|
+
});
|
|
17047
|
+
var oauthClientSchema = exports_external.object({ clientId: exports_external.string().min(1) });
|
|
17034
17048
|
var configSchema = exports_external.object({
|
|
17035
17049
|
apiKey: exports_external.string().min(1).optional(),
|
|
17036
17050
|
organizationId: exports_external.string().min(1).optional(),
|
|
17037
|
-
libraries: exports_external.array(exports_external.string()).optional()
|
|
17051
|
+
libraries: exports_external.array(exports_external.string()).optional(),
|
|
17052
|
+
oauth: oauthTokensSchema.optional(),
|
|
17053
|
+
clients: exports_external.record(exports_external.string(), oauthClientSchema).optional(),
|
|
17054
|
+
defaultOrg: exports_external.string().min(1).optional(),
|
|
17055
|
+
issuer: exports_external.url().optional()
|
|
17038
17056
|
});
|
|
17039
17057
|
function configDir(env) {
|
|
17040
17058
|
if (env.RASTER_CONFIG_HOME)
|
|
@@ -17070,17 +17088,348 @@ async function writeConfig(config2, env) {
|
|
|
17070
17088
|
`, { mode: CONFIG_FILE_MODE });
|
|
17071
17089
|
await chmod(path2, CONFIG_FILE_MODE);
|
|
17072
17090
|
}
|
|
17073
|
-
function
|
|
17091
|
+
async function writeOAuthTokens(tokens, env) {
|
|
17092
|
+
const config2 = await readConfig(env);
|
|
17093
|
+
await writeConfig({ ...config2, oauth: tokens }, env);
|
|
17094
|
+
}
|
|
17095
|
+
async function clearOAuthTokens(env) {
|
|
17096
|
+
const config2 = await readConfig(env);
|
|
17097
|
+
delete config2.oauth;
|
|
17098
|
+
await writeConfig(config2, env);
|
|
17099
|
+
}
|
|
17100
|
+
async function getCachedClientId(issuer, env) {
|
|
17101
|
+
return (await readConfig(env)).clients?.[issuer]?.clientId ?? null;
|
|
17102
|
+
}
|
|
17103
|
+
async function cacheClientId(issuer, clientId, env) {
|
|
17104
|
+
const config2 = await readConfig(env);
|
|
17105
|
+
await writeConfig({ ...config2, clients: { ...config2.clients, [issuer]: { clientId } } }, env);
|
|
17106
|
+
}
|
|
17107
|
+
|
|
17108
|
+
// src/oauth/issuer.ts
|
|
17109
|
+
var DEFAULT_ISSUER = "https://raster.app";
|
|
17110
|
+
var RESOURCE_INDICATOR = "https://api.raster.app";
|
|
17111
|
+
var USER_SCOPE = "user";
|
|
17112
|
+
var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
17113
|
+
function resolveIssuer(options) {
|
|
17114
|
+
const raw = options.env.RASTER_AUTH_ISSUER ?? options.config.issuer ?? DEFAULT_ISSUER;
|
|
17115
|
+
return assertRasterOrLocalHost(raw, "auth issuer");
|
|
17116
|
+
}
|
|
17117
|
+
|
|
17118
|
+
// src/oauth/open-browser.ts
|
|
17119
|
+
import { spawn } from "node:child_process";
|
|
17120
|
+
import { platform } from "node:os";
|
|
17121
|
+
function openerCommand() {
|
|
17122
|
+
const current = platform();
|
|
17123
|
+
if (current === "darwin")
|
|
17124
|
+
return { command: "open", args: [] };
|
|
17125
|
+
if (current === "win32")
|
|
17126
|
+
return { command: "cmd", args: ["/c", "start", ""] };
|
|
17127
|
+
if (current === "linux")
|
|
17128
|
+
return { command: "xdg-open", args: [] };
|
|
17129
|
+
return null;
|
|
17130
|
+
}
|
|
17131
|
+
function canOpenBrowser() {
|
|
17132
|
+
if (!openerCommand())
|
|
17133
|
+
return false;
|
|
17134
|
+
if (platform() === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
|
|
17135
|
+
return false;
|
|
17136
|
+
}
|
|
17137
|
+
return true;
|
|
17138
|
+
}
|
|
17139
|
+
async function openBrowser(url2) {
|
|
17140
|
+
const opener = openerCommand();
|
|
17141
|
+
if (!opener)
|
|
17142
|
+
throw new Error("No browser opener is available on this platform.");
|
|
17143
|
+
await new Promise((resolve, reject) => {
|
|
17144
|
+
const child = spawn(opener.command, [...opener.args, url2], { stdio: "ignore", detached: true });
|
|
17145
|
+
child.once("error", reject);
|
|
17146
|
+
child.once("spawn", () => {
|
|
17147
|
+
child.unref();
|
|
17148
|
+
resolve();
|
|
17149
|
+
});
|
|
17150
|
+
});
|
|
17151
|
+
}
|
|
17152
|
+
|
|
17153
|
+
// src/oauth/http.ts
|
|
17154
|
+
var FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
|
|
17155
|
+
var OAUTH_REQUEST_TIMEOUT_MS = 30000;
|
|
17156
|
+
var oauthErrorSchema = exports_external.object({
|
|
17157
|
+
error: exports_external.string(),
|
|
17158
|
+
error_description: exports_external.string().optional()
|
|
17159
|
+
});
|
|
17160
|
+
|
|
17161
|
+
class OAuthError extends CliError {
|
|
17162
|
+
error;
|
|
17163
|
+
constructor(error51, description, status) {
|
|
17164
|
+
super(description ? `${error51}: ${description}` : error51, EXIT_CODES.auth);
|
|
17165
|
+
this.error = error51;
|
|
17166
|
+
this.status = status;
|
|
17167
|
+
}
|
|
17168
|
+
status;
|
|
17169
|
+
}
|
|
17170
|
+
|
|
17171
|
+
class AuthServerHttpError extends CliError {
|
|
17172
|
+
status;
|
|
17173
|
+
constructor(status) {
|
|
17174
|
+
super(`Authorization server request failed with HTTP ${status}.`, EXIT_CODES.auth);
|
|
17175
|
+
this.status = status;
|
|
17176
|
+
}
|
|
17177
|
+
}
|
|
17178
|
+
function parseOAuthError(body, status) {
|
|
17179
|
+
const parsed = oauthErrorSchema.safeParse(body);
|
|
17180
|
+
if (!parsed.success)
|
|
17181
|
+
return null;
|
|
17182
|
+
return new OAuthError(parsed.data.error, parsed.data.error_description, status);
|
|
17183
|
+
}
|
|
17184
|
+
async function readJson(response) {
|
|
17185
|
+
try {
|
|
17186
|
+
return await response.json();
|
|
17187
|
+
} catch {
|
|
17188
|
+
return null;
|
|
17189
|
+
}
|
|
17190
|
+
}
|
|
17191
|
+
async function postForm(url2, form, fetch) {
|
|
17192
|
+
let response;
|
|
17193
|
+
try {
|
|
17194
|
+
response = await fetch(url2, {
|
|
17195
|
+
method: "POST",
|
|
17196
|
+
headers: { "Content-Type": FORM_CONTENT_TYPE, Accept: "application/json" },
|
|
17197
|
+
body: new URLSearchParams(form).toString(),
|
|
17198
|
+
signal: AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS)
|
|
17199
|
+
});
|
|
17200
|
+
} catch (error51) {
|
|
17201
|
+
throw new NetworkError(`Could not reach the authorization server: ${describeError(error51)}`);
|
|
17202
|
+
}
|
|
17203
|
+
const body = await readJson(response);
|
|
17204
|
+
const oauthError = parseOAuthError(body, response.status);
|
|
17205
|
+
if (oauthError)
|
|
17206
|
+
throw oauthError;
|
|
17207
|
+
if (!response.ok) {
|
|
17208
|
+
throw new AuthServerHttpError(response.status);
|
|
17209
|
+
}
|
|
17210
|
+
return body;
|
|
17211
|
+
}
|
|
17212
|
+
async function postJson(url2, payload, fetch) {
|
|
17213
|
+
let response;
|
|
17214
|
+
try {
|
|
17215
|
+
response = await fetch(url2, {
|
|
17216
|
+
method: "POST",
|
|
17217
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
17218
|
+
body: JSON.stringify(payload),
|
|
17219
|
+
signal: AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS)
|
|
17220
|
+
});
|
|
17221
|
+
} catch (error51) {
|
|
17222
|
+
throw new NetworkError(`Could not reach the authorization server: ${describeError(error51)}`);
|
|
17223
|
+
}
|
|
17224
|
+
const body = await readJson(response);
|
|
17225
|
+
const oauthError = parseOAuthError(body, response.status);
|
|
17226
|
+
if (oauthError)
|
|
17227
|
+
throw oauthError;
|
|
17228
|
+
if (!response.ok) {
|
|
17229
|
+
throw new AuthServerHttpError(response.status);
|
|
17230
|
+
}
|
|
17231
|
+
return body;
|
|
17232
|
+
}
|
|
17233
|
+
async function getJson(url2, fetch) {
|
|
17234
|
+
let response;
|
|
17235
|
+
try {
|
|
17236
|
+
response = await fetch(url2, {
|
|
17237
|
+
method: "GET",
|
|
17238
|
+
headers: { Accept: "application/json" },
|
|
17239
|
+
signal: AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS)
|
|
17240
|
+
});
|
|
17241
|
+
} catch (error51) {
|
|
17242
|
+
throw new NetworkError(`Could not reach the authorization server: ${describeError(error51)}`);
|
|
17243
|
+
}
|
|
17244
|
+
if (!response.ok) {
|
|
17245
|
+
throw new AuthServerHttpError(response.status);
|
|
17246
|
+
}
|
|
17247
|
+
return readJson(response);
|
|
17248
|
+
}
|
|
17249
|
+
function describeError(error51) {
|
|
17250
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
17251
|
+
}
|
|
17252
|
+
|
|
17253
|
+
// src/oauth/discovery.ts
|
|
17254
|
+
var WELL_KNOWN_PATH = "/.well-known/oauth-authorization-server";
|
|
17255
|
+
var metadataSchema = exports_external.object({
|
|
17256
|
+
issuer: exports_external.string(),
|
|
17257
|
+
authorization_endpoint: exports_external.url(),
|
|
17258
|
+
token_endpoint: exports_external.url(),
|
|
17259
|
+
device_authorization_endpoint: exports_external.url(),
|
|
17260
|
+
registration_endpoint: exports_external.url(),
|
|
17261
|
+
revocation_endpoint: exports_external.url()
|
|
17262
|
+
});
|
|
17263
|
+
var metadataCache = new Map;
|
|
17264
|
+
async function discover(issuer, fetch) {
|
|
17265
|
+
const cached2 = metadataCache.get(issuer);
|
|
17266
|
+
if (cached2)
|
|
17267
|
+
return cached2;
|
|
17268
|
+
const body = await getJson(`${issuer}${WELL_KNOWN_PATH}`, fetch);
|
|
17269
|
+
const parsed = metadataSchema.safeParse(body);
|
|
17270
|
+
if (!parsed.success) {
|
|
17271
|
+
throw new CliError(`The authorization server metadata at ${issuer} is missing required endpoints.`, EXIT_CODES.auth);
|
|
17272
|
+
}
|
|
17273
|
+
const metadata = {
|
|
17274
|
+
issuer: parsed.data.issuer,
|
|
17275
|
+
authorizationEndpoint: assertRasterOrLocalHost(parsed.data.authorization_endpoint, "authorization_endpoint"),
|
|
17276
|
+
tokenEndpoint: assertRasterOrLocalHost(parsed.data.token_endpoint, "token_endpoint"),
|
|
17277
|
+
deviceAuthorizationEndpoint: assertRasterOrLocalHost(parsed.data.device_authorization_endpoint, "device_authorization_endpoint"),
|
|
17278
|
+
registrationEndpoint: assertRasterOrLocalHost(parsed.data.registration_endpoint, "registration_endpoint"),
|
|
17279
|
+
revocationEndpoint: assertRasterOrLocalHost(parsed.data.revocation_endpoint, "revocation_endpoint")
|
|
17280
|
+
};
|
|
17281
|
+
metadataCache.set(issuer, metadata);
|
|
17282
|
+
return metadata;
|
|
17283
|
+
}
|
|
17284
|
+
|
|
17285
|
+
// src/oauth/register.ts
|
|
17286
|
+
var DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
17287
|
+
var REGISTRATION_REQUEST = {
|
|
17288
|
+
client_name: "Raster CLI",
|
|
17289
|
+
token_endpoint_auth_method: "none",
|
|
17290
|
+
grant_types: [DEVICE_CODE_GRANT, "authorization_code", "refresh_token"],
|
|
17291
|
+
response_types: ["code"],
|
|
17292
|
+
redirect_uris: ["http://127.0.0.1/callback"]
|
|
17293
|
+
};
|
|
17294
|
+
var registrationResponseSchema = exports_external.object({ client_id: exports_external.string().min(1) });
|
|
17295
|
+
async function ensureClientId(options) {
|
|
17296
|
+
if (options.pinnedClientId)
|
|
17297
|
+
return options.pinnedClientId;
|
|
17298
|
+
const cached2 = await getCachedClientId(options.issuer, options.env);
|
|
17299
|
+
if (cached2)
|
|
17300
|
+
return cached2;
|
|
17301
|
+
const body = await postJson(options.metadata.registrationEndpoint, REGISTRATION_REQUEST, options.fetch);
|
|
17302
|
+
const parsed = registrationResponseSchema.safeParse(body);
|
|
17303
|
+
if (!parsed.success) {
|
|
17304
|
+
throw new CliError("Dynamic client registration did not return a client_id.", EXIT_CODES.auth);
|
|
17305
|
+
}
|
|
17306
|
+
await cacheClientId(options.issuer, parsed.data.client_id, options.env);
|
|
17307
|
+
return parsed.data.client_id;
|
|
17308
|
+
}
|
|
17309
|
+
|
|
17310
|
+
// src/oauth/tokens.ts
|
|
17311
|
+
var ACCESS_TOKEN_SKEW_MS = 60000;
|
|
17312
|
+
var SECONDS_TO_MS = 1000;
|
|
17313
|
+
var tokenResponseSchema = exports_external.object({
|
|
17314
|
+
access_token: exports_external.string().min(1),
|
|
17315
|
+
refresh_token: exports_external.string().min(1),
|
|
17316
|
+
expires_in: exports_external.number().positive(),
|
|
17317
|
+
token_type: exports_external.string().optional(),
|
|
17318
|
+
scope: exports_external.string().optional()
|
|
17319
|
+
});
|
|
17320
|
+
function parseTokenResponse(body) {
|
|
17321
|
+
const parsed = tokenResponseSchema.parse(body);
|
|
17322
|
+
return {
|
|
17323
|
+
accessToken: parsed.access_token,
|
|
17324
|
+
refreshToken: parsed.refresh_token,
|
|
17325
|
+
expiresIn: parsed.expires_in,
|
|
17326
|
+
scope: parsed.scope
|
|
17327
|
+
};
|
|
17328
|
+
}
|
|
17329
|
+
function toStoredTokens(response, context) {
|
|
17330
|
+
return {
|
|
17331
|
+
accessToken: response.accessToken,
|
|
17332
|
+
refreshToken: response.refreshToken,
|
|
17333
|
+
expiresAt: context.now + response.expiresIn * SECONDS_TO_MS,
|
|
17334
|
+
issuer: context.issuer,
|
|
17335
|
+
scope: response.scope ?? context.fallbackScope ?? "user",
|
|
17336
|
+
resource: context.resource
|
|
17337
|
+
};
|
|
17338
|
+
}
|
|
17339
|
+
function isAccessTokenFresh(tokens, now, skewMs = ACCESS_TOKEN_SKEW_MS) {
|
|
17340
|
+
return now < tokens.expiresAt - skewMs;
|
|
17341
|
+
}
|
|
17342
|
+
|
|
17343
|
+
// src/oauth/refresh.ts
|
|
17344
|
+
var INVALID_GRANT = "invalid_grant";
|
|
17345
|
+
|
|
17346
|
+
class OAuthReuseError extends CliError {
|
|
17347
|
+
constructor() {
|
|
17348
|
+
super("Your saved login is no longer valid. Run `raster auth login` to sign in again.", EXIT_CODES.auth);
|
|
17349
|
+
}
|
|
17350
|
+
}
|
|
17351
|
+
async function refreshTokens(options) {
|
|
17352
|
+
let body;
|
|
17353
|
+
try {
|
|
17354
|
+
body = await postForm(options.metadata.tokenEndpoint, {
|
|
17355
|
+
grant_type: "refresh_token",
|
|
17356
|
+
refresh_token: options.tokens.refreshToken,
|
|
17357
|
+
client_id: options.clientId,
|
|
17358
|
+
resource: RESOURCE_INDICATOR
|
|
17359
|
+
}, options.fetch);
|
|
17360
|
+
} catch (error51) {
|
|
17361
|
+
if (error51 instanceof OAuthError && error51.error === INVALID_GRANT)
|
|
17362
|
+
throw new OAuthReuseError;
|
|
17363
|
+
if (error51 instanceof AuthServerHttpError && error51.status >= 400 && error51.status < 500) {
|
|
17364
|
+
throw new OAuthReuseError;
|
|
17365
|
+
}
|
|
17366
|
+
throw error51;
|
|
17367
|
+
}
|
|
17368
|
+
return toStoredTokens(parseTokenResponse(body), {
|
|
17369
|
+
issuer: options.tokens.issuer,
|
|
17370
|
+
resource: options.tokens.resource,
|
|
17371
|
+
now: options.now,
|
|
17372
|
+
fallbackScope: options.tokens.scope
|
|
17373
|
+
});
|
|
17374
|
+
}
|
|
17375
|
+
|
|
17376
|
+
// src/oauth/session.ts
|
|
17377
|
+
async function ensureFreshOAuthSession(options) {
|
|
17378
|
+
if (isAccessTokenFresh(options.tokens, options.now)) {
|
|
17379
|
+
return { status: "active", tokens: options.tokens };
|
|
17380
|
+
}
|
|
17381
|
+
const issuer = assertRasterOrLocalHost(options.tokens.issuer, "auth issuer");
|
|
17382
|
+
const metadata = await discover(issuer, options.fetch);
|
|
17383
|
+
const clientId = await ensureClientId({
|
|
17384
|
+
issuer,
|
|
17385
|
+
metadata,
|
|
17386
|
+
env: options.env,
|
|
17387
|
+
fetch: options.fetch,
|
|
17388
|
+
pinnedClientId: options.pinnedClientId
|
|
17389
|
+
});
|
|
17390
|
+
try {
|
|
17391
|
+
const rotated = await refreshTokens({
|
|
17392
|
+
tokens: options.tokens,
|
|
17393
|
+
metadata,
|
|
17394
|
+
clientId,
|
|
17395
|
+
fetch: options.fetch,
|
|
17396
|
+
now: options.now
|
|
17397
|
+
});
|
|
17398
|
+
await writeOAuthTokens(rotated, options.env);
|
|
17399
|
+
return { status: "active", tokens: rotated };
|
|
17400
|
+
} catch (error51) {
|
|
17401
|
+
if (error51 instanceof OAuthReuseError) {
|
|
17402
|
+
await clearOAuthTokens(options.env);
|
|
17403
|
+
return { status: "reauth-required" };
|
|
17404
|
+
}
|
|
17405
|
+
throw error51;
|
|
17406
|
+
}
|
|
17407
|
+
}
|
|
17408
|
+
|
|
17409
|
+
// src/lib/credential.ts
|
|
17410
|
+
function resolveCredential(options) {
|
|
17074
17411
|
if (options.flagKey)
|
|
17075
|
-
return { key: options.flagKey, source: "flag" };
|
|
17412
|
+
return { kind: "apiKey", key: options.flagKey, source: "flag" };
|
|
17413
|
+
if (options.config.oauth)
|
|
17414
|
+
return { kind: "oauth", tokens: options.config.oauth };
|
|
17076
17415
|
if (options.env.RASTER_API_KEY)
|
|
17077
|
-
return { key: options.env.RASTER_API_KEY, source: "env" };
|
|
17416
|
+
return { kind: "apiKey", key: options.env.RASTER_API_KEY, source: "env" };
|
|
17078
17417
|
if (options.config.apiKey)
|
|
17079
|
-
return { key: options.config.apiKey, source: "config" };
|
|
17418
|
+
return { kind: "apiKey", key: options.config.apiKey, source: "config" };
|
|
17080
17419
|
return null;
|
|
17081
17420
|
}
|
|
17082
17421
|
|
|
17083
17422
|
// src/lib/output.ts
|
|
17423
|
+
var ANSI = { reset: "\x1B[0m", bold: "\x1B[1m", dim: "\x1B[2m", cyan: "\x1B[36m" };
|
|
17424
|
+
function paint(text, code, enabled) {
|
|
17425
|
+
return enabled ? `${code}${text}${ANSI.reset}` : text;
|
|
17426
|
+
}
|
|
17427
|
+
function bold(text, enabled) {
|
|
17428
|
+
return paint(text, ANSI.bold, enabled);
|
|
17429
|
+
}
|
|
17430
|
+
function dim(text, enabled) {
|
|
17431
|
+
return paint(text, ANSI.dim, enabled);
|
|
17432
|
+
}
|
|
17084
17433
|
function printJson(output, payload) {
|
|
17085
17434
|
output.stdout(`${JSON.stringify(payload, null, 2)}
|
|
17086
17435
|
`);
|
|
@@ -17193,73 +17542,144 @@ function promptSecret(message) {
|
|
|
17193
17542
|
}
|
|
17194
17543
|
|
|
17195
17544
|
// src/lib/context.ts
|
|
17545
|
+
var RASTER_ORG_ENV = "RASTER_ORG";
|
|
17546
|
+
var PINNED_CLIENT_ID_ENV = "RASTER_OAUTH_CLIENT_ID";
|
|
17547
|
+
function toLibraryIds(libraries) {
|
|
17548
|
+
return (libraries ?? []).map((entry) => typeof entry === "string" ? entry : entry?.library).filter((id) => typeof id === "string");
|
|
17549
|
+
}
|
|
17196
17550
|
async function createCommandContext(globals, overrides = {}) {
|
|
17197
17551
|
const env = overrides.env ?? process.env;
|
|
17198
17552
|
const config2 = await readConfig(env);
|
|
17199
|
-
const
|
|
17553
|
+
const now = overrides.now ?? Date.now;
|
|
17200
17554
|
const output = {
|
|
17201
17555
|
json: globals.json === true,
|
|
17202
17556
|
verbose: globals.verbose === true,
|
|
17557
|
+
color: overrides.stderr === undefined && process.stderr.isTTY === true && env.NO_COLOR === undefined,
|
|
17203
17558
|
stdout: overrides.stdout ?? ((text) => void process.stdout.write(text)),
|
|
17204
17559
|
stderr: overrides.stderr ?? ((text) => void process.stderr.write(text))
|
|
17205
17560
|
};
|
|
17206
17561
|
const baseUrl = validateBaseUrl(env.RASTER_API_BASE_URL ?? DEFAULT_BASE_URL);
|
|
17207
17562
|
const baseFetch = overrides.fetch ?? globalThis.fetch;
|
|
17208
|
-
const
|
|
17563
|
+
const credential = await resolveActiveCredential({
|
|
17564
|
+
flagKey: globals.apiKey,
|
|
17565
|
+
env,
|
|
17566
|
+
config: config2,
|
|
17567
|
+
fetch: instrumentFetch(baseFetch, output, null),
|
|
17568
|
+
now: now()
|
|
17569
|
+
});
|
|
17570
|
+
const apiKey = credential?.kind === "apiKey" ? { key: credential.key, source: credential.source } : null;
|
|
17571
|
+
const bearer = credentialBearer(credential);
|
|
17572
|
+
const instrumentedFetch = instrumentFetch(baseFetch, output, bearer ? maskApiKey(bearer) : null);
|
|
17209
17573
|
const clientFactory = (clientOverrides) => createApiClient({
|
|
17210
17574
|
baseUrl,
|
|
17211
|
-
apiKey: clientOverrides ? clientOverrides.apiKey :
|
|
17575
|
+
apiKey: clientOverrides ? clientOverrides.apiKey : bearer,
|
|
17212
17576
|
fetch: instrumentedFetch
|
|
17213
17577
|
});
|
|
17214
17578
|
let viewerPromise = null;
|
|
17215
17579
|
const loadViewer = () => {
|
|
17216
17580
|
if (!viewerPromise) {
|
|
17217
|
-
if (!
|
|
17218
|
-
throw new AuthSetupError("
|
|
17581
|
+
if (!credential) {
|
|
17582
|
+
throw new AuthSetupError("Not authenticated. Run `raster auth login` or set RASTER_API_KEY.");
|
|
17219
17583
|
}
|
|
17220
|
-
viewerPromise = unwrapResult(() => clientFactory(
|
|
17584
|
+
viewerPromise = unwrapResult(() => clientFactory().GET("/me", { params: { header: VERSION_HEADER } }));
|
|
17221
17585
|
}
|
|
17222
17586
|
return viewerPromise;
|
|
17223
17587
|
};
|
|
17224
17588
|
const cacheMatchesKey = config2.apiKey !== undefined && apiKey?.key === config2.apiKey;
|
|
17589
|
+
const resolveOrgId = async () => {
|
|
17590
|
+
if (globals.org)
|
|
17591
|
+
return globals.org;
|
|
17592
|
+
if (env[RASTER_ORG_ENV])
|
|
17593
|
+
return env[RASTER_ORG_ENV];
|
|
17594
|
+
if (credential?.kind === "oauth") {
|
|
17595
|
+
if (credential.tokens.scope === "org") {
|
|
17596
|
+
const viewer2 = await loadViewer();
|
|
17597
|
+
if (viewer2.organizationId)
|
|
17598
|
+
return viewer2.organizationId;
|
|
17599
|
+
throw new CliError("This login is bound to an organization that is no longer available. Run `raster auth login` again.", EXIT_CODES.auth);
|
|
17600
|
+
}
|
|
17601
|
+
if (config2.defaultOrg)
|
|
17602
|
+
return config2.defaultOrg;
|
|
17603
|
+
throw new UsageError("This login can access multiple organizations. Pass --org <id> (or set RASTER_ORG); run `raster whoami` to list them.");
|
|
17604
|
+
}
|
|
17605
|
+
if (cacheMatchesKey && config2.organizationId)
|
|
17606
|
+
return config2.organizationId;
|
|
17607
|
+
const viewer = await loadViewer();
|
|
17608
|
+
if (!viewer.organizationId) {
|
|
17609
|
+
throw new CliError("Could not determine the organization from the API key.", EXIT_CODES.generic);
|
|
17610
|
+
}
|
|
17611
|
+
return viewer.organizationId;
|
|
17612
|
+
};
|
|
17613
|
+
const librariesForTarget = async () => {
|
|
17614
|
+
const cached2 = cacheMatchesKey ? config2.libraries : undefined;
|
|
17615
|
+
if (cached2)
|
|
17616
|
+
return cached2;
|
|
17617
|
+
const viewer = await loadViewer();
|
|
17618
|
+
if (credential?.kind === "oauth" && credential.tokens.scope !== "org") {
|
|
17619
|
+
const organizationId = await resolveOrgId();
|
|
17620
|
+
const entry = viewer.organizations?.find((organization) => organization?.organizationId === organizationId);
|
|
17621
|
+
return entry?.libraries ?? [];
|
|
17622
|
+
}
|
|
17623
|
+
return viewer.libraries ?? [];
|
|
17624
|
+
};
|
|
17225
17625
|
return {
|
|
17226
17626
|
...output,
|
|
17227
17627
|
env,
|
|
17228
17628
|
baseUrl,
|
|
17229
17629
|
isInteractive: overrides.isInteractive ?? process.stdin.isTTY === true,
|
|
17630
|
+
credential,
|
|
17230
17631
|
apiKey,
|
|
17632
|
+
bearerToken: bearer,
|
|
17231
17633
|
fetch: instrumentedFetch,
|
|
17232
17634
|
clientFactory,
|
|
17233
17635
|
confirm: overrides.confirm ?? promptConfirm,
|
|
17234
17636
|
promptSecret: overrides.promptSecret ?? promptSecret,
|
|
17637
|
+
now,
|
|
17638
|
+
issuer: resolveIssuer({ env, config: config2 }),
|
|
17639
|
+
pinnedClientId: env[PINNED_CLIENT_ID_ENV],
|
|
17640
|
+
openBrowser: overrides.openBrowser ?? openBrowser,
|
|
17641
|
+
canOpenBrowser: overrides.canOpenBrowser ?? canOpenBrowser,
|
|
17235
17642
|
explicitLibrary: globals.library,
|
|
17236
|
-
resolveOrg:
|
|
17237
|
-
if (globals.org)
|
|
17238
|
-
return globals.org;
|
|
17239
|
-
if (cacheMatchesKey && config2.organizationId)
|
|
17240
|
-
return config2.organizationId;
|
|
17241
|
-
const viewer = await loadViewer();
|
|
17242
|
-
if (!viewer.organizationId) {
|
|
17243
|
-
throw new CliError("Could not determine the organization from the API key.", EXIT_CODES.generic);
|
|
17244
|
-
}
|
|
17245
|
-
return viewer.organizationId;
|
|
17246
|
-
},
|
|
17643
|
+
resolveOrg: resolveOrgId,
|
|
17247
17644
|
resolveLibrary: async () => {
|
|
17248
17645
|
if (globals.library)
|
|
17249
17646
|
return globals.library;
|
|
17250
|
-
const
|
|
17251
|
-
const source = cachedLibraries ?? (await loadViewer()).libraries ?? [];
|
|
17252
|
-
const libraries = source.filter((value) => typeof value === "string");
|
|
17647
|
+
const libraries = toLibraryIds(await librariesForTarget());
|
|
17253
17648
|
const onlyLibrary = libraries[0];
|
|
17254
17649
|
if (libraries.length === 1 && onlyLibrary)
|
|
17255
17650
|
return onlyLibrary;
|
|
17256
17651
|
if (libraries.length === 0) {
|
|
17257
|
-
throw new AuthSetupError("
|
|
17652
|
+
throw new AuthSetupError("No library access for this login. Pass --library <id>.");
|
|
17258
17653
|
}
|
|
17259
|
-
throw new UsageError(`
|
|
17654
|
+
throw new UsageError(`This login can access ${libraries.length} libraries (${libraries.join(", ")}). Pass --library <id>.`);
|
|
17260
17655
|
}
|
|
17261
17656
|
};
|
|
17262
17657
|
}
|
|
17658
|
+
function credentialBearer(credential) {
|
|
17659
|
+
if (!credential)
|
|
17660
|
+
return null;
|
|
17661
|
+
return credential.kind === "apiKey" ? credential.key : credential.tokens.accessToken;
|
|
17662
|
+
}
|
|
17663
|
+
async function resolveActiveCredential(options) {
|
|
17664
|
+
const credential = resolveCredential({ flagKey: options.flagKey, env: options.env, config: options.config });
|
|
17665
|
+
if (credential?.kind !== "oauth")
|
|
17666
|
+
return credential;
|
|
17667
|
+
const session = await ensureFreshOAuthSession({
|
|
17668
|
+
tokens: credential.tokens,
|
|
17669
|
+
env: options.env,
|
|
17670
|
+
fetch: options.fetch,
|
|
17671
|
+
pinnedClientId: options.env[PINNED_CLIENT_ID_ENV],
|
|
17672
|
+
now: options.now
|
|
17673
|
+
});
|
|
17674
|
+
if (session.status === "reauth-required") {
|
|
17675
|
+
return resolveCredential({
|
|
17676
|
+
flagKey: options.flagKey,
|
|
17677
|
+
env: options.env,
|
|
17678
|
+
config: { ...options.config, oauth: undefined }
|
|
17679
|
+
});
|
|
17680
|
+
}
|
|
17681
|
+
return { kind: "oauth", tokens: session.tokens };
|
|
17682
|
+
}
|
|
17263
17683
|
function instrumentFetch(baseFetch, output, maskedKey) {
|
|
17264
17684
|
return async (input, init) => {
|
|
17265
17685
|
const request = input instanceof Request ? input : new Request(String(input), init);
|
|
@@ -17276,10 +17696,15 @@ function instrumentFetch(baseFetch, output, maskedKey) {
|
|
|
17276
17696
|
}
|
|
17277
17697
|
};
|
|
17278
17698
|
}
|
|
17279
|
-
function
|
|
17280
|
-
if (context.
|
|
17281
|
-
return context.
|
|
17282
|
-
throw new AuthSetupError("
|
|
17699
|
+
function requireAuth(context) {
|
|
17700
|
+
if (context.credential)
|
|
17701
|
+
return context.credential;
|
|
17702
|
+
throw new AuthSetupError("Not authenticated. Run `raster auth login` or set RASTER_API_KEY.");
|
|
17703
|
+
}
|
|
17704
|
+
function requireBearer(context) {
|
|
17705
|
+
if (context.bearerToken)
|
|
17706
|
+
return context.bearerToken;
|
|
17707
|
+
throw new AuthSetupError("Not authenticated. Run `raster auth login` or set RASTER_API_KEY.");
|
|
17283
17708
|
}
|
|
17284
17709
|
async function executeCommand(context, action) {
|
|
17285
17710
|
try {
|
|
@@ -17333,7 +17758,7 @@ async function listAssetsCommand(context, options) {
|
|
|
17333
17758
|
if (options.tag.length > MAX_TAGS_PER_QUERY) {
|
|
17334
17759
|
throw new ValidationError(`At most ${MAX_TAGS_PER_QUERY} --tag filters are supported per query.`);
|
|
17335
17760
|
}
|
|
17336
|
-
|
|
17761
|
+
requireAuth(context);
|
|
17337
17762
|
const organizationId = await context.resolveOrg();
|
|
17338
17763
|
const libraryId = await context.resolveLibrary();
|
|
17339
17764
|
const client = context.clientFactory();
|
|
@@ -17355,7 +17780,7 @@ async function listAssetsCommand(context, options) {
|
|
|
17355
17780
|
renderTable(context, assets, ASSET_COLUMNS);
|
|
17356
17781
|
}
|
|
17357
17782
|
async function getAssetCommand(context, assetId) {
|
|
17358
|
-
|
|
17783
|
+
requireAuth(context);
|
|
17359
17784
|
const asset = await fetchAsset(context, assetId);
|
|
17360
17785
|
if (context.json) {
|
|
17361
17786
|
printJson(context, asset);
|
|
@@ -17385,7 +17810,7 @@ var searchOptionsSchema = exports_external.object({
|
|
|
17385
17810
|
pageSize: exports_external.coerce.number().int().min(1).optional()
|
|
17386
17811
|
});
|
|
17387
17812
|
async function searchAssetsCommand(context, query, options) {
|
|
17388
|
-
|
|
17813
|
+
requireAuth(context);
|
|
17389
17814
|
const organizationId = await context.resolveOrg();
|
|
17390
17815
|
const client = context.clientFactory();
|
|
17391
17816
|
const result = await unwrapResult(() => client.GET("/organizations/{organizationId}/search/assets", {
|
|
@@ -17412,7 +17837,7 @@ var downloadOptionsSchema = exports_external.object({
|
|
|
17412
17837
|
force: exports_external.boolean().default(false)
|
|
17413
17838
|
});
|
|
17414
17839
|
async function downloadAssetCommand(context, assetId, options) {
|
|
17415
|
-
|
|
17840
|
+
requireAuth(context);
|
|
17416
17841
|
const asset = await fetchAsset(context, assetId);
|
|
17417
17842
|
const fileUrl = asset.url;
|
|
17418
17843
|
if (!fileUrl)
|
|
@@ -17440,7 +17865,7 @@ var uploadedAssetsSchema = exports_external.object({
|
|
|
17440
17865
|
async function uploadAssetsCommand(context, files) {
|
|
17441
17866
|
if (files.length === 0)
|
|
17442
17867
|
throw new UsageError("Provide at least one file to upload.");
|
|
17443
|
-
const
|
|
17868
|
+
const bearer = requireBearer(context);
|
|
17444
17869
|
const organizationId = await context.resolveOrg();
|
|
17445
17870
|
const libraryId = await context.resolveLibrary();
|
|
17446
17871
|
const batches = chunk(files, MAX_UPLOAD_FILES);
|
|
@@ -17460,7 +17885,7 @@ async function uploadAssetsCommand(context, files) {
|
|
|
17460
17885
|
if (batches.length > 1) {
|
|
17461
17886
|
note(context, `Uploading batch ${batchIndex + 1}/${batches.length} (${batch.length} files)…`);
|
|
17462
17887
|
}
|
|
17463
|
-
const response = await context.fetch(`${context.baseUrl}/organizations/${organizationId}/libraries/${libraryId}/assets`, { method: "POST", headers: buildRequestHeaders(
|
|
17888
|
+
const response = await context.fetch(`${context.baseUrl}/organizations/${organizationId}/libraries/${libraryId}/assets`, { method: "POST", headers: buildRequestHeaders(bearer), body: formData });
|
|
17464
17889
|
const payload = await parseEnvelope(response);
|
|
17465
17890
|
payloads.push(payload);
|
|
17466
17891
|
const parsed = uploadedAssetsSchema.safeParse(payload);
|
|
@@ -17487,7 +17912,7 @@ async function removeAssetsCommand(context, assetIds, options) {
|
|
|
17487
17912
|
if (assetIds.length > MAX_DELETE_IDS) {
|
|
17488
17913
|
throw new ValidationError(`At most ${MAX_DELETE_IDS} asset ids are supported per call.`);
|
|
17489
17914
|
}
|
|
17490
|
-
|
|
17915
|
+
requireAuth(context);
|
|
17491
17916
|
const organizationId = await context.resolveOrg();
|
|
17492
17917
|
const libraryId = await context.resolveLibrary();
|
|
17493
17918
|
if (!options.yes && context.isInteractive) {
|
|
@@ -17506,13 +17931,13 @@ async function removeAssetsCommand(context, assetIds, options) {
|
|
|
17506
17931
|
printJson(context, result);
|
|
17507
17932
|
return;
|
|
17508
17933
|
}
|
|
17509
|
-
note(context, `Moved ${assetIds.length} asset(s) to trash
|
|
17934
|
+
note(context, `Moved ${assetIds.length} asset(s) to trash.`);
|
|
17510
17935
|
}
|
|
17511
17936
|
var describeOptionsSchema = exports_external.object({
|
|
17512
17937
|
text: exports_external.string().min(1)
|
|
17513
17938
|
});
|
|
17514
17939
|
async function describeAssetCommand(context, assetId, options) {
|
|
17515
|
-
|
|
17940
|
+
requireAuth(context);
|
|
17516
17941
|
const organizationId = await context.resolveOrg();
|
|
17517
17942
|
const libraryId = await context.resolveLibrary();
|
|
17518
17943
|
const client = context.clientFactory();
|
|
@@ -17535,7 +17960,7 @@ var transferOptionsSchema = exports_external.object({
|
|
|
17535
17960
|
async function transferAssetsCommand(context, assetIds, options) {
|
|
17536
17961
|
if (assetIds.length === 0)
|
|
17537
17962
|
throw new UsageError("Provide at least one asset id.");
|
|
17538
|
-
|
|
17963
|
+
requireAuth(context);
|
|
17539
17964
|
const organizationId = await context.resolveOrg();
|
|
17540
17965
|
const libraryId = await context.resolveLibrary();
|
|
17541
17966
|
const client = context.clientFactory();
|
|
@@ -17561,48 +17986,418 @@ function registerAssetCommands(program2, runAction) {
|
|
|
17561
17986
|
assets.command("transfer").description("Move assets to another library").argument("<assetIds...>", "Asset ids to move").requiredOption("--to <libraryId>", "Target library id").action((assetIds, options, command) => runAction(command, (context) => transferAssetsCommand(context, assetIds, parseOptions(transferOptionsSchema, options))));
|
|
17562
17987
|
}
|
|
17563
17988
|
|
|
17989
|
+
// src/oauth/login.ts
|
|
17990
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
17991
|
+
|
|
17992
|
+
// src/oauth/loopback.ts
|
|
17993
|
+
import { createServer } from "node:http";
|
|
17994
|
+
var CALLBACK_PATH = "/callback";
|
|
17995
|
+
var LOOPBACK_HOST = "127.0.0.1";
|
|
17996
|
+
var SUCCESS_PAGE = "<!doctype html><meta charset=utf-8><title>Raster CLI</title>" + '<body style="font-family:system-ui;text-align:center;padding-top:4rem">' + "<h1>Signed in</h1><p>You can close this tab and return to the terminal.</p></body>";
|
|
17997
|
+
var FAILURE_PAGE = "<!doctype html><meta charset=utf-8><title>Raster CLI</title>" + '<body style="font-family:system-ui;text-align:center;padding-top:4rem">' + "<h1>Sign-in failed</h1><p>You can close this tab and return to the terminal.</p></body>";
|
|
17998
|
+
async function startLoopbackServer() {
|
|
17999
|
+
let resolveCallback;
|
|
18000
|
+
let rejectCallback;
|
|
18001
|
+
const callbackPromise = new Promise((resolve, reject) => {
|
|
18002
|
+
resolveCallback = resolve;
|
|
18003
|
+
rejectCallback = reject;
|
|
18004
|
+
});
|
|
18005
|
+
let settled = false;
|
|
18006
|
+
const server = createServer((request, response) => {
|
|
18007
|
+
const url2 = new URL(request.url ?? "/", `http://${LOOPBACK_HOST}`);
|
|
18008
|
+
if (url2.pathname !== CALLBACK_PATH) {
|
|
18009
|
+
response.writeHead(404).end();
|
|
18010
|
+
return;
|
|
18011
|
+
}
|
|
18012
|
+
const code = url2.searchParams.get("code");
|
|
18013
|
+
const state = url2.searchParams.get("state");
|
|
18014
|
+
if (code && state) {
|
|
18015
|
+
response.writeHead(200, { "Content-Type": "text/html", Connection: "close" }).end(SUCCESS_PAGE);
|
|
18016
|
+
if (!settled) {
|
|
18017
|
+
settled = true;
|
|
18018
|
+
resolveCallback({ code, state });
|
|
18019
|
+
}
|
|
18020
|
+
return;
|
|
18021
|
+
}
|
|
18022
|
+
response.writeHead(400, { "Content-Type": "text/html", Connection: "close" }).end(FAILURE_PAGE);
|
|
18023
|
+
if (!settled) {
|
|
18024
|
+
settled = true;
|
|
18025
|
+
const error51 = url2.searchParams.get("error");
|
|
18026
|
+
rejectCallback(new CliError(error51 ? `The authorization server returned an error: ${error51}.` : "The authorization server returned no code.", EXIT_CODES.auth));
|
|
18027
|
+
}
|
|
18028
|
+
});
|
|
18029
|
+
let port;
|
|
18030
|
+
try {
|
|
18031
|
+
port = await listenOnEphemeralPort(server);
|
|
18032
|
+
} catch (error51) {
|
|
18033
|
+
server.close();
|
|
18034
|
+
throw error51;
|
|
18035
|
+
}
|
|
18036
|
+
return {
|
|
18037
|
+
redirectUri: `http://${LOOPBACK_HOST}:${port}${CALLBACK_PATH}`,
|
|
18038
|
+
waitForCallback: () => callbackPromise,
|
|
18039
|
+
close: () => {
|
|
18040
|
+
server.close();
|
|
18041
|
+
server.closeAllConnections?.();
|
|
18042
|
+
}
|
|
18043
|
+
};
|
|
18044
|
+
}
|
|
18045
|
+
function listenOnEphemeralPort(server) {
|
|
18046
|
+
return new Promise((resolve, reject) => {
|
|
18047
|
+
server.once("error", reject);
|
|
18048
|
+
server.listen(0, LOOPBACK_HOST, () => {
|
|
18049
|
+
const address = server.address();
|
|
18050
|
+
if (address !== null && typeof address === "object" && typeof address.port === "number") {
|
|
18051
|
+
resolve(address.port);
|
|
18052
|
+
return;
|
|
18053
|
+
}
|
|
18054
|
+
reject(new CliError("Could not bind a loopback port for the browser login.", EXIT_CODES.generic));
|
|
18055
|
+
});
|
|
18056
|
+
});
|
|
18057
|
+
}
|
|
18058
|
+
|
|
18059
|
+
// src/oauth/pkce.ts
|
|
18060
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
18061
|
+
var VERIFIER_BYTES = 32;
|
|
18062
|
+
var STATE_BYTES = 16;
|
|
18063
|
+
function base64UrlEncode(input) {
|
|
18064
|
+
return input.toString("base64url");
|
|
18065
|
+
}
|
|
18066
|
+
function createPkce() {
|
|
18067
|
+
const verifier = base64UrlEncode(randomBytes(VERIFIER_BYTES));
|
|
18068
|
+
const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
|
|
18069
|
+
return { verifier, challenge, method: "S256" };
|
|
18070
|
+
}
|
|
18071
|
+
function createState() {
|
|
18072
|
+
return base64UrlEncode(randomBytes(STATE_BYTES));
|
|
18073
|
+
}
|
|
18074
|
+
|
|
18075
|
+
// src/oauth/browser.ts
|
|
18076
|
+
var DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60000;
|
|
18077
|
+
async function withDeadline(promise2, ms) {
|
|
18078
|
+
let timer;
|
|
18079
|
+
try {
|
|
18080
|
+
return await Promise.race([
|
|
18081
|
+
promise2,
|
|
18082
|
+
new Promise((_, reject) => {
|
|
18083
|
+
timer = setTimeout(() => reject(new CliError("Timed out waiting for browser sign-in. Run `raster auth login` again.", EXIT_CODES.auth)), ms);
|
|
18084
|
+
})
|
|
18085
|
+
]);
|
|
18086
|
+
} finally {
|
|
18087
|
+
if (timer)
|
|
18088
|
+
clearTimeout(timer);
|
|
18089
|
+
}
|
|
18090
|
+
}
|
|
18091
|
+
async function runBrowserLogin(options) {
|
|
18092
|
+
const pkce = createPkce();
|
|
18093
|
+
const state = createState();
|
|
18094
|
+
const server = await startLoopbackServer();
|
|
18095
|
+
try {
|
|
18096
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
18097
|
+
metadata: options.metadata,
|
|
18098
|
+
clientId: options.clientId,
|
|
18099
|
+
redirectUri: server.redirectUri,
|
|
18100
|
+
challenge: pkce.challenge,
|
|
18101
|
+
state
|
|
18102
|
+
});
|
|
18103
|
+
options.onAuthorizeUrl?.(authorizeUrl);
|
|
18104
|
+
await options.openBrowser(authorizeUrl);
|
|
18105
|
+
const callback = await withDeadline(server.waitForCallback(), options.loginTimeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS);
|
|
18106
|
+
if (callback.state !== state) {
|
|
18107
|
+
throw new CliError("The browser login response failed its state check. Run `raster auth login` again.", EXIT_CODES.auth);
|
|
18108
|
+
}
|
|
18109
|
+
const body = await postForm(options.metadata.tokenEndpoint, {
|
|
18110
|
+
grant_type: "authorization_code",
|
|
18111
|
+
code: callback.code,
|
|
18112
|
+
redirect_uri: server.redirectUri,
|
|
18113
|
+
client_id: options.clientId,
|
|
18114
|
+
code_verifier: pkce.verifier,
|
|
18115
|
+
resource: RESOURCE_INDICATOR
|
|
18116
|
+
}, options.fetch);
|
|
18117
|
+
return toStoredTokens(parseTokenResponse(body), {
|
|
18118
|
+
issuer: options.issuer,
|
|
18119
|
+
resource: RESOURCE_INDICATOR,
|
|
18120
|
+
now: options.now()
|
|
18121
|
+
});
|
|
18122
|
+
} finally {
|
|
18123
|
+
server.close();
|
|
18124
|
+
}
|
|
18125
|
+
}
|
|
18126
|
+
function buildAuthorizeUrl(options) {
|
|
18127
|
+
const url2 = new URL(options.metadata.authorizationEndpoint);
|
|
18128
|
+
url2.search = new URLSearchParams({
|
|
18129
|
+
response_type: "code",
|
|
18130
|
+
client_id: options.clientId,
|
|
18131
|
+
redirect_uri: options.redirectUri,
|
|
18132
|
+
scope: USER_SCOPE,
|
|
18133
|
+
resource: RESOURCE_INDICATOR,
|
|
18134
|
+
code_challenge: options.challenge,
|
|
18135
|
+
code_challenge_method: "S256",
|
|
18136
|
+
state: options.state
|
|
18137
|
+
}).toString();
|
|
18138
|
+
return url2.toString();
|
|
18139
|
+
}
|
|
18140
|
+
|
|
18141
|
+
// src/oauth/device.ts
|
|
18142
|
+
var SLOW_DOWN_INCREMENT_SECONDS = 5;
|
|
18143
|
+
var SECONDS_TO_MS2 = 1000;
|
|
18144
|
+
var deviceResponseSchema = exports_external.object({
|
|
18145
|
+
device_code: exports_external.string().min(1),
|
|
18146
|
+
user_code: exports_external.string().min(1),
|
|
18147
|
+
verification_uri: exports_external.url(),
|
|
18148
|
+
verification_uri_complete: exports_external.url().optional(),
|
|
18149
|
+
expires_in: exports_external.number().positive(),
|
|
18150
|
+
interval: exports_external.number().positive().optional()
|
|
18151
|
+
});
|
|
18152
|
+
var DEFAULT_INTERVAL_SECONDS = 5;
|
|
18153
|
+
async function requestDeviceCode(options) {
|
|
18154
|
+
const body = await postForm(options.metadata.deviceAuthorizationEndpoint, { client_id: options.clientId, scope: USER_SCOPE, resource: RESOURCE_INDICATOR }, options.fetch);
|
|
18155
|
+
const parsed = deviceResponseSchema.safeParse(body);
|
|
18156
|
+
if (!parsed.success) {
|
|
18157
|
+
throw new CliError("The device authorization response was malformed.", EXIT_CODES.auth);
|
|
18158
|
+
}
|
|
18159
|
+
return {
|
|
18160
|
+
deviceCode: parsed.data.device_code,
|
|
18161
|
+
userCode: parsed.data.user_code,
|
|
18162
|
+
verificationUri: parsed.data.verification_uri,
|
|
18163
|
+
verificationUriComplete: parsed.data.verification_uri_complete,
|
|
18164
|
+
expiresInSeconds: parsed.data.expires_in,
|
|
18165
|
+
intervalSeconds: parsed.data.interval ?? DEFAULT_INTERVAL_SECONDS
|
|
18166
|
+
};
|
|
18167
|
+
}
|
|
18168
|
+
function nextPollAction(errorCode, interval) {
|
|
18169
|
+
if (errorCode === "authorization_pending")
|
|
18170
|
+
return { kind: "wait", interval };
|
|
18171
|
+
if (errorCode === "slow_down")
|
|
18172
|
+
return { kind: "wait", interval: interval + SLOW_DOWN_INCREMENT_SECONDS };
|
|
18173
|
+
return { kind: "fail" };
|
|
18174
|
+
}
|
|
18175
|
+
async function pollForToken(options) {
|
|
18176
|
+
const deadline = options.now() + options.expiresInSeconds * SECONDS_TO_MS2;
|
|
18177
|
+
let interval = options.intervalSeconds;
|
|
18178
|
+
while (true) {
|
|
18179
|
+
if (options.now() >= deadline) {
|
|
18180
|
+
throw new CliError("The device code expired before you finished signing in. Run `raster auth login` again.", EXIT_CODES.auth);
|
|
18181
|
+
}
|
|
18182
|
+
let outcome;
|
|
18183
|
+
try {
|
|
18184
|
+
outcome = await pollOnce(options);
|
|
18185
|
+
} catch (error51) {
|
|
18186
|
+
if (!(error51 instanceof NetworkError))
|
|
18187
|
+
throw error51;
|
|
18188
|
+
outcome = { kind: "error", error: "authorization_pending" };
|
|
18189
|
+
}
|
|
18190
|
+
if (outcome.kind === "tokens")
|
|
18191
|
+
return outcome.tokens;
|
|
18192
|
+
const action = nextPollAction(outcome.error, interval);
|
|
18193
|
+
if (action.kind === "fail") {
|
|
18194
|
+
throw new CliError(`Device authorization failed: ${outcome.error}. Run \`raster auth login\` again.`, EXIT_CODES.auth);
|
|
18195
|
+
}
|
|
18196
|
+
interval = action.interval;
|
|
18197
|
+
await options.sleep(interval * SECONDS_TO_MS2);
|
|
18198
|
+
}
|
|
18199
|
+
}
|
|
18200
|
+
async function pollOnce(options) {
|
|
18201
|
+
try {
|
|
18202
|
+
const body = await postForm(options.metadata.tokenEndpoint, { grant_type: DEVICE_CODE_GRANT_TYPE, device_code: options.deviceCode, client_id: options.clientId }, options.fetch);
|
|
18203
|
+
const tokens = toStoredTokens(parseTokenResponse(body), {
|
|
18204
|
+
issuer: options.issuer,
|
|
18205
|
+
resource: RESOURCE_INDICATOR,
|
|
18206
|
+
now: options.now()
|
|
18207
|
+
});
|
|
18208
|
+
return { kind: "tokens", tokens };
|
|
18209
|
+
} catch (error51) {
|
|
18210
|
+
if (error51 instanceof OAuthError)
|
|
18211
|
+
return { kind: "error", error: error51.error };
|
|
18212
|
+
throw error51;
|
|
18213
|
+
}
|
|
18214
|
+
}
|
|
18215
|
+
|
|
18216
|
+
// src/oauth/login.ts
|
|
18217
|
+
async function runOAuthLogin(method, deps) {
|
|
18218
|
+
const metadata = await discover(deps.issuer, deps.fetch);
|
|
18219
|
+
const clientId = await ensureClientId({
|
|
18220
|
+
issuer: deps.issuer,
|
|
18221
|
+
metadata,
|
|
18222
|
+
env: deps.env,
|
|
18223
|
+
fetch: deps.fetch,
|
|
18224
|
+
pinnedClientId: deps.pinnedClientId
|
|
18225
|
+
});
|
|
18226
|
+
if (method === "browser") {
|
|
18227
|
+
if (!deps.openBrowser) {
|
|
18228
|
+
throw new CliError("No browser opener is available for the authorization-code flow.", EXIT_CODES.auth);
|
|
18229
|
+
}
|
|
18230
|
+
return runBrowserLogin({
|
|
18231
|
+
metadata,
|
|
18232
|
+
issuer: deps.issuer,
|
|
18233
|
+
clientId,
|
|
18234
|
+
fetch: deps.fetch,
|
|
18235
|
+
openBrowser: deps.openBrowser,
|
|
18236
|
+
now: deps.now,
|
|
18237
|
+
onAuthorizeUrl: deps.onAuthorizeUrl
|
|
18238
|
+
});
|
|
18239
|
+
}
|
|
18240
|
+
const device = await requestDeviceCode({ metadata, clientId, fetch: deps.fetch });
|
|
18241
|
+
deps.onDevicePrompt?.(device);
|
|
18242
|
+
return pollForToken({
|
|
18243
|
+
metadata,
|
|
18244
|
+
issuer: deps.issuer,
|
|
18245
|
+
clientId,
|
|
18246
|
+
deviceCode: device.deviceCode,
|
|
18247
|
+
intervalSeconds: device.intervalSeconds,
|
|
18248
|
+
expiresInSeconds: device.expiresInSeconds,
|
|
18249
|
+
fetch: deps.fetch,
|
|
18250
|
+
sleep: deps.sleep ?? ((ms) => sleep(ms)),
|
|
18251
|
+
now: deps.now
|
|
18252
|
+
});
|
|
18253
|
+
}
|
|
18254
|
+
|
|
18255
|
+
// src/oauth/revoke.ts
|
|
18256
|
+
async function revokeToken(options) {
|
|
18257
|
+
try {
|
|
18258
|
+
const issuer = assertRasterOrLocalHost(options.tokens.issuer, "auth issuer");
|
|
18259
|
+
const metadata = await discover(issuer, options.fetch);
|
|
18260
|
+
const clientId = await ensureClientId({
|
|
18261
|
+
issuer,
|
|
18262
|
+
metadata,
|
|
18263
|
+
env: options.env,
|
|
18264
|
+
fetch: options.fetch,
|
|
18265
|
+
pinnedClientId: options.pinnedClientId
|
|
18266
|
+
});
|
|
18267
|
+
const response = await options.fetch(metadata.revocationEndpoint, {
|
|
18268
|
+
method: "POST",
|
|
18269
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
18270
|
+
body: new URLSearchParams({
|
|
18271
|
+
token: options.tokens.refreshToken,
|
|
18272
|
+
token_type_hint: "refresh_token",
|
|
18273
|
+
client_id: clientId
|
|
18274
|
+
}).toString(),
|
|
18275
|
+
signal: AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS)
|
|
18276
|
+
});
|
|
18277
|
+
return response.ok;
|
|
18278
|
+
} catch {
|
|
18279
|
+
return false;
|
|
18280
|
+
}
|
|
18281
|
+
}
|
|
18282
|
+
|
|
17564
18283
|
// src/commands/auth.ts
|
|
17565
18284
|
async function fetchViewer(context, apiKey) {
|
|
17566
18285
|
const client = context.clientFactory({ apiKey });
|
|
17567
18286
|
return unwrapResult(() => client.GET("/me", { params: { header: VERSION_HEADER } }));
|
|
17568
18287
|
}
|
|
17569
|
-
async function loginCommand(context) {
|
|
17570
|
-
|
|
17571
|
-
|
|
17572
|
-
|
|
17573
|
-
throw new UsageError("Provide a key with --api-key, or run `raster auth login` in an interactive terminal.");
|
|
18288
|
+
async function loginCommand(context, options = {}) {
|
|
18289
|
+
if (context.apiKey?.source === "flag") {
|
|
18290
|
+
await apiKeyLogin(context, context.apiKey.key);
|
|
18291
|
+
return;
|
|
17574
18292
|
}
|
|
18293
|
+
if (context.env.RASTER_API_KEY && !context.isInteractive && !options.device && !options.browser) {
|
|
18294
|
+
await apiKeyLogin(context, context.env.RASTER_API_KEY);
|
|
18295
|
+
return;
|
|
18296
|
+
}
|
|
18297
|
+
await oauthLogin(context, options);
|
|
18298
|
+
}
|
|
18299
|
+
async function apiKeyLogin(context, key) {
|
|
17575
18300
|
const viewer = await fetchViewer(context, key);
|
|
17576
18301
|
const config2 = await readConfig(context.env);
|
|
17577
18302
|
await writeConfig({
|
|
17578
18303
|
...config2,
|
|
17579
18304
|
apiKey: key,
|
|
17580
18305
|
organizationId: viewer?.organizationId,
|
|
17581
|
-
libraries: (viewer?.libraries ?? []).
|
|
18306
|
+
libraries: (viewer?.libraries ?? []).map((entry) => entry.library),
|
|
18307
|
+
oauth: undefined
|
|
17582
18308
|
}, context.env);
|
|
17583
18309
|
if (context.json) {
|
|
17584
18310
|
printJson(context, viewer);
|
|
17585
18311
|
return;
|
|
17586
18312
|
}
|
|
17587
18313
|
const organizationLabel = viewer?.organizationName ?? viewer?.organizationId ?? "your organization";
|
|
17588
|
-
note(context, `Logged in to ${organizationLabel}
|
|
18314
|
+
note(context, `Logged in to ${organizationLabel}.`);
|
|
18315
|
+
}
|
|
18316
|
+
function chooseLoginMethod(context, options) {
|
|
18317
|
+
if (options.device && options.browser) {
|
|
18318
|
+
throw new UsageError("Pass only one of --device or --browser.");
|
|
18319
|
+
}
|
|
18320
|
+
if (options.browser) {
|
|
18321
|
+
if (!context.canOpenBrowser()) {
|
|
18322
|
+
throw new UsageError("No browser is available to open. Use --device, or run on a machine with a display.");
|
|
18323
|
+
}
|
|
18324
|
+
return "browser";
|
|
18325
|
+
}
|
|
18326
|
+
if (options.device)
|
|
18327
|
+
return "device";
|
|
18328
|
+
return context.isInteractive && context.canOpenBrowser() ? "browser" : "device";
|
|
18329
|
+
}
|
|
18330
|
+
async function oauthLogin(context, options) {
|
|
18331
|
+
const method = chooseLoginMethod(context, options);
|
|
18332
|
+
const tokens = await runOAuthLogin(method, {
|
|
18333
|
+
issuer: context.issuer,
|
|
18334
|
+
env: context.env,
|
|
18335
|
+
fetch: context.fetch,
|
|
18336
|
+
pinnedClientId: context.pinnedClientId,
|
|
18337
|
+
now: context.now,
|
|
18338
|
+
openBrowser: context.openBrowser,
|
|
18339
|
+
onDevicePrompt: (prompt) => {
|
|
18340
|
+
const target = prompt.verificationUriComplete ? bold(prompt.verificationUriComplete, context.color) : `${bold(prompt.verificationUri, context.color)} and enter code ${bold(prompt.userCode, context.color)}`;
|
|
18341
|
+
note(context, "");
|
|
18342
|
+
note(context, `Visit ${target}`);
|
|
18343
|
+
note(context, "");
|
|
18344
|
+
note(context, dim("Waiting for authentication…", context.color));
|
|
18345
|
+
},
|
|
18346
|
+
onAuthorizeUrl: (url2) => {
|
|
18347
|
+
note(context, "");
|
|
18348
|
+
note(context, `Visit ${bold(url2, context.color)}`);
|
|
18349
|
+
note(context, "");
|
|
18350
|
+
note(context, dim("Waiting for authentication…", context.color));
|
|
18351
|
+
}
|
|
18352
|
+
});
|
|
18353
|
+
await writeOAuthTokens(tokens, context.env);
|
|
18354
|
+
if (context.json) {
|
|
18355
|
+
printJson(context, { issuer: tokens.issuer, scope: tokens.scope, resource: tokens.resource });
|
|
18356
|
+
return;
|
|
18357
|
+
}
|
|
18358
|
+
note(context, "Logged in.");
|
|
17589
18359
|
}
|
|
17590
18360
|
async function logoutCommand(context) {
|
|
17591
18361
|
const config2 = await readConfig(context.env);
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
18362
|
+
if (config2.oauth) {
|
|
18363
|
+
await revokeToken({
|
|
18364
|
+
tokens: config2.oauth,
|
|
18365
|
+
env: context.env,
|
|
18366
|
+
fetch: context.fetch,
|
|
18367
|
+
pinnedClientId: context.pinnedClientId
|
|
18368
|
+
});
|
|
18369
|
+
}
|
|
18370
|
+
const cleared = { ...config2 };
|
|
18371
|
+
delete cleared.apiKey;
|
|
18372
|
+
delete cleared.oauth;
|
|
18373
|
+
await writeConfig(cleared, context.env);
|
|
18374
|
+
note(context, "Logged out.");
|
|
17595
18375
|
}
|
|
17596
18376
|
async function statusCommand(context) {
|
|
17597
|
-
if (!context.
|
|
18377
|
+
if (!context.credential) {
|
|
17598
18378
|
if (context.json) {
|
|
17599
18379
|
printJson(context, { authenticated: false });
|
|
17600
18380
|
return;
|
|
17601
18381
|
}
|
|
17602
|
-
note(context, "Not authenticated. Run `raster auth login
|
|
18382
|
+
note(context, "Not authenticated. Run `raster auth login`.");
|
|
18383
|
+
return;
|
|
18384
|
+
}
|
|
18385
|
+
if (context.credential.kind === "oauth") {
|
|
18386
|
+
statusForOAuth(context, context.credential.tokens);
|
|
18387
|
+
return;
|
|
18388
|
+
}
|
|
18389
|
+
await statusForApiKey(context, context.credential.key, context.credential.source);
|
|
18390
|
+
}
|
|
18391
|
+
function statusForOAuth(context, tokens) {
|
|
18392
|
+
const fresh = isAccessTokenFresh(tokens, context.now());
|
|
18393
|
+
if (context.json) {
|
|
18394
|
+
printJson(context, { authenticated: true, kind: "oauth", issuer: tokens.issuer, scope: tokens.scope, fresh });
|
|
17603
18395
|
return;
|
|
17604
18396
|
}
|
|
17605
|
-
|
|
18397
|
+
note(context, `OAuth login on ${tokens.issuer}, ${tokens.scope} scope.`);
|
|
18398
|
+
note(context, fresh ? "Token fresh." : "Token refreshes on next use.");
|
|
18399
|
+
}
|
|
18400
|
+
async function statusForApiKey(context, key, source) {
|
|
17606
18401
|
let viewer = null;
|
|
17607
18402
|
let failureMessage = null;
|
|
17608
18403
|
try {
|
|
@@ -17611,15 +18406,10 @@ async function statusCommand(context) {
|
|
|
17611
18406
|
failureMessage = error51 instanceof Error ? error51.message : String(error51);
|
|
17612
18407
|
}
|
|
17613
18408
|
if (context.json) {
|
|
17614
|
-
printJson(context, {
|
|
17615
|
-
authenticated: true,
|
|
17616
|
-
source,
|
|
17617
|
-
keyPrefix: maskApiKey(key),
|
|
17618
|
-
organization: viewer ?? null
|
|
17619
|
-
});
|
|
18409
|
+
printJson(context, { authenticated: true, kind: "apiKey", source, keyPrefix: maskApiKey(key), organization: viewer ?? null });
|
|
17620
18410
|
return;
|
|
17621
18411
|
}
|
|
17622
|
-
note(context, `API key ${maskApiKey(key)}
|
|
18412
|
+
note(context, `API key ${maskApiKey(key)}, from ${source}.`);
|
|
17623
18413
|
if (viewer) {
|
|
17624
18414
|
note(context, `Organization: ${viewer.organizationName ?? viewer.organizationId ?? "unknown"} · plan: ${viewer.plan ?? "unknown"}`);
|
|
17625
18415
|
return;
|
|
@@ -17627,25 +18417,34 @@ async function statusCommand(context) {
|
|
|
17627
18417
|
note(context, `Could not validate the key against the API: ${failureMessage ?? "unknown error"}`);
|
|
17628
18418
|
}
|
|
17629
18419
|
async function whoamiCommand(context) {
|
|
17630
|
-
|
|
17631
|
-
const viewer = await
|
|
18420
|
+
requireAuth(context);
|
|
18421
|
+
const viewer = await unwrapResult(() => context.clientFactory().GET("/me", { params: { header: VERSION_HEADER } }));
|
|
17632
18422
|
if (context.json) {
|
|
17633
18423
|
printJson(context, viewer);
|
|
17634
18424
|
return;
|
|
17635
18425
|
}
|
|
18426
|
+
const isBroadOAuthLogin = context.credential?.kind === "oauth" && context.credential.tokens.scope !== "org";
|
|
18427
|
+
const organizations = viewer?.organizations ?? [];
|
|
18428
|
+
if (isBroadOAuthLogin && organizations.length > 0) {
|
|
18429
|
+
renderTable(context, organizations, [
|
|
18430
|
+
{ header: "organization", value: (organization) => organization.organizationId },
|
|
18431
|
+
{ header: "name", value: (organization) => organization.organizationName ?? "" }
|
|
18432
|
+
]);
|
|
18433
|
+
return;
|
|
18434
|
+
}
|
|
17636
18435
|
renderRecord(context, [
|
|
17637
18436
|
{ label: "organization", value: viewer?.organizationId ?? "" },
|
|
17638
18437
|
{ label: "name", value: viewer?.organizationName ?? "" },
|
|
17639
18438
|
{ label: "plan", value: viewer?.plan ?? "" },
|
|
17640
|
-
{ label: "libraries", value: (viewer?.libraries ?? []).join(", ") }
|
|
18439
|
+
{ label: "libraries", value: (viewer?.libraries ?? []).map((entry) => entry.library).join(", ") }
|
|
17641
18440
|
]);
|
|
17642
18441
|
}
|
|
17643
18442
|
function registerAuthCommands(program2, runAction) {
|
|
17644
18443
|
const auth = program2.command("auth").description("Manage CLI authentication");
|
|
17645
|
-
auth.command("login").description("
|
|
17646
|
-
auth.command("logout").description("
|
|
17647
|
-
auth.command("status").description("Show which
|
|
17648
|
-
program2.command("whoami").description("Show the
|
|
18444
|
+
auth.command("login").description("Sign in via OAuth (opens a browser when available, otherwise a device code; force with --device/--browser), or validate an API key with --api-key").option("--device", "Force the device authorization grant (SSH/headless friendly)").option("--browser", "Use the browser authorization-code flow").action((options, command) => runAction(command, (context) => loginCommand(context, { device: options.device, browser: options.browser })));
|
|
18445
|
+
auth.command("logout").description("Revoke the OAuth token (if any) and remove stored credentials").action((_options, command) => runAction(command, (context) => logoutCommand(context)));
|
|
18446
|
+
auth.command("status").description("Show which credential is in use and where it came from").action((_options, command) => runAction(command, (context) => statusCommand(context)));
|
|
18447
|
+
program2.command("whoami").description("Show the organizations and libraries the current credential can access").action((_options, command) => runAction(command, (context) => whoamiCommand(context)));
|
|
17649
18448
|
}
|
|
17650
18449
|
|
|
17651
18450
|
// src/commands/libraries.ts
|
|
@@ -17661,7 +18460,7 @@ var listOptionsSchema2 = exports_external.object({
|
|
|
17661
18460
|
pageSize: exports_external.coerce.number().int().min(1).optional()
|
|
17662
18461
|
});
|
|
17663
18462
|
async function listLibrariesCommand(context, options) {
|
|
17664
|
-
|
|
18463
|
+
requireAuth(context);
|
|
17665
18464
|
const organizationId = await context.resolveOrg();
|
|
17666
18465
|
const client = context.clientFactory();
|
|
17667
18466
|
const libraries = await unwrapResult(() => client.GET("/organizations/{organizationId}/libraries", {
|
|
@@ -17682,7 +18481,7 @@ var createOptionsSchema = exports_external.object({
|
|
|
17682
18481
|
slug: exports_external.string().min(1).optional()
|
|
17683
18482
|
});
|
|
17684
18483
|
async function createLibraryCommand(context, options) {
|
|
17685
|
-
|
|
18484
|
+
requireAuth(context);
|
|
17686
18485
|
const organizationId = await context.resolveOrg();
|
|
17687
18486
|
const client = context.clientFactory();
|
|
17688
18487
|
const library = await unwrapResult(() => client.POST("/organizations/{organizationId}/libraries", {
|
|
@@ -17703,7 +18502,7 @@ var renameOptionsSchema = exports_external.object({
|
|
|
17703
18502
|
name: exports_external.string().min(1)
|
|
17704
18503
|
});
|
|
17705
18504
|
async function renameLibraryCommand(context, options) {
|
|
17706
|
-
|
|
18505
|
+
requireAuth(context);
|
|
17707
18506
|
const organizationId = await context.resolveOrg();
|
|
17708
18507
|
const libraryId = await context.resolveLibrary();
|
|
17709
18508
|
const client = context.clientFactory();
|
|
@@ -17779,7 +18578,7 @@ var listOptionsSchema3 = exports_external.object({
|
|
|
17779
18578
|
limit: exports_external.coerce.number().int().min(1).optional()
|
|
17780
18579
|
});
|
|
17781
18580
|
async function listTagsCommand(context, options) {
|
|
17782
|
-
|
|
18581
|
+
requireAuth(context);
|
|
17783
18582
|
const organizationId = await context.resolveOrg();
|
|
17784
18583
|
const libraryId = await context.resolveLibrary();
|
|
17785
18584
|
const client = context.clientFactory();
|
|
@@ -17802,7 +18601,7 @@ var mutateOptionsSchema = exports_external.object({
|
|
|
17802
18601
|
async function tagAssetsCommand(context, assetIds, options) {
|
|
17803
18602
|
if (assetIds.length === 0)
|
|
17804
18603
|
throw new UsageError("Provide at least one asset id.");
|
|
17805
|
-
|
|
18604
|
+
requireAuth(context);
|
|
17806
18605
|
const organizationId = await context.resolveOrg();
|
|
17807
18606
|
const libraryId = await context.resolveLibrary();
|
|
17808
18607
|
const client = context.clientFactory();
|
|
@@ -17819,7 +18618,7 @@ async function tagAssetsCommand(context, assetIds, options) {
|
|
|
17819
18618
|
async function untagAssetsCommand(context, assetIds, options) {
|
|
17820
18619
|
if (assetIds.length === 0)
|
|
17821
18620
|
throw new UsageError("Provide at least one asset id.");
|
|
17822
|
-
|
|
18621
|
+
requireAuth(context);
|
|
17823
18622
|
const organizationId = await context.resolveOrg();
|
|
17824
18623
|
const libraryId = await context.resolveLibrary();
|
|
17825
18624
|
const client = context.clientFactory();
|