@backstage/cli-module-auth 0.0.0-nightly-20260317031259

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @backstage/cli-module-auth
2
+
3
+ ## 0.0.0-nightly-20260317031259
4
+
5
+ ### Minor Changes
6
+
7
+ - 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @backstage/cli-node@0.0.0-nightly-20260317031259
13
+ - @backstage/errors@1.2.7
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # @backstage/cli-module-auth
2
+
3
+ CLI module that provides authentication commands for the Backstage CLI, enabling login, logout, and credential management for Backstage instances.
4
+
5
+ ## Commands
6
+
7
+ | Command | Description |
8
+ | :----------------- | :------------------------------------------------------- |
9
+ | `auth login` | Log in the CLI to a Backstage instance |
10
+ | `auth logout` | Log out the CLI and clear stored credentials |
11
+ | `auth show` | Show details of an authenticated instance |
12
+ | `auth list` | List authenticated instances |
13
+ | `auth print-token` | Print an access token to stdout (auto-refresh if needed) |
14
+ | `auth select` | Select the default instance |
15
+
16
+ ## Documentation
17
+
18
+ - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
19
+ - [Backstage Documentation](https://backstage.io/docs)
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright 2024 The Backstage Authors
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ const path = require('node:path');
19
+
20
+ /* eslint-disable-next-line no-restricted-syntax */
21
+ const isLocal = require('node:fs').existsSync(
22
+ path.resolve(__dirname, '../src'),
23
+ );
24
+
25
+ if (isLocal) {
26
+ require('@backstage/cli-node/config/nodeTransform.cjs');
27
+ }
28
+
29
+ const { runCliModule } = require('@backstage/cli-node');
30
+ const cliModule = require(isLocal ? '../src/index' : '..').default;
31
+ const pkg = require('../package.json');
32
+ runCliModule({ module: cliModule, name: pkg.name, version: pkg.version });
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var cleye = require('cleye');
6
+ var storage = require('../lib/storage.cjs.js');
7
+
8
+ var list = async ({ args, info }) => {
9
+ cleye.cli({ help: info }, void 0, args);
10
+ const { instances, selected } = await storage.getAllInstances();
11
+ if (!instances.length) {
12
+ process.stderr.write("No instances found\n");
13
+ return;
14
+ }
15
+ for (const inst of instances) {
16
+ const mark = inst.name === selected?.name ? "* " : " ";
17
+ process.stdout.write(`${mark}${inst.name} - ${inst.baseUrl}
18
+ `);
19
+ }
20
+ };
21
+
22
+ exports.default = list;
23
+ //# sourceMappingURL=list.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.cjs.js","sources":["../../src/commands/list.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { cli } from 'cleye';\nimport type { CliCommandContext } from '@backstage/cli-node';\nimport { getAllInstances } from '../lib/storage';\n\nexport default async ({ args, info }: CliCommandContext) => {\n cli({ help: info }, undefined, args);\n\n const { instances, selected } = await getAllInstances();\n if (!instances.length) {\n process.stderr.write('No instances found\\n');\n return;\n }\n for (const inst of instances) {\n const mark = inst.name === selected?.name ? '* ' : ' ';\n process.stdout.write(`${mark}${inst.name} - ${inst.baseUrl}\\n`);\n }\n};\n"],"names":["cli","getAllInstances"],"mappings":";;;;;;;AAoBA,WAAe,OAAO,EAAE,IAAA,EAAM,IAAA,EAAK,KAAyB;AAC1D,EAAAA,SAAA,CAAI,EAAE,IAAA,EAAM,IAAA,EAAK,EAAG,QAAW,IAAI,CAAA;AAEnC,EAAA,MAAM,EAAE,SAAA,EAAW,QAAA,EAAS,GAAI,MAAMC,uBAAA,EAAgB;AACtD,EAAA,IAAI,CAAC,UAAU,MAAA,EAAQ;AACrB,IAAA,OAAA,CAAQ,MAAA,CAAO,MAAM,sBAAsB,CAAA;AAC3C,IAAA;AAAA,EACF;AACA,EAAA,KAAA,MAAW,QAAQ,SAAA,EAAW;AAC5B,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,KAAS,QAAA,EAAU,OAAO,IAAA,GAAO,IAAA;AACnD,IAAA,OAAA,CAAQ,MAAA,CAAO,MAAM,CAAA,EAAG,IAAI,GAAG,IAAA,CAAK,IAAI,CAAA,GAAA,EAAM,IAAA,CAAK,OAAO;AAAA,CAAI,CAAA;AAAA,EAChE;AACF,CAAA;;;;"}
@@ -0,0 +1,316 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var cleye = require('cleye');
6
+ var localServer = require('../lib/localServer.cjs.js');
7
+ var node_child_process = require('node:child_process');
8
+ var pkce = require('../lib/pkce.cjs.js');
9
+ var http = require('../lib/http.cjs.js');
10
+ var storage = require('../lib/storage.cjs.js');
11
+ var secretStore = require('../lib/secretStore.cjs.js');
12
+ var crypto = require('node:crypto');
13
+ var fs = require('fs-extra');
14
+ var path = require('node:path');
15
+ var glob = require('glob');
16
+ var YAML = require('yaml');
17
+ var inquirer = require('inquirer');
18
+
19
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
20
+
21
+ var crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);
22
+ var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
23
+ var path__default = /*#__PURE__*/_interopDefaultCompat(path);
24
+ var glob__default = /*#__PURE__*/_interopDefaultCompat(glob);
25
+ var YAML__default = /*#__PURE__*/_interopDefaultCompat(YAML);
26
+ var inquirer__default = /*#__PURE__*/_interopDefaultCompat(inquirer);
27
+
28
+ const TOKEN_EXCHANGE_TIMEOUT_MS = 3e4;
29
+ var login = async ({ args, info }) => {
30
+ const {
31
+ flags: { backendUrl, noBrowser, instance: instanceFlag }
32
+ } = cleye.cli(
33
+ {
34
+ help: info,
35
+ flags: {
36
+ backendUrl: { type: String, description: "Backend base URL" },
37
+ noBrowser: {
38
+ type: Boolean,
39
+ description: "Do not open browser automatically"
40
+ },
41
+ instance: {
42
+ type: String,
43
+ description: "Name for this instance (used by other auth commands)"
44
+ }
45
+ }
46
+ },
47
+ void 0,
48
+ args
49
+ );
50
+ const { instances, selected } = await storage.getAllInstances();
51
+ let backendBaseUrl;
52
+ let instanceName;
53
+ if (instanceFlag) {
54
+ instanceName = instanceFlag;
55
+ const targetInstance = instances.find((i) => i.name === instanceFlag);
56
+ if (targetInstance) {
57
+ backendBaseUrl = normalizeUrl(backendUrl) ?? targetInstance.baseUrl;
58
+ } else {
59
+ backendBaseUrl = normalizeUrl(backendUrl) ?? await pickBaseUrl();
60
+ }
61
+ } else if (backendUrl) {
62
+ backendBaseUrl = normalizeUrl(backendUrl);
63
+ instanceName = deriveInstanceName(backendBaseUrl);
64
+ } else if (instances.length > 0) {
65
+ const choice = await promptForInstance(instances, selected);
66
+ if (choice === "__new__") {
67
+ backendBaseUrl = await pickBaseUrl();
68
+ instanceName = deriveInstanceName(backendBaseUrl);
69
+ } else {
70
+ const targetInstance = instances.find((i) => i.name === choice);
71
+ if (!targetInstance) {
72
+ throw new Error("Instance not found");
73
+ }
74
+ backendBaseUrl = targetInstance.baseUrl;
75
+ instanceName = targetInstance.name;
76
+ }
77
+ } else {
78
+ backendBaseUrl = await pickBaseUrl();
79
+ instanceName = deriveInstanceName(backendBaseUrl);
80
+ }
81
+ const authBaseUrl = `${backendBaseUrl}/api/auth`;
82
+ const clientId = `${authBaseUrl}/.well-known/oauth-client/cli.json`;
83
+ const metadataResponse = await fetch(clientId, {
84
+ signal: AbortSignal.timeout(3e4)
85
+ });
86
+ if (!metadataResponse.ok) {
87
+ throw new Error(
88
+ `Server does not support CLI authentication. Ensure CIMD is enabled on the backend.`
89
+ );
90
+ }
91
+ const { verifier, challenge, state } = createPkceState();
92
+ const callback = await localServer.startCallbackServer({ state });
93
+ try {
94
+ const authorizeUrl = buildAuthorizeUrl({
95
+ authBaseUrl,
96
+ clientId,
97
+ redirectUri: callback.url,
98
+ state,
99
+ challenge
100
+ });
101
+ await openBrowserOrPrint(authorizeUrl, noBrowser);
102
+ const code = await waitForAuthorizationCode(callback, state);
103
+ const token = await exchangeAuthorizationCode({
104
+ authBaseUrl,
105
+ code,
106
+ redirectUri: callback.url,
107
+ verifier
108
+ });
109
+ await persistInstance({
110
+ instanceName,
111
+ backendBaseUrl,
112
+ clientId,
113
+ token
114
+ });
115
+ process.stdout.write("Login successful\n");
116
+ } finally {
117
+ await callback.close();
118
+ }
119
+ };
120
+ async function promptForInstance(instances, selected) {
121
+ const choices = instances.map((i) => ({
122
+ name: `${i.name === selected?.name ? "* " : " "}${i.name} (${i.baseUrl})`,
123
+ value: i.name
124
+ }));
125
+ choices.push({
126
+ name: "Add new instance...",
127
+ value: "__new__"
128
+ });
129
+ const { choice } = await inquirer__default.default.prompt([
130
+ {
131
+ type: "list",
132
+ name: "choice",
133
+ message: "Select instance to authenticate:",
134
+ choices,
135
+ default: selected?.name ?? "__new__"
136
+ }
137
+ ]);
138
+ return choice;
139
+ }
140
+ async function pickBaseUrl() {
141
+ const cwd = process.cwd();
142
+ const candidates = [];
143
+ const patterns = [
144
+ "app-config.yaml",
145
+ "app-config.*.yaml",
146
+ "packages/*/app-config.yaml",
147
+ "packages/*/app-config.*.yaml"
148
+ ];
149
+ const files = patterns.flatMap((p) => glob__default.default.sync(p, { cwd, nodir: true }));
150
+ for (const file of files) {
151
+ try {
152
+ const content = await fs__default.default.readFile(path__default.default.resolve(cwd, file), "utf8");
153
+ const doc = YAML__default.default.parse(content);
154
+ const url = doc?.backend?.baseUrl;
155
+ if (url) {
156
+ candidates.push({ url: normalizeUrl(url), file });
157
+ }
158
+ } catch {
159
+ }
160
+ }
161
+ const list = [...new Map(candidates.map((c) => [c.url, c])).values()];
162
+ if (list.length === 0) {
163
+ const { manual } = await inquirer__default.default.prompt([
164
+ { type: "input", name: "manual", message: "Enter backend base URL" }
165
+ ]);
166
+ return normalizeUrl(manual);
167
+ }
168
+ if (list.length === 1) {
169
+ return list[0].url;
170
+ }
171
+ const { picked } = await inquirer__default.default.prompt([
172
+ {
173
+ type: "list",
174
+ name: "picked",
175
+ message: "Select backend base URL",
176
+ choices: [
177
+ ...list.map((e) => ({ name: `${e.url} (${e.file})`, value: e.url })),
178
+ { name: "Enter manually", value: "__manual__" }
179
+ ]
180
+ }
181
+ ]);
182
+ if (picked === "__manual__") {
183
+ const { manual } = await inquirer__default.default.prompt([
184
+ { type: "input", name: "manual", message: "Enter backend base URL" }
185
+ ]);
186
+ return normalizeUrl(manual);
187
+ }
188
+ return picked;
189
+ }
190
+ function normalizeUrl(u) {
191
+ if (u === void 0) {
192
+ return void 0;
193
+ }
194
+ try {
195
+ const url = new URL(u);
196
+ return url.toString().replace(/\/$/, "");
197
+ } catch {
198
+ throw new Error(`'${u}' is not a valid URL`);
199
+ }
200
+ }
201
+ function deriveInstanceName(url) {
202
+ return new URL(url).host;
203
+ }
204
+ function createPkceState() {
205
+ const verifier = pkce.generateVerifier();
206
+ const challenge = pkce.challengeFromVerifier(verifier);
207
+ const state = cryptoRandom();
208
+ return { verifier, challenge, state };
209
+ }
210
+ function buildAuthorizeUrl(options) {
211
+ const { authBaseUrl, clientId, redirectUri, state, challenge } = options;
212
+ const authorize = new URL(`${authBaseUrl}/v1/authorize`);
213
+ authorize.searchParams.set("client_id", clientId);
214
+ authorize.searchParams.set("redirect_uri", redirectUri);
215
+ authorize.searchParams.set("response_type", "code");
216
+ authorize.searchParams.set("scope", "openid offline_access");
217
+ authorize.searchParams.set("state", state);
218
+ authorize.searchParams.set("code_challenge", challenge);
219
+ authorize.searchParams.set("code_challenge_method", "S256");
220
+ return authorize.toString();
221
+ }
222
+ async function openBrowserOrPrint(url, noBrowser) {
223
+ if (noBrowser) {
224
+ process.stdout.write(`Open this URL to continue: ${url}
225
+ `);
226
+ } else {
227
+ process.stdout.write(`Opening the following URL: ${url}
228
+ `);
229
+ openInBrowser(url);
230
+ }
231
+ }
232
+ async function waitForAuthorizationCode(callback, expectedState) {
233
+ const { code, state } = await callback.waitForCode();
234
+ if (state !== expectedState) {
235
+ throw new Error("State mismatch");
236
+ }
237
+ return code;
238
+ }
239
+ async function exchangeAuthorizationCode(options) {
240
+ const { authBaseUrl, code, redirectUri, verifier } = options;
241
+ return await http.httpJson(`${authBaseUrl}/v1/token`, {
242
+ method: "POST",
243
+ body: {
244
+ grant_type: "authorization_code",
245
+ code,
246
+ redirect_uri: redirectUri,
247
+ code_verifier: verifier
248
+ },
249
+ signal: AbortSignal.timeout(TOKEN_EXCHANGE_TIMEOUT_MS)
250
+ });
251
+ }
252
+ async function persistInstance(options) {
253
+ const { instanceName, backendBaseUrl, clientId, token } = options;
254
+ const secretStore$1 = await secretStore.getSecretStore();
255
+ await storage.withMetadataLock(async () => {
256
+ const service = `backstage-cli:auth-instance:${instanceName}`;
257
+ await secretStore$1.set(service, "accessToken", token.access_token);
258
+ if (token.refresh_token) {
259
+ await secretStore$1.set(service, "refreshToken", token.refresh_token);
260
+ } else {
261
+ process.stderr.write(
262
+ "Warning: No refresh token received. You will need to re-authenticate when the access token expires.\n"
263
+ );
264
+ }
265
+ let existing;
266
+ try {
267
+ existing = await storage.getInstanceByName(instanceName);
268
+ } catch {
269
+ }
270
+ await storage.upsertInstance({
271
+ name: instanceName,
272
+ baseUrl: backendBaseUrl,
273
+ clientId,
274
+ issuedAt: Date.now(),
275
+ accessTokenExpiresAt: Date.now() + token.expires_in * 1e3,
276
+ selected: existing?.selected
277
+ });
278
+ });
279
+ }
280
+ function cryptoRandom() {
281
+ return crypto__default.default.randomBytes(32).toString("hex");
282
+ }
283
+ function openInBrowser(url) {
284
+ const handleError = (error) => {
285
+ const message = error instanceof Error ? error.message : "Unknown error";
286
+ process.stderr.write(
287
+ `Warning: Failed to open browser automatically: ${message}
288
+ `
289
+ );
290
+ process.stderr.write(`Please open this URL manually: ${url}
291
+ `);
292
+ };
293
+ const spawnOpts = { detached: true, stdio: "ignore" };
294
+ let child;
295
+ try {
296
+ if (process.platform === "darwin") {
297
+ child = node_child_process.spawn("open", [url], spawnOpts);
298
+ } else if (process.platform === "win32") {
299
+ child = node_child_process.spawn(
300
+ "powershell",
301
+ ["-Command", `Start-Process '${url.replace(/'/g, "''")}'`],
302
+ spawnOpts
303
+ );
304
+ } else {
305
+ child = node_child_process.spawn("xdg-open", [url], spawnOpts);
306
+ }
307
+ child.unref();
308
+ child.on("error", handleError);
309
+ } catch (error) {
310
+ handleError(error);
311
+ }
312
+ }
313
+
314
+ exports.default = login;
315
+ exports.openInBrowser = openInBrowser;
316
+ //# sourceMappingURL=login.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.cjs.js","sources":["../../src/commands/login.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { cli } from 'cleye';\nimport type { CliCommandContext } from '@backstage/cli-node';\nimport { startCallbackServer } from '../lib/localServer';\nimport { spawn } from 'node:child_process';\nimport { challengeFromVerifier, generateVerifier } from '../lib/pkce';\nimport { httpJson } from '../lib/http';\nimport {\n upsertInstance,\n withMetadataLock,\n getAllInstances,\n getInstanceByName,\n StoredInstance,\n} from '../lib/storage';\nimport { getSecretStore } from '../lib/secretStore';\nimport crypto from 'node:crypto';\nimport fs from 'fs-extra';\nimport path from 'node:path';\nimport glob from 'glob';\nimport YAML from 'yaml';\nimport inquirer from 'inquirer';\n\nconst TOKEN_EXCHANGE_TIMEOUT_MS = 30_000;\n\nexport default async ({ args, info }: CliCommandContext) => {\n const {\n flags: { backendUrl, noBrowser, instance: instanceFlag },\n } = cli(\n {\n help: info,\n flags: {\n backendUrl: { type: String, description: 'Backend base URL' },\n noBrowser: {\n type: Boolean,\n description: 'Do not open browser automatically',\n },\n instance: {\n type: String,\n description: 'Name for this instance (used by other auth commands)',\n },\n },\n },\n undefined,\n args,\n );\n\n const { instances, selected } = await getAllInstances();\n\n let backendBaseUrl: string;\n let instanceName: string;\n\n if (instanceFlag) {\n instanceName = instanceFlag;\n const targetInstance = instances.find(i => i.name === instanceFlag);\n if (targetInstance) {\n backendBaseUrl = normalizeUrl(backendUrl) ?? targetInstance.baseUrl;\n } else {\n backendBaseUrl = normalizeUrl(backendUrl) ?? (await pickBaseUrl());\n }\n } else if (backendUrl) {\n backendBaseUrl = normalizeUrl(backendUrl);\n instanceName = deriveInstanceName(backendBaseUrl);\n } else if (instances.length > 0) {\n const choice = await promptForInstance(instances, selected);\n if (choice === '__new__') {\n backendBaseUrl = await pickBaseUrl();\n instanceName = deriveInstanceName(backendBaseUrl);\n } else {\n const targetInstance = instances.find(i => i.name === choice);\n if (!targetInstance) {\n throw new Error('Instance not found');\n }\n backendBaseUrl = targetInstance.baseUrl;\n instanceName = targetInstance.name;\n }\n } else {\n backendBaseUrl = await pickBaseUrl();\n instanceName = deriveInstanceName(backendBaseUrl);\n }\n\n const authBaseUrl = `${backendBaseUrl}/api/auth`;\n const clientId = `${authBaseUrl}/.well-known/oauth-client/cli.json`;\n\n const metadataResponse = await fetch(clientId, {\n signal: AbortSignal.timeout(30_000),\n });\n if (!metadataResponse.ok) {\n throw new Error(\n `Server does not support CLI authentication. Ensure CIMD is enabled on the backend.`,\n );\n }\n\n const { verifier, challenge, state } = createPkceState();\n const callback = await startCallbackServer({ state });\n\n try {\n const authorizeUrl = buildAuthorizeUrl({\n authBaseUrl,\n clientId,\n redirectUri: callback.url,\n state,\n challenge,\n });\n\n await openBrowserOrPrint(authorizeUrl, noBrowser);\n\n const code = await waitForAuthorizationCode(callback, state);\n\n const token = await exchangeAuthorizationCode({\n authBaseUrl,\n code,\n redirectUri: callback.url,\n verifier,\n });\n\n await persistInstance({\n instanceName,\n backendBaseUrl,\n clientId,\n token,\n });\n\n process.stdout.write('Login successful\\n');\n } finally {\n await callback.close();\n }\n};\n\nasync function promptForInstance(\n instances: StoredInstance[],\n selected: StoredInstance | undefined,\n): Promise<string> {\n const choices = instances.map(i => ({\n name: `${i.name === selected?.name ? '* ' : ' '}${i.name} (${i.baseUrl})`,\n value: i.name,\n }));\n\n choices.push({\n name: 'Add new instance...',\n value: '__new__',\n });\n\n const { choice } = await inquirer.prompt<{ choice: string }>([\n {\n type: 'list',\n name: 'choice',\n message: 'Select instance to authenticate:',\n choices,\n default: selected?.name ?? '__new__',\n },\n ]);\n\n return choice;\n}\n\nasync function pickBaseUrl() {\n const cwd = process.cwd();\n const candidates: Array<{ url: string; file: string }> = [];\n\n const patterns = [\n 'app-config.yaml',\n 'app-config.*.yaml',\n 'packages/*/app-config.yaml',\n 'packages/*/app-config.*.yaml',\n ];\n const files = patterns.flatMap(p => glob.sync(p, { cwd, nodir: true }));\n for (const file of files) {\n try {\n const content = await fs.readFile(path.resolve(cwd, file), 'utf8');\n const doc = YAML.parse(content);\n const url = doc?.backend?.baseUrl as string | undefined;\n if (url) {\n candidates.push({ url: normalizeUrl(url), file });\n }\n } catch {\n // ignore parse errors\n }\n }\n\n const list = [...new Map(candidates.map(c => [c.url, c])).values()];\n if (list.length === 0) {\n const { manual } = await inquirer.prompt<{ manual: string }>([\n { type: 'input', name: 'manual', message: 'Enter backend base URL' },\n ]);\n return normalizeUrl(manual);\n }\n if (list.length === 1) {\n return list[0].url;\n }\n\n const { picked } = await inquirer.prompt<{ picked: string }>([\n {\n type: 'list',\n name: 'picked',\n message: 'Select backend base URL',\n choices: [\n ...list.map(e => ({ name: `${e.url} (${e.file})`, value: e.url })),\n { name: 'Enter manually', value: '__manual__' },\n ],\n },\n ]);\n if (picked === '__manual__') {\n const { manual } = await inquirer.prompt<{ manual: string }>([\n { type: 'input', name: 'manual', message: 'Enter backend base URL' },\n ]);\n return normalizeUrl(manual);\n }\n return picked;\n}\n\nfunction normalizeUrl(u: string): string;\nfunction normalizeUrl(u: string | undefined): string | undefined;\nfunction normalizeUrl(u: string | undefined): string | undefined {\n if (u === undefined) {\n return undefined;\n }\n try {\n const url = new URL(u);\n return url.toString().replace(/\\/$/, '');\n } catch {\n throw new Error(`'${u}' is not a valid URL`);\n }\n}\n\nfunction deriveInstanceName(url: string): string {\n return new URL(url).host;\n}\n\nfunction createPkceState() {\n const verifier = generateVerifier();\n const challenge = challengeFromVerifier(verifier);\n const state = cryptoRandom();\n return { verifier, challenge, state };\n}\n\nfunction buildAuthorizeUrl(options: {\n authBaseUrl: string;\n clientId: string;\n redirectUri: string;\n state: string;\n challenge: string;\n}): string {\n const { authBaseUrl, clientId, redirectUri, state, challenge } = options;\n const authorize = new URL(`${authBaseUrl}/v1/authorize`);\n authorize.searchParams.set('client_id', clientId);\n authorize.searchParams.set('redirect_uri', redirectUri);\n authorize.searchParams.set('response_type', 'code');\n authorize.searchParams.set('scope', 'openid offline_access');\n authorize.searchParams.set('state', state);\n authorize.searchParams.set('code_challenge', challenge);\n authorize.searchParams.set('code_challenge_method', 'S256');\n return authorize.toString();\n}\n\nasync function openBrowserOrPrint(url: string, noBrowser?: boolean) {\n if (noBrowser) {\n process.stdout.write(`Open this URL to continue: ${url}\\n`);\n } else {\n process.stdout.write(`Opening the following URL: ${url}\\n`);\n openInBrowser(url);\n }\n}\n\nasync function waitForAuthorizationCode(\n callback: Awaited<ReturnType<typeof startCallbackServer>>,\n expectedState: string,\n) {\n const { code, state } = await callback.waitForCode();\n if (state !== expectedState) {\n throw new Error('State mismatch');\n }\n return code;\n}\n\nasync function exchangeAuthorizationCode(options: {\n authBaseUrl: string;\n code: string;\n redirectUri: string;\n verifier: string;\n}) {\n const { authBaseUrl, code, redirectUri, verifier } = options;\n return await httpJson<{\n access_token: string;\n token_type: string;\n expires_in: number;\n id_token?: string;\n refresh_token?: string;\n }>(`${authBaseUrl}/v1/token`, {\n method: 'POST',\n body: {\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n code_verifier: verifier,\n },\n signal: AbortSignal.timeout(TOKEN_EXCHANGE_TIMEOUT_MS),\n });\n}\n\nasync function persistInstance(options: {\n instanceName: string;\n backendBaseUrl: string;\n clientId: string;\n token: { access_token: string; refresh_token?: string; expires_in: number };\n}) {\n const { instanceName, backendBaseUrl, clientId, token } = options;\n const secretStore = await getSecretStore();\n await withMetadataLock(async () => {\n const service = `backstage-cli:auth-instance:${instanceName}`;\n await secretStore.set(service, 'accessToken', token.access_token);\n if (token.refresh_token) {\n await secretStore.set(service, 'refreshToken', token.refresh_token);\n } else {\n process.stderr.write(\n 'Warning: No refresh token received. You will need to re-authenticate when the access token expires.\\n',\n );\n }\n let existing: StoredInstance | undefined;\n try {\n existing = await getInstanceByName(instanceName);\n } catch {\n // new instance\n }\n await upsertInstance({\n name: instanceName,\n baseUrl: backendBaseUrl,\n clientId,\n issuedAt: Date.now(),\n accessTokenExpiresAt: Date.now() + token.expires_in * 1000,\n selected: existing?.selected,\n });\n });\n}\n\nfunction cryptoRandom(): string {\n return crypto.randomBytes(32).toString('hex');\n}\n\n// The react-dev-utils/openBrowser breaks the login URL by encoding the URL parameters again\nexport function openInBrowser(url: string): void {\n const handleError = (error: unknown) => {\n const message = error instanceof Error ? error.message : 'Unknown error';\n process.stderr.write(\n `Warning: Failed to open browser automatically: ${message}\\n`,\n );\n process.stderr.write(`Please open this URL manually: ${url}\\n`);\n };\n\n const spawnOpts = { detached: true, stdio: 'ignore' } as const;\n let child;\n try {\n if (process.platform === 'darwin') {\n child = spawn('open', [url], spawnOpts);\n } else if (process.platform === 'win32') {\n child = spawn(\n 'powershell',\n ['-Command', `Start-Process '${url.replace(/'/g, \"''\")}'`],\n spawnOpts,\n );\n } else {\n child = spawn('xdg-open', [url], spawnOpts);\n }\n child.unref();\n child.on('error', handleError);\n } catch (error) {\n handleError(error);\n }\n}\n"],"names":["cli","getAllInstances","startCallbackServer","inquirer","glob","fs","path","YAML","generateVerifier","challengeFromVerifier","httpJson","secretStore","getSecretStore","withMetadataLock","getInstanceByName","upsertInstance","crypto","spawn"],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var cleye = require('cleye');
6
+ var secretStore = require('../lib/secretStore.cjs.js');
7
+ var storage = require('../lib/storage.cjs.js');
8
+ var http = require('../lib/http.cjs.js');
9
+ var prompt = require('../lib/prompt.cjs.js');
10
+
11
+ var logout = async ({ args, info }) => {
12
+ const {
13
+ flags: { instance: instanceFlag }
14
+ } = cleye.cli(
15
+ {
16
+ help: info,
17
+ flags: {
18
+ instance: {
19
+ type: String,
20
+ description: "Name of the instance to log out"
21
+ }
22
+ }
23
+ },
24
+ void 0,
25
+ args
26
+ );
27
+ const { name: instanceName } = await prompt.pickInstance(instanceFlag);
28
+ await storage.withMetadataLock(async () => {
29
+ const instance = await storage.getInstanceByName(instanceName);
30
+ const secretStore$1 = await secretStore.getSecretStore();
31
+ const service = `backstage-cli:auth-instance:${instanceName}`;
32
+ const refreshToken = await secretStore$1.get(service, "refreshToken") ?? "";
33
+ if (refreshToken) {
34
+ try {
35
+ const authBaseUrl = new URL("/api/auth", instance.baseUrl).toString().replace(/\/$/, "");
36
+ await http.httpJson(`${authBaseUrl}/v1/revoke`, {
37
+ method: "POST",
38
+ body: {
39
+ token: refreshToken,
40
+ token_type_hint: "refresh_token"
41
+ },
42
+ signal: AbortSignal.timeout(3e4)
43
+ });
44
+ } catch {
45
+ }
46
+ }
47
+ await secretStore$1.delete(service, "accessToken");
48
+ await secretStore$1.delete(service, "refreshToken");
49
+ await storage.removeInstance(instance.name);
50
+ });
51
+ process.stdout.write("Logged out\n");
52
+ };
53
+
54
+ exports.default = logout;
55
+ //# sourceMappingURL=logout.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.cjs.js","sources":["../../src/commands/logout.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { cli } from 'cleye';\nimport type { CliCommandContext } from '@backstage/cli-node';\nimport { getSecretStore } from '../lib/secretStore';\nimport {\n removeInstance,\n withMetadataLock,\n getInstanceByName,\n} from '../lib/storage';\nimport { httpJson } from '../lib/http';\nimport { pickInstance } from '../lib/prompt';\n\nexport default async ({ args, info }: CliCommandContext) => {\n const {\n flags: { instance: instanceFlag },\n } = cli(\n {\n help: info,\n flags: {\n instance: {\n type: String,\n description: 'Name of the instance to log out',\n },\n },\n },\n undefined,\n args,\n );\n\n const { name: instanceName } = await pickInstance(instanceFlag);\n\n await withMetadataLock(async () => {\n const instance = await getInstanceByName(instanceName);\n const secretStore = await getSecretStore();\n const service = `backstage-cli:auth-instance:${instanceName}`;\n const refreshToken = (await secretStore.get(service, 'refreshToken')) ?? '';\n\n if (refreshToken) {\n try {\n const authBaseUrl = new URL('/api/auth', instance.baseUrl)\n .toString()\n .replace(/\\/$/, '');\n await httpJson(`${authBaseUrl}/v1/revoke`, {\n method: 'POST',\n body: {\n token: refreshToken,\n token_type_hint: 'refresh_token',\n },\n signal: AbortSignal.timeout(30_000),\n });\n } catch {\n // ignore errors per RFC 7009\n }\n }\n\n await secretStore.delete(service, 'accessToken');\n await secretStore.delete(service, 'refreshToken');\n await removeInstance(instance.name);\n });\n\n process.stdout.write('Logged out\\n');\n};\n"],"names":["cli","pickInstance","withMetadataLock","getInstanceByName","secretStore","getSecretStore","httpJson","removeInstance"],"mappings":";;;;;;;;;;AA2BA,aAAe,OAAO,EAAE,IAAA,EAAM,IAAA,EAAK,KAAyB;AAC1D,EAAA,MAAM;AAAA,IACJ,KAAA,EAAO,EAAE,QAAA,EAAU,YAAA;AAAa,GAClC,GAAIA,SAAA;AAAA,IACF;AAAA,MACE,IAAA,EAAM,IAAA;AAAA,MACN,KAAA,EAAO;AAAA,QACL,QAAA,EAAU;AAAA,UACR,IAAA,EAAM,MAAA;AAAA,UACN,WAAA,EAAa;AAAA;AACf;AACF,KACF;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,MAAM,EAAE,IAAA,EAAM,YAAA,EAAa,GAAI,MAAMC,oBAAa,YAAY,CAAA;AAE9D,EAAA,MAAMC,yBAAiB,YAAY;AACjC,IAAA,MAAM,QAAA,GAAW,MAAMC,yBAAA,CAAkB,YAAY,CAAA;AACrD,IAAA,MAAMC,aAAA,GAAc,MAAMC,0BAAA,EAAe;AACzC,IAAA,MAAM,OAAA,GAAU,+BAA+B,YAAY,CAAA,CAAA;AAC3D,IAAA,MAAM,eAAgB,MAAMD,aAAA,CAAY,GAAA,CAAI,OAAA,EAAS,cAAc,CAAA,IAAM,EAAA;AAEzE,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,IAAI;AACF,QAAA,MAAM,WAAA,GAAc,IAAI,GAAA,CAAI,WAAA,EAAa,QAAA,CAAS,OAAO,CAAA,CACtD,QAAA,EAAS,CACT,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AACpB,QAAA,MAAME,aAAA,CAAS,CAAA,EAAG,WAAW,CAAA,UAAA,CAAA,EAAc;AAAA,UACzC,MAAA,EAAQ,MAAA;AAAA,UACR,IAAA,EAAM;AAAA,YACJ,KAAA,EAAO,YAAA;AAAA,YACP,eAAA,EAAiB;AAAA,WACnB;AAAA,UACA,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,GAAM;AAAA,SACnC,CAAA;AAAA,MACH,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAEA,IAAA,MAAMF,aAAA,CAAY,MAAA,CAAO,OAAA,EAAS,aAAa,CAAA;AAC/C,IAAA,MAAMA,aAAA,CAAY,MAAA,CAAO,OAAA,EAAS,cAAc,CAAA;AAChD,IAAA,MAAMG,sBAAA,CAAe,SAAS,IAAI,CAAA;AAAA,EACpC,CAAC,CAAA;AAED,EAAA,OAAA,CAAQ,MAAA,CAAO,MAAM,cAAc,CAAA;AACrC,CAAA;;;;"}
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var cleye = require('cleye');
6
+ var auth = require('../lib/auth.cjs.js');
7
+ var storage = require('../lib/storage.cjs.js');
8
+ var secretStore = require('../lib/secretStore.cjs.js');
9
+
10
+ var printToken = async ({ args, info }) => {
11
+ const {
12
+ flags: { instance: instanceFlag }
13
+ } = cleye.cli(
14
+ {
15
+ help: info,
16
+ flags: {
17
+ instance: {
18
+ type: String,
19
+ description: "Name of the instance to use"
20
+ }
21
+ }
22
+ },
23
+ void 0,
24
+ args
25
+ );
26
+ let instance = await storage.getSelectedInstance(instanceFlag);
27
+ if (auth.accessTokenNeedsRefresh(instance)) {
28
+ instance = await auth.refreshAccessToken(instance.name);
29
+ }
30
+ const secretStore$1 = await secretStore.getSecretStore();
31
+ const service = `backstage-cli:auth-instance:${instance.name}`;
32
+ const accessToken = await secretStore$1.get(service, "accessToken");
33
+ if (!accessToken) {
34
+ throw new Error('No access token found. Run "auth login" to authenticate.');
35
+ }
36
+ process.stdout.write(`${accessToken}
37
+ `);
38
+ };
39
+
40
+ exports.default = printToken;
41
+ //# sourceMappingURL=printToken.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"printToken.cjs.js","sources":["../../src/commands/printToken.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { cli } from 'cleye';\nimport type { CliCommandContext } from '@backstage/cli-node';\nimport { accessTokenNeedsRefresh, refreshAccessToken } from '../lib/auth';\nimport { getSelectedInstance } from '../lib/storage';\nimport { getSecretStore } from '../lib/secretStore';\n\nexport default async ({ args, info }: CliCommandContext) => {\n const {\n flags: { instance: instanceFlag },\n } = cli(\n {\n help: info,\n flags: {\n instance: {\n type: String,\n description: 'Name of the instance to use',\n },\n },\n },\n undefined,\n args,\n );\n\n let instance = await getSelectedInstance(instanceFlag);\n\n if (accessTokenNeedsRefresh(instance)) {\n instance = await refreshAccessToken(instance.name);\n }\n\n const secretStore = await getSecretStore();\n const service = `backstage-cli:auth-instance:${instance.name}`;\n const accessToken = await secretStore.get(service, 'accessToken');\n if (!accessToken) {\n throw new Error('No access token found. Run \"auth login\" to authenticate.');\n }\n\n process.stdout.write(`${accessToken}\\n`);\n};\n"],"names":["cli","getSelectedInstance","accessTokenNeedsRefresh","refreshAccessToken","secretStore","getSecretStore"],"mappings":";;;;;;;;;AAsBA,iBAAe,OAAO,EAAE,IAAA,EAAM,IAAA,EAAK,KAAyB;AAC1D,EAAA,MAAM;AAAA,IACJ,KAAA,EAAO,EAAE,QAAA,EAAU,YAAA;AAAa,GAClC,GAAIA,SAAA;AAAA,IACF;AAAA,MACE,IAAA,EAAM,IAAA;AAAA,MACN,KAAA,EAAO;AAAA,QACL,QAAA,EAAU;AAAA,UACR,IAAA,EAAM,MAAA;AAAA,UACN,WAAA,EAAa;AAAA;AACf;AACF,KACF;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,IAAI,QAAA,GAAW,MAAMC,2BAAA,CAAoB,YAAY,CAAA;AAErD,EAAA,IAAIC,4BAAA,CAAwB,QAAQ,CAAA,EAAG;AACrC,IAAA,QAAA,GAAW,MAAMC,uBAAA,CAAmB,QAAA,CAAS,IAAI,CAAA;AAAA,EACnD;AAEA,EAAA,MAAMC,aAAA,GAAc,MAAMC,0BAAA,EAAe;AACzC,EAAA,MAAM,OAAA,GAAU,CAAA,4BAAA,EAA+B,QAAA,CAAS,IAAI,CAAA,CAAA;AAC5D,EAAA,MAAM,WAAA,GAAc,MAAMD,aAAA,CAAY,GAAA,CAAI,SAAS,aAAa,CAAA;AAChE,EAAA,IAAI,CAAC,WAAA,EAAa;AAChB,IAAA,MAAM,IAAI,MAAM,0DAA0D,CAAA;AAAA,EAC5E;AAEA,EAAA,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,WAAW;AAAA,CAAI,CAAA;AACzC,CAAA;;;;"}
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var cleye = require('cleye');
6
+ var storage = require('../lib/storage.cjs.js');
7
+ var prompt = require('../lib/prompt.cjs.js');
8
+
9
+ var select = async ({ args, info }) => {
10
+ const {
11
+ flags: { instance: instanceFlag }
12
+ } = cleye.cli(
13
+ {
14
+ help: info,
15
+ flags: {
16
+ instance: {
17
+ type: String,
18
+ description: "Name of the instance to select"
19
+ }
20
+ }
21
+ },
22
+ void 0,
23
+ args
24
+ );
25
+ const instance = await prompt.pickInstance(instanceFlag);
26
+ await storage.setSelectedInstance(instance.name);
27
+ process.stderr.write(`Selected instance '${instance.name}'
28
+ `);
29
+ };
30
+
31
+ exports.default = select;
32
+ //# sourceMappingURL=select.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.cjs.js","sources":["../../src/commands/select.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { cli } from 'cleye';\nimport type { CliCommandContext } from '@backstage/cli-node';\nimport { setSelectedInstance } from '../lib/storage';\nimport { pickInstance } from '../lib/prompt';\n\nexport default async ({ args, info }: CliCommandContext) => {\n const {\n flags: { instance: instanceFlag },\n } = cli(\n {\n help: info,\n flags: {\n instance: {\n type: String,\n description: 'Name of the instance to select',\n },\n },\n },\n undefined,\n args,\n );\n\n const instance = await pickInstance(instanceFlag);\n\n await setSelectedInstance(instance.name);\n process.stderr.write(`Selected instance '${instance.name}'\\n`);\n};\n"],"names":["cli","pickInstance","setSelectedInstance"],"mappings":";;;;;;;;AAqBA,aAAe,OAAO,EAAE,IAAA,EAAM,IAAA,EAAK,KAAyB;AAC1D,EAAA,MAAM;AAAA,IACJ,KAAA,EAAO,EAAE,QAAA,EAAU,YAAA;AAAa,GAClC,GAAIA,SAAA;AAAA,IACF;AAAA,MACE,IAAA,EAAM,IAAA;AAAA,MACN,KAAA,EAAO;AAAA,QACL,QAAA,EAAU;AAAA,UACR,IAAA,EAAM,MAAA;AAAA,UACN,WAAA,EAAa;AAAA;AACf;AACF,KACF;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,MAAM,QAAA,GAAW,MAAMC,mBAAA,CAAa,YAAY,CAAA;AAEhD,EAAA,MAAMC,2BAAA,CAAoB,SAAS,IAAI,CAAA;AACvC,EAAA,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,mBAAA,EAAsB,QAAA,CAAS,IAAI,CAAA;AAAA,CAAK,CAAA;AAC/D,CAAA;;;;"}