@raster-app/cli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -46
- package/dist/index.js +866 -80
- 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.0",
|
|
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,13 +17088,334 @@ 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
|
|
|
@@ -17193,10 +17532,15 @@ function promptSecret(message) {
|
|
|
17193
17532
|
}
|
|
17194
17533
|
|
|
17195
17534
|
// src/lib/context.ts
|
|
17535
|
+
var RASTER_ORG_ENV = "RASTER_ORG";
|
|
17536
|
+
var PINNED_CLIENT_ID_ENV = "RASTER_OAUTH_CLIENT_ID";
|
|
17537
|
+
function toLibraryIds(libraries) {
|
|
17538
|
+
return (libraries ?? []).map((entry) => typeof entry === "string" ? entry : entry?.library).filter((id) => typeof id === "string");
|
|
17539
|
+
}
|
|
17196
17540
|
async function createCommandContext(globals, overrides = {}) {
|
|
17197
17541
|
const env = overrides.env ?? process.env;
|
|
17198
17542
|
const config2 = await readConfig(env);
|
|
17199
|
-
const
|
|
17543
|
+
const now = overrides.now ?? Date.now;
|
|
17200
17544
|
const output = {
|
|
17201
17545
|
json: globals.json === true,
|
|
17202
17546
|
verbose: globals.verbose === true,
|
|
@@ -17205,61 +17549,126 @@ async function createCommandContext(globals, overrides = {}) {
|
|
|
17205
17549
|
};
|
|
17206
17550
|
const baseUrl = validateBaseUrl(env.RASTER_API_BASE_URL ?? DEFAULT_BASE_URL);
|
|
17207
17551
|
const baseFetch = overrides.fetch ?? globalThis.fetch;
|
|
17208
|
-
const
|
|
17552
|
+
const credential = await resolveActiveCredential({
|
|
17553
|
+
flagKey: globals.apiKey,
|
|
17554
|
+
env,
|
|
17555
|
+
config: config2,
|
|
17556
|
+
fetch: instrumentFetch(baseFetch, output, null),
|
|
17557
|
+
now: now()
|
|
17558
|
+
});
|
|
17559
|
+
const apiKey = credential?.kind === "apiKey" ? { key: credential.key, source: credential.source } : null;
|
|
17560
|
+
const bearer = credentialBearer(credential);
|
|
17561
|
+
const instrumentedFetch = instrumentFetch(baseFetch, output, bearer ? maskApiKey(bearer) : null);
|
|
17209
17562
|
const clientFactory = (clientOverrides) => createApiClient({
|
|
17210
17563
|
baseUrl,
|
|
17211
|
-
apiKey: clientOverrides ? clientOverrides.apiKey :
|
|
17564
|
+
apiKey: clientOverrides ? clientOverrides.apiKey : bearer,
|
|
17212
17565
|
fetch: instrumentedFetch
|
|
17213
17566
|
});
|
|
17214
17567
|
let viewerPromise = null;
|
|
17215
17568
|
const loadViewer = () => {
|
|
17216
17569
|
if (!viewerPromise) {
|
|
17217
|
-
if (!
|
|
17218
|
-
throw new AuthSetupError("
|
|
17570
|
+
if (!credential) {
|
|
17571
|
+
throw new AuthSetupError("Not authenticated. Run `raster auth login` or set RASTER_API_KEY.");
|
|
17219
17572
|
}
|
|
17220
|
-
viewerPromise = unwrapResult(() => clientFactory(
|
|
17573
|
+
viewerPromise = unwrapResult(() => clientFactory().GET("/me", { params: { header: VERSION_HEADER } }));
|
|
17221
17574
|
}
|
|
17222
17575
|
return viewerPromise;
|
|
17223
17576
|
};
|
|
17224
17577
|
const cacheMatchesKey = config2.apiKey !== undefined && apiKey?.key === config2.apiKey;
|
|
17578
|
+
const resolveOrgId = async () => {
|
|
17579
|
+
if (globals.org)
|
|
17580
|
+
return globals.org;
|
|
17581
|
+
if (env[RASTER_ORG_ENV])
|
|
17582
|
+
return env[RASTER_ORG_ENV];
|
|
17583
|
+
if (credential?.kind === "oauth") {
|
|
17584
|
+
if (credential.tokens.scope === "org") {
|
|
17585
|
+
const viewer2 = await loadViewer();
|
|
17586
|
+
if (viewer2.organizationId)
|
|
17587
|
+
return viewer2.organizationId;
|
|
17588
|
+
throw new CliError("This login is bound to an organization that is no longer available. Run `raster auth login` again.", EXIT_CODES.auth);
|
|
17589
|
+
}
|
|
17590
|
+
if (config2.defaultOrg)
|
|
17591
|
+
return config2.defaultOrg;
|
|
17592
|
+
throw new UsageError("This login can access multiple organizations. Pass --org <id> (or set RASTER_ORG); run `raster whoami` to list them.");
|
|
17593
|
+
}
|
|
17594
|
+
if (cacheMatchesKey && config2.organizationId)
|
|
17595
|
+
return config2.organizationId;
|
|
17596
|
+
const viewer = await loadViewer();
|
|
17597
|
+
if (!viewer.organizationId) {
|
|
17598
|
+
throw new CliError("Could not determine the organization from the API key.", EXIT_CODES.generic);
|
|
17599
|
+
}
|
|
17600
|
+
return viewer.organizationId;
|
|
17601
|
+
};
|
|
17602
|
+
const librariesForTarget = async () => {
|
|
17603
|
+
const cached2 = cacheMatchesKey ? config2.libraries : undefined;
|
|
17604
|
+
if (cached2)
|
|
17605
|
+
return cached2;
|
|
17606
|
+
const viewer = await loadViewer();
|
|
17607
|
+
if (credential?.kind === "oauth" && credential.tokens.scope !== "org") {
|
|
17608
|
+
const organizationId = await resolveOrgId();
|
|
17609
|
+
const entry = viewer.organizations?.find((organization) => organization?.organizationId === organizationId);
|
|
17610
|
+
return entry?.libraries ?? [];
|
|
17611
|
+
}
|
|
17612
|
+
return viewer.libraries ?? [];
|
|
17613
|
+
};
|
|
17225
17614
|
return {
|
|
17226
17615
|
...output,
|
|
17227
17616
|
env,
|
|
17228
17617
|
baseUrl,
|
|
17229
17618
|
isInteractive: overrides.isInteractive ?? process.stdin.isTTY === true,
|
|
17619
|
+
credential,
|
|
17230
17620
|
apiKey,
|
|
17621
|
+
bearerToken: bearer,
|
|
17231
17622
|
fetch: instrumentedFetch,
|
|
17232
17623
|
clientFactory,
|
|
17233
17624
|
confirm: overrides.confirm ?? promptConfirm,
|
|
17234
17625
|
promptSecret: overrides.promptSecret ?? promptSecret,
|
|
17626
|
+
now,
|
|
17627
|
+
issuer: resolveIssuer({ env, config: config2 }),
|
|
17628
|
+
pinnedClientId: env[PINNED_CLIENT_ID_ENV],
|
|
17629
|
+
openBrowser: overrides.openBrowser ?? openBrowser,
|
|
17630
|
+
canOpenBrowser: overrides.canOpenBrowser ?? canOpenBrowser,
|
|
17235
17631
|
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
|
-
},
|
|
17632
|
+
resolveOrg: resolveOrgId,
|
|
17247
17633
|
resolveLibrary: async () => {
|
|
17248
17634
|
if (globals.library)
|
|
17249
17635
|
return globals.library;
|
|
17250
|
-
const
|
|
17251
|
-
const source = cachedLibraries ?? (await loadViewer()).libraries ?? [];
|
|
17252
|
-
const libraries = source.filter((value) => typeof value === "string");
|
|
17636
|
+
const libraries = toLibraryIds(await librariesForTarget());
|
|
17253
17637
|
const onlyLibrary = libraries[0];
|
|
17254
17638
|
if (libraries.length === 1 && onlyLibrary)
|
|
17255
17639
|
return onlyLibrary;
|
|
17256
17640
|
if (libraries.length === 0) {
|
|
17257
|
-
throw new AuthSetupError("
|
|
17641
|
+
throw new AuthSetupError("No library access for this login. Pass --library <id>.");
|
|
17258
17642
|
}
|
|
17259
|
-
throw new UsageError(`
|
|
17643
|
+
throw new UsageError(`This login can access ${libraries.length} libraries (${libraries.join(", ")}). Pass --library <id>.`);
|
|
17260
17644
|
}
|
|
17261
17645
|
};
|
|
17262
17646
|
}
|
|
17647
|
+
function credentialBearer(credential) {
|
|
17648
|
+
if (!credential)
|
|
17649
|
+
return null;
|
|
17650
|
+
return credential.kind === "apiKey" ? credential.key : credential.tokens.accessToken;
|
|
17651
|
+
}
|
|
17652
|
+
async function resolveActiveCredential(options) {
|
|
17653
|
+
const credential = resolveCredential({ flagKey: options.flagKey, env: options.env, config: options.config });
|
|
17654
|
+
if (credential?.kind !== "oauth")
|
|
17655
|
+
return credential;
|
|
17656
|
+
const session = await ensureFreshOAuthSession({
|
|
17657
|
+
tokens: credential.tokens,
|
|
17658
|
+
env: options.env,
|
|
17659
|
+
fetch: options.fetch,
|
|
17660
|
+
pinnedClientId: options.env[PINNED_CLIENT_ID_ENV],
|
|
17661
|
+
now: options.now
|
|
17662
|
+
});
|
|
17663
|
+
if (session.status === "reauth-required") {
|
|
17664
|
+
return resolveCredential({
|
|
17665
|
+
flagKey: options.flagKey,
|
|
17666
|
+
env: options.env,
|
|
17667
|
+
config: { ...options.config, oauth: undefined }
|
|
17668
|
+
});
|
|
17669
|
+
}
|
|
17670
|
+
return { kind: "oauth", tokens: session.tokens };
|
|
17671
|
+
}
|
|
17263
17672
|
function instrumentFetch(baseFetch, output, maskedKey) {
|
|
17264
17673
|
return async (input, init) => {
|
|
17265
17674
|
const request = input instanceof Request ? input : new Request(String(input), init);
|
|
@@ -17276,10 +17685,15 @@ function instrumentFetch(baseFetch, output, maskedKey) {
|
|
|
17276
17685
|
}
|
|
17277
17686
|
};
|
|
17278
17687
|
}
|
|
17279
|
-
function
|
|
17280
|
-
if (context.
|
|
17281
|
-
return context.
|
|
17282
|
-
throw new AuthSetupError("
|
|
17688
|
+
function requireAuth(context) {
|
|
17689
|
+
if (context.credential)
|
|
17690
|
+
return context.credential;
|
|
17691
|
+
throw new AuthSetupError("Not authenticated. Run `raster auth login` or set RASTER_API_KEY.");
|
|
17692
|
+
}
|
|
17693
|
+
function requireBearer(context) {
|
|
17694
|
+
if (context.bearerToken)
|
|
17695
|
+
return context.bearerToken;
|
|
17696
|
+
throw new AuthSetupError("Not authenticated. Run `raster auth login` or set RASTER_API_KEY.");
|
|
17283
17697
|
}
|
|
17284
17698
|
async function executeCommand(context, action) {
|
|
17285
17699
|
try {
|
|
@@ -17333,7 +17747,7 @@ async function listAssetsCommand(context, options) {
|
|
|
17333
17747
|
if (options.tag.length > MAX_TAGS_PER_QUERY) {
|
|
17334
17748
|
throw new ValidationError(`At most ${MAX_TAGS_PER_QUERY} --tag filters are supported per query.`);
|
|
17335
17749
|
}
|
|
17336
|
-
|
|
17750
|
+
requireAuth(context);
|
|
17337
17751
|
const organizationId = await context.resolveOrg();
|
|
17338
17752
|
const libraryId = await context.resolveLibrary();
|
|
17339
17753
|
const client = context.clientFactory();
|
|
@@ -17355,7 +17769,7 @@ async function listAssetsCommand(context, options) {
|
|
|
17355
17769
|
renderTable(context, assets, ASSET_COLUMNS);
|
|
17356
17770
|
}
|
|
17357
17771
|
async function getAssetCommand(context, assetId) {
|
|
17358
|
-
|
|
17772
|
+
requireAuth(context);
|
|
17359
17773
|
const asset = await fetchAsset(context, assetId);
|
|
17360
17774
|
if (context.json) {
|
|
17361
17775
|
printJson(context, asset);
|
|
@@ -17385,7 +17799,7 @@ var searchOptionsSchema = exports_external.object({
|
|
|
17385
17799
|
pageSize: exports_external.coerce.number().int().min(1).optional()
|
|
17386
17800
|
});
|
|
17387
17801
|
async function searchAssetsCommand(context, query, options) {
|
|
17388
|
-
|
|
17802
|
+
requireAuth(context);
|
|
17389
17803
|
const organizationId = await context.resolveOrg();
|
|
17390
17804
|
const client = context.clientFactory();
|
|
17391
17805
|
const result = await unwrapResult(() => client.GET("/organizations/{organizationId}/search/assets", {
|
|
@@ -17412,7 +17826,7 @@ var downloadOptionsSchema = exports_external.object({
|
|
|
17412
17826
|
force: exports_external.boolean().default(false)
|
|
17413
17827
|
});
|
|
17414
17828
|
async function downloadAssetCommand(context, assetId, options) {
|
|
17415
|
-
|
|
17829
|
+
requireAuth(context);
|
|
17416
17830
|
const asset = await fetchAsset(context, assetId);
|
|
17417
17831
|
const fileUrl = asset.url;
|
|
17418
17832
|
if (!fileUrl)
|
|
@@ -17440,7 +17854,7 @@ var uploadedAssetsSchema = exports_external.object({
|
|
|
17440
17854
|
async function uploadAssetsCommand(context, files) {
|
|
17441
17855
|
if (files.length === 0)
|
|
17442
17856
|
throw new UsageError("Provide at least one file to upload.");
|
|
17443
|
-
const
|
|
17857
|
+
const bearer = requireBearer(context);
|
|
17444
17858
|
const organizationId = await context.resolveOrg();
|
|
17445
17859
|
const libraryId = await context.resolveLibrary();
|
|
17446
17860
|
const batches = chunk(files, MAX_UPLOAD_FILES);
|
|
@@ -17460,7 +17874,7 @@ async function uploadAssetsCommand(context, files) {
|
|
|
17460
17874
|
if (batches.length > 1) {
|
|
17461
17875
|
note(context, `Uploading batch ${batchIndex + 1}/${batches.length} (${batch.length} files)…`);
|
|
17462
17876
|
}
|
|
17463
|
-
const response = await context.fetch(`${context.baseUrl}/organizations/${organizationId}/libraries/${libraryId}/assets`, { method: "POST", headers: buildRequestHeaders(
|
|
17877
|
+
const response = await context.fetch(`${context.baseUrl}/organizations/${organizationId}/libraries/${libraryId}/assets`, { method: "POST", headers: buildRequestHeaders(bearer), body: formData });
|
|
17464
17878
|
const payload = await parseEnvelope(response);
|
|
17465
17879
|
payloads.push(payload);
|
|
17466
17880
|
const parsed = uploadedAssetsSchema.safeParse(payload);
|
|
@@ -17487,7 +17901,7 @@ async function removeAssetsCommand(context, assetIds, options) {
|
|
|
17487
17901
|
if (assetIds.length > MAX_DELETE_IDS) {
|
|
17488
17902
|
throw new ValidationError(`At most ${MAX_DELETE_IDS} asset ids are supported per call.`);
|
|
17489
17903
|
}
|
|
17490
|
-
|
|
17904
|
+
requireAuth(context);
|
|
17491
17905
|
const organizationId = await context.resolveOrg();
|
|
17492
17906
|
const libraryId = await context.resolveLibrary();
|
|
17493
17907
|
if (!options.yes && context.isInteractive) {
|
|
@@ -17512,7 +17926,7 @@ var describeOptionsSchema = exports_external.object({
|
|
|
17512
17926
|
text: exports_external.string().min(1)
|
|
17513
17927
|
});
|
|
17514
17928
|
async function describeAssetCommand(context, assetId, options) {
|
|
17515
|
-
|
|
17929
|
+
requireAuth(context);
|
|
17516
17930
|
const organizationId = await context.resolveOrg();
|
|
17517
17931
|
const libraryId = await context.resolveLibrary();
|
|
17518
17932
|
const client = context.clientFactory();
|
|
@@ -17535,7 +17949,7 @@ var transferOptionsSchema = exports_external.object({
|
|
|
17535
17949
|
async function transferAssetsCommand(context, assetIds, options) {
|
|
17536
17950
|
if (assetIds.length === 0)
|
|
17537
17951
|
throw new UsageError("Provide at least one asset id.");
|
|
17538
|
-
|
|
17952
|
+
requireAuth(context);
|
|
17539
17953
|
const organizationId = await context.resolveOrg();
|
|
17540
17954
|
const libraryId = await context.resolveLibrary();
|
|
17541
17955
|
const client = context.clientFactory();
|
|
@@ -17561,24 +17975,329 @@ function registerAssetCommands(program2, runAction) {
|
|
|
17561
17975
|
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
17976
|
}
|
|
17563
17977
|
|
|
17978
|
+
// src/oauth/login.ts
|
|
17979
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
17980
|
+
|
|
17981
|
+
// src/oauth/loopback.ts
|
|
17982
|
+
import { createServer } from "node:http";
|
|
17983
|
+
var CALLBACK_PATH = "/callback";
|
|
17984
|
+
var LOOPBACK_HOST = "127.0.0.1";
|
|
17985
|
+
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>";
|
|
17986
|
+
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>";
|
|
17987
|
+
async function startLoopbackServer() {
|
|
17988
|
+
let resolveCallback;
|
|
17989
|
+
let rejectCallback;
|
|
17990
|
+
const callbackPromise = new Promise((resolve, reject) => {
|
|
17991
|
+
resolveCallback = resolve;
|
|
17992
|
+
rejectCallback = reject;
|
|
17993
|
+
});
|
|
17994
|
+
let settled = false;
|
|
17995
|
+
const server = createServer((request, response) => {
|
|
17996
|
+
const url2 = new URL(request.url ?? "/", `http://${LOOPBACK_HOST}`);
|
|
17997
|
+
if (url2.pathname !== CALLBACK_PATH) {
|
|
17998
|
+
response.writeHead(404).end();
|
|
17999
|
+
return;
|
|
18000
|
+
}
|
|
18001
|
+
const code = url2.searchParams.get("code");
|
|
18002
|
+
const state = url2.searchParams.get("state");
|
|
18003
|
+
if (code && state) {
|
|
18004
|
+
response.writeHead(200, { "Content-Type": "text/html" }).end(SUCCESS_PAGE);
|
|
18005
|
+
if (!settled) {
|
|
18006
|
+
settled = true;
|
|
18007
|
+
resolveCallback({ code, state });
|
|
18008
|
+
}
|
|
18009
|
+
return;
|
|
18010
|
+
}
|
|
18011
|
+
response.writeHead(400, { "Content-Type": "text/html" }).end(FAILURE_PAGE);
|
|
18012
|
+
if (!settled) {
|
|
18013
|
+
settled = true;
|
|
18014
|
+
const error51 = url2.searchParams.get("error");
|
|
18015
|
+
rejectCallback(new CliError(error51 ? `The authorization server returned an error: ${error51}.` : "The authorization server returned no code.", EXIT_CODES.auth));
|
|
18016
|
+
}
|
|
18017
|
+
});
|
|
18018
|
+
let port;
|
|
18019
|
+
try {
|
|
18020
|
+
port = await listenOnEphemeralPort(server);
|
|
18021
|
+
} catch (error51) {
|
|
18022
|
+
server.close();
|
|
18023
|
+
throw error51;
|
|
18024
|
+
}
|
|
18025
|
+
return {
|
|
18026
|
+
redirectUri: `http://${LOOPBACK_HOST}:${port}${CALLBACK_PATH}`,
|
|
18027
|
+
waitForCallback: () => callbackPromise,
|
|
18028
|
+
close: () => {
|
|
18029
|
+
server.close();
|
|
18030
|
+
}
|
|
18031
|
+
};
|
|
18032
|
+
}
|
|
18033
|
+
function listenOnEphemeralPort(server) {
|
|
18034
|
+
return new Promise((resolve, reject) => {
|
|
18035
|
+
server.once("error", reject);
|
|
18036
|
+
server.listen(0, LOOPBACK_HOST, () => {
|
|
18037
|
+
const address = server.address();
|
|
18038
|
+
if (address !== null && typeof address === "object" && typeof address.port === "number") {
|
|
18039
|
+
resolve(address.port);
|
|
18040
|
+
return;
|
|
18041
|
+
}
|
|
18042
|
+
reject(new CliError("Could not bind a loopback port for the browser login.", EXIT_CODES.generic));
|
|
18043
|
+
});
|
|
18044
|
+
});
|
|
18045
|
+
}
|
|
18046
|
+
|
|
18047
|
+
// src/oauth/pkce.ts
|
|
18048
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
18049
|
+
var VERIFIER_BYTES = 32;
|
|
18050
|
+
var STATE_BYTES = 16;
|
|
18051
|
+
function base64UrlEncode(input) {
|
|
18052
|
+
return input.toString("base64url");
|
|
18053
|
+
}
|
|
18054
|
+
function createPkce() {
|
|
18055
|
+
const verifier = base64UrlEncode(randomBytes(VERIFIER_BYTES));
|
|
18056
|
+
const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
|
|
18057
|
+
return { verifier, challenge, method: "S256" };
|
|
18058
|
+
}
|
|
18059
|
+
function createState() {
|
|
18060
|
+
return base64UrlEncode(randomBytes(STATE_BYTES));
|
|
18061
|
+
}
|
|
18062
|
+
|
|
18063
|
+
// src/oauth/browser.ts
|
|
18064
|
+
var DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60000;
|
|
18065
|
+
async function withDeadline(promise2, ms) {
|
|
18066
|
+
let timer;
|
|
18067
|
+
try {
|
|
18068
|
+
return await Promise.race([
|
|
18069
|
+
promise2,
|
|
18070
|
+
new Promise((_, reject) => {
|
|
18071
|
+
timer = setTimeout(() => reject(new CliError("Timed out waiting for browser sign-in. Run `raster auth login` again.", EXIT_CODES.auth)), ms);
|
|
18072
|
+
})
|
|
18073
|
+
]);
|
|
18074
|
+
} finally {
|
|
18075
|
+
if (timer)
|
|
18076
|
+
clearTimeout(timer);
|
|
18077
|
+
}
|
|
18078
|
+
}
|
|
18079
|
+
async function runBrowserLogin(options) {
|
|
18080
|
+
const pkce = createPkce();
|
|
18081
|
+
const state = createState();
|
|
18082
|
+
const server = await startLoopbackServer();
|
|
18083
|
+
try {
|
|
18084
|
+
const authorizeUrl = buildAuthorizeUrl({
|
|
18085
|
+
metadata: options.metadata,
|
|
18086
|
+
clientId: options.clientId,
|
|
18087
|
+
redirectUri: server.redirectUri,
|
|
18088
|
+
challenge: pkce.challenge,
|
|
18089
|
+
state
|
|
18090
|
+
});
|
|
18091
|
+
await options.openBrowser(authorizeUrl);
|
|
18092
|
+
const callback = await withDeadline(server.waitForCallback(), options.loginTimeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS);
|
|
18093
|
+
if (callback.state !== state) {
|
|
18094
|
+
throw new CliError("The browser login response failed its state check. Run `raster auth login` again.", EXIT_CODES.auth);
|
|
18095
|
+
}
|
|
18096
|
+
const body = await postForm(options.metadata.tokenEndpoint, {
|
|
18097
|
+
grant_type: "authorization_code",
|
|
18098
|
+
code: callback.code,
|
|
18099
|
+
redirect_uri: server.redirectUri,
|
|
18100
|
+
client_id: options.clientId,
|
|
18101
|
+
code_verifier: pkce.verifier,
|
|
18102
|
+
resource: RESOURCE_INDICATOR
|
|
18103
|
+
}, options.fetch);
|
|
18104
|
+
return toStoredTokens(parseTokenResponse(body), {
|
|
18105
|
+
issuer: options.issuer,
|
|
18106
|
+
resource: RESOURCE_INDICATOR,
|
|
18107
|
+
now: options.now()
|
|
18108
|
+
});
|
|
18109
|
+
} finally {
|
|
18110
|
+
server.close();
|
|
18111
|
+
}
|
|
18112
|
+
}
|
|
18113
|
+
function buildAuthorizeUrl(options) {
|
|
18114
|
+
const url2 = new URL(options.metadata.authorizationEndpoint);
|
|
18115
|
+
url2.search = new URLSearchParams({
|
|
18116
|
+
response_type: "code",
|
|
18117
|
+
client_id: options.clientId,
|
|
18118
|
+
redirect_uri: options.redirectUri,
|
|
18119
|
+
scope: USER_SCOPE,
|
|
18120
|
+
resource: RESOURCE_INDICATOR,
|
|
18121
|
+
code_challenge: options.challenge,
|
|
18122
|
+
code_challenge_method: "S256",
|
|
18123
|
+
state: options.state
|
|
18124
|
+
}).toString();
|
|
18125
|
+
return url2.toString();
|
|
18126
|
+
}
|
|
18127
|
+
|
|
18128
|
+
// src/oauth/device.ts
|
|
18129
|
+
var SLOW_DOWN_INCREMENT_SECONDS = 5;
|
|
18130
|
+
var SECONDS_TO_MS2 = 1000;
|
|
18131
|
+
var deviceResponseSchema = exports_external.object({
|
|
18132
|
+
device_code: exports_external.string().min(1),
|
|
18133
|
+
user_code: exports_external.string().min(1),
|
|
18134
|
+
verification_uri: exports_external.url(),
|
|
18135
|
+
verification_uri_complete: exports_external.url().optional(),
|
|
18136
|
+
expires_in: exports_external.number().positive(),
|
|
18137
|
+
interval: exports_external.number().positive().optional()
|
|
18138
|
+
});
|
|
18139
|
+
var DEFAULT_INTERVAL_SECONDS = 5;
|
|
18140
|
+
async function requestDeviceCode(options) {
|
|
18141
|
+
const body = await postForm(options.metadata.deviceAuthorizationEndpoint, { client_id: options.clientId, scope: USER_SCOPE, resource: RESOURCE_INDICATOR }, options.fetch);
|
|
18142
|
+
const parsed = deviceResponseSchema.safeParse(body);
|
|
18143
|
+
if (!parsed.success) {
|
|
18144
|
+
throw new CliError("The device authorization response was malformed.", EXIT_CODES.auth);
|
|
18145
|
+
}
|
|
18146
|
+
return {
|
|
18147
|
+
deviceCode: parsed.data.device_code,
|
|
18148
|
+
userCode: parsed.data.user_code,
|
|
18149
|
+
verificationUri: parsed.data.verification_uri,
|
|
18150
|
+
verificationUriComplete: parsed.data.verification_uri_complete,
|
|
18151
|
+
expiresInSeconds: parsed.data.expires_in,
|
|
18152
|
+
intervalSeconds: parsed.data.interval ?? DEFAULT_INTERVAL_SECONDS
|
|
18153
|
+
};
|
|
18154
|
+
}
|
|
18155
|
+
function nextPollAction(errorCode, interval) {
|
|
18156
|
+
if (errorCode === "authorization_pending")
|
|
18157
|
+
return { kind: "wait", interval };
|
|
18158
|
+
if (errorCode === "slow_down")
|
|
18159
|
+
return { kind: "wait", interval: interval + SLOW_DOWN_INCREMENT_SECONDS };
|
|
18160
|
+
return { kind: "fail" };
|
|
18161
|
+
}
|
|
18162
|
+
async function pollForToken(options) {
|
|
18163
|
+
const deadline = options.now() + options.expiresInSeconds * SECONDS_TO_MS2;
|
|
18164
|
+
let interval = options.intervalSeconds;
|
|
18165
|
+
while (true) {
|
|
18166
|
+
if (options.now() >= deadline) {
|
|
18167
|
+
throw new CliError("The device code expired before you finished signing in. Run `raster auth login` again.", EXIT_CODES.auth);
|
|
18168
|
+
}
|
|
18169
|
+
let outcome;
|
|
18170
|
+
try {
|
|
18171
|
+
outcome = await pollOnce(options);
|
|
18172
|
+
} catch (error51) {
|
|
18173
|
+
if (!(error51 instanceof NetworkError))
|
|
18174
|
+
throw error51;
|
|
18175
|
+
outcome = { kind: "error", error: "authorization_pending" };
|
|
18176
|
+
}
|
|
18177
|
+
if (outcome.kind === "tokens")
|
|
18178
|
+
return outcome.tokens;
|
|
18179
|
+
const action = nextPollAction(outcome.error, interval);
|
|
18180
|
+
if (action.kind === "fail") {
|
|
18181
|
+
throw new CliError(`Device authorization failed: ${outcome.error}. Run \`raster auth login\` again.`, EXIT_CODES.auth);
|
|
18182
|
+
}
|
|
18183
|
+
interval = action.interval;
|
|
18184
|
+
await options.sleep(interval * SECONDS_TO_MS2);
|
|
18185
|
+
}
|
|
18186
|
+
}
|
|
18187
|
+
async function pollOnce(options) {
|
|
18188
|
+
try {
|
|
18189
|
+
const body = await postForm(options.metadata.tokenEndpoint, { grant_type: DEVICE_CODE_GRANT_TYPE, device_code: options.deviceCode, client_id: options.clientId }, options.fetch);
|
|
18190
|
+
const tokens = toStoredTokens(parseTokenResponse(body), {
|
|
18191
|
+
issuer: options.issuer,
|
|
18192
|
+
resource: RESOURCE_INDICATOR,
|
|
18193
|
+
now: options.now()
|
|
18194
|
+
});
|
|
18195
|
+
return { kind: "tokens", tokens };
|
|
18196
|
+
} catch (error51) {
|
|
18197
|
+
if (error51 instanceof OAuthError)
|
|
18198
|
+
return { kind: "error", error: error51.error };
|
|
18199
|
+
throw error51;
|
|
18200
|
+
}
|
|
18201
|
+
}
|
|
18202
|
+
|
|
18203
|
+
// src/oauth/login.ts
|
|
18204
|
+
async function runOAuthLogin(method, deps) {
|
|
18205
|
+
const metadata = await discover(deps.issuer, deps.fetch);
|
|
18206
|
+
const clientId = await ensureClientId({
|
|
18207
|
+
issuer: deps.issuer,
|
|
18208
|
+
metadata,
|
|
18209
|
+
env: deps.env,
|
|
18210
|
+
fetch: deps.fetch,
|
|
18211
|
+
pinnedClientId: deps.pinnedClientId
|
|
18212
|
+
});
|
|
18213
|
+
if (method === "browser") {
|
|
18214
|
+
if (!deps.openBrowser) {
|
|
18215
|
+
throw new CliError("No browser opener is available for the authorization-code flow.", EXIT_CODES.auth);
|
|
18216
|
+
}
|
|
18217
|
+
return runBrowserLogin({
|
|
18218
|
+
metadata,
|
|
18219
|
+
issuer: deps.issuer,
|
|
18220
|
+
clientId,
|
|
18221
|
+
fetch: deps.fetch,
|
|
18222
|
+
openBrowser: deps.openBrowser,
|
|
18223
|
+
now: deps.now
|
|
18224
|
+
});
|
|
18225
|
+
}
|
|
18226
|
+
const device = await requestDeviceCode({ metadata, clientId, fetch: deps.fetch });
|
|
18227
|
+
deps.onDevicePrompt?.(device);
|
|
18228
|
+
return pollForToken({
|
|
18229
|
+
metadata,
|
|
18230
|
+
issuer: deps.issuer,
|
|
18231
|
+
clientId,
|
|
18232
|
+
deviceCode: device.deviceCode,
|
|
18233
|
+
intervalSeconds: device.intervalSeconds,
|
|
18234
|
+
expiresInSeconds: device.expiresInSeconds,
|
|
18235
|
+
fetch: deps.fetch,
|
|
18236
|
+
sleep: deps.sleep ?? ((ms) => sleep(ms)),
|
|
18237
|
+
now: deps.now
|
|
18238
|
+
});
|
|
18239
|
+
}
|
|
18240
|
+
|
|
18241
|
+
// src/oauth/revoke.ts
|
|
18242
|
+
async function revokeToken(options) {
|
|
18243
|
+
try {
|
|
18244
|
+
const issuer = assertRasterOrLocalHost(options.tokens.issuer, "auth issuer");
|
|
18245
|
+
const metadata = await discover(issuer, options.fetch);
|
|
18246
|
+
const clientId = await ensureClientId({
|
|
18247
|
+
issuer,
|
|
18248
|
+
metadata,
|
|
18249
|
+
env: options.env,
|
|
18250
|
+
fetch: options.fetch,
|
|
18251
|
+
pinnedClientId: options.pinnedClientId
|
|
18252
|
+
});
|
|
18253
|
+
const response = await options.fetch(metadata.revocationEndpoint, {
|
|
18254
|
+
method: "POST",
|
|
18255
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
18256
|
+
body: new URLSearchParams({
|
|
18257
|
+
token: options.tokens.refreshToken,
|
|
18258
|
+
token_type_hint: "refresh_token",
|
|
18259
|
+
client_id: clientId
|
|
18260
|
+
}).toString(),
|
|
18261
|
+
signal: AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS)
|
|
18262
|
+
});
|
|
18263
|
+
return response.ok;
|
|
18264
|
+
} catch {
|
|
18265
|
+
return false;
|
|
18266
|
+
}
|
|
18267
|
+
}
|
|
18268
|
+
|
|
17564
18269
|
// src/commands/auth.ts
|
|
17565
18270
|
async function fetchViewer(context, apiKey) {
|
|
17566
18271
|
const client = context.clientFactory({ apiKey });
|
|
17567
18272
|
return unwrapResult(() => client.GET("/me", { params: { header: VERSION_HEADER } }));
|
|
17568
18273
|
}
|
|
17569
|
-
async function loginCommand(context) {
|
|
17570
|
-
|
|
17571
|
-
|
|
17572
|
-
|
|
17573
|
-
|
|
18274
|
+
async function loginCommand(context, options = {}) {
|
|
18275
|
+
if (context.apiKey?.source === "flag") {
|
|
18276
|
+
await apiKeyLogin(context, context.apiKey.key);
|
|
18277
|
+
return;
|
|
18278
|
+
}
|
|
18279
|
+
if (!options.device && !options.browser && context.isInteractive) {
|
|
18280
|
+
const promptedKey = await context.promptSecret("Paste your Raster API key (or press Enter to use OAuth)");
|
|
18281
|
+
if (promptedKey) {
|
|
18282
|
+
await apiKeyLogin(context, promptedKey);
|
|
18283
|
+
return;
|
|
18284
|
+
}
|
|
18285
|
+
}
|
|
18286
|
+
if (context.env.RASTER_API_KEY && !context.isInteractive && !options.device && !options.browser) {
|
|
18287
|
+
await apiKeyLogin(context, context.env.RASTER_API_KEY);
|
|
18288
|
+
return;
|
|
17574
18289
|
}
|
|
18290
|
+
await oauthLogin(context, options);
|
|
18291
|
+
}
|
|
18292
|
+
async function apiKeyLogin(context, key) {
|
|
17575
18293
|
const viewer = await fetchViewer(context, key);
|
|
17576
18294
|
const config2 = await readConfig(context.env);
|
|
17577
18295
|
await writeConfig({
|
|
17578
18296
|
...config2,
|
|
17579
18297
|
apiKey: key,
|
|
17580
18298
|
organizationId: viewer?.organizationId,
|
|
17581
|
-
libraries: (viewer?.libraries ?? []).
|
|
18299
|
+
libraries: (viewer?.libraries ?? []).map((entry) => entry.library),
|
|
18300
|
+
oauth: undefined
|
|
17582
18301
|
}, context.env);
|
|
17583
18302
|
if (context.json) {
|
|
17584
18303
|
printJson(context, viewer);
|
|
@@ -17587,14 +18306,62 @@ async function loginCommand(context) {
|
|
|
17587
18306
|
const organizationLabel = viewer?.organizationName ?? viewer?.organizationId ?? "your organization";
|
|
17588
18307
|
note(context, `Logged in to ${organizationLabel} as ${maskApiKey(key)}. Key stored in ${configPath(context.env)}.`);
|
|
17589
18308
|
}
|
|
18309
|
+
function chooseLoginMethod(context, options) {
|
|
18310
|
+
if (options.device && options.browser) {
|
|
18311
|
+
throw new UsageError("Pass only one of --device or --browser.");
|
|
18312
|
+
}
|
|
18313
|
+
if (options.browser) {
|
|
18314
|
+
if (!context.canOpenBrowser()) {
|
|
18315
|
+
throw new UsageError("No browser is available to open. Use --device, or run on a machine with a display.");
|
|
18316
|
+
}
|
|
18317
|
+
return "browser";
|
|
18318
|
+
}
|
|
18319
|
+
if (options.device)
|
|
18320
|
+
return "device";
|
|
18321
|
+
return context.isInteractive && context.canOpenBrowser() ? "browser" : "device";
|
|
18322
|
+
}
|
|
18323
|
+
async function oauthLogin(context, options) {
|
|
18324
|
+
const method = chooseLoginMethod(context, options);
|
|
18325
|
+
const tokens = await runOAuthLogin(method, {
|
|
18326
|
+
issuer: context.issuer,
|
|
18327
|
+
env: context.env,
|
|
18328
|
+
fetch: context.fetch,
|
|
18329
|
+
pinnedClientId: context.pinnedClientId,
|
|
18330
|
+
now: context.now,
|
|
18331
|
+
openBrowser: context.openBrowser,
|
|
18332
|
+
onDevicePrompt: (prompt) => {
|
|
18333
|
+
note(context, `To sign in, visit ${prompt.verificationUri} and enter the code: ${prompt.userCode}`);
|
|
18334
|
+
if (prompt.verificationUriComplete) {
|
|
18335
|
+
note(context, `Or open this link directly: ${prompt.verificationUriComplete}`);
|
|
18336
|
+
}
|
|
18337
|
+
note(context, "Waiting for you to finish signing in…");
|
|
18338
|
+
}
|
|
18339
|
+
});
|
|
18340
|
+
await writeOAuthTokens(tokens, context.env);
|
|
18341
|
+
if (context.json) {
|
|
18342
|
+
printJson(context, { issuer: tokens.issuer, scope: tokens.scope, resource: tokens.resource });
|
|
18343
|
+
return;
|
|
18344
|
+
}
|
|
18345
|
+
note(context, `Logged in via OAuth (${tokens.scope} scope). Tokens stored in ${configPath(context.env)}. ` + "Pass --org <id> or set RASTER_ORG to choose an organization; run `raster whoami` to list them.");
|
|
18346
|
+
}
|
|
17590
18347
|
async function logoutCommand(context) {
|
|
17591
18348
|
const config2 = await readConfig(context.env);
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
18349
|
+
if (config2.oauth) {
|
|
18350
|
+
await revokeToken({
|
|
18351
|
+
tokens: config2.oauth,
|
|
18352
|
+
env: context.env,
|
|
18353
|
+
fetch: context.fetch,
|
|
18354
|
+
pinnedClientId: context.pinnedClientId
|
|
18355
|
+
});
|
|
18356
|
+
}
|
|
18357
|
+
const cleared = { ...config2 };
|
|
18358
|
+
delete cleared.apiKey;
|
|
18359
|
+
delete cleared.oauth;
|
|
18360
|
+
await writeConfig(cleared, context.env);
|
|
18361
|
+
note(context, "Logged out. Stored credentials removed.");
|
|
17595
18362
|
}
|
|
17596
18363
|
async function statusCommand(context) {
|
|
17597
|
-
if (!context.
|
|
18364
|
+
if (!context.credential) {
|
|
17598
18365
|
if (context.json) {
|
|
17599
18366
|
printJson(context, { authenticated: false });
|
|
17600
18367
|
return;
|
|
@@ -17602,7 +18369,22 @@ async function statusCommand(context) {
|
|
|
17602
18369
|
note(context, "Not authenticated. Run `raster auth login` or set RASTER_API_KEY.");
|
|
17603
18370
|
return;
|
|
17604
18371
|
}
|
|
17605
|
-
|
|
18372
|
+
if (context.credential.kind === "oauth") {
|
|
18373
|
+
statusForOAuth(context, context.credential.tokens);
|
|
18374
|
+
return;
|
|
18375
|
+
}
|
|
18376
|
+
await statusForApiKey(context, context.credential.key, context.credential.source);
|
|
18377
|
+
}
|
|
18378
|
+
function statusForOAuth(context, tokens) {
|
|
18379
|
+
const fresh = isAccessTokenFresh(tokens, context.now());
|
|
18380
|
+
if (context.json) {
|
|
18381
|
+
printJson(context, { authenticated: true, kind: "oauth", issuer: tokens.issuer, scope: tokens.scope, fresh });
|
|
18382
|
+
return;
|
|
18383
|
+
}
|
|
18384
|
+
note(context, `OAuth login on ${tokens.issuer} (${tokens.scope} scope).`);
|
|
18385
|
+
note(context, fresh ? "Access token is fresh." : "Access token will be refreshed on the next request.");
|
|
18386
|
+
}
|
|
18387
|
+
async function statusForApiKey(context, key, source) {
|
|
17606
18388
|
let viewer = null;
|
|
17607
18389
|
let failureMessage = null;
|
|
17608
18390
|
try {
|
|
@@ -17611,12 +18393,7 @@ async function statusCommand(context) {
|
|
|
17611
18393
|
failureMessage = error51 instanceof Error ? error51.message : String(error51);
|
|
17612
18394
|
}
|
|
17613
18395
|
if (context.json) {
|
|
17614
|
-
printJson(context, {
|
|
17615
|
-
authenticated: true,
|
|
17616
|
-
source,
|
|
17617
|
-
keyPrefix: maskApiKey(key),
|
|
17618
|
-
organization: viewer ?? null
|
|
17619
|
-
});
|
|
18396
|
+
printJson(context, { authenticated: true, kind: "apiKey", source, keyPrefix: maskApiKey(key), organization: viewer ?? null });
|
|
17620
18397
|
return;
|
|
17621
18398
|
}
|
|
17622
18399
|
note(context, `API key ${maskApiKey(key)} (from ${source}).`);
|
|
@@ -17627,25 +18404,34 @@ async function statusCommand(context) {
|
|
|
17627
18404
|
note(context, `Could not validate the key against the API: ${failureMessage ?? "unknown error"}`);
|
|
17628
18405
|
}
|
|
17629
18406
|
async function whoamiCommand(context) {
|
|
17630
|
-
|
|
17631
|
-
const viewer = await
|
|
18407
|
+
requireAuth(context);
|
|
18408
|
+
const viewer = await unwrapResult(() => context.clientFactory().GET("/me", { params: { header: VERSION_HEADER } }));
|
|
17632
18409
|
if (context.json) {
|
|
17633
18410
|
printJson(context, viewer);
|
|
17634
18411
|
return;
|
|
17635
18412
|
}
|
|
18413
|
+
const isBroadOAuthLogin = context.credential?.kind === "oauth" && context.credential.tokens.scope !== "org";
|
|
18414
|
+
const organizations = viewer?.organizations ?? [];
|
|
18415
|
+
if (isBroadOAuthLogin && organizations.length > 0) {
|
|
18416
|
+
renderTable(context, organizations, [
|
|
18417
|
+
{ header: "organization", value: (organization) => organization.organizationId },
|
|
18418
|
+
{ header: "name", value: (organization) => organization.organizationName ?? "" }
|
|
18419
|
+
]);
|
|
18420
|
+
return;
|
|
18421
|
+
}
|
|
17636
18422
|
renderRecord(context, [
|
|
17637
18423
|
{ label: "organization", value: viewer?.organizationId ?? "" },
|
|
17638
18424
|
{ label: "name", value: viewer?.organizationName ?? "" },
|
|
17639
18425
|
{ label: "plan", value: viewer?.plan ?? "" },
|
|
17640
|
-
{ label: "libraries", value: (viewer?.libraries ?? []).join(", ") }
|
|
18426
|
+
{ label: "libraries", value: (viewer?.libraries ?? []).map((entry) => entry.library).join(", ") }
|
|
17641
18427
|
]);
|
|
17642
18428
|
}
|
|
17643
18429
|
function registerAuthCommands(program2, runAction) {
|
|
17644
18430
|
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
|
|
18431
|
+
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 })));
|
|
18432
|
+
auth.command("logout").description("Revoke the OAuth token (if any) and remove stored credentials").action((_options, command) => runAction(command, (context) => logoutCommand(context)));
|
|
18433
|
+
auth.command("status").description("Show which credential is in use and where it came from").action((_options, command) => runAction(command, (context) => statusCommand(context)));
|
|
18434
|
+
program2.command("whoami").description("Show the organizations and libraries the current credential can access").action((_options, command) => runAction(command, (context) => whoamiCommand(context)));
|
|
17649
18435
|
}
|
|
17650
18436
|
|
|
17651
18437
|
// src/commands/libraries.ts
|
|
@@ -17661,7 +18447,7 @@ var listOptionsSchema2 = exports_external.object({
|
|
|
17661
18447
|
pageSize: exports_external.coerce.number().int().min(1).optional()
|
|
17662
18448
|
});
|
|
17663
18449
|
async function listLibrariesCommand(context, options) {
|
|
17664
|
-
|
|
18450
|
+
requireAuth(context);
|
|
17665
18451
|
const organizationId = await context.resolveOrg();
|
|
17666
18452
|
const client = context.clientFactory();
|
|
17667
18453
|
const libraries = await unwrapResult(() => client.GET("/organizations/{organizationId}/libraries", {
|
|
@@ -17682,7 +18468,7 @@ var createOptionsSchema = exports_external.object({
|
|
|
17682
18468
|
slug: exports_external.string().min(1).optional()
|
|
17683
18469
|
});
|
|
17684
18470
|
async function createLibraryCommand(context, options) {
|
|
17685
|
-
|
|
18471
|
+
requireAuth(context);
|
|
17686
18472
|
const organizationId = await context.resolveOrg();
|
|
17687
18473
|
const client = context.clientFactory();
|
|
17688
18474
|
const library = await unwrapResult(() => client.POST("/organizations/{organizationId}/libraries", {
|
|
@@ -17703,7 +18489,7 @@ var renameOptionsSchema = exports_external.object({
|
|
|
17703
18489
|
name: exports_external.string().min(1)
|
|
17704
18490
|
});
|
|
17705
18491
|
async function renameLibraryCommand(context, options) {
|
|
17706
|
-
|
|
18492
|
+
requireAuth(context);
|
|
17707
18493
|
const organizationId = await context.resolveOrg();
|
|
17708
18494
|
const libraryId = await context.resolveLibrary();
|
|
17709
18495
|
const client = context.clientFactory();
|
|
@@ -17779,7 +18565,7 @@ var listOptionsSchema3 = exports_external.object({
|
|
|
17779
18565
|
limit: exports_external.coerce.number().int().min(1).optional()
|
|
17780
18566
|
});
|
|
17781
18567
|
async function listTagsCommand(context, options) {
|
|
17782
|
-
|
|
18568
|
+
requireAuth(context);
|
|
17783
18569
|
const organizationId = await context.resolveOrg();
|
|
17784
18570
|
const libraryId = await context.resolveLibrary();
|
|
17785
18571
|
const client = context.clientFactory();
|
|
@@ -17802,7 +18588,7 @@ var mutateOptionsSchema = exports_external.object({
|
|
|
17802
18588
|
async function tagAssetsCommand(context, assetIds, options) {
|
|
17803
18589
|
if (assetIds.length === 0)
|
|
17804
18590
|
throw new UsageError("Provide at least one asset id.");
|
|
17805
|
-
|
|
18591
|
+
requireAuth(context);
|
|
17806
18592
|
const organizationId = await context.resolveOrg();
|
|
17807
18593
|
const libraryId = await context.resolveLibrary();
|
|
17808
18594
|
const client = context.clientFactory();
|
|
@@ -17819,7 +18605,7 @@ async function tagAssetsCommand(context, assetIds, options) {
|
|
|
17819
18605
|
async function untagAssetsCommand(context, assetIds, options) {
|
|
17820
18606
|
if (assetIds.length === 0)
|
|
17821
18607
|
throw new UsageError("Provide at least one asset id.");
|
|
17822
|
-
|
|
18608
|
+
requireAuth(context);
|
|
17823
18609
|
const organizationId = await context.resolveOrg();
|
|
17824
18610
|
const libraryId = await context.resolveLibrary();
|
|
17825
18611
|
const client = context.clientFactory();
|